@@ -62,7 +62,7 @@ use crate::client::base::BaseClient;
6262use crate :: config:: Config ;
6363use crate :: error:: { ErrorContext , KeylimectlError } ;
6464use base64:: { engine:: general_purpose:: STANDARD , Engine } ;
65- use log:: { debug, warn} ;
65+ use log:: { debug, info , warn} ;
6666use reqwest:: { Method , StatusCode } ;
6767use 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)
7373const 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