Skip to content

Commit c25ad0a

Browse files
authored
Merge pull request #806 from Dstack-TEE/codex/external-attestation-root-ca
refactor(attestation): explicit trust configuration and mock E2E
2 parents f8ed2f1 + 70f66d4 commit c25ad0a

89 files changed

Lines changed: 7860 additions & 1277 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

dstack/Cargo.lock

Lines changed: 129 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dstack/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ members = [
6868
"crates/dstack-auth",
6969
"crates/api-auth",
7070
"crates/build-info",
71+
"crates/mock-attestation",
7172
]
7273
resolver = "2"
7374

dstack/cert-client/src/lib.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,16 @@
22
//
33
// SPDX-License-Identifier: Apache-2.0
44

5+
use std::sync::Arc;
6+
57
use anyhow::{Context, Result};
68
use dstack_kms_rpc::{kms_client::KmsClient, SignCertRequest};
79
use dstack_types::{AppKeys, KeyProvider};
810
use ra_rpc::client::{RaClient, RaClientConfig};
9-
use ra_tls::cert::{generate_ra_cert, CaCert, CertSigningRequestV2};
11+
use ra_tls::{
12+
attestation::AttestationVerifier,
13+
cert::{generate_ra_cert, CaCert, CertSigningRequestV2},
14+
};
1015

1116
pub enum CertRequestClient {
1217
Local {
@@ -54,7 +59,7 @@ impl CertRequestClient {
5459

5560
pub async fn create(
5661
keys: &AppKeys,
57-
pccs_url: Option<&str>,
62+
attestation_verifier: Arc<AttestationVerifier>,
5863
vm_config: String,
5964
) -> Result<CertRequestClient> {
6065
match &keys.key_provider {
@@ -79,7 +84,7 @@ impl CertRequestClient {
7984
.tls_client_key(client_cert.key_pem)
8085
.tls_ca_cert(keys.ca_cert.clone())
8186
.tls_built_in_root_certs(false)
82-
.maybe_pccs_url(pccs_url.map(|s| s.to_string()))
87+
.attestation_verifier(attestation_verifier)
8388
.build()
8489
.into_client()
8590
.context("Failed to create RA client")?;
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# SPDX-FileCopyrightText: © 2026 Phala Network <dstack@phala.network>
2+
#
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
[package]
6+
name = "mock-attestation"
7+
version.workspace = true
8+
authors.workspace = true
9+
edition.workspace = true
10+
license.workspace = true
11+
description = "Development-only cryptographically valid attestation evidence generator"
12+
13+
[[bin]]
14+
name = "dstack-mock-attestation"
15+
path = "src/main.rs"
16+
17+
[dependencies]
18+
anyhow.workspace = true
19+
clap.workspace = true
20+
fs-err.workspace = true
21+
rcgen.workspace = true
22+
ciborium.workspace = true
23+
p384 = { workspace = true, features = ["ecdsa", "pkcs8"] }
24+
p256 = { workspace = true, features = ["ecdsa", "pkcs8"] }
25+
sha2.workspace = true
26+
sev.workspace = true
27+
sev-snp-qvl.workspace = true
28+
tpm-types.workspace = true
29+
tpm-qvl.workspace = true
30+
dstack-types.workspace = true
31+
dcap-qvl.workspace = true
32+
scale.workspace = true
33+
yasna.workspace = true
34+
chrono.workspace = true
35+
rand.workspace = true
36+
tokio = { workspace = true, features = ["full"] }
37+
axum = "0.8"
38+
hex.workspace = true
39+
urlencoding = "2"
40+
reqwest.workspace = true
41+
serde.workspace = true
42+
serde_json.workspace = true
43+
time.workspace = true
44+
45+
[dev-dependencies]
46+
tempfile.workspace = true
47+
nsm-qvl.workspace = true
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# mock-attestation
2+
3+
Development-only, cryptographically valid attestation evidence for CI and the
4+
`dstack-tee-simulator` dev image.
5+
6+
The crate derives a development PKI from a 32-byte seed and dynamically signs evidence for:
7+
8+
- Intel TDX/DCAP, including TCB Info, QE Identity, PCK certificates and CRLs;
9+
- AMD SEV-SNP, including ARK, ASK, VCEK and reports;
10+
- TPM, including AK certificates, quotes, AIA and CRLs;
11+
- AWS NSM, including the certificate bundle and COSE Sign1 document.
12+
13+
All evidence is verified in the test suite by the production QVLs. These keys
14+
are test credentials and must never be copied into a production image.
15+
16+
## CLI
17+
18+
Generate matching public roots and `sys-config-fragment.json`:
19+
20+
```console
21+
dstack-mock-attestation generate \
22+
--output ./mock-roots \
23+
--collateral-base-url http://HOST_REACHABLE_FROM_VERIFIER:8088
24+
```
25+
26+
Merge the generated fragment into the VM sys-config, then start the host-side
27+
Mock PCCS/KDS/AIA service from that same config:
28+
29+
```console
30+
dstack-mock-attestation serve \
31+
--listen 127.0.0.1:8088 \
32+
--sys-config ./sys-config.json \
33+
--output ./active-mock-roots
34+
```
35+
36+
The server exposes only production-shaped public collateral endpoints. Evidence
37+
is generated inside the development guest by `dstack-tee-simulator`; the HTTP
38+
service deliberately provides no unauthenticated signing endpoint.
39+
40+
## Dev image
41+
42+
Select the platform in `.sys-config.json`; omission defaults to TDX:
43+
44+
```json
45+
{
46+
"tee_simulator": {
47+
"platform": "dstack-amd-sev-snp",
48+
"mock_attestation_seed": "<64 hex characters>",
49+
"collateral_base_url": "http://HOST_REACHABLE_FROM_VERIFIER:8088"
50+
}
51+
}
52+
```
53+
54+
Valid values mirror the supported attestation modes: `dstack-tdx`,
55+
`dstack-gcp-tdx`, `dstack-amd-sev-snp`, `dstack-nitro-enclave`, and
56+
`dstack-aws-nitro-tpm`. The simulator exposes
57+
the production guest ABI for the selected platform (TSM configfs, vTPM, or an
58+
NSM CUSE character device); attester libraries contain no mock HTTP or
59+
environment-variable path. The guest only reads the
60+
seed from `.sys-config.json`; it never writes credentials or roots back into
61+
`/dstack/.host-shared`. CI retains the generated public roots and mounts them
62+
into verifier/KMS/gateway. The independently running host collateral service
63+
reconstructs the same hierarchy from the seed. Configure it under
64+
`[attestation.urls]`: TDX uses `pccs`, and SEV-SNP uses `amd_kds`.
65+
66+
Every verifier process must also explicitly set
67+
`attestation.insecure_allow_external_trust_anchors = true`. Merely mounting and
68+
configuring a mock root is rejected at startup while this flag remains false.
69+
70+
The seed adds only 64 hex bytes (the generated fragment is well below 1 KiB),
71+
so the existing 32 KiB sys-config copy limit does not need to be enlarged.
72+
73+
## Required negative tests
74+
75+
The crate tests ensure every platform rejects a different root and modified
76+
signed bytes. SEV-SNP additionally performs expected `report_data` comparison
77+
inside its QVL API. TDX/TPM/NSM expose their authenticated binding field for
78+
the caller; the production `dstack-attest` layer performs the final equality
79+
check.

0 commit comments

Comments
 (0)