Skip to content

Commit 4991e48

Browse files
author
Marek Safarik
committed
get failed agents
Signed-off-by: Marek Safarik <[email protected]>
1 parent 1422cfc commit 4991e48

File tree

6 files changed

+154
-36
lines changed

6 files changed

+154
-36
lines changed

.env.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# Keylime API endpoints
44
KEYLIME_VERIFIER_URL=https://localhost:8881
55
KEYLIME_REGISTRAR_URL=https://localhost:8891
6-
KEYLIME_API_VERSION=v2.3
6+
KEYLIME_API_VERSION=v2.4
77

88
# TLS Configuration
99
KEYLIME_TLS_ENABLED=true

backend/client.go

Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,14 @@ import (
1212

1313
// newKeylimeClient creates HTTP client for Keylime API with mTLS support
1414
func newKeylimeClient(baseURL string) *KeylimeClient {
15-
apiVersion := getEnv("KEYLIME_API_VERSION", "v2.3")
16-
1715
// Remove "http(s)://" prefix if present
1816
baseURL = strings.TrimPrefix(baseURL, "https://")
1917
baseURL = strings.TrimPrefix(baseURL, "http://")
2018

21-
// Check if TLS is enabled
22-
tlsEnabled := getEnv("KEYLIME_TLS_ENABLED", "true") == "true"
23-
2419
var finalURL string
2520
var httpClient *http.Client
2621

27-
if tlsEnabled {
22+
if config.TLSEnabled {
2823
finalURL = "https://" + strings.TrimSuffix(baseURL, "/")
2924
tlsConfig := createTLSConfig()
3025
httpClient = &http.Client{
@@ -39,22 +34,16 @@ func newKeylimeClient(baseURL string) *KeylimeClient {
3934

4035
return &KeylimeClient{
4136
baseURL: finalURL,
42-
apiVersion: apiVersion,
37+
apiVersion: config.APIVersion,
4338
httpClient: httpClient,
4439
}
4540
}
4641

4742
// createTLSConfig creates TLS configuration with mTLS support
4843
// Equivalent to Python's HostNameIgnoreAdapter with SSL context
4944
func createTLSConfig() *tls.Config {
50-
// Default certificate paths (same as Keylime default)
51-
certDir := getEnv("KEYLIME_CERT_DIR", "/var/lib/keylime/cv_ca")
52-
clientCert := getEnv("KEYLIME_CLIENT_CERT", certDir+"/client-cert.crt")
53-
clientKey := getEnv("KEYLIME_CLIENT_KEY", certDir+"/client-private.pem")
54-
caCert := getEnv("KEYLIME_CA_CERT", certDir+"/cacert.crt")
55-
5645
// Load client certificate and key
57-
cert, err := tls.LoadX509KeyPair(clientCert, clientKey)
46+
cert, err := tls.LoadX509KeyPair(config.ClientCert, config.ClientKey)
5847
if err != nil {
5948
log.Printf("Warning: Failed to load client certificate: %v", err)
6049
log.Printf("Attempting to connect without client cert (may fail with mTLS servers)")
@@ -65,7 +54,7 @@ func createTLSConfig() *tls.Config {
6554
}
6655

6756
// Load CA certificate
68-
caCertPEM, err := os.ReadFile(caCert)
57+
caCertPEM, err := os.ReadFile(config.CAPath)
6958
if err != nil {
7059
log.Printf("Warning: Failed to load CA certificate: %v", err)
7160
log.Printf("Using system CA pool")
@@ -79,15 +68,12 @@ func createTLSConfig() *tls.Config {
7968
}
8069
}
8170

82-
// Check if hostname verification should be ignored
83-
ignoreHostname := getEnv("KEYLIME_IGNORE_HOSTNAME", "true") == "true"
84-
8571
tlsConfig := &tls.Config{
8672
Certificates: []tls.Certificate{cert},
8773
RootCAs: caCertPool,
8874
// Ignore hostname verification (like Python's HostNameIgnoreAdapter)
8975
// This is needed because Keylime certs often don't have correct hostname
90-
InsecureSkipVerify: ignoreHostname,
76+
InsecureSkipVerify: config.IgnoreHostname,
9177
}
9278

9379
return tlsConfig
@@ -112,10 +98,3 @@ func (kc *KeylimeClient) Delete(endpoint string) (*http.Response, error) {
11298
}
11399
return kc.httpClient.Do(req)
114100
}
115-
116-
func getEnv(key, defaultValue string) string {
117-
if value := os.Getenv(key); value != "" {
118-
return value
119-
}
120-
return defaultValue
121-
}

backend/main.go

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"log"
66
"net/http"
7+
"os"
78

89
"github.com/joho/godotenv"
910
"github.com/modelcontextprotocol/go-sdk/mcp"
@@ -12,6 +13,8 @@ import (
1213
var (
1314
keylimeVerifierClient *KeylimeClient
1415
keylimeRegistrarClient *KeylimeClient
16+
17+
config Config
1518
)
1619

1720
func main() {
@@ -20,35 +23,58 @@ func main() {
2023
if err1 != nil && err2 != nil {
2124
log.Printf("No .env file found, using defaults")
2225
}
23-
verifierBaseURL := getEnv("KEYLIME_VERIFIER_URL", "https://localhost:8881")
24-
registrarBaseURL := getEnv("KEYLIME_REGISTRAR_URL", "https://localhost:8891")
25-
keylimeVerifierClient = newKeylimeClient(verifierBaseURL)
26-
keylimeRegistrarClient = newKeylimeClient(registrarBaseURL)
26+
loadConfig()
27+
keylimeVerifierClient = newKeylimeClient(config.VerifierURL)
28+
keylimeRegistrarClient = newKeylimeClient(config.RegistrarURL)
2729

28-
port := getEnv("PORT", "8080")
2930
http.HandleFunc("/health", healthHandler)
3031
http.HandleFunc("/agents", getAllAgentsHandler)
3132

3233
runMode := getEnv("RUN_MODE", "mcp")
3334

3435
if runMode == "http" {
3536
// HTTP-only mode (for containers)
36-
log.Printf("Starting HTTP server on :%s", port)
37-
log.Fatal(http.ListenAndServe(":"+port, nil))
37+
log.Printf("Starting HTTP server on :%s", config.Port)
38+
log.Fatal(http.ListenAndServe(":"+config.Port, nil))
3839
} else {
3940
// MCP mode (for Claude Desktop)
4041
go func() {
41-
log.Printf("HTTP test server: http://localhost:%s", port)
42-
if err := http.ListenAndServe(":"+port, nil); err != nil {
42+
log.Printf("HTTP test server: http://localhost:%s", config.Port)
43+
if err := http.ListenAndServe(":"+config.Port, nil); err != nil {
4344
log.Printf("HTTP server error: %v", err)
4445
}
4546
}()
4647

4748
server := mcp.NewServer(&mcp.Implementation{Name: "Keylime", Version: "v1.0.0"}, nil)
4849
mcp.AddTool(server, &mcp.Tool{Name: "Get_all_agents", Description: "Retrieves a list of all registered agent UUIDs"}, getAllAgents)
4950
mcp.AddTool(server, &mcp.Tool{Name: "Get_agent_status", Description: "Retrieves the current status information for a specific agent identified by its UUID"}, getAgentStatus)
51+
mcp.AddTool(server, &mcp.Tool{Name: "Get_failed_agents", Description: "Retrieves all agents currently in a failed operational state with their detailed status information including attestation history and failure reasons"}, getFailedAgents)
5052
if err := server.Run(context.Background(), &mcp.StdioTransport{}); err != nil {
5153
log.Fatal(err)
5254
}
5355
}
5456
}
57+
58+
func loadConfig() {
59+
certDir := getEnv("KEYLIME_CERT_DIR", "/home/msafarik/.keylime/certs") // TODO: Make this configurable or better way to set this (just for debugging purposes)
60+
61+
config = Config{
62+
VerifierURL: getEnv("KEYLIME_VERIFIER_URL", "https://localhost:8881"),
63+
RegistrarURL: getEnv("KEYLIME_REGISTRAR_URL", "https://localhost:8891"),
64+
CertDir: certDir,
65+
TLSEnabled: getEnv("KEYLIME_TLS_ENABLED", "true") == "true",
66+
IgnoreHostname: getEnv("KEYLIME_IGNORE_HOSTNAME", "true") == "true",
67+
APIVersion: getEnv("KEYLIME_API_VERSION", "v2.3"),
68+
ClientCert: getEnv("KEYLIME_CLIENT_CERT", certDir+"/client-cert.crt"),
69+
ClientKey: getEnv("KEYLIME_CLIENT_KEY", certDir+"/client-private.pem"),
70+
CAPath: getEnv("KEYLIME_CA_CERT", certDir+"/cacert.crt"),
71+
Port: getEnv("PORT", "8080"),
72+
}
73+
}
74+
75+
func getEnv(key, defaultValue string) string {
76+
if value := os.Getenv(key); value != "" {
77+
return value
78+
}
79+
return defaultValue
80+
}

backend/server

20.9 KB
Binary file not shown.

backend/tools.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,66 @@ func getAgentStatus(ctx context.Context, req *mcp.CallToolRequest, input getAgen
6363
LastEventID: agentStatus.Results.LastEventID,
6464
}, nil
6565
}
66+
67+
func getFailedAgents(ctx context.Context, req *mcp.CallToolRequest, input getFailedAgentsInput) (
68+
*mcp.CallToolResult,
69+
getFailedAgentsOutput,
70+
error,
71+
) {
72+
resp, err := keylimeRegistrarClient.Get("agents")
73+
if err != nil {
74+
log.Printf("Error fetching agents: %v", err)
75+
return nil, getFailedAgentsOutput{}, err
76+
}
77+
defer resp.Body.Close()
78+
79+
var agents keylimeAgentListResponse
80+
err = json.NewDecoder(resp.Body).Decode(&agents)
81+
if err != nil {
82+
log.Printf("Error decoding agents: %v", err)
83+
return nil, getFailedAgentsOutput{}, err
84+
}
85+
86+
var failedAgents getFailedAgentsOutput
87+
for _, agentUUID := range agents.Results.UUIDs {
88+
agentResp, err := keylimeVerifierClient.Get(fmt.Sprintf("agents/%s", agentUUID))
89+
if err != nil {
90+
log.Printf("Error fetching agent status: %v", err)
91+
return nil, getFailedAgentsOutput{}, err
92+
}
93+
94+
var agentStatus keylimeAgentStatusResponse
95+
err = json.NewDecoder(agentResp.Body).Decode(&agentStatus)
96+
agentResp.Body.Close() // Close immediately after use
97+
98+
if err != nil {
99+
log.Printf("Error decoding agent status: %v", err)
100+
return nil, getFailedAgentsOutput{}, err
101+
}
102+
103+
// Check if agent is in failed state
104+
if agentStatus.Results.OperationalState == StateFailed {
105+
failedAgents.FailedAgents = append(failedAgents.FailedAgents, getAgentStatusOutput{
106+
AgentUUID: agentUUID,
107+
OperationalState: agentStatus.Results.OperationalState,
108+
OperationalStateDescription: stateToString(agentStatus.Results.OperationalState),
109+
IP: agentStatus.Results.IP,
110+
Port: agentStatus.Results.Port,
111+
AttestationCount: agentStatus.Results.AttestationCount,
112+
LastReceivedQuote: agentStatus.Results.LastReceivedQuote,
113+
LastSuccessfulAttestation: agentStatus.Results.LastSuccessfulAttestation,
114+
SeverityLevel: agentStatus.Results.SeverityLevel,
115+
LastEventID: agentStatus.Results.LastEventID,
116+
HashAlgorithm: agentStatus.Results.HashAlg,
117+
EncryptionAlgorithm: agentStatus.Results.EncAlg,
118+
SigningAlgorithm: agentStatus.Results.SignAlg,
119+
VerifierID: agentStatus.Results.VerifierID,
120+
VerifierAddress: fmt.Sprintf("%s:%d", agentStatus.Results.VerifierIP, agentStatus.Results.VerifierPort),
121+
HasMeasuredBoot: agentStatus.Results.HasMbRefstate != 0,
122+
HasRuntimePolicy: agentStatus.Results.HasRuntimePolicy != 0,
123+
})
124+
}
125+
}
126+
127+
return nil, failedAgents, nil
128+
}

backend/types.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,20 @@ func stateToString(state int) string {
3838
return "Unknown"
3939
}
4040

41+
type Config struct {
42+
VerifierURL string
43+
RegistrarURL string
44+
CertDir string
45+
TLSEnabled bool
46+
IgnoreHostname bool
47+
48+
APIVersion string
49+
ClientCert string
50+
ClientKey string
51+
CAPath string
52+
Port string
53+
}
54+
4155
type HealthResponse struct {
4256
Status string `json:"status"`
4357
Service string `json:"service"`
@@ -93,6 +107,42 @@ type keylimeAgentStatusResponse struct {
93107
} `json:"results"`
94108
}
95109

110+
type getFailedAgentsInput struct{}
111+
112+
type verifierAgentStatusResponse struct {
113+
Code int `json:"code"`
114+
Status string `json:"status"`
115+
Results struct {
116+
OperationalState int `json:"operational_state"`
117+
V string `json:"v"`
118+
IP string `json:"ip"`
119+
Port int `json:"port"`
120+
TPMPolicy string `json:"tpm_policy"`
121+
VTPMPolicy string `json:"vtpm_policy"`
122+
MetaData string `json:"meta_data"`
123+
HasMbRefstate int `json:"has_mb_refstate"`
124+
HasRuntimePolicy int `json:"has_runtime_policy"`
125+
AcceptTPMHashAlgs []string `json:"accept_tpm_hash_algs"`
126+
AcceptTPMEncryptionAlgs []string `json:"accept_tpm_encryption_algs"`
127+
AcceptTPMSigningAlgs []string `json:"accept_tpm_signing_algs"`
128+
HashAlg string `json:"hash_alg"`
129+
EncAlg string `json:"enc_alg"`
130+
SignAlg string `json:"sign_alg"`
131+
VerifierID string `json:"verifier_id"`
132+
VerifierIP string `json:"verifier_ip"`
133+
VerifierPort int `json:"verifier_port"`
134+
SeverityLevel int `json:"severity_level"`
135+
LastEventID string `json:"last_event_id"`
136+
AttestationCount int `json:"attestation_count"`
137+
LastReceivedQuote int `json:"last_received_quote"`
138+
LastSuccessfulAttestation int `json:"last_successful_attestation"`
139+
} `json:"results"`
140+
}
141+
142+
type getFailedAgentsOutput struct {
143+
FailedAgents []getAgentStatusOutput `json:"failed_agents"`
144+
}
145+
96146
type getAgentStatusInput struct {
97147
AgentUUID string `json:"agent_uuid"`
98148
}

0 commit comments

Comments
 (0)