Skip to content

Commit 6d5a5f5

Browse files
committed
keylimectl: Fix API version detection on agent
Use the response from /version endpoint instead of trying all the API versions right away. Signed-off-by: Anderson Toshiyuki Sasaki <[email protected]>
1 parent d0f2973 commit 6d5a5f5

File tree

1 file changed

+87
-12
lines changed

1 file changed

+87
-12
lines changed

keylimectl/src/client/agent.rs

Lines changed: 87 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ use crate::client::base::BaseClient;
6262
use crate::config::Config;
6363
use crate::error::{ErrorContext, KeylimectlError};
6464
use base64::{engine::general_purpose::STANDARD, Engine};
65-
use log::{debug, warn};
65+
use log::{debug, info, warn};
6666
use reqwest::{Method, StatusCode};
6767
use serde_json::{json, Value};
6868

@@ -72,6 +72,22 @@ const UNKNOWN_API_VERSION: &str = "unknown";
7272
/// Supported API versions for agent communication (all < 3.0)
7373
const SUPPORTED_AGENT_API_VERSIONS: &[&str] = &["2.0", "2.1", "2.2"];
7474

75+
/// Response structure for agent version endpoint
76+
#[derive(serde::Deserialize, Debug)]
77+
struct AgentVersionResponse {
78+
#[allow(dead_code)]
79+
code: serde_json::Number,
80+
#[allow(dead_code)]
81+
status: String,
82+
results: AgentVersionResults,
83+
}
84+
85+
/// Agent version results structure
86+
#[derive(serde::Deserialize, Debug)]
87+
struct AgentVersionResults {
88+
supported_version: String,
89+
}
90+
7591
/// Client for communicating with Keylime agents in pull model (API < 3.0)
7692
///
7793
/// The `AgentClient` provides direct communication with Keylime agents when
@@ -334,9 +350,8 @@ impl AgentClient {
334350

335351
/// Auto-detect and set the API version
336352
///
337-
/// Attempts to determine the agent's API version by trying each supported
338-
/// API version from newest to oldest until one works. Since agents in API < 3.0
339-
/// don't typically have a /version endpoint, this uses a test request approach.
353+
/// Attempts to determine the agent's API version by first trying the `/version` endpoint
354+
/// and then falling back to testing each API version individually if needed.
340355
///
341356
/// # Returns
342357
///
@@ -345,33 +360,93 @@ impl AgentClient {
345360
///
346361
/// # Behavior
347362
///
348-
/// 1. Tries API versions from newest to oldest
349-
/// 2. On success, caches the detected version for future requests
350-
/// 3. On complete failure, leaves default version unchanged
363+
/// 1. First try the `/version` endpoint to get the supported_version
364+
/// 2. If `/version` fails, fall back to testing each API version from newest to oldest
365+
/// 3. On success, caches the detected version for future requests
366+
/// 4. On complete failure, leaves default version unchanged
351367
async fn detect_api_version(&mut self) -> Result<(), KeylimectlError> {
352-
// Try each supported version from newest to oldest
368+
info!("Starting agent API version detection");
369+
370+
// Step 1: Try the /version endpoint first
371+
match self.get_agent_api_version().await {
372+
Ok(version) => {
373+
info!("Successfully detected agent API version from /version endpoint: {version}");
374+
self.api_version = version;
375+
return Ok(());
376+
}
377+
Err(e) => {
378+
debug!("Failed to get version from /version endpoint ({e}), falling back to version probing");
379+
}
380+
}
381+
382+
// Step 2: Fall back to testing each version individually (newest to oldest)
383+
info!("Falling back to individual version testing");
353384
for &api_version in SUPPORTED_AGENT_API_VERSIONS.iter().rev() {
354-
debug!("Trying agent API version {api_version}");
385+
debug!("Testing agent API version {api_version}");
355386

356387
// Test this version by making a simple request (quotes endpoint with dummy nonce)
357388
if self.test_api_version(api_version).await.is_ok() {
358-
debug!(
389+
info!(
359390
"Successfully detected agent API version: {api_version}"
360391
);
361392
self.api_version = api_version.to_string();
362393
return Ok(());
363394
}
364395
}
365396

366-
// If all versions failed, set to unknown and continue with default
397+
// If all versions failed, continue with default version
367398
warn!(
368399
"Could not detect agent API version, using default: {}",
369400
self.api_version
370401
);
371-
self.api_version = UNKNOWN_API_VERSION.to_string();
372402
Ok(())
373403
}
374404

405+
/// Get the agent API version from the '/version' endpoint
406+
///
407+
/// Attempts to retrieve the agent's supported API version using the `/version` endpoint.
408+
/// The expected response format is:
409+
/// ```json
410+
/// {
411+
/// "code": 200,
412+
/// "status": "Success",
413+
/// "results": {
414+
/// "supported_version": "2.2"
415+
/// }
416+
/// }
417+
/// ```
418+
async fn get_agent_api_version(&self) -> Result<String, KeylimectlError> {
419+
let url = format!("{}/version", self.base.base_url);
420+
421+
info!("Requesting agent API version from {url}");
422+
debug!("GET {url}");
423+
424+
let response = self
425+
.base
426+
.client
427+
.get_request(Method::GET, &url)
428+
.send()
429+
.await
430+
.with_context(|| {
431+
format!("Failed to send version request to agent at {url}")
432+
})?;
433+
434+
if !response.status().is_success() {
435+
return Err(KeylimectlError::api_error(
436+
response.status().as_u16(),
437+
"Agent does not support the /version endpoint".to_string(),
438+
None,
439+
));
440+
}
441+
442+
let resp: AgentVersionResponse =
443+
response.json().await.with_context(|| {
444+
"Failed to parse version response from agent".to_string()
445+
})?;
446+
447+
Ok(resp.results.supported_version)
448+
}
449+
375450
/// Test if a specific API version works by making a simple request
376451
async fn test_api_version(
377452
&self,

0 commit comments

Comments
 (0)