From 485cdd2a15ac8fca0858a20179ac6e5eded0b917 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Mon, 27 Jul 2026 21:06:44 +0000 Subject: [PATCH 1/5] fix(verifier): label development trust evidence --- dstack/dstack-attest/Cargo.toml | 2 ++ dstack/dstack-attest/src/attestation.rs | 33 ++++++++++++++++++++++++- dstack/verifier/src/types.rs | 2 ++ dstack/verifier/src/verification.rs | 5 +++- 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/dstack/dstack-attest/Cargo.toml b/dstack/dstack-attest/Cargo.toml index 3c41c5e0d..5d5465489 100644 --- a/dstack/dstack-attest/Cargo.toml +++ b/dstack/dstack-attest/Cargo.toml @@ -62,3 +62,5 @@ quote = [ futures = { workspace = true } tokio = { workspace = true, features = ["full"] } dstack-mr = { workspace = true } +mock-attestation = { path = "../crates/mock-attestation" } +tempfile = { workspace = true } diff --git a/dstack/dstack-attest/src/attestation.rs b/dstack/dstack-attest/src/attestation.rs index a3b5b323c..4b6ef17a2 100644 --- a/dstack/dstack-attest/src/attestation.rs +++ b/dstack/dstack-attest/src/attestation.rs @@ -61,6 +61,7 @@ pub struct AttestationVerifier { aws_nitro_tpm: nsm_qvl::QuoteVerifier, sev_snp: sev_snp_qvl::QuoteVerifier, amd_kds: AmdKdsClient, + external_trust_anchors: bool, } impl AttestationVerifier { @@ -148,6 +149,7 @@ impl AttestationVerifier { aws_nitro_tpm: nsm(aws_nitro_tpm.as_deref(), "AWS NitroTPM")?, sev_snp, amd_kds: AmdKdsClient::with_base_url(amd_kds)?, + external_trust_anchors: external_requested, }) } @@ -173,9 +175,18 @@ impl AttestationVerifier { .filter(|url| !url.trim().is_empty()) .unwrap_or(sev_snp_qvl::AMD_KDS_DEFAULT_BASE_URL), )?, + external_trust_anchors: false, }) } + /// Whether this verifier accepts development-only external trust roots. + /// + /// A true value must be surfaced as simulated evidence by every caller; + /// production roots never set this flag. + pub fn is_simulated(&self) -> bool { + self.external_trust_anchors + } + async fn verify_tdx_quote(&self, quote: &[u8]) -> Result { let collateral = self.tdx_collateral.fetch(quote).await?; let now = SystemTime::now() @@ -2515,8 +2526,28 @@ mod tests { #[test] fn production_attestation_verifier_loads_all_safe_defaults() { - AttestationVerifier::load(&AttestationVerifierConfig::default()) + let verifier = AttestationVerifier::load(&AttestationVerifierConfig::default()) .expect("production roots and URLs must load"); + assert!(!verifier.is_simulated()); + } + + #[test] + fn opted_in_mock_root_is_labeled_simulated() { + let directory = tempfile::tempdir().expect("temporary mock root directory"); + let root = directory.path().join("tdx-root.pem"); + let generator = + mock_attestation::tdx::TdxGenerator::from_seed([0x31; 32]).expect("mock TDX hierarchy"); + fs_err::write(&root, generator.root_ca_pem()).expect("write mock root"); + let verifier = AttestationVerifier::load(&AttestationVerifierConfig { + insecure_allow_external_trust_anchors: true, + root_ca: RootCaPaths { + tdx: Some(root), + ..Default::default() + }, + ..Default::default() + }) + .expect("explicit development verifier"); + assert!(verifier.is_simulated()); } #[test] diff --git a/dstack/verifier/src/types.rs b/dstack/verifier/src/types.rs index 1d4851526..50ff28d42 100644 --- a/dstack/verifier/src/types.rs +++ b/dstack/verifier/src/types.rs @@ -78,6 +78,8 @@ impl PolicyBootInfo { #[derive(Debug, Clone, Default, Serialize)] pub struct VerificationDetails { + /// True when verification used explicitly opted-in development trust roots. + pub simulated: bool, pub quote_verified: bool, /// Indicates that the event log was verified against the quote. /// diff --git a/dstack/verifier/src/verification.rs b/dstack/verifier/src/verification.rs index d38bfa1d8..79630d67b 100644 --- a/dstack/verifier/src/verification.rs +++ b/dstack/verifier/src/verification.rs @@ -585,7 +585,10 @@ impl CvmVerifier { } else { bail!("Quote is required"); }; - let mut details = VerificationDetails::default(); + let mut details = VerificationDetails { + simulated: self.attestation_verifier.is_simulated(), + ..Default::default() + }; let debug = request.debug.unwrap_or(false); let attestation = attestation.into_v1(); From a000333ac186eeab1ebb32f6186245b8654b00a8 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Mon, 27 Jul 2026 21:10:37 +0000 Subject: [PATCH 2/5] chore(attestation): update development test lockfile --- dstack/Cargo.lock | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dstack/Cargo.lock b/dstack/Cargo.lock index a0167f769..6f4b039c2 100644 --- a/dstack/Cargo.lock +++ b/dstack/Cargo.lock @@ -1780,6 +1780,7 @@ dependencies = [ "hex_fmt", "hmac 0.12.1", "insta", + "mock-attestation", "nsm-attest", "nsm-qvl", "or-panic", @@ -1798,6 +1799,7 @@ dependencies = [ "sha2 0.10.9", "sha3", "tdx-attest", + "tempfile", "tokio", "tpm-attest", "tpm-qvl", From c2936f6c13f4d52bff4ca40a320f5ceb86ad0530 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Mon, 3 Aug 2026 23:26:23 -0700 Subject: [PATCH 3/5] Revert "chore(attestation): update development test lockfile" This reverts commit a000333ac186eeab1ebb32f6186245b8654b00a8. --- dstack/Cargo.lock | 2 -- 1 file changed, 2 deletions(-) diff --git a/dstack/Cargo.lock b/dstack/Cargo.lock index 6f4b039c2..a0167f769 100644 --- a/dstack/Cargo.lock +++ b/dstack/Cargo.lock @@ -1780,7 +1780,6 @@ dependencies = [ "hex_fmt", "hmac 0.12.1", "insta", - "mock-attestation", "nsm-attest", "nsm-qvl", "or-panic", @@ -1799,7 +1798,6 @@ dependencies = [ "sha2 0.10.9", "sha3", "tdx-attest", - "tempfile", "tokio", "tpm-attest", "tpm-qvl", From 87d4dc1842bf7e2136c3897e698928747b06a35d Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Mon, 3 Aug 2026 23:26:23 -0700 Subject: [PATCH 4/5] Revert "fix(verifier): label development trust evidence" This reverts commit 485cdd2a15ac8fca0858a20179ac6e5eded0b917. --- dstack/dstack-attest/Cargo.toml | 2 -- dstack/dstack-attest/src/attestation.rs | 33 +------------------------ dstack/verifier/src/types.rs | 2 -- dstack/verifier/src/verification.rs | 5 +--- 4 files changed, 2 insertions(+), 40 deletions(-) diff --git a/dstack/dstack-attest/Cargo.toml b/dstack/dstack-attest/Cargo.toml index 5d5465489..3c41c5e0d 100644 --- a/dstack/dstack-attest/Cargo.toml +++ b/dstack/dstack-attest/Cargo.toml @@ -62,5 +62,3 @@ quote = [ futures = { workspace = true } tokio = { workspace = true, features = ["full"] } dstack-mr = { workspace = true } -mock-attestation = { path = "../crates/mock-attestation" } -tempfile = { workspace = true } diff --git a/dstack/dstack-attest/src/attestation.rs b/dstack/dstack-attest/src/attestation.rs index 4b6ef17a2..a3b5b323c 100644 --- a/dstack/dstack-attest/src/attestation.rs +++ b/dstack/dstack-attest/src/attestation.rs @@ -61,7 +61,6 @@ pub struct AttestationVerifier { aws_nitro_tpm: nsm_qvl::QuoteVerifier, sev_snp: sev_snp_qvl::QuoteVerifier, amd_kds: AmdKdsClient, - external_trust_anchors: bool, } impl AttestationVerifier { @@ -149,7 +148,6 @@ impl AttestationVerifier { aws_nitro_tpm: nsm(aws_nitro_tpm.as_deref(), "AWS NitroTPM")?, sev_snp, amd_kds: AmdKdsClient::with_base_url(amd_kds)?, - external_trust_anchors: external_requested, }) } @@ -175,18 +173,9 @@ impl AttestationVerifier { .filter(|url| !url.trim().is_empty()) .unwrap_or(sev_snp_qvl::AMD_KDS_DEFAULT_BASE_URL), )?, - external_trust_anchors: false, }) } - /// Whether this verifier accepts development-only external trust roots. - /// - /// A true value must be surfaced as simulated evidence by every caller; - /// production roots never set this flag. - pub fn is_simulated(&self) -> bool { - self.external_trust_anchors - } - async fn verify_tdx_quote(&self, quote: &[u8]) -> Result { let collateral = self.tdx_collateral.fetch(quote).await?; let now = SystemTime::now() @@ -2526,28 +2515,8 @@ mod tests { #[test] fn production_attestation_verifier_loads_all_safe_defaults() { - let verifier = AttestationVerifier::load(&AttestationVerifierConfig::default()) + AttestationVerifier::load(&AttestationVerifierConfig::default()) .expect("production roots and URLs must load"); - assert!(!verifier.is_simulated()); - } - - #[test] - fn opted_in_mock_root_is_labeled_simulated() { - let directory = tempfile::tempdir().expect("temporary mock root directory"); - let root = directory.path().join("tdx-root.pem"); - let generator = - mock_attestation::tdx::TdxGenerator::from_seed([0x31; 32]).expect("mock TDX hierarchy"); - fs_err::write(&root, generator.root_ca_pem()).expect("write mock root"); - let verifier = AttestationVerifier::load(&AttestationVerifierConfig { - insecure_allow_external_trust_anchors: true, - root_ca: RootCaPaths { - tdx: Some(root), - ..Default::default() - }, - ..Default::default() - }) - .expect("explicit development verifier"); - assert!(verifier.is_simulated()); } #[test] diff --git a/dstack/verifier/src/types.rs b/dstack/verifier/src/types.rs index 50ff28d42..1d4851526 100644 --- a/dstack/verifier/src/types.rs +++ b/dstack/verifier/src/types.rs @@ -78,8 +78,6 @@ impl PolicyBootInfo { #[derive(Debug, Clone, Default, Serialize)] pub struct VerificationDetails { - /// True when verification used explicitly opted-in development trust roots. - pub simulated: bool, pub quote_verified: bool, /// Indicates that the event log was verified against the quote. /// diff --git a/dstack/verifier/src/verification.rs b/dstack/verifier/src/verification.rs index 79630d67b..d38bfa1d8 100644 --- a/dstack/verifier/src/verification.rs +++ b/dstack/verifier/src/verification.rs @@ -585,10 +585,7 @@ impl CvmVerifier { } else { bail!("Quote is required"); }; - let mut details = VerificationDetails { - simulated: self.attestation_verifier.is_simulated(), - ..Default::default() - }; + let mut details = VerificationDetails::default(); let debug = request.debug.unwrap_or(false); let attestation = attestation.into_v1(); From 20e9059167c3b7823bdb7b8dd5a45d20fe5000d9 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Mon, 3 Aug 2026 23:27:59 -0700 Subject: [PATCH 5/5] test(attestation): verify simulator trust-root isolation --- dstack/tests/e2e/attestation/run-platform.sh | 37 +++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/dstack/tests/e2e/attestation/run-platform.sh b/dstack/tests/e2e/attestation/run-platform.sh index 8b8fc2bc9..8a826b9c6 100755 --- a/dstack/tests/e2e/attestation/run-platform.sh +++ b/dstack/tests/e2e/attestation/run-platform.sh @@ -210,4 +210,39 @@ jq -e '.details.quote_verified == true' "$WORK/request.json.verification.json" > jq -e '.is_valid == true' "$WORK/request.json.verification.json" >/dev/null jq -e '.details.os_image_hash_verified == true' "$WORK/request.json.verification.json" >/dev/null jq -e '.details.event_log_verified == true' "$WORK/request.json.verification.json" >/dev/null -echo "[$TEE_PLATFORM${TDX_ATTESTATION_VARIANT:+/$TDX_ATTESTATION_VARIANT}] dstack-util -> verifier full E2E passed" +cp "$WORK/request.json.verification.json" "$WORK/development-root-verification.json" +echo '{"development_root_accepted":true}' + +# The same simulator evidence must fail against the verifier's built-in +# production roots. Keep the mock collateral endpoints so this assertion tests +# the trust-anchor boundary rather than network or collateral availability. +cat > "$WORK/production-verifier.toml" <"$WORK/production-verifier.log" 2>&1 +PRODUCTION_VERIFIER_RC=$? +set -e +if [[ ! -s "$WORK/request.json.verification.json" ]]; then + cat "$WORK/production-verifier.log" >&2 + echo "production-root verifier did not emit a verification result" >&2 + exit 1 +fi +cat "$WORK/request.json.verification.json" +if (( PRODUCTION_VERIFIER_RC == 0 )) || ! jq -e '.is_valid == false' "$WORK/request.json.verification.json" >/dev/null; then + cat "$WORK/production-verifier.log" >&2 + echo "simulator evidence unexpectedly passed production-root verification" >&2 + exit 1 +fi +echo '{"production_root_rejected":true}' +echo "[$TEE_PLATFORM${TDX_ATTESTATION_VARIANT:+/$TDX_ATTESTATION_VARIANT}] dstack-util -> verifier trust-root isolation E2E passed"