Skip to content

Commit 3ebb6b6

Browse files
committed
refactor(attestation): pass verifier trust configuration explicitly
1 parent 6cfa1f6 commit 3ebb6b6

73 files changed

Lines changed: 2223 additions & 1520 deletions

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: 7 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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")?;

dstack/crates/mock-attestation/Cargo.toml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,3 @@ time.workspace = true
4545
[dev-dependencies]
4646
tempfile.workspace = true
4747
nsm-qvl.workspace = true
48-
tdx-attest.workspace = true
49-
sev-snp-attest.workspace = true
50-
nsm-attest.workspace = true
51-
tpm-attest.workspace = true

dstack/crates/mock-attestation/README.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,19 +43,27 @@ Select the platform in `.sys-config.json`; omission defaults to TDX:
4343
```json
4444
{
4545
"tee_simulator": {
46-
"platform": "sev-snp",
46+
"platform": "dstack-amd-sev-snp",
4747
"mock_attestation_seed": "<64 hex characters>",
4848
"collateral_base_url": "http://HOST_REACHABLE_FROM_VERIFIER:8088"
4949
}
5050
}
5151
```
5252

53-
Valid values are `tdx`, `sev-snp`, `tpm`, and `nsm`. The guest only reads the
53+
Valid values mirror the supported attestation modes: `dstack-tdx`, `gcp-tdx`,
54+
`amd-sev-snp`, `aws-nitro-enclave`, and `aws-nitro-tpm`. The simulator exposes
55+
the production guest ABI for the selected platform (TSM configfs, vTPM, or an
56+
NSM CUSE character device); attester libraries contain no mock HTTP or
57+
environment-variable path. The guest only reads the
5458
seed from `.sys-config.json`; it never writes credentials or roots back into
5559
`/dstack/.host-shared`. CI retains the generated public roots and mounts them
5660
into verifier/KMS/gateway. The independently running host collateral service
57-
reconstructs the same hierarchy from the seed. TDX uses it as `pccs_url` and
58-
SEV-SNP uses its `/vcek/v1` path as the KDS base.
61+
reconstructs the same hierarchy from the seed. Configure it under
62+
`[attestation.urls]`: TDX uses `pccs`, and SEV-SNP uses `amd_kds`.
63+
64+
Every verifier process must also explicitly set
65+
`attestation.insecure_allow_external_trust_anchors = true`. Merely mounting and
66+
configuring a mock root is rejected at startup while this flag remains false.
5967

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

