Skip to content

Commit

Permalink
version: Implement API version validation and ordering
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
ansasaki committed Dec 23, 2024
1 parent fbc7312 commit a9aec72
Show file tree
Hide file tree
Showing 3 changed files with 233 additions and 6 deletions.
86 changes: 82 additions & 4 deletions keylime-agent/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use keylime::{
hostname_parser::{parse_hostname, HostnameParsingError},
ip_parser::{parse_ip, IpParsingError},
list_parser::{parse_list, ListParsingError},
version::{self, GetErrorInput},
};
use log::*;
use serde::{Deserialize, Serialize, Serializer};
Expand All @@ -20,6 +21,7 @@ use std::{
collections::HashMap,
env,
path::{Path, PathBuf},
str::FromStr,
};
use thiserror::Error;
use uuid::Uuid;
Expand Down Expand Up @@ -621,15 +623,34 @@ fn config_translate_keywords(
}
}
versions => {

Check warning on line 625 in keylime-agent/src/config.rs

View check run for this annotation

Codecov / codecov/patch

keylime-agent/src/config.rs#L625

Added line #L625 was not covered by tests
let parsed: Vec::<String> = match parse_list(&config.agent.api_versions) {
Ok(list) => list
let parsed: Vec<String> = match parse_list(
&config.agent.api_versions,
) {

Check warning on line 628 in keylime-agent/src/config.rs

View check run for this annotation

Codecov / codecov/patch

keylime-agent/src/config.rs#L628

Added line #L628 was not covered by tests
Ok(list) => {
let mut filtered_versions = list
.iter()

Check warning on line 631 in keylime-agent/src/config.rs

View check run for this annotation

Codecov / codecov/patch

keylime-agent/src/config.rs#L631

Added line #L631 was not covered by tests
.inspect(|e| { if !SUPPORTED_API_VERSIONS.contains(e) {
warn!("Skipping API version \"{e}\" obtained from 'api_versions' configuration option")
}})

Check warning on line 634 in keylime-agent/src/config.rs

View check run for this annotation

Codecov / codecov/patch

keylime-agent/src/config.rs#L634

Added line #L634 was not covered by tests
.filter(|e| SUPPORTED_API_VERSIONS.contains(e))
.map(|&s| s.into())
.collect(),
.map(|&s| version::Version::from_str(s))
.inspect(|err| if let Err(e) = err {
warn!("Skipping API version \"{}\" obtained from 'api_versions' configuration option", e.input());
})

Check warning on line 639 in keylime-agent/src/config.rs

View check run for this annotation

Codecov / codecov/patch

keylime-agent/src/config.rs#L638-L639

Added lines #L638 - L639 were not covered by tests
.filter(|e| e.is_ok())
.map(|v| {
let Ok(ver) = v else {unreachable!();};
ver
})
.collect::<Vec<version::Version>>();

// Sort the versions from the configuration from the oldest to the newest

Check warning on line 647 in keylime-agent/src/config.rs

View check run for this annotation

Codecov / codecov/patch

keylime-agent/src/config.rs#L643-L647

Added lines #L643 - L647 were not covered by tests
filtered_versions.sort();
filtered_versions
.iter()

Check warning on line 650 in keylime-agent/src/config.rs

View check run for this annotation

Codecov / codecov/patch

keylime-agent/src/config.rs#L650

Added line #L650 was not covered by tests
.map(|v| v.to_string())
.collect::<Vec<String>>()

Check warning on line 652 in keylime-agent/src/config.rs

View check run for this annotation

Codecov / codecov/patch

keylime-agent/src/config.rs#L652

Added line #L652 was not covered by tests
}
Err(e) => {
warn!("Failed to parse list from 'api_versions' configuration option; using default supported versions");
SUPPORTED_API_VERSIONS.iter().map(|&s| s.into()).collect()

Check warning on line 656 in keylime-agent/src/config.rs

View check run for this annotation

Codecov / codecov/patch

keylime-agent/src/config.rs#L654-L656

Added lines #L654 - L656 were not covered by tests
Expand Down Expand Up @@ -996,6 +1017,63 @@ mod tests {
assert_eq!(version, old);
}

#[test]
fn test_translate_invalid_api_versions_filtered() {
let old = SUPPORTED_API_VERSIONS[0];

let mut test_config = KeylimeConfig {
agent: AgentConfig {
api_versions: format!("a.b, {old}, c.d"),
..Default::default()
},
};
let result = config_translate_keywords(&test_config);
assert!(result.is_ok());
let config = result.unwrap(); //#[allow_ci]
let version = config.agent.api_versions;
assert_eq!(version, old);
}

#[test]
fn test_translate_invalid_api_versions_fallback_default() {
let old = SUPPORTED_API_VERSIONS;

let mut test_config = KeylimeConfig {
agent: AgentConfig {
api_versions: "a.b, c.d".to_string(),
..Default::default()
},
};
let result = config_translate_keywords(&test_config);
assert!(result.is_ok());
let config = result.unwrap(); //#[allow_ci]
let version = config.agent.api_versions;
assert_eq!(version, old.join(", "));
}

#[test]
fn test_translate_api_versions_sort() {
let old = SUPPORTED_API_VERSIONS;
let reversed = SUPPORTED_API_VERSIONS
.iter()
.rev()
.copied()
.collect::<Vec<_>>()
.join(", ");

let mut test_config = KeylimeConfig {
agent: AgentConfig {
api_versions: reversed,
..Default::default()
},
};
let result = config_translate_keywords(&test_config);
assert!(result.is_ok());
let config = result.unwrap(); //#[allow_ci]
let version = config.agent.api_versions;
assert_eq!(version, old.join(", "));
}

#[test]
fn test_get_uuid() {
assert_eq!(get_uuid("hash_ek"), "hash_ek");
Expand Down
6 changes: 4 additions & 2 deletions keylime/src/registrar_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,8 @@ impl<'a> RegistrarClientBuilder<'a> {
Ok(registrar_api_version.to_string())
} else {
// Check if one of the API versions that the registrar supports is enabled
// from the latest to the oldest
// from the latest to the oldest, assuming the reported versions are ordered from the
// oldest to the newest
for reg_supported_version in
resp.results.supported_versions.iter().rev()
{
Expand Down Expand Up @@ -632,7 +633,8 @@ impl RegistrarClient<'_> {
// In case the registrar does not support the '/version' endpoint, try the enabled API
// versions
if self.api_version == UNKNOWN_API_VERSION {
for api_version in &self.enabled_api_versions {
// Assume the list of enabled versions is ordered from the oldest to the newest
for api_version in self.enabled_api_versions.iter().rev() {
info!("Trying to register agent using API version {api_version}");
let r = self.try_register_agent(api_version).await;

Expand Down
147 changes: 147 additions & 0 deletions keylime/src/version.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use serde::{Deserialize, Serialize};
use std::{fmt, str::FromStr};
use thiserror::Error;

#[derive(Serialize, Deserialize, Debug)]

Check warning on line 5 in keylime/src/version.rs

View check run for this annotation

Codecov / codecov/patch

keylime/src/version.rs#L5

Added line #L5 was not covered by tests
pub struct KeylimeVersion {
Expand All @@ -10,3 +12,148 @@ pub struct KeylimeRegistrarVersion {
pub current_version: String,
pub supported_versions: Vec<String>,
}

Check warning on line 15 in keylime/src/version.rs

View check run for this annotation

Codecov / codecov/patch

keylime/src/version.rs#L13-L15

Added lines #L13 - L15 were not covered by tests
pub trait GetErrorInput {
fn input(&self) -> String;
}

#[derive(Error, Debug)]
pub enum VersionParsingError {
/// The version input was malformed
#[error("input '{input}' malformed as a version")]
MalformedVersion { input: String },

Check warning on line 24 in keylime/src/version.rs

View check run for this annotation

Codecov / codecov/patch

keylime/src/version.rs#L19-L24

Added lines #L19 - L24 were not covered by tests

/// The parts of the version were not numbers

Check warning on line 26 in keylime/src/version.rs

View check run for this annotation

Codecov / codecov/patch

keylime/src/version.rs#L26

Added line #L26 was not covered by tests
#[error("parts of version '{input}' were not numbers")]
ParseError {
input: String,
source: std::num::ParseIntError,
},
}

impl GetErrorInput for VersionParsingError {
fn input(&self) -> String {
match self {
VersionParsingError::MalformedVersion { input } => input.into(),
VersionParsingError::ParseError { input, source: _ } => {
input.into()
}
}
}

Check warning on line 42 in keylime/src/version.rs

View check run for this annotation

Codecov / codecov/patch

keylime/src/version.rs#L30-L42

Added lines #L30 - L42 were not covered by tests
}

// Implement the trait for all the references
impl<T: GetErrorInput> GetErrorInput for &T
where
T: GetErrorInput,
{
fn input(&self) -> String {
(**self).input()
}
}

#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct Version {

Check warning on line 56 in keylime/src/version.rs

View check run for this annotation

Codecov / codecov/patch

keylime/src/version.rs#L51-L56

Added lines #L51 - L56 were not covered by tests
major: u32,
minor: u32,
}

impl fmt::Display for Version {

Check warning on line 61 in keylime/src/version.rs

View check run for this annotation

Codecov / codecov/patch

keylime/src/version.rs#L60-L61

Added lines #L60 - L61 were not covered by tests
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}.{}", self.major, self.minor)
}
}

Check warning on line 65 in keylime/src/version.rs

View check run for this annotation

Codecov / codecov/patch

keylime/src/version.rs#L64-L65

Added lines #L64 - L65 were not covered by tests

impl FromStr for Version {
type Err = VersionParsingError;

Check warning on line 68 in keylime/src/version.rs

View check run for this annotation

Codecov / codecov/patch

keylime/src/version.rs#L67-L68

Added lines #L67 - L68 were not covered by tests

fn from_str(input: &str) -> Result<Self, Self::Err> {
let mut parts = input.split('.');
match (parts.next(), parts.next()) {
(Some(major), Some(minor)) => Ok(Version {
major: major.parse().map_err(|e| {
VersionParsingError::ParseError {
input: input.to_string(),
source: e,
}
})?,

Check warning on line 79 in keylime/src/version.rs

View check run for this annotation

Codecov / codecov/patch

keylime/src/version.rs#L77-L79

Added lines #L77 - L79 were not covered by tests
minor: minor.parse().map_err(|e| {
VersionParsingError::ParseError {
input: input.to_string(),
source: e,
}
})?,
}),

Check warning on line 86 in keylime/src/version.rs

View check run for this annotation

Codecov / codecov/patch

keylime/src/version.rs#L83-L86

Added lines #L83 - L86 were not covered by tests
_ => Err(VersionParsingError::MalformedVersion {
input: input.to_string(),
}),
}
}

Check warning on line 91 in keylime/src/version.rs

View check run for this annotation

Codecov / codecov/patch

keylime/src/version.rs#L89-L91

Added lines #L89 - L91 were not covered by tests
}

impl TryFrom<&str> for Version {
type Error = VersionParsingError;

Check warning on line 96 in keylime/src/version.rs

View check run for this annotation

Codecov / codecov/patch

keylime/src/version.rs#L95-L96

Added lines #L95 - L96 were not covered by tests
fn try_from(input: &str) -> Result<Self, Self::Error> {
Version::from_str(input)
}
}

Check warning on line 101 in keylime/src/version.rs

View check run for this annotation

Codecov / codecov/patch

keylime/src/version.rs#L99-L101

Added lines #L99 - L101 were not covered by tests
impl TryFrom<String> for Version {
type Error = VersionParsingError;

fn try_from(input: String) -> Result<Self, Self::Error> {
Version::from_str(input.as_str())
}

Check warning on line 107 in keylime/src/version.rs

View check run for this annotation

Codecov / codecov/patch

keylime/src/version.rs#L107

Added line #L107 was not covered by tests
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn test_from_str() {
let v = Version::from_str("1.2").unwrap(); //#[allow_ci]
assert_eq!(v, Version { major: 1, minor: 2 });
let v2: Version = "3.4".try_into().unwrap(); //#[allow_ci]
assert_eq!(v2, Version { major: 3, minor: 4 });
let v3: Version = "5.6".to_string().try_into().unwrap(); //#[allow_ci]
assert_eq!(v3, Version { major: 5, minor: 6 });
}

#[test]
fn test_display() {
let s = format!("{}", Version { major: 1, minor: 2 });
assert_eq!(s, "1.2".to_string());
}

#[test]
fn test_ord() {
let v11: Version = "1.1".try_into().unwrap(); //#[allow_ci]
let v12: Version = "1.2".try_into().unwrap(); //#[allow_ci]
let v21: Version = "2.1".try_into().unwrap(); //#[allow_ci]
let v110: Version = "1.10".try_into().unwrap(); //#[allow_ci]
assert!(v11 < v12);
assert!(v12 < v110);
assert!(v110 < v21);

let mut v = vec![v12.clone(), v110.clone(), v11.clone()];
v.sort();
let expected = vec![v11, v12, v110];
assert_eq!(v, expected);
}

#[test]
fn test_invalid() {
let result = Version::from_str("a.b");
assert!(result.is_err());
let result = Version::from_str("1.b");
assert!(result.is_err());
let result = Version::from_str("a.2");
assert!(result.is_err());
let result = Version::from_str("22");
assert!(result.is_err());
let result = Version::from_str(".12");
assert!(result.is_err());
}
}

0 comments on commit a9aec72

Please sign in to comment.