From f90bff05362a79ec6dee3b22263393af4f5ffda6 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Sat, 14 Nov 2026 09:01:10 -0800 Subject: [PATCH] feat(guest): present the launch ticket to the KMS Signed-off-by: Kevin Wang --- cert-client/src/lib.rs | 18 ++++++++++++++---- dstack-types/src/lib.rs | 6 ++++++ dstack-util/src/system_setup.rs | 3 ++- gateway/src/main.rs | 1 + guest-agent/src/rpc_service.rs | 10 +++++----- kms/rpc/proto/kms_rpc.proto | 5 +++++ 6 files changed, 33 insertions(+), 10 deletions(-) diff --git a/cert-client/src/lib.rs b/cert-client/src/lib.rs index a6b4c67f9..8da71b6be 100644 --- a/cert-client/src/lib.rs +++ b/cert-client/src/lib.rs @@ -4,7 +4,7 @@ use anyhow::{bail, Context, Result}; use dstack_kms_rpc::{kms_client::KmsClient, SignCertRequest}; -use dstack_types::{AppKeys, KeyProvider}; +use dstack_types::{AppKeys, KeyProvider, SysConfig}; use ra_rpc::client::{CertInfo, RaClient, RaClientConfig}; use ra_tls::cert::{generate_ra_cert, CaCert, CertSigningRequestV2}; @@ -41,6 +41,7 @@ pub enum CertRequestClient { Kms { client: KmsClient, vm_config: String, + launch_ticket: String, }, } @@ -57,13 +58,18 @@ impl CertRequestClient { .context("Failed to sign certificate")?; Ok(vec![cert.pem(), ca.pem_cert.clone()]) } - CertRequestClient::Kms { client, vm_config } => { + CertRequestClient::Kms { + client, + vm_config, + launch_ticket, + } => { let response = client .sign_cert(SignCertRequest { api_version: 2, csr: csr.to_vec(), signature: signature.to_vec(), vm_config: vm_config.clone(), + launch_ticket: launch_ticket.clone(), }) .await?; Ok(response.certificate_chain) @@ -81,7 +87,7 @@ impl CertRequestClient { pub async fn create( keys: &AppKeys, pccs_url: Option<&str>, - vm_config: String, + sys_config: &SysConfig, ) -> Result { match &keys.key_provider { KeyProvider::None { key } @@ -114,7 +120,11 @@ impl CertRequestClient { .into_client() .context("Failed to create RA client")?; let client = KmsClient::new(ra_client); - Ok(CertRequestClient::Kms { client, vm_config }) + Ok(CertRequestClient::Kms { + client, + vm_config: sys_config.vm_config.clone(), + launch_ticket: sys_config.launch_ticket.clone().unwrap_or_default(), + }) } } } diff --git a/dstack-types/src/lib.rs b/dstack-types/src/lib.rs index 56aed014b..15d5ac613 100644 --- a/dstack-types/src/lib.rs +++ b/dstack-types/src/lib.rs @@ -185,6 +185,12 @@ pub struct SysConfig { pub pccs_url: Option, pub docker_registry: Option, pub host_api_url: Option, + /// Launch ticket the guest presents to the KMS, which checks its hash + /// against the app's on-chain allowlist before releasing keys. A secret + /// the host holds: unlike `vm_config`, it is never embedded in an + /// attestation or returned by the guest agent. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub launch_ticket: Option, // JSON serialized VmConfig pub vm_config: String, } diff --git a/dstack-util/src/system_setup.rs b/dstack-util/src/system_setup.rs index 7e9e11630..7289345bc 100644 --- a/dstack-util/src/system_setup.rs +++ b/dstack-util/src/system_setup.rs @@ -478,6 +478,7 @@ impl HostShared { vm_config: self.sys_config.vm_config.clone(), root_epoch: epochs.map(|(root, _)| root), app_epoch: epochs.map(|(_, app)| app), + launch_ticket: self.sys_config.launch_ticket.clone().unwrap_or_default(), }) .await .context("Failed to get app key")?; @@ -749,7 +750,7 @@ impl<'a> GatewayContext<'a> { let cert_client = CertRequestClient::create( self.keys, self.shared.sys_config.pccs_url.as_deref(), - self.shared.sys_config.vm_config.clone(), + &self.shared.sys_config, ) .await .context("Failed to create cert client")?; diff --git a/gateway/src/main.rs b/gateway/src/main.rs index 1349d9667..08a9232f8 100644 --- a/gateway/src/main.rs +++ b/gateway/src/main.rs @@ -205,6 +205,7 @@ async fn gen_debug_certs( csr: csr.to_vec(), signature, vm_config: vm_config_json.to_string(), + ..Default::default() }) .await .context("Failed to sign certificate via KMS")?; diff --git a/guest-agent/src/rpc_service.rs b/guest-agent/src/rpc_service.rs index bb5429d03..c4e7783b4 100644 --- a/guest-agent/src/rpc_service.rs +++ b/guest-agent/src/rpc_service.rs @@ -227,11 +227,10 @@ impl AppState { let sys_config: SysConfig = serde_json::from_str(&fs::read_to_string(&config.sys_config_file)?) .context("Failed to parse VM config")?; + let cert_client = CertRequestClient::create(&keys, config.pccs_url.as_deref(), &sys_config) + .await + .context("Failed to create cert signer")?; let vm_config = sys_config.vm_config; - let cert_client = - CertRequestClient::create(&keys, config.pccs_url.as_deref(), vm_config.clone()) - .await - .context("Failed to create cert signer")?; let me = Self { inner: Arc::new(AppStateInner { config, @@ -922,7 +921,8 @@ pNs85uhOZE8z2jr8Pg== }, }; - let dummy_cert_client = CertRequestClient::create(&dummy_keys, None, String::new()) + let sys_config = serde_json::from_value(serde_json::json!({ "vm_config": "" })).unwrap(); + let dummy_cert_client = CertRequestClient::create(&dummy_keys, None, &sys_config) .await .expect("Failed to create CertRequestClient"); diff --git a/kms/rpc/proto/kms_rpc.proto b/kms/rpc/proto/kms_rpc.proto index f7de032fd..4b1c73df8 100644 --- a/kms/rpc/proto/kms_rpc.proto +++ b/kms/rpc/proto/kms_rpc.proto @@ -15,6 +15,9 @@ message GetAppKeyRequest { // the original root keys. Older epochs remain available so CVMs can migrate. optional uint32 root_epoch = 3; optional uint32 app_epoch = 4; + // The launch ticket from the CVM's sys-config; empty when it has none. Its + // hash is checked against the app's allowlist. + string launch_ticket = 5; } message AppId { @@ -94,6 +97,8 @@ message SignCertRequest { bytes csr = 2; bytes signature = 3; string vm_config = 4; + // As in GetAppKeyRequest. + string launch_ticket = 5; } message SignCertResponse {