From d3624ec1ef8ff804929c9d029ee1b5af10670881 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Wed, 8 Jul 2026 08:44:00 -0700 Subject: [PATCH] feat(vmm): support tdx acpi table requirement --- dstack-types/src/lib.rs | 26 +++- dstack-util/src/system_setup.rs | 114 +++++++++++++++++- sdk/go/dstack/compose_hash.go | 5 +- sdk/js/src/get-compose-hash.ts | 1 + sdk/python/src/dstack_sdk/get_compose_hash.py | 4 + verifier/README.md | 5 + verifier/fixtures/tdx-lite.README.md | 3 +- verifier/src/main.rs | 4 + verifier/src/types.rs | 7 ++ verifier/src/verification.rs | 35 +++++- vmm/src/app.rs | 99 +++++++++++++-- vmm/src/one_shot.rs | 8 +- vmm/ui/src/composables/useVmManager.ts | 1 + 13 files changed, 292 insertions(+), 20 deletions(-) diff --git a/dstack-types/src/lib.rs b/dstack-types/src/lib.rs index 44a3b2b4c..e154abafc 100644 --- a/dstack-types/src/lib.rs +++ b/dstack-types/src/lib.rs @@ -126,11 +126,18 @@ pub struct Requirements { /// an explicit empty list means no platform is allowed. #[serde(skip_serializing_if = "Option::is_none")] pub platforms: Option>, + /// TDX-only ACPI table measurement requirement. When set, this overrides + /// the VMM-side TDX lite attestation policy: `true` requires legacy mode + /// with ACPI tables measured, while `false` requires lite mode. + #[serde(skip_serializing_if = "Option::is_none")] + pub tdx_measure_acpi_tables: Option, } impl Requirements { pub fn is_empty(&self) -> bool { - self.os_version.is_none() && self.platforms.is_none() + self.os_version.is_none() + && self.platforms.is_none() + && self.tdx_measure_acpi_tables.is_none() } } @@ -381,7 +388,8 @@ mod app_compose_tests { "runner": "docker-compose", "requirements": { "os_version": ">=0.6.1", - "platforms": ["dstack-gcp-tdx", "dstack-tdx"] + "platforms": ["dstack-gcp-tdx", "dstack-tdx"], + "tdx_measure_acpi_tables": true } })) .unwrap(); @@ -391,6 +399,7 @@ mod app_compose_tests { requirements.platforms, Some(vec!["dstack-gcp-tdx".to_string(), "dstack-tdx".to_string()]) ); + assert_eq!(requirements.tdx_measure_acpi_tables, Some(true)); let err = serde_json::from_value::(serde_json::json!({ "manifest_version": "3", @@ -429,6 +438,19 @@ mod app_compose_tests { let requirements = explicit_empty.requirements.as_ref().unwrap(); assert_eq!(requirements.platforms, Some(vec![])); assert!(!requirements.is_empty()); + + let acpi_tables: AppCompose = serde_json::from_value(serde_json::json!({ + "manifest_version": "3", + "name": "test", + "runner": "docker-compose", + "requirements": { + "tdx_measure_acpi_tables": false + } + })) + .unwrap(); + let requirements = acpi_tables.requirements.as_ref().unwrap(); + assert_eq!(requirements.tdx_measure_acpi_tables, Some(false)); + assert!(!requirements.is_empty()); } } diff --git a/dstack-util/src/system_setup.rs b/dstack-util/src/system_setup.rs index bc65dedff..c5df5dfec 100644 --- a/dstack-util/src/system_setup.rs +++ b/dstack-util/src/system_setup.rs @@ -774,7 +774,7 @@ fn verify_manifest_version(app_compose: &AppCompose) -> Result { Ok(manifest_version) } -fn verify_app_compose_policy(app_compose: &AppCompose) -> Result<()> { +fn verify_app_compose_policy(app_compose: &AppCompose, sys_config: &SysConfig) -> Result<()> { verify_manifest_feature_requirements(app_compose)?; let Some(requirements) = app_compose.requirements.as_ref() else { return Ok(()); @@ -789,9 +789,18 @@ fn verify_app_compose_policy(app_compose: &AppCompose) -> Result<()> { bail!("Unsupported attestation platform: requirements.platforms is empty"); } let current_platform = - AttestationMode::detect().context("Failed to detect current attestation platform")?; + AttestationMode::detect().context("failed to detect current attestation platform")?; verify_platform_requirements(app_compose, current_platform)?; } + if requirements.tdx_measure_acpi_tables.is_some() { + let current_platform = + AttestationMode::detect().context("failed to detect current attestation platform")?; + verify_tdx_measure_acpi_tables_requirement( + app_compose, + &sys_config.vm_config, + current_platform, + )?; + } Ok(()) } @@ -867,6 +876,41 @@ fn format_requirement_platforms(platforms: &[String]) -> String { platforms.join(", ") } +fn verify_tdx_measure_acpi_tables_requirement( + app_compose: &AppCompose, + vm_config: &str, + current_platform: AttestationMode, +) -> Result<()> { + let Some(measure_acpi_tables) = app_compose + .requirements + .as_ref() + .and_then(|requirements| requirements.tdx_measure_acpi_tables) + else { + return Ok(()); + }; + if current_platform != AttestationMode::DstackTdx { + return Ok(()); + } + let vm_config: dstack_types::VmConfig = serde_json::from_str(vm_config) + .context("failed to parse vm_config for requirements.tdx_measure_acpi_tables")?; + let uses_lite = vm_config.tdx_attestation_variant.is_lite(); + if measure_acpi_tables && uses_lite { + bail!( + "unsupported TDX attestation mode: requirements.tdx_measure_acpi_tables=true requires ACPI table measurement" + ); + } + if !measure_acpi_tables && !uses_lite { + bail!( + "unsupported TDX attestation mode: requirements.tdx_measure_acpi_tables=false requires TDX lite attestation" + ); + } + info!( + "tdx ACPI table measurement requirement satisfied: measure_acpi_tables={}", + measure_acpi_tables + ); + Ok(()) +} + fn read_current_os_version() -> Result { const OS_RELEASE_PATHS: &[&str] = &["/etc/os-release", "/usr/lib/os-release"]; for path in OS_RELEASE_PATHS { @@ -930,7 +974,7 @@ pub async fn cmd_sys_setup(args: SetupArgs) -> Result<()> { } async fn do_sys_setup(stage0: Stage0<'_>) -> Result<()> { - verify_app_compose_policy(&stage0.shared.app_compose) + verify_app_compose_policy(&stage0.shared.app_compose, &stage0.shared.sys_config) .context("Failed to verify app-compose policy")?; if stage0.shared.app_compose.secure_time { info!("Waiting for the system time to be synchronized"); @@ -2198,6 +2242,70 @@ fn test_platform_requirements_reject_invalid_platform_value() { .contains("Invalid requirements.platforms[0]")); } +#[test] +fn test_tdx_measure_acpi_tables_requirement_matches_vm_config() { + let app_compose: AppCompose = serde_json::from_value(serde_json::json!({ + "manifest_version": "3", + "name": "test", + "runner": "docker-compose", + "requirements": { + "tdx_measure_acpi_tables": true + } + })) + .unwrap(); + verify_tdx_measure_acpi_tables_requirement(&app_compose, r#"{}"#, AttestationMode::DstackTdx) + .unwrap(); + let err = verify_tdx_measure_acpi_tables_requirement( + &app_compose, + r#"{"tdx_attestation_variant":"lite"}"#, + AttestationMode::DstackTdx, + ) + .unwrap_err(); + assert!(err.to_string().contains("tdx_measure_acpi_tables=true")); + + let app_compose: AppCompose = serde_json::from_value(serde_json::json!({ + "manifest_version": "3", + "name": "test", + "runner": "docker-compose", + "requirements": { + "tdx_measure_acpi_tables": false + } + })) + .unwrap(); + verify_tdx_measure_acpi_tables_requirement( + &app_compose, + r#"{"tdx_attestation_variant":"lite"}"#, + AttestationMode::DstackTdx, + ) + .unwrap(); + let err = verify_tdx_measure_acpi_tables_requirement( + &app_compose, + r#"{}"#, + AttestationMode::DstackTdx, + ) + .unwrap_err(); + assert!(err.to_string().contains("tdx_measure_acpi_tables=false")); +} + +#[test] +fn test_tdx_measure_acpi_tables_requirement_ignored_on_non_tdx() { + let app_compose: AppCompose = serde_json::from_value(serde_json::json!({ + "manifest_version": "3", + "name": "test", + "runner": "docker-compose", + "requirements": { + "tdx_measure_acpi_tables": true + } + })) + .unwrap(); + verify_tdx_measure_acpi_tables_requirement( + &app_compose, + r#"{"tdx_attestation_variant":"lite"}"#, + AttestationMode::DstackAmdSevSnp, + ) + .unwrap(); +} + #[test] fn test_os_release_value_parses_quoted_version_id() { let content = r#" diff --git a/sdk/go/dstack/compose_hash.go b/sdk/go/dstack/compose_hash.go index c5accce44..30e7a8e11 100644 --- a/sdk/go/dstack/compose_hash.go +++ b/sdk/go/dstack/compose_hash.go @@ -39,8 +39,9 @@ const ( // Requirements represents guest-side requirements. type Requirements struct { - OsVersion string `json:"os_version,omitempty"` - Platforms *[]RequirementPlatform `json:"platforms,omitempty"` + OsVersion string `json:"os_version,omitempty"` + Platforms *[]RequirementPlatform `json:"platforms,omitempty"` + TdxMeasureAcpiTables *bool `json:"tdx_measure_acpi_tables,omitempty"` } // AppCompose represents the application composition structure diff --git a/sdk/js/src/get-compose-hash.ts b/sdk/js/src/get-compose-hash.ts index 27d3938a5..3b022cfb4 100644 --- a/sdk/js/src/get-compose-hash.ts +++ b/sdk/js/src/get-compose-hash.ts @@ -51,6 +51,7 @@ export interface DockerConfig extends SortableObject { export interface Requirements extends SortableObject { os_version?: string; platforms?: RequirementPlatform[]; + tdx_measure_acpi_tables?: boolean; } export interface AppCompose extends SortableObject { diff --git a/sdk/python/src/dstack_sdk/get_compose_hash.py b/sdk/python/src/dstack_sdk/get_compose_hash.py index e83d176d7..a06a9e230 100644 --- a/sdk/python/src/dstack_sdk/get_compose_hash.py +++ b/sdk/python/src/dstack_sdk/get_compose_hash.py @@ -53,10 +53,12 @@ def __init__( self, os_version: Optional[str] = None, platforms: Optional[List[str]] = None, + tdx_measure_acpi_tables: Optional[bool] = None, ) -> None: """Initialize a new ``Requirements`` instance.""" self.os_version = os_version self.platforms = platforms + self.tdx_measure_acpi_tables = tdx_measure_acpi_tables def to_dict(self) -> Dict[str, Any]: """Return a dictionary representation excluding ``None`` fields.""" @@ -65,6 +67,8 @@ def to_dict(self) -> Dict[str, Any]: result["os_version"] = self.os_version if self.platforms is not None: result["platforms"] = self.platforms + if self.tdx_measure_acpi_tables is not None: + result["tdx_measure_acpi_tables"] = self.tdx_measure_acpi_tables return result diff --git a/verifier/README.md b/verifier/README.md index 92a6f8264..043821b2f 100644 --- a/verifier/README.md +++ b/verifier/README.md @@ -33,6 +33,7 @@ or "quote_verified": true, "event_log_verified": true, // See "Verification Process" for semantics "os_image_hash_verified": true, + "acpi_tables_verified": true, // true only when TDX ACPI table contents are verified "os_image_is_dev": false, // true=dev image, false=prod, null=unknown/N/A "os_image_version": "0.5.10", // dstack OS version, null if unknown "attestation_mode": "dstack-tdx", // dstack-tdx | dstack-gcp-tdx | dstack-nitro-enclave | dstack-amd-sev-snp @@ -154,6 +155,7 @@ $ curl -s -d @quote.json localhost:8080/verify | jq "quote_verified": true, "event_log_verified": true, "os_image_hash_verified": true, + "acpi_tables_verified": true, "report_data": "12340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "tcb_status": "UpToDate", "advisory_ids": [], @@ -188,6 +190,8 @@ The verifier performs three main verification steps: - For the full-image TDX path, downloads or loads the image identified by `os_image_hash`, checks the image checksum manifest, uses dstack-mr to compute expected MRTD/RTMR0-2, and compares them against the verified measurements from the quote - For TDX lite, AMD SEV-SNP, and GCP TDX, verifies that `os_image_hash = sha256(sha256sum.txt)`, where `sha256sum.txt` is the image build's checksum manifest (` ` lines for image files), that the manifest entry for `measurement.tdx.cbor`, `measurement.snp.cbor`, or `measurement.gcp.cbor` matches the supplied measurement material, and that the measurement material replays to the quote's hardware-signed measurements or GCP TPM UKI event +`details.acpi_tables_verified` is `true` only for the full-image TDX path, where the verifier recomputes ACPI table contents and checks the resulting RTMRs against the quote. It is `false` for TDX lite, which uses the quote's named ACPI DATA digests without validating table contents, and for non-TDX platforms where ACPI table verification is not applicable. + All three steps must pass for the verification to be considered valid. ### Identifying the deployment @@ -197,6 +201,7 @@ Beyond pass/fail, the result carries a few descriptive fields so a relying party - **`os_image_is_dev`** — `true` for a development OS image, `false` for production. Dev images are built for local testing and are not hardened for production use, so a relying party generally wants to reject them. - **`os_image_version`** — the dstack OS version (e.g. `0.5.10`), useful for enforcing a minimum version. - **`attestation_mode`** — the attestation mode that produced the verified quote, serialized as `AttestationMode`: `dstack-tdx`, `dstack-gcp-tdx`, `dstack-nitro-enclave`, or `dstack-amd-sev-snp`. +- **`acpi_tables_verified`** — whether TDX ACPI table contents were verified. This is useful for relying parties that require `requirements.tdx_measure_acpi_tables = true`. - **`key_provider`** — the decoded `app_info.key_provider_info` (`{name, id}`); `name` is e.g. `kms` or `local`. A `local` key provider means the CVM is not KMS-backed, which is itself a dev/insecure posture signal. The raw bytes remain in `app_info.key_provider_info`. `os_image_is_dev` and `os_image_version` are read from the image's `metadata.json`, which is part of `sha256sum.txt` and therefore bound to the `os_image_hash` that step 3 verifies against the quote — so they are as trustworthy as the os-image-hash check itself. They are `null` when the platform does not expose them (e.g. GCP TDX / Nitro Enclave) or when the image predates the field (images without `is_dev` are always production). diff --git a/verifier/fixtures/tdx-lite.README.md b/verifier/fixtures/tdx-lite.README.md index 1b2160f81..01b977e92 100644 --- a/verifier/fixtures/tdx-lite.README.md +++ b/verifier/fixtures/tdx-lite.README.md @@ -62,4 +62,5 @@ dstack-verifier --config verifier-no-download.toml \ ``` Expected result: `Valid: true`, with quote, event log, and OS image hash all -verified. +verified, and `ACPI tables verified: false` because lite mode does not validate +ACPI table contents. diff --git a/verifier/src/main.rs b/verifier/src/main.rs index 49d698ac1..b21ecebfb 100644 --- a/verifier/src/main.rs +++ b/verifier/src/main.rs @@ -115,6 +115,10 @@ async fn run_oneshot(file_path: &str, config: &Config) -> anyhow::Result<()> { "OS image hash verified: {}", response.details.os_image_hash_verified ); + println!( + "ACPI tables verified: {}", + response.details.acpi_tables_verified + ); if let Some(tcb_status) = &response.details.tcb_status { println!("TCB status: {}", tcb_status); diff --git a/verifier/src/types.rs b/verifier/src/types.rs index 85f80e302..e679b704a 100644 --- a/verifier/src/types.rs +++ b/verifier/src/types.rs @@ -41,6 +41,13 @@ pub struct VerificationDetails { /// event log payloads. pub event_log_verified: bool, pub os_image_hash_verified: bool, + /// Indicates that TDX ACPI table contents were verified. + /// + /// This is true for the full-image TDX path, where the verifier recomputes + /// ACPI tables and checks the resulting RTMRs against the quote. It remains + /// false for TDX lite, which replays ACPI DATA digests from the event log + /// without validating the table contents. + pub acpi_tables_verified: bool, /// dev vs prod OS image, from metadata.json (bound to os_image_hash). None if not exposed. pub os_image_is_dev: Option, /// dstack OS version, from the same metadata.json. diff --git a/verifier/src/verification.rs b/verifier/src/verification.rs index d9be64bf6..9faf03459 100644 --- a/verifier/src/verification.rs +++ b/verifier/src/verification.rs @@ -794,7 +794,9 @@ impl CvmVerifier { event_log, debug, details, - ) + )?; + details.acpi_tables_verified = true; + Ok(()) } async fn verify_os_image_hash_for_dstack_tdx_lite( @@ -1264,6 +1266,7 @@ mod tests { assert!(response.details.quote_verified); assert!(response.details.event_log_verified); assert!(response.details.os_image_hash_verified); + assert!(!response.details.acpi_tables_verified); assert_eq!( response.details.attestation_mode, Some(ra_tls::attestation::AttestationMode::DstackAmdSevSnp) @@ -1297,4 +1300,34 @@ mod tests { Some(ra_tls::attestation::AttestationMode::DstackAmdSevSnp) ); } + + #[tokio::test] + async fn verifies_tdx_lite_fixture_without_acpi_table_verification() { + let request: VerificationRequest = + serde_json::from_str(include_str!("../fixtures/tdx-lite-attestation.json")) + .expect("TDX lite verifier fixture parses"); + let cache = tempfile::tempdir().expect("temp cache dir"); + let image_cache_dir = cache.path().join("cache"); + let verifier = CvmVerifier::new( + image_cache_dir.display().to_string(), + "http://127.0.0.1:9/should-not-download/{OS_IMAGE_HASH}.tar.gz".to_string(), + Duration::from_secs(1), + None, + ); + + let response = verifier.verify(request).await.expect("verifier runs"); + assert!(response.is_valid, "{:?}", response.reason); + assert!(response.details.quote_verified); + assert!(response.details.event_log_verified); + assert!(response.details.os_image_hash_verified); + assert!(!response.details.acpi_tables_verified); + assert_eq!( + response.details.attestation_mode, + Some(ra_tls::attestation::AttestationMode::DstackTdx) + ); + assert!( + !image_cache_dir.exists(), + "TDX lite verification must not download or cache OS images" + ); + } } diff --git a/vmm/src/app.rs b/vmm/src/app.rs index de47add88..1d7c358cf 100644 --- a/vmm/src/app.rs +++ b/vmm/src/app.rs @@ -1108,8 +1108,13 @@ impl App { } else { None }; - let sys_config_str = - make_sys_config(cfg, &manifest, &hex::encode(compose_hash), mr_config)?; + let sys_config_str = make_sys_config( + cfg, + &manifest, + &hex::encode(compose_hash), + mr_config, + app_compose.requirements.as_ref(), + )?; fs::write(shared_dir.join(SYS_CONFIG), sys_config_str) .context("Failed to write vm config")?; Ok(()) @@ -1252,6 +1257,7 @@ pub(crate) fn make_sys_config( manifest: &Manifest, compose_hash: &str, mr_config: Option, + requirements: Option<&dstack_types::Requirements>, ) -> Result { let image_path = cfg.image.path.join(&manifest.image); let image = Image::load(image_path).context("Failed to load image info")?; @@ -1270,13 +1276,21 @@ pub(crate) fn make_sys_config( bail!("Unsupported image version: {img_ver:?}"); } + let vm_config = make_vm_config( + cfg, + manifest, + &image, + compose_hash, + mr_config.clone(), + requirements, + )?; let mut sys_config = json!({ "kms_urls": kms_urls, "gateway_urls": gateway_urls, "pccs_url": cfg.cvm.pccs_url, "docker_registry": cfg.cvm.docker_registry, "host_api_url": format!("vsock://2:{}/api", cfg.host_api.port), - "vm_config": serde_json::to_string(&make_vm_config(cfg, manifest, &image, compose_hash, mr_config.clone())?)?, + "vm_config": serde_json::to_string(&vm_config)?, }); if let Some(mr_config) = mr_config { MrConfigV3::from_document(&mr_config).context("Invalid mr_config document")?; @@ -1319,20 +1333,37 @@ fn image_supports_tdx_lite(image: &Image) -> bool { && image.tdx_measurement.is_some() } +fn tdx_attestation_variant_from_requirements( + requirements: Option<&dstack_types::Requirements>, +) -> Option { + requirements + .and_then(|requirements| requirements.tdx_measure_acpi_tables) + .map(|measure_acpi_tables| { + if measure_acpi_tables { + dstack_types::TdxAttestationVariant::Legacy + } else { + dstack_types::TdxAttestationVariant::Lite + } + }) +} + fn make_vm_config( cfg: &Config, manifest: &Manifest, image: &Image, _compose_hash: &str, mr_config: Option, + requirements: Option<&dstack_types::Requirements>, ) -> Result { let platform = cfg.cvm.resolved_platform(); let is_amd_sev_snp = platform == crate::config::TeePlatform::AmdSevSnp && !manifest.no_tee; let is_tdx = platform == crate::config::TeePlatform::Tdx && !manifest.no_tee; let tdx_attestation_variant = if is_tdx { - cfg.cvm - .tdx_attestation_variant - .resolve(manifest.memory, image_supports_tdx_lite(image)) + tdx_attestation_variant_from_requirements(requirements).unwrap_or_else(|| { + cfg.cvm + .tdx_attestation_variant + .resolve(manifest.memory, image_supports_tdx_lite(image)) + }) } else { dstack_types::TdxAttestationVariant::Legacy }; @@ -1593,7 +1624,7 @@ mod tests { let config = test_tdx_config()?; let manifest = test_manifest(1024); let image = test_tdx_image(true); - let vm_config = make_vm_config(&config, &manifest, &image, &hex_of(0x22, 32), None)?; + let vm_config = make_vm_config(&config, &manifest, &image, &hex_of(0x22, 32), None, None)?; assert!(vm_config.get("tdx_attestation_variant").is_none()); assert!(vm_config.get("tdx_measurement").is_none()); @@ -1611,7 +1642,7 @@ mod tests { let config = test_tdx_config()?; let manifest = test_manifest(2048); let image = test_tdx_image(true); - let vm_config = make_vm_config(&config, &manifest, &image, &hex_of(0x22, 32), None)?; + let vm_config = make_vm_config(&config, &manifest, &image, &hex_of(0x22, 32), None, None)?; assert_eq!(vm_config["tdx_attestation_variant"], "lite"); assert!(vm_config.get("tdx_measurement").is_some()); @@ -1629,7 +1660,7 @@ mod tests { let config = test_tdx_config()?; let manifest = test_manifest(3072); let image = test_tdx_image(false); - let vm_config = make_vm_config(&config, &manifest, &image, &hex_of(0x22, 32), None)?; + let vm_config = make_vm_config(&config, &manifest, &image, &hex_of(0x22, 32), None, None)?; assert!(vm_config.get("tdx_attestation_variant").is_none()); assert!(vm_config.get("tdx_measurement").is_none()); @@ -1642,6 +1673,54 @@ mod tests { Ok(()) } + #[test] + fn tdx_requirements_measure_acpi_tables_overrides_lite_to_legacy() -> Result<()> { + let mut config = test_tdx_config()?; + config.cvm.tdx_attestation_variant = TdxAttestationVariantConfig::Lite; + let manifest = test_manifest(2048); + let image = test_tdx_image(true); + let requirements = dstack_types::Requirements { + tdx_measure_acpi_tables: Some(true), + ..Default::default() + }; + let vm_config = make_vm_config( + &config, + &manifest, + &image, + &hex_of(0x22, 32), + None, + Some(&requirements), + )?; + + assert!(vm_config.get("tdx_attestation_variant").is_none()); + assert!(vm_config.get("tdx_measurement").is_none()); + Ok(()) + } + + #[test] + fn tdx_requirements_skip_acpi_tables_overrides_legacy_to_lite() -> Result<()> { + let mut config = test_tdx_config()?; + config.cvm.tdx_attestation_variant = TdxAttestationVariantConfig::Legacy; + let manifest = test_manifest(2048); + let image = test_tdx_image(true); + let requirements = dstack_types::Requirements { + tdx_measure_acpi_tables: Some(false), + ..Default::default() + }; + let vm_config = make_vm_config( + &config, + &manifest, + &image, + &hex_of(0x22, 32), + None, + Some(&requirements), + )?; + + assert_eq!(vm_config["tdx_attestation_variant"], "lite"); + assert!(vm_config.get("tdx_measurement").is_some()); + Ok(()) + } + #[test] fn amd_sev_snp_sys_config_includes_measurement_input_and_mr_config() -> Result<()> { let temp = std::env::temp_dir().join(format!( @@ -1727,7 +1806,7 @@ mod tests { fs::write(image_dir.join("digest.txt"), hex::encode(&build_hash))?; let sys_config_document = - make_sys_config(&config, &manifest, &compose_hash, Some(mr_config))?; + make_sys_config(&config, &manifest, &compose_hash, Some(mr_config), None)?; let sys_config: serde_json::Value = serde_json::from_str(&sys_config_document)?; let vm_config: serde_json::Value = serde_json::from_str( sys_config["vm_config"] diff --git a/vmm/src/one_shot.rs b/vmm/src/one_shot.rs index 2b9b49f8f..3773fbcb2 100644 --- a/vmm/src/one_shot.rs +++ b/vmm/src/one_shot.rs @@ -253,7 +253,13 @@ Compose file content (first 200 chars): } else { None }; - let sys_config_str = make_sys_config(&config, &manifest, &compose_hash, mr_config)?; + let sys_config_str = make_sys_config( + &config, + &manifest, + &compose_hash, + mr_config, + app_compose.requirements.as_ref(), + )?; let sys_config_path = vm_work_dir.shared_dir().join(".sys-config.json"); fs_err::write(&sys_config_path, sys_config_str).context("Failed to write sys config")?; diff --git a/vmm/ui/src/composables/useVmManager.ts b/vmm/ui/src/composables/useVmManager.ts index 31af3c345..c716d1cc3 100644 --- a/vmm/ui/src/composables/useVmManager.ts +++ b/vmm/ui/src/composables/useVmManager.ts @@ -43,6 +43,7 @@ type RequirementPlatform = type Requirements = { os_version?: string; platforms?: RequirementPlatform[]; + tdx_measure_acpi_tables?: boolean; }; const x25519 = require('../lib/x25519.js');