diff --git a/docs/amd-sev-snp.md b/docs/amd-sev-snp.md index d2ebe8139..0bad1b2d0 100644 --- a/docs/amd-sev-snp.md +++ b/docs/amd-sev-snp.md @@ -130,18 +130,32 @@ for production TEE deployments. ## AMD KDS collateral The verifier obtains AMD certificate collateral from the built-in AMD KDS URL -when the attestation evidence does not already contain the required chain. An -operator can set an AMD-KDS-compatible mirror or cache: +when the attestation evidence does not already contain the required chain. The +public endpoint may be temporarily unavailable because of maintenance, +outage, routing restrictions, or a disconnected deployment. An operator can +set an AMD-KDS-compatible mirror or cache for both the KMS CVM and application +CVMs during installation: + +```sh +sudo dstackup install --platform amd-sev-snp \ + --sev-snp-kds-url "https://mirror.example.com/vcek/v1" +``` + +The value is rendered into the KMS configuration as: ```toml -[core] -amd_kds_base_url = "https://mirror.example.com/vcek/v1" +[core.attestation.urls] +amd_kds = "https://mirror.example.com/vcek/v1" ``` -Leave the value empty to use the built-in default. A custom endpoint is part of -the verification trust and availability boundary: use a controlled mirror, -preserve TLS validation, and do not make verification succeed without valid -AMD signatures. +The same value is passed to application CVMs as +`collateral_urls.amd_kds`. Leave the option unset to use the built-in default. +A custom endpoint is part of the verification trust and availability boundary: +use a controlled mirror, preserve TLS validation, and do not make verification +succeed without valid AMD signatures. The endpoint must implement the AMD KDS +API, including certificate-chain and chip/TCB-specific VCEK requests. A +pre-populated cache can continue serving known collateral during a public KDS +maintenance window or outage; cache misses must fail closed. ## Troubleshooting diff --git a/dstack/crates/dstack-cli-core/src/config.rs b/dstack/crates/dstack-cli-core/src/config.rs index 4309bd9bb..e4e145116 100644 --- a/dstack/crates/dstack-cli-core/src/config.rs +++ b/dstack/crates/dstack-cli-core/src/config.rs @@ -102,6 +102,8 @@ pub struct HostConfig { pub verify_os_image: bool, /// confidential-computing platform (selects SNP-specific KMS settings). pub platform: Platform, + /// AMD KDS-compatible collateral mirror/cache URL as seen from CVMs. + pub sev_snp_kds_url: String, } impl Default for HostConfig { @@ -113,6 +115,7 @@ impl Default for HostConfig { image_download_url: DEFAULT_IMAGE_DOWNLOAD_URL.to_string(), verify_os_image: true, platform: Platform::Tdx, + sev_snp_kds_url: String::new(), } } } @@ -162,8 +165,14 @@ port = 8000 // `sev_snp_key_release`, which defaults to false — so it must be set on // SNP or the KMS refuses to release keys. Harmless/ignored on TDX. sev_snp = match cfg.platform { - Platform::AmdSevSnp => "sev_snp_key_release = true\namd_kds_base_url = \"\"\n", - Platform::Tdx => "", + Platform::AmdSevSnp if cfg.sev_snp_kds_url.trim().is_empty() => { + "sev_snp_key_release = true\n".to_string() + } + Platform::AmdSevSnp => format!( + "sev_snp_key_release = true\n\n[core.attestation.urls]\namd_kds = {:?}\n", + cfg.sev_snp_kds_url + ), + Platform::Tdx => String::new(), }, verify = cfg.verify_os_image, download_url = cfg.image_download_url, @@ -272,6 +281,8 @@ pub struct VmmRender { pub key_provider_port: u32, /// KMS URLs injected into app CVMs (the guest-visible KMS address). pub kms_urls: Vec, + /// AMD KDS-compatible collateral mirror/cache URL injected into app CVMs. + pub sev_snp_kds_url: String, /// confidential-computing platform (selects qemu/share-mode for the CVMs). pub platform: Platform, /// gate the management API behind a bearer/Basic token (`[auth] enabled`). @@ -296,6 +307,7 @@ impl Default for VmmRender { key_provider_addr: "127.0.0.1".to_string(), key_provider_port: 3443, kms_urls: Vec::new(), + sev_snp_kds_url: String::new(), platform: Platform::Tdx, auth_enabled: false, auth_token: String::new(), @@ -334,6 +346,7 @@ qemu_path = "{qemu_path}" kms_urls = [{kms_urls}] gateway_urls = [] pccs_url = "" +sev_snp_kds_url = "{sev_snp_kds_url}" docker_registry = "" cid_start = {cid_start} cid_pool_size = {cid_pool_size} @@ -423,6 +436,7 @@ port = {kp_port} .map(|u| format!("\"{u}\"")) .collect::>() .join(", "), + sev_snp_kds_url = r.sev_snp_kds_url, cid_start = r.cid_start, cid_pool_size = r.cid_pool_size, supervisor_exe = r.supervisor_exe, diff --git a/dstack/crates/dstackup/src/cli.rs b/dstack/crates/dstackup/src/cli.rs index cfcfbd927..2786e3897 100644 --- a/dstack/crates/dstackup/src/cli.rs +++ b/dstack/crates/dstackup/src/cli.rs @@ -221,6 +221,10 @@ pub(crate) struct InstallOpts { #[arg(long, default_value = config::DEFAULT_KMS_IMAGE)] pub(crate) kms_image: String, + /// AMD KDS-compatible collateral mirror/cache URL, as seen from CVMs. + #[arg(long, value_name = "URL")] + pub(crate) sev_snp_kds_url: Option, + /// host port for the KMS RPC (default: an auto-picked free port). #[arg(long)] pub(crate) kms_port: Option, diff --git a/dstack/crates/dstackup/src/install.rs b/dstack/crates/dstackup/src/install.rs index b32f9fea0..483232d5f 100644 --- a/dstack/crates/dstackup/src/install.rs +++ b/dstack/crates/dstackup/src/install.rs @@ -167,6 +167,7 @@ pub(crate) async fn cmd_install(mut o: InstallOpts, release_api_base_url: &str) key_provider_addr: kp_addr, key_provider_port: kp_port as u32, kms_urls: kms_urls.clone(), + sev_snp_kds_url: o.sev_snp_kds_url.clone().unwrap_or_default(), platform, auth_enabled: true, auth_token: vmm_token.clone(), @@ -186,6 +187,7 @@ pub(crate) async fn cmd_install(mut o: InstallOpts, release_api_base_url: &str) os_image_hash: os_image_hash.unwrap_or_default(), verify_os_image: false, platform, + sev_snp_kds_url: o.sev_snp_kds_url.clone().unwrap_or_default(), ..Default::default() }; let kms = config::kms_toml(&host_cfg); diff --git a/dstack/vmm/src/app.rs b/dstack/vmm/src/app.rs index c63fcff74..627b0feb2 100644 --- a/dstack/vmm/src/app.rs +++ b/dstack/vmm/src/app.rs @@ -2024,7 +2024,10 @@ pub(crate) fn make_sys_config( "gateway_urls": gateway_urls, "gateway_clusters": gateway_clusters, "pccs_url": cfg.cvm.pccs_url, - "collateral_urls": { "pccs": cfg.cvm.pccs_url }, + "collateral_urls": { + "pccs": cfg.cvm.pccs_url, + "amd_kds": cfg.cvm.sev_snp_kds_url, + }, "nvidia_attestation_proxy_url": cfg.cvm.nvidia_attestation_proxy_url, "docker_registry": cfg.cvm.docker_registry, "host_api_url": format!("vsock://2:{}/api", cfg.host_api.port), @@ -3560,6 +3563,10 @@ mod tests { } assert_eq!(sys_config["pccs_url"], config.cvm.pccs_url); assert_eq!(sys_config["collateral_urls"]["pccs"], config.cvm.pccs_url); + assert_eq!( + sys_config["collateral_urls"]["amd_kds"], + config.cvm.sev_snp_kds_url + ); let vm_config: serde_json::Value = serde_json::from_str( sys_config["vm_config"] .as_str() diff --git a/dstack/vmm/src/config.rs b/dstack/vmm/src/config.rs index 23684737c..6860dfd85 100644 --- a/dstack/vmm/src/config.rs +++ b/dstack/vmm/src/config.rs @@ -410,6 +410,9 @@ pub struct CvmConfig { /// The URL of the PCCS server #[serde(default)] pub pccs_url: String, + /// Optional AMD KDS-compatible collateral mirror/cache URL passed to guests. + #[serde(default)] + pub sev_snp_kds_url: String, /// Node-local credentials and collateral settings used when an individual /// VM requests a simulated TEE platform. #[serde(default)] @@ -943,6 +946,10 @@ impl Config { } for (name, value) in [ ("cvm.pccs_url", Some(self.cvm.pccs_url.as_str())), + ( + "cvm.sev_snp_kds_url", + Some(self.cvm.sev_snp_kds_url.as_str()), + ), ( "cvm.nvidia_attestation_proxy_url", self.cvm.nvidia_attestation_proxy_url.as_deref(),