From 2388ea175edf5e1d6ee8399bd4fdbd05ffcefa33 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Mon, 9 Nov 2026 19:58:32 -0800 Subject: [PATCH] feat(kms)!: turn off the public KMS key handover by default Root-key handover now lives on the admin listener, so the public KMS.GetKmsKey entry is only needed while a 0.5.x KMS, or an onboarding request without source_token, still onboards from the cluster. Default core.onboard.public_key_handover to false in kms.toml and in the serde default, and make the refusal name the option to enable. The onboarding test runbooks onboard without a source token, so they now set the option on the source KMS explicitly. Signed-off-by: Kevin Wang --- CHANGELOG.md | 4 ++++ docs/deployment.md | 2 +- docs/security/security-model.md | 3 ++- docs/upgrading-to-0.6.md | 6 ++++-- dstack/kms/README.md | 5 +++-- dstack/kms/kms.toml | 6 +++--- dstack/kms/src/config.rs | 8 ++++++-- dstack/kms/src/main_service.rs | 5 ++++- dstack/kms/src/www/onboard.html | 3 ++- dstack/tests/docs/kms-bootstrap-onboard.md | 3 +++ dstack/tests/docs/kms-self-authorization.md | 3 +++ 11 files changed, 35 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cf5d26c9..4f991acf5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- KMS: `core.onboard.public_key_handover` defaults to `false`, so + `KMS.GetKmsKey` is no longer served on the public port. Onboard through the + source's admin listener with `source_token`; a source that still onboards + 0.5.x KMS nodes must set the option to `true` until they are upgraded. - KMS: onboarding refuses a source whose certificate carries no OS image hash, which drops the fallback for KMS 0.5.x sources. Join only from a 0.6+ source; upgrade a 0.5.x cluster to 0.6.x first. diff --git a/docs/deployment.md b/docs/deployment.md index 1f1ea705f..a64710d1c 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -533,7 +533,7 @@ curl http://:9203/finish > **Admin authentication.** The KMS *admin* RPCs (for example `ClearImageCache`) are served on a dedicated `[core.admin]` listener behind the shared HTTP authenticator, just like the VMM and gateway: set `[core.admin] enabled = true` with an `auth_token` (or the `DSTACK_KMS_ADMIN_TOKEN` / `ADMIN_API_TOKEN` env vars), and clients send `Authorization: Bearer ` or `X-Admin-Token`. Enabled with neither `auth_token` nor `htpasswd_file` (and `insecure_no_auth = false`) fails closed — the KMS refuses to start. See [dstack-kms admin authentication](../dstack/kms/README.md#admin-api-authentication). > -> **Key handover.** It is served as `Admin.GetKmsKey` on the `[core.admin]` listener, which needs `[core.admin.tls]` so it can verify the onboarding KMS's RA-TLS certificate. Pass the source admin URL and its token (`source_token`) in the onboarding request. `core.onboard.public_key_handover = true` keeps the legacy public `KMS.GetKmsKey`. +> **Key handover.** It is served as `Admin.GetKmsKey` on the `[core.admin]` listener, which needs `[core.admin.tls]` so it can verify the onboarding KMS's RA-TLS certificate. Pass the source admin URL and its token (`source_token`) in the onboarding request. The legacy public `KMS.GetKmsKey` is off by default; set `core.onboard.public_key_handover = true` on the source only when an onboarding KMS cannot use the admin listener, such as a 0.5.x KMS or an onboarding request without `source_token`. #### Rotating KMS Root Keys diff --git a/docs/security/security-model.md b/docs/security/security-model.md index ccf70629f..24f9ae264 100644 --- a/docs/security/security-model.md +++ b/docs/security/security-model.md @@ -429,7 +429,8 @@ The unauthenticated or non-client-certificate surface includes bootstrap, env-en KMS-to-KMS root-key handover is served by `Admin.GetKmsKey` on the authenticated admin listener, which additionally requires a verified RA-TLS client certificate. -The legacy public `KMS.GetKmsKey` is controlled by `core.onboard.public_key_handover`. +The legacy public `KMS.GetKmsKey` is off by default and is served only when +`core.onboard.public_key_handover = true` is set explicitly. ## Limitations diff --git a/docs/upgrading-to-0.6.md b/docs/upgrading-to-0.6.md index a9c61d977..df4e269f9 100644 --- a/docs/upgrading-to-0.6.md +++ b/docs/upgrading-to-0.6.md @@ -232,8 +232,10 @@ Configuration changes: - Key release on AMD SEV-SNP, AWS NitroTPM, and AWS Nitro Enclaves stays off until you set `sev_snp_key_release`, `aws_nitro_tpm_key_release`, or `nitro_enclave_key_release`. -- Keep `core.onboard.public_key_handover = true` while any KMS still onboards - through the public port, and turn it off afterwards. +- `core.onboard.public_key_handover` now defaults to `false`, which removes + `KMS.GetKmsKey` from the public port. While a 0.5.x KMS still onboards from + this cluster, it can only use that public entry, so set the option to `true` + on the source node, and remove it once every node is upgraded. - Auto-bootstrap refuses to overwrite existing root keys. If you use `auth-simple`, replace it with `dstack-auth`, which reads the same diff --git a/dstack/kms/README.md b/dstack/kms/README.md index 0b3e4136c..e14fd8dac 100644 --- a/dstack/kms/README.md +++ b/dstack/kms/README.md @@ -71,8 +71,9 @@ below, which decides whether a CVM may boot and receive keys. KMS-to-KMS onboarding calls `Admin.GetKmsKey` with a self-issued RA-TLS client certificate plus the source's admin bearer token; other admin RPCs need no client -certificate. `core.onboard.public_key_handover = true` (the default) keeps -`KMS.GetKmsKey` on the public listener for older clients. +certificate. `core.onboard.public_key_handover` is off by default; set it to +`true` only to also serve `KMS.GetKmsKey` on the public listener for older +clients. Three settings relax authorization, each over a different scope: diff --git a/dstack/kms/kms.toml b/dstack/kms/kms.toml index 22ee6d7b5..5673d189b 100644 --- a/dstack/kms/kms.toml +++ b/dstack/kms/kms.toml @@ -127,8 +127,8 @@ gateway_app_id = "any" [core.onboard] enabled = true auto_bootstrap_domain = "" -# Keep KMS.GetKmsKey on the public RPC listener for older onboarding clients. -# New deployments should point onboarding at the admin listener instead. -public_key_handover = true +# Also serve KMS.GetKmsKey on the public RPC listener, for onboarding clients +# that cannot reach the admin listener (KMS 0.5.x, or no source admin token). +public_key_handover = false address = "0.0.0.0" port = 8000 diff --git a/dstack/kms/src/config.rs b/dstack/kms/src/config.rs index 507e4f136..079ecb2e3 100644 --- a/dstack/kms/src/config.rs +++ b/dstack/kms/src/config.rs @@ -241,8 +241,8 @@ pub(crate) struct RootKeyPaths { pub(crate) struct OnboardConfig { pub enabled: bool, pub auto_bootstrap_domain: String, - /// Keep GetKmsKey available on the public RPC listener for compatibility. - #[serde(default = "default_true")] + /// Also serve GetKmsKey on the public RPC listener, for legacy onboarding clients. + #[serde(default)] pub public_key_handover: bool, } @@ -264,6 +264,10 @@ mod tests { "default admin token must be empty (fail-closed)" ); assert!(!config.admin.insecure_no_auth); + assert!( + !config.onboard.public_key_handover, + "public key handover must be off by default" + ); } #[test] diff --git a/dstack/kms/src/main_service.rs b/dstack/kms/src/main_service.rs index 2937001c6..876fdf796 100644 --- a/dstack/kms/src/main_service.rs +++ b/dstack/kms/src/main_service.rs @@ -934,7 +934,10 @@ impl KmsRpc for RpcHandler { async fn get_kms_key(self, request: GetKmsKeyRequest) -> Result { if !self.state.config.onboard.public_key_handover { - bail!("public KMS key handover is disabled; use the admin listener"); + bail!( + "public KMS key handover is disabled; onboard through the admin listener with \ + the source admin token, or set core.onboard.public_key_handover = true" + ); } self.handover_keys(KeyRelease::RpcHandover, request).await } diff --git a/dstack/kms/src/www/onboard.html b/dstack/kms/src/www/onboard.html index d476e64e8..b5ee3d42a 100644 --- a/dstack/kms/src/www/onboard.html +++ b/dstack/kms/src/www/onboard.html @@ -259,7 +259,8 @@

Onboard from an Existing KMS Instance

+ autocomplete="off" + placeholder="Without it, the source needs core.onboard.public_key_handover = true">