Skip to content

Commit bf52563

Browse files
committed
version: Implement API version validation and ordering
Validate the values set in the `api_versions` configuration option, and filter only the supported versions. The configured versions are also sorted so that the agent can try the enabled versions from the newest to the oldest. If none of the configured options are supported, fallback to use all the supported API versions instead. This is part of the implementation of the enhancement proposal 114: keylime/enhancements#115 Signed-off-by: Anderson Toshiyuki Sasaki <[email protected]>
1 parent 190ec88 commit bf52563

File tree

3 files changed

+233
-6
lines changed

3 files changed

+233
-6
lines changed

keylime-agent/src/config.rs

+82-4
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use keylime::{
1212
hostname_parser::{parse_hostname, HostnameParsingError},
1313
ip_parser::{parse_ip, IpParsingError},
1414
list_parser::{parse_list, ListParsingError},
15+
version::{self, GetErrorInput},
1516
};
1617
use log::*;
1718
use serde::{Deserialize, Serialize, Serializer};
@@ -20,6 +21,7 @@ use std::{
2021
collections::HashMap,
2122
env,
2223
path::{Path, PathBuf},
24+
str::FromStr,
2325
};
2426
use thiserror::Error;
2527
use uuid::Uuid;
@@ -621,15 +623,34 @@ fn config_translate_keywords(
621623
}
622624
}
623625
versions => {
624-
let parsed: Vec::<String> = match parse_list(&config.agent.api_versions) {
625-
Ok(list) => list
626+
let parsed: Vec<String> = match parse_list(
627+
&config.agent.api_versions,
628+
) {
629+
Ok(list) => {
630+
let mut filtered_versions = list
626631
.iter()
627632
.inspect(|e| { if !SUPPORTED_API_VERSIONS.contains(e) {
628633
warn!("Skipping API version \"{e}\" obtained from 'api_versions' configuration option")
629634
}})
630635
.filter(|e| SUPPORTED_API_VERSIONS.contains(e))
631-
.map(|&s| s.into())
632-
.collect(),
636+
.map(|&s| version::Version::from_str(s))
637+
.inspect(|err| if let Err(e) = err {
638+
warn!("Skipping API version \"{}\" obtained from 'api_versions' configuration option", e.input());
639+
})
640+
.filter(|e| e.is_ok())
641+
.map(|v| {
642+
let Ok(ver) = v else {unreachable!();};
643+
ver
644+
})
645+
.collect::<Vec<version::Version>>();
646+
647+
// Sort the versions from the configuration from the oldest to the newest
648+
filtered_versions.sort();
649+
filtered_versions
650+
.iter()
651+
.map(|v| v.to_string())
652+
.collect::<Vec<String>>()
653+
}
633654
Err(e) => {
634655
warn!("Failed to parse list from 'api_versions' configuration option; using default supported versions");
635656
SUPPORTED_API_VERSIONS.iter().map(|&s| s.into()).collect()
@@ -996,6 +1017,63 @@ mod tests {
9961017
assert_eq!(version, old);
9971018
}
9981019

1020+
#[test]
1021+
fn test_translate_invalid_api_versions_filtered() {
1022+
let old = SUPPORTED_API_VERSIONS[0];
1023+
1024+
let mut test_config = KeylimeConfig {
1025+
agent: AgentConfig {
1026+
api_versions: format!("a.b, {old}, c.d"),
1027+
..Default::default()
1028+
},
1029+
};
1030+
let result = config_translate_keywords(&test_config);
1031+
assert!(result.is_ok());
1032+
let config = result.unwrap(); //#[allow_ci]
1033+
let version = config.agent.api_versions;
1034+
assert_eq!(version, old);
1035+
}
1036+
1037+
#[test]
1038+
fn test_translate_invalid_api_versions_fallback_default() {
1039+
let old = SUPPORTED_API_VERSIONS;
1040+
1041+
let mut test_config = KeylimeConfig {
1042+
agent: AgentConfig {
1043+
api_versions: "a.b, c.d".to_string(),
1044+
..Default::default()
1045+
},
1046+
};
1047+
let result = config_translate_keywords(&test_config);
1048+
assert!(result.is_ok());
1049+
let config = result.unwrap(); //#[allow_ci]
1050+
let version = config.agent.api_versions;
1051+
assert_eq!(version, old.join(", "));
1052+
}
1053+
1054+
#[test]
1055+
fn test_translate_api_versions_sort() {
1056+
let old = SUPPORTED_API_VERSIONS;
1057+
let reversed = SUPPORTED_API_VERSIONS
1058+
.iter()
1059+
.rev()
1060+
.copied()
1061+
.collect::<Vec<_>>()
1062+
.join(", ");
1063+
1064+
let mut test_config = KeylimeConfig {
1065+
agent: AgentConfig {
1066+
api_versions: reversed,
1067+
..Default::default()
1068+
},
1069+
};
1070+
let result = config_translate_keywords(&test_config);
1071+
assert!(result.is_ok());
1072+
let config = result.unwrap(); //#[allow_ci]
1073+
let version = config.agent.api_versions;
1074+
assert_eq!(version, old.join(", "));
1075+
}
1076+
9991077
#[test]
10001078
fn test_get_uuid() {
10011079
assert_eq!(get_uuid("hash_ek"), "hash_ek");

keylime/src/registrar_client.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,8 @@ impl<'a> RegistrarClientBuilder<'a> {
341341
Ok(registrar_api_version.to_string())
342342
} else {
343343
// Check if one of the API versions that the registrar supports is enabled
344-
// from the latest to the oldest
344+
// from the latest to the oldest, assuming the reported versions are ordered from the
345+
// oldest to the newest
345346
for reg_supported_version in
346347
resp.results.supported_versions.iter().rev()
347348
{
@@ -632,7 +633,8 @@ impl RegistrarClient<'_> {
632633
// In case the registrar does not support the '/version' endpoint, try the enabled API
633634
// versions
634635
if self.api_version == UNKNOWN_API_VERSION {
635-
for api_version in &self.enabled_api_versions {
636+
// Assume the list of enabled versions is ordered from the oldest to the newest
637+
for api_version in self.enabled_api_versions.iter().rev() {
636638
info!("Trying to register agent using API version {api_version}");
637639
let r = self.try_register_agent(api_version).await;
638640

keylime/src/version.rs

+147
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
use serde::{Deserialize, Serialize};
2+
use std::{fmt, str::FromStr};
3+
use thiserror::Error;
24

35
#[derive(Serialize, Deserialize, Debug)]
46
pub struct KeylimeVersion {
@@ -10,3 +12,148 @@ pub struct KeylimeRegistrarVersion {
1012
pub current_version: String,
1113
pub supported_versions: Vec<String>,
1214
}
15+
16+
pub trait GetErrorInput {
17+
fn input(&self) -> String;
18+
}
19+
20+
#[derive(Error, Debug)]
21+
pub enum VersionParsingError {
22+
/// The version input was malformed
23+
#[error("input '{input}' malformed as a version")]
24+
MalformedVersion { input: String },
25+
26+
/// The parts of the version were not numbers
27+
#[error("parts of version '{input}' were not numbers")]
28+
ParseError {
29+
input: String,
30+
source: std::num::ParseIntError,
31+
},
32+
}
33+
34+
impl GetErrorInput for VersionParsingError {
35+
fn input(&self) -> String {
36+
match self {
37+
VersionParsingError::MalformedVersion { input } => input.into(),
38+
VersionParsingError::ParseError { input, source: _ } => {
39+
input.into()
40+
}
41+
}
42+
}
43+
}
44+
45+
// Implement the trait for all the references
46+
impl<T: GetErrorInput> GetErrorInput for &T
47+
where
48+
T: GetErrorInput,
49+
{
50+
fn input(&self) -> String {
51+
(**self).input()
52+
}
53+
}
54+
55+
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
56+
pub struct Version {
57+
major: u32,
58+
minor: u32,
59+
}
60+
61+
impl fmt::Display for Version {
62+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
63+
write!(f, "{}.{}", self.major, self.minor)
64+
}
65+
}
66+
67+
impl FromStr for Version {
68+
type Err = VersionParsingError;
69+
70+
fn from_str(input: &str) -> Result<Self, Self::Err> {
71+
let mut parts = input.split('.');
72+
match (parts.next(), parts.next()) {
73+
(Some(major), Some(minor)) => Ok(Version {
74+
major: major.parse().map_err(|e| {
75+
VersionParsingError::ParseError {
76+
input: input.to_string(),
77+
source: e,
78+
}
79+
})?,
80+
minor: minor.parse().map_err(|e| {
81+
VersionParsingError::ParseError {
82+
input: input.to_string(),
83+
source: e,
84+
}
85+
})?,
86+
}),
87+
_ => Err(VersionParsingError::MalformedVersion {
88+
input: input.to_string(),
89+
}),
90+
}
91+
}
92+
}
93+
94+
impl TryFrom<&str> for Version {
95+
type Error = VersionParsingError;
96+
97+
fn try_from(input: &str) -> Result<Self, Self::Error> {
98+
Version::from_str(input)
99+
}
100+
}
101+
102+
impl TryFrom<String> for Version {
103+
type Error = VersionParsingError;
104+
105+
fn try_from(input: String) -> Result<Self, Self::Error> {
106+
Version::from_str(input.as_str())
107+
}
108+
}
109+
110+
#[cfg(test)]
111+
mod test {
112+
use super::*;
113+
114+
#[test]
115+
fn test_from_str() {
116+
let v = Version::from_str("1.2").unwrap(); //#[allow_ci]
117+
assert_eq!(v, Version { major: 1, minor: 2 });
118+
let v2: Version = "3.4".try_into().unwrap(); //#[allow_ci]
119+
assert_eq!(v2, Version { major: 3, minor: 4 });
120+
let v3: Version = "5.6".to_string().try_into().unwrap(); //#[allow_ci]
121+
assert_eq!(v3, Version { major: 5, minor: 6 });
122+
}
123+
124+
#[test]
125+
fn test_display() {
126+
let s = format!("{}", Version { major: 1, minor: 2 });
127+
assert_eq!(s, "1.2".to_string());
128+
}
129+
130+
#[test]
131+
fn test_ord() {
132+
let v11: Version = "1.1".try_into().unwrap(); //#[allow_ci]
133+
let v12: Version = "1.2".try_into().unwrap(); //#[allow_ci]
134+
let v21: Version = "2.1".try_into().unwrap(); //#[allow_ci]
135+
let v110: Version = "1.10".try_into().unwrap(); //#[allow_ci]
136+
assert!(v11 < v12);
137+
assert!(v12 < v110);
138+
assert!(v110 < v21);
139+
140+
let mut v = vec![v12.clone(), v110.clone(), v11.clone()];
141+
v.sort();
142+
let expected = vec![v11, v12, v110];
143+
assert_eq!(v, expected);
144+
}
145+
146+
#[test]
147+
fn test_invalid() {
148+
let result = Version::from_str("a.b");
149+
assert!(result.is_err());
150+
let result = Version::from_str("1.b");
151+
assert!(result.is_err());
152+
let result = Version::from_str("a.2");
153+
assert!(result.is_err());
154+
let result = Version::from_str("22");
155+
assert!(result.is_err());
156+
let result = Version::from_str(".12");
157+
assert!(result.is_err());
158+
}
159+
}

0 commit comments

Comments
 (0)