diff --git a/src/main.rs b/src/main.rs index 72b153e20..4065fb594 100644 --- a/src/main.rs +++ b/src/main.rs @@ -362,6 +362,14 @@ fn create_app(default_vcpus: String, default_memory: String, default_rng: String .num_args(1) .value_parser(["true", "false", "log"]) .default_value("true"), + ) + .arg( + Arg::new("tpm") + .long("tpm") + .num_args(1) + .help(config::TpmConfig::SYNTAX) + .group("vmm-config"), + ); #[cfg(target_arch = "x86_64")] @@ -713,6 +721,7 @@ mod unit_tests { #[cfg(feature = "guest_debug")] gdb: false, platform: None, + tpm: None, }; assert_eq!(expected_vm_config, result_vm_config); @@ -1632,4 +1641,26 @@ mod unit_tests { compare_vm_config_cli_vs_json(cli, openapi, *equal); }); } + + #[test] + fn test_valid_vm_config_tpm_socket() { + vec![( + vec![ + "cloud-hypervisor", + "--kernel", + "/path/to/kernel", + "--tpm", + "socket=/path/to/tpm/sock", + ], + r#"{ + "payload": {"kernel": "/path/to/kernel"}, + "tpm": {"socket": "/path/to/tpm/sock"} + }"#, + true, + )] + .iter() + .for_each(|(cli, openapi, equal)| { + compare_vm_config_cli_vs_json(cli, openapi, *equal); + }); + } } diff --git a/vmm/src/api/openapi/cloud-hypervisor.yaml b/vmm/src/api/openapi/cloud-hypervisor.yaml index 2fbdbab43..dee8bdbf0 100644 --- a/vmm/src/api/openapi/cloud-hypervisor.yaml +++ b/vmm/src/api/openapi/cloud-hypervisor.yaml @@ -573,6 +573,8 @@ components: default: false platform: $ref: "#/components/schemas/PlatformConfig" + tpm: + $ref: "#/components/schemas/TpmConfig" description: Virtual machine configuration CpuAffinity: @@ -958,6 +960,14 @@ components: id: type: string + TpmConfig: + required: + - socket + type: object + properties: + socket: + type: string + VdpaConfig: required: - path diff --git a/vmm/src/config.rs b/vmm/src/config.rs index 45623308b..14b654e57 100644 --- a/vmm/src/config.rs +++ b/vmm/src/config.rs @@ -95,6 +95,10 @@ pub enum Error { ParseVdpa(OptionParserError), /// Missing path for vDPA device ParseVdpaPathMissing, + /// Failed parsing TPM device + ParseTpm(OptionParserError), + /// Missing path for TPM device + ParseTpmPathMissing, } #[derive(Debug, PartialEq, Eq, Error)] @@ -331,6 +335,8 @@ impl fmt::Display for Error { ParsePlatform(o) => write!(f, "Error parsing --platform: {}", o), ParseVdpa(o) => write!(f, "Error parsing --vdpa: {}", o), ParseVdpaPathMissing => write!(f, "Error parsing --vdpa: path missing"), + ParseTpm(o) => write!(f, "Error parsing --tpm: {}", o), + ParseTpmPathMissing => write!(f, "Error parsing --tpm: path missing"), } } } @@ -372,6 +378,7 @@ pub struct VmParams<'a> { #[cfg(feature = "guest_debug")] pub gdb: bool, pub platform: Option<&'a str>, + pub tpm: Option<&'a str>, } impl<'a> VmParams<'a> { @@ -423,6 +430,7 @@ impl<'a> VmParams<'a> { let platform = args.get_one::("platform").map(|x| x as &str); #[cfg(feature = "guest_debug")] let gdb = args.contains_id("gdb"); + let tpm: Option<&str> = args.get_one::("tpm").map(|x| x as &str); VmParams { cpus, memory, @@ -450,6 +458,7 @@ impl<'a> VmParams<'a> { #[cfg(feature = "guest_debug")] gdb, platform, + tpm, } } } @@ -1751,6 +1760,21 @@ impl RestoreConfig { } } +impl TpmConfig { + pub const SYNTAX: &'static str = "TPM device \ + \"(UNIX Domain Socket from swtpm) socket=\""; + pub fn parse(tpm: &str) -> Result { + let mut parser = OptionParser::new(); + parser.add("socket"); + parser.parse(tpm).map_err(Error::ParseTpm)?; + let socket = parser + .get("socket") + .map(PathBuf::from) + .ok_or(Error::ParseTpmPathMissing)?; + Ok(TpmConfig { socket }) + } +} + impl VmConfig { fn validate_identifier( id_list: &mut BTreeSet, @@ -2137,6 +2161,14 @@ impl VmConfig { None }; + let mut tpm: Option = None; + if let Some(tc) = vm_params.tpm { + let tpm_conf = TpmConfig::parse(tc)?; + tpm = Some(TpmConfig { + socket: tpm_conf.socket, + }); + } + #[cfg(feature = "guest_debug")] let gdb = vm_params.gdb; @@ -2164,6 +2196,7 @@ impl VmConfig { #[cfg(feature = "guest_debug")] gdb, platform, + tpm, }; config.validate().map_err(Error::Validation)?; Ok(config) @@ -2688,6 +2721,19 @@ mod tests { Ok(()) } + #[test] + fn test_tpm_parsing() -> Result<()> { + // path is required + assert!(TpmConfig::parse("").is_err()); + assert_eq!( + TpmConfig::parse("socket=/var/run/tpm.sock")?, + TpmConfig { + socket: PathBuf::from("/var/run/tpm.sock"), + } + ); + Ok(()) + } + #[test] fn test_vsock_parsing() -> Result<()> { // socket and cid is required @@ -2771,6 +2817,7 @@ mod tests { #[cfg(feature = "guest_debug")] gdb: false, platform: None, + tpm: None, }; assert!(valid_config.validate().is_ok()); diff --git a/vmm/src/lib.rs b/vmm/src/lib.rs index 89c3163aa..112be7e25 100644 --- a/vmm/src/lib.rs +++ b/vmm/src/lib.rs @@ -2099,6 +2099,7 @@ mod unit_tests { #[cfg(feature = "guest_debug")] gdb: false, platform: None, + tpm: None, })) } diff --git a/vmm/src/vm_config.rs b/vmm/src/vm_config.rs index 0d611d5f7..376f2057d 100644 --- a/vmm/src/vm_config.rs +++ b/vmm/src/vm_config.rs @@ -555,6 +555,11 @@ pub fn default_console() -> ConsoleConfig { } } +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub struct TpmConfig { + pub socket: PathBuf, +} + #[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct VmConfig { #[serde(default)] @@ -587,4 +592,5 @@ pub struct VmConfig { #[cfg(feature = "guest_debug")] pub gdb: bool, pub platform: Option, + pub tpm: Option, }