dstack/crates/mock-attestation/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ pub fn generate_assets_from_seed(
123123
fs_err::write(
124124
output.join("sys-config-fragment.json"),
125125
serde_json::to_vec_pretty(&serde_json::json!({
126-
"tee_simulator": { "platform": "tdx", "mock_attestation_seed": hex::encode(seed), "collateral_base_url": base_url }
126+
"tee_simulator": { "platform": "dstack-tdx", "mock_attestation_seed": hex::encode(seed), "collateral_base_url": base_url }
127127
}))?,
128128
)?;
129129
Ok(manifest)

dstack/crates/mock-attestation/src/nsm.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,21 @@ impl NsmGenerator {
7575
}
7676

7777
pub fn attest(&self, report_data: &[u8]) -> Result<Vec<u8>> {
78+
let pcrs = (0..=2)
79+
.map(|index| (index, vec![index as u8; 48]))
80+
.collect();
81+
self.attest_with_pcrs(report_data, pcrs)
82+
}
83+
84+
pub fn attest_with_pcrs(
85+
&self,
86+
report_data: &[u8],
87+
pcrs: BTreeMap<u16, Vec<u8>>,
88+
) -> Result<Vec<u8>> {
7889
let timestamp = SystemTime::now()
7990
.duration_since(UNIX_EPOCH)
8091
.context("system clock before UNIX epoch")?
8192
.as_millis() as u64;
82-
let pcrs = (0..=2)
83-
.map(|index| (index, vec![index as u8; 48]))
84-
.collect();
8593
let document = Document {
8694
module_id: "mock-nsm".into(),
8795
digest: "SHA384".into(),

dstack/crates/mock-attestation/src/server.rs

Lines changed: 0 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -420,54 +420,4 @@ mod tests {
420420
.unwrap();
421421
task.abort();
422422
}
423-
424-
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
425-
async fn production_attest_clients_use_dynamic_simulator_evidence() {
426-
static ENV_LOCK: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(());
427-
let _guard = ENV_LOCK.lock().await;
428-
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
429-
let addr = listener.local_addr().unwrap();
430-
let state =
431-
Arc::new(MockCollateralState::with_base_url(&format!("http://{addr}")).unwrap());
432-
let task = tokio::spawn(serve_listener(listener, state.clone()));
433-
std::env::set_var("DSTACK_MOCK_ATTESTATION_URL", format!("http://{addr}"));
434-
435-
let rd = [0x51; 64];
436-
let tdx = tdx_attest::get_quote(&rd).unwrap();
437-
let now = std::time::SystemTime::now()
438-
.duration_since(std::time::UNIX_EPOCH)
439-
.unwrap()
440-
.as_secs();
441-
dcap_qvl::verify::QuoteVerifier::new(state.tdx.root_ca_der())
442-
.verify(&tdx, &state.tdx.sample_collateral().unwrap(), now)
443-
.unwrap();
444-
445-
let sev = sev_snp_attest::get_report(rd).unwrap();
446-
sev_snp_qvl::QuoteVerifier::new_with_root(
447-
sev_snp_qvl::AmdSnpProduct::Milan,
448-
state.sev_snp.root_ca_pem().into_bytes(),
449-
)
450-
.verify(&sev.report, &sev.cert_chain, &rd)
451-
.unwrap();
452-
453-
let nsm = nsm_attest::NsmContext::new()
454-
.unwrap()
455-
.get_attestation_doc(Some(&rd), None, None)
456-
.unwrap();
457-
nsm_qvl::QuoteVerifier::new(state.nsm.root_ca_pem())
458-
.verify(&nsm, None, None)
459-
.unwrap();
460-
461-
let qualifying = [0x52; 32];
462-
let tpm = tpm_attest::TpmContext::detect()
463-
.unwrap()
464-
.create_quote(&qualifying, &tpm_types::PcrSelection::sha256(&[14]))
465-
.unwrap();
466-
tpm_qvl::QuoteVerifier::new(state.tpm.root_ca_pem())
467-
.verify(&tpm, &state.tpm.collateral())
468-
.unwrap();
469-
470-
std::env::remove_var("DSTACK_MOCK_ATTESTATION_URL");
471-
task.abort();
472-
}
473423
}

dstack/crates/mock-attestation/src/sev_snp.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,30 @@ impl SevSnpGenerator {
7676
}
7777

7878
pub fn attest(&self, report_data: [u8; 64]) -> Result<SevSnpEvidence> {
79+
self.attest_with_host_data(report_data, [0x22; 32])
80+
}
81+
82+
pub fn attest_with_host_data(
83+
&self,
84+
report_data: [u8; 64],
85+
host_data: [u8; 32],
86+
) -> Result<SevSnpEvidence> {
87+
self.attest_with_measurement(report_data, host_data, [0x33; 48])
88+
}
89+
90+
pub fn attest_with_measurement(
91+
&self,
92+
report_data: [u8; 64],
93+
host_data: [u8; 32],
94+
measurement: [u8; 48],
95+
) -> Result<SevSnpEvidence> {
7996
let mut encoded = Vec::new();
8097
AttestationReport::default().write_bytes(&mut encoded)?;
8198
encoded[0..4].copy_from_slice(&2u32.to_le_bytes());
8299
encoded[52..56].copy_from_slice(&1u32.to_le_bytes());
83100
encoded[0x50..0x90].copy_from_slice(&report_data);
84-
encoded[0x90..0xc0].fill(0x11);
85-
encoded[0xc0..0xe0].fill(0x22);
101+
encoded[0x90..0xc0].copy_from_slice(&measurement);
102+
encoded[0xc0..0xe0].copy_from_slice(&host_data);
86103
encoded[0x1a0..0x1e0].fill(0x33);
87104
let signing_key = SigningKey::from_pkcs8_pem(&self.vcek_key.serialize_pem())?;
88105
let signature: P384Signature =

dstack/crates/mock-attestation/src/tdx.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,15 @@ impl TdxGenerator {
129129
&self,
130130
report_data: [u8; 64],
131131
rtmrs: [[u8; 48]; 4],
132+
) -> Result<TdxEvidence> {
133+
self.attest_with_measurements(report_data, [0x11; 48], rtmrs)
134+
}
135+
136+
pub fn attest_with_measurements(
137+
&self,
138+
report_data: [u8; 64],
139+
mrtd: [u8; 48],
140+
rtmrs: [[u8; 48]; 4],
132141
) -> Result<TdxEvidence> {
133142
let auth_key = SigningKey::random(&mut rand::thread_rng());
134143
let auth_pub = auth_key.verifying_key().to_encoded_point(false);
@@ -175,7 +184,7 @@ impl TdxGenerator {
175184
seam_attributes: [0; 8],
176185
td_attributes: [0, 0, 0, 0x10, 0, 0, 0, 0],
177186
xfam: [0; 8],
178-
mr_td: [0x11; 48],
187+
mr_td: mrtd,
179188
mr_config_id: [0; 48],
180189
mr_owner: [0; 48],
181190
mr_owner_config: [0; 48],

dstack/ct_monitor/src/main.rs

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ struct VerificationRequest {
4242
quote: String,
4343
event_log: String,
4444
vm_config: String,
45-
pccs_url: Option<String>,
4645
}
4746

4847
/// Response from dstack-verifier
@@ -88,7 +87,6 @@ struct AcmeInfoResponse {
8887
struct Monitor {
8988
gateway_uri: String,
9089
verifier_url: String,
91-
pccs_url: Option<String>,
9290
base_domain: String,
9391
known_keys: BTreeSet<Vec<u8>>,
9492
last_checked: Option<u64>,
@@ -112,13 +110,12 @@ struct CTLog {
112110
impl Monitor {
113111
/// Create a new monitor
114112
/// `gateway` format: `base_domain[:port]`, e.g., `example.com` or `example.com:8443`
115-
fn new(gateway: String, verifier_url: String, pccs_url: Option<String>) -> Result<Self> {
113+
fn new(gateway: String, verifier_url: String) -> Result<Self> {
116114
let (base_domain, gateway_uri) = Self::parse_gateway(&gateway)?;
117115
validate_domain(&base_domain)?;
118116
Ok(Self {
119117
gateway_uri,
120118
verifier_url,
121-
pccs_url,
122119
base_domain,
123120
known_keys: BTreeSet::new(),
124121
last_checked: None,
@@ -175,7 +172,6 @@ impl Monitor {
175172
quote: hex::encode(&quote_response.quote),
176173
event_log: quote_response.event_log,
177174
vm_config: quote_response.vm_config,
178-
pccs_url: self.pccs_url.clone(),
179175
};
180176

181177
// Call verifier
@@ -402,10 +398,6 @@ struct Args {
402398
/// The dstack-verifier URL
403399
#[arg(short, long, env = "VERIFIER_URL")]
404400
verifier_url: String,
405-
406-
/// PCCS URL for TDX collateral fetching (optional)
407-
#[arg(long, env = "PCCS_URL")]
408-
pccs_url: Option<String>,
409401
}
410402

411403
#[tokio::main]
@@ -416,7 +408,7 @@ async fn main() -> anyhow::Result<()> {
416408
fmt().with_env_filter(filter).with_ansi(false).init();
417409
}
418410
let args = Args::parse();
419-
let mut monitor = Monitor::new(args.gateway, args.verifier_url, args.pccs_url)?;
411+
let mut monitor = Monitor::new(args.gateway, args.verifier_url)?;
420412
monitor.run().await;
421413
Ok(())
422414
}

0 commit comments

Comments
 (0)