Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 129 additions & 0 deletions dstack/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions dstack/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ members = [
"crates/dstack-auth",
"crates/api-auth",
"crates/build-info",
"crates/mock-attestation",
]
resolver = "2"

Expand Down
11 changes: 8 additions & 3 deletions dstack/cert-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@
//
// SPDX-License-Identifier: Apache-2.0

use std::sync::Arc;

use anyhow::{Context, Result};
use dstack_kms_rpc::{kms_client::KmsClient, SignCertRequest};
use dstack_types::{AppKeys, KeyProvider};
use ra_rpc::client::{RaClient, RaClientConfig};
use ra_tls::cert::{generate_ra_cert, CaCert, CertSigningRequestV2};
use ra_tls::{
attestation::AttestationVerifier,
cert::{generate_ra_cert, CaCert, CertSigningRequestV2},
};

pub enum CertRequestClient {
Local {
Expand Down Expand Up @@ -54,7 +59,7 @@ impl CertRequestClient {

pub async fn create(
keys: &AppKeys,
pccs_url: Option<&str>,
attestation_verifier: Arc<AttestationVerifier>,
vm_config: String,
) -> Result<CertRequestClient> {
match &keys.key_provider {
Expand All @@ -79,7 +84,7 @@ impl CertRequestClient {
.tls_client_key(client_cert.key_pem)
.tls_ca_cert(keys.ca_cert.clone())
.tls_built_in_root_certs(false)
.maybe_pccs_url(pccs_url.map(|s| s.to_string()))
.attestation_verifier(attestation_verifier)
.build()
.into_client()
.context("Failed to create RA client")?;
Expand Down
47 changes: 47 additions & 0 deletions dstack/crates/mock-attestation/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# SPDX-FileCopyrightText: © 2026 Phala Network <dstack@phala.network>
#
# SPDX-License-Identifier: Apache-2.0

[package]
name = "mock-attestation"
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
description = "Development-only cryptographically valid attestation evidence generator"

[[bin]]
name = "dstack-mock-attestation"
path = "src/main.rs"

[dependencies]
anyhow.workspace = true
clap.workspace = true
fs-err.workspace = true
rcgen.workspace = true
ciborium.workspace = true
p384 = { workspace = true, features = ["ecdsa", "pkcs8"] }
p256 = { workspace = true, features = ["ecdsa", "pkcs8"] }
sha2.workspace = true
sev.workspace = true
sev-snp-qvl.workspace = true
tpm-types.workspace = true
tpm-qvl.workspace = true
dstack-types.workspace = true
dcap-qvl.workspace = true
scale.workspace = true
yasna.workspace = true
chrono.workspace = true
rand.workspace = true
tokio = { workspace = true, features = ["full"] }
axum = "0.8"
hex.workspace = true
urlencoding = "2"
reqwest.workspace = true
serde.workspace = true
serde_json.workspace = true
time.workspace = true

[dev-dependencies]
tempfile.workspace = true
nsm-qvl.workspace = true
79 changes: 79 additions & 0 deletions dstack/crates/mock-attestation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# mock-attestation

Development-only, cryptographically valid attestation evidence for CI and the
`dstack-tee-simulator` dev image.

The crate derives a development PKI from a 32-byte seed and dynamically signs evidence for:

- Intel TDX/DCAP, including TCB Info, QE Identity, PCK certificates and CRLs;
- AMD SEV-SNP, including ARK, ASK, VCEK and reports;
- TPM, including AK certificates, quotes, AIA and CRLs;
- AWS NSM, including the certificate bundle and COSE Sign1 document.

All evidence is verified in the test suite by the production QVLs. These keys
are test credentials and must never be copied into a production image.

## CLI

Generate matching public roots and `sys-config-fragment.json`:

```console
dstack-mock-attestation generate \
--output ./mock-roots \
--collateral-base-url http://HOST_REACHABLE_FROM_VERIFIER:8088
```

Merge the generated fragment into the VM sys-config, then start the host-side
Mock PCCS/KDS/AIA service from that same config:

```console
dstack-mock-attestation serve \
--listen 127.0.0.1:8088 \
--sys-config ./sys-config.json \
--output ./active-mock-roots
```

The server exposes only production-shaped public collateral endpoints. Evidence
is generated inside the development guest by `dstack-tee-simulator`; the HTTP
service deliberately provides no unauthenticated signing endpoint.

## Dev image

Select the platform in `.sys-config.json`; omission defaults to TDX:

```json
{
"tee_simulator": {
"platform": "dstack-amd-sev-snp",
"mock_attestation_seed": "<64 hex characters>",
"collateral_base_url": "http://HOST_REACHABLE_FROM_VERIFIER:8088"
}
}
```

Valid values mirror the supported attestation modes: `dstack-tdx`,
`dstack-gcp-tdx`, `dstack-amd-sev-snp`, `dstack-nitro-enclave`, and
`dstack-aws-nitro-tpm`. The simulator exposes
the production guest ABI for the selected platform (TSM configfs, vTPM, or an
NSM CUSE character device); attester libraries contain no mock HTTP or
environment-variable path. The guest only reads the
seed from `.sys-config.json`; it never writes credentials or roots back into
`/dstack/.host-shared`. CI retains the generated public roots and mounts them
into verifier/KMS/gateway. The independently running host collateral service
reconstructs the same hierarchy from the seed. Configure it under
`[attestation.urls]`: TDX uses `pccs`, and SEV-SNP uses `amd_kds`.

Every verifier process must also explicitly set
`attestation.insecure_allow_external_trust_anchors = true`. Merely mounting and
configuring a mock root is rejected at startup while this flag remains false.

The seed adds only 64 hex bytes (the generated fragment is well below 1 KiB),
so the existing 32 KiB sys-config copy limit does not need to be enlarged.

## Required negative tests

The crate tests ensure every platform rejects a different root and modified
signed bytes. SEV-SNP additionally performs expected `report_data` comparison
inside its QVL API. TDX/TPM/NSM expose their authenticated binding field for
the caller; the production `dstack-attest` layer performs the final equality
check.
Loading
Loading