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
1 change: 0 additions & 1 deletion docs/tutorials/attestation-verification.md
Original file line number Diff line number Diff line change
Expand Up @@ -526,7 +526,6 @@ These are the standard events you'll see in the log:
| `gpu-attestation` | Verified GPU state and digest of the boot-time `nvattest` JSON | Required for an attested GPU launch; verify as described below |
| `instance-id` | Unique instance identifier | Should match `instance_id` from response |
| `boot-mr-done` | Boot measurements complete | Marker event |
| `mr-kms` | KMS identity measurement | KMS public key hash |
| `os-image-hash` | Guest OS image hash | Should match `tcb_info.os_image_hash` |
| `key-provider` | Key provider type | e.g., `kms` |
| `storage-fs` | Storage filesystem type | Storage configuration |
Expand Down
62 changes: 20 additions & 42 deletions dstack/dstack-util/src/system_setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,6 @@ async fn sign_cert_request(

mod config_id_verifier;

fn is_unsupported_app_info_quote(err: &anyhow::Error) -> bool {
let message = format!("{err:#}");
message.contains("Unsupported attestation quote")
|| message.contains("unsupported attestation quote for app info decoding")
}

#[derive(clap::Parser)]
/// Prepare full disk encryption
pub struct SetupArgs {
Expand Down Expand Up @@ -1993,6 +1987,25 @@ pub async fn cmd_gateway_refresh(args: GatewayRefreshArgs) -> Result<()> {
.await
}

/// Accept only a certificate the KMS issued for its own RPC endpoint.
///
/// The attestation behind this certificate is already verified by the RA-TLS
/// layer, and the KMS identity that matters to the guest is its CA public key,
/// pinned separately by `verify_key_provider_id`. All that is left here is
/// refusing a certificate minted for some other purpose.
fn validate_kms_rpc_cert(cert: Option<CertInfo>) -> Result<()> {
let Some(cert) = cert else {
bail!("missing server cert");
};
let Some(usage) = cert.special_usage else {
bail!("missing server cert usage");
};
Comment thread
kvinwang marked this conversation as resolved.
if usage != "kms:rpc" {
bail!("Invalid server cert usage: {usage}");
}
Ok(())
}

struct AppIdValidator {
allowed_app_id: String,
}
Expand Down Expand Up @@ -2093,8 +2106,6 @@ impl<'a> Stage0<'a> {
};
let cert_pair = generate_ra_cert(tmp_ca.temp_ca_cert.clone(), tmp_ca.temp_ca_key.clone())?;
let attestation_verifier = attestation_verifier(&self.shared.sys_config)?;
let verified_kms_measurement = Arc::new(std::sync::Mutex::new(None::<[u8; 32]>));
let captured_kms_measurement = verified_kms_measurement.clone();
let ra_client = RaClientConfig::builder()
.tls_no_check(false)
.tls_built_in_root_certs(false)
Expand All @@ -2103,32 +2114,7 @@ impl<'a> Stage0<'a> {
.tls_client_key(cert_pair.key_pem)
.tls_ca_cert(tmp_ca.ca_cert.clone())
.attestation_verifier(attestation_verifier)
.cert_validator(Box::new(move |cert| {
let Some(cert) = cert else {
bail!("Missing server cert");
};
let Some(usage) = cert.special_usage else {
bail!("Missing server cert usage");
};
if usage != "kms:rpc" {
bail!("Invalid server cert usage: {usage}");
}
if let Some(att) = &cert.attestation {
match att.decode_app_info(false) {
Ok(kms_info) => {
*captured_kms_measurement
.lock()
.map_err(|_| anyhow!("KMS measurement capture lock poisoned"))? =
Some(kms_info.mr_aggregated);
}
Err(err) if is_unsupported_app_info_quote(&err) => {
warn!("Skipping mr-kms runtime event for unsupported attestation quote: {err:#}");
}
Err(err) => return Err(err).context("Failed to decode app_info"),
}
}
Ok(())
}))
.cert_validator(Box::new(validate_kms_rpc_cert))
.build()
.into_client()
.context("Failed to create client")?;
Expand All @@ -2141,14 +2127,6 @@ impl<'a> Stage0<'a> {
.await
.context("Failed to get app key")?;

let kms_measurement = verified_kms_measurement
.lock()
.map_err(|_| anyhow!("KMS measurement capture lock poisoned"))?
.take();
if let Some(kms_measurement) = kms_measurement {
emit_runtime_event("mr-kms", &kms_measurement)
.context("failed to extend mr-kms to the launch measurement")?;
}
emit_runtime_event("os-image-hash", &response.os_image_hash)
.context("failed to extend os-image-hash to the launch measurement")?;

Expand Down
5 changes: 2 additions & 3 deletions dstack/kms/kms.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,8 @@ enforce_self_authorization = true
# Whether the KMS embeds an attestation in its own RPC certificate. Set false
# only for local dev/testing where the KMS runs outside a TEE. This narrows what
# the KMS asserts about itself and relaxes no verification: quotes presented to
# the KMS are still fully checked, a guest that accepts an unattested
# certificate simply does not extend mr-kms, and another KMS refuses to onboard
# from one.
# the KMS are still fully checked, and another KMS refuses to onboard from an
# unattested one.
attest_rpc_cert = true
# AMD SEV-SNP key/cert release remains disabled unless this local KMS gate is
# explicitly enabled. External auth policy must still allow the verified
Expand Down
6 changes: 2 additions & 4 deletions dstack/kms/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,8 @@ pub(crate) struct KmsConfig {
///
/// This narrows what the KMS asserts about itself; it relaxes no
/// verification anywhere. Quotes presented *to* the KMS are still fully
/// checked, so key release stays gated, and relying parties keep their own
/// policy: a guest accepts an unattested KMS certificate but then does not
/// extend `mr-kms`, so a remote verifier can still tell, and another KMS
/// refuses to onboard from one.
/// checked, so key release stays gated, and another KMS refuses to onboard
/// from an unattested one.
#[serde(default = "default_true")]
pub attest_rpc_cert: bool,
/// Whether trusted RPCs require the KMS to first attest itself to its
Expand Down
4 changes: 2 additions & 2 deletions dstack/kms/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,8 @@ async fn main() -> Result<()> {
if !config.attest_rpc_cert {
warn!(
"attest_rpc_cert = false; the KMS RPC certificate carries no attestation, so \
guests cannot verify which KMS they are talking to and will not extend \
mr-kms. Intended for local development only"
guests cannot verify which KMS they are talking to. Intended for local \
development only"
);
}

Expand Down
Loading