[STACKED on #880] feat(simulator): add deterministic seeded attestations - #964
Merged
Conversation
Base automatically changed from
codex/fix-verifier-development-trust-label
to
master
August 5, 2026 00:47
…r-seeded-attestation # Conflicts: # dstack/dstack-attest/src/attestation.rs # dstack/dstack-util/src/system_setup.rs
kvinwang
force-pushed
the
codex/feat-simulator-seeded-attestation
branch
6 times, most recently
from
August 5, 2026 10:24
4ae5f1e to
8c50307
Compare
kvinwang
force-pushed
the
codex/feat-simulator-seeded-attestation
branch
from
August 5, 2026 10:33
8c50307 to
11a334c
Compare
kvinwang
added a commit
that referenced
this pull request
Aug 5, 2026
…file [STACKED on #964] fix(verifier): validate one-shot certificate profile
kvinwang
added a commit
that referenced
this pull request
Aug 5, 2026
…ap-collateral [STACKED on #964] fix(mock-attestation): model complete DCAP collateral
kvinwang
added a commit
that referenced
this pull request
Aug 5, 2026
…testation [STACKED on #964] fix(simulator): preserve legacy attestation responses
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Builds on #880 (merged).
Problem
A development guest cannot verify the KMS and the gateway it talks to when those
run under the simulated PKI, and two independent defects stood in the way.
1. The seeded mock TDX PKI was not reproducible across processes. The seed
only fed key derivation; everything around it was nondeterministic:
cert_paramsand the root CRL usedOffsetDateTime::now_utc(), so validitywindows differed per process and per run.
rcgen's ownKeyPair, whose ECDSAbackend draws a random nonce, so the same key over the same TBS bytes
produced a different signature each time.
The host collateral service and the in-guest simulator therefore reconstructed
different root certificates from the same seed. A quote produced in one
process could not be verified against collateral produced in the other, which is
exactly the arrangement a simulated deployment runs in.
2. The guest verifier was hard-wired to production roots. Both
dstack-util(stage0 and the gateway path) andguest-agentcalledAttestationVerifier::new_prod(...)directly, so a simulated guest had no way toauthenticate a simulated key provider. The obvious fix — let the host hand the
guest a root through sys-config — is not available: the host sits outside the
trust boundary, and a host-chosen trust anchor lets it stand up a fake KMS and
hand the guest keys it never earned.
Fix
Make the seeded PKI byte-identical across processes. Fixed
MOCK_PKI_NOT_BEFORE/MOCK_PKI_NOT_AFTERconstants replace wall-clockvalidity, and every certificate is now signed through
rcgen::RemoteKeyPairbacked by ap256::ecdsa::SigningKey, whose RFC 6979nonce makes the signature a pure function of key and message. Same seed ⇒ same
roots, same CRL, same collateral, in any process.
Move the trust anchor handoff entirely inside the guest.
dstack-tee-simulatorderives the roots from the same seed it already uses tosign evidence, and writes them to
/run/dstack/attestation— guest tmpfs thehost cannot reach — before
dstack-prepareruns. The host supplies a seed for afake TEE device through
.tee-simulator.jsonand never names a trust anchor.One decision point on the reader side.
dstack_attest::default_verifierisnow the only place a guest component picks a trust anchor, and both
dstack-utilandguest-agentgo through it. It readstrust_anchors::load_anchors, which accepts only that one directory andvalidates what it finds:
symlink_metadataso a plantedsymlink cannot pass while resolving elsewhere;
or tampered
roots.jsoncannot redirect the verifier at a host-shared file.When the directory is absent — the only outcome on a production image, which
does not ship the simulator and whose contents are measured —
new_prodrootsapply, unchanged. The systemd unit removes the directory in
ExecStopPost, soexternal roots never outlive the simulator run that published them.
Services still configured with a mock root through hand-written TOML (KMS,
gateway,
dstack-verifier) keep needing an explicitinsecure_allow_external_trust_anchors = true. The guest path sets itinternally because there is no operator to warn: one program in this guest wrote
the roots and the next authenticated the directory before reading them.
Seeded guest-agent-simulator. An optional
mock_attestation_seedmakes thesimulator re-sign fixture attestations with the seeded PKI, preserving the
fixture's
mr_tdand RTMRs while binding the requestedreport_data, so thequote it returns actually verifies.
Changed paths
dstack/crates/mock-attestation/src/tdx.rsdstack/crates/mock-attestation/README.mddstack/dstack-attest/src/lib.rsdefault_verifier— the single guest-side trust anchor decisiondstack/dstack-attest/src/trust_anchors.rsANCHOR_DIR,roots_path,load_anchorsplus the ownership/mode/containment checksdstack/dstack-util/src/system_setup.rsdefault_verifierdstack/guest-agent/src/rpc_service.rsdstack/tee-simulator/src/main.rsdstack/tee-simulator/tests/process_e2e.rsdstack/guest-agent-simulator/src/{main,simulator}.rsdstack/vmm/src/app.rsos/.../dstack-tee-simulator.service/run/dstack/attestationon stopVerification
cargo clippy --all-targetsis clean workspace-wide; tests pass for everytouched crate, including the process-level e2e. New coverage:
mock-attestation:seeded_hierarchies_are_cross_process_compatible— twoTdxGenerator::from_seedinstances produce identical roots and CRL, and aquote from one verifies under
dcap_qvlagainst collateral from the other.This is the test that would have failed before the determinism fix.
dstack-attest::trust_anchors: absent directory selects production roots;published roots round-trip and load into a real
AttestationVerifier; amalformed root fails verifier construction; world-writable roots are rejected;
a root outside the published directory is rejected.
dstack-tee-simulator: the default runtime dir resolves toANCHOR_DIR; thepublished TDX root equals the seeded PKI's root; a seedless config publishes
nothing and fails loudly.
dstack-tee-simulatore2e: a separately spawned simulator process publishes aroot that verifies the quote read back through the simulated TSM device.
guest-agent-simulator:seeded_simulator_resigns_certificate_attestation—the re-signed certificate attestation verifies under the seeded root and
carries the RA-TLS
report_datafor the requested public key.dstack-vmm:make_sys_configoutput contains noroot_ca/trust_anchorkey in any deployment mode.