Skip to content

feat(kms): make RPC certificate attestation configurable - #830

Open
kvinwang wants to merge 2 commits into
masterfrom
fix/kms-unattested-rpc-cert
Open

feat(kms): make RPC certificate attestation configurable#830
kvinwang wants to merge 2 commits into
masterfrom
fix/kms-unattested-rpc-cert

Conversation

@kvinwang

@kvinwang kvinwang commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Problem

The KMS cannot run outside a TEE, and two tools plus two security documents assume it can.

Keys::from_keys embeds a quote in the KMS RPC certificate unconditionally, so without a reachable dstack guest agent the KMS fails to bootstrap. The switch that used to control this, [core.onboard] quote_enabled, no longer exists — there are zero .rs references to it — but it was never cleaned up:

  • tools/dev-stack.sh still writes quote_enabled = false into the KMS config it generates, and run_in_dstack = false into the gateway config. serde ignores unknown keys, so both lines are silently dropped and the tool cannot start either component. It describes itself as a "Legacy all-in-one host development helper", so this is exactly its stated use case.
  • docs/security/security-model.md lists quote_enabled = false as a current development switch.
  • docs/security/security-best-practices.md tells operators not to deploy production KMS with it.

The gateway already solved this problem: [core.debug] insecure_skip_attestation switches certificate generation off the guest agent, and gen_debug_key exists to set it up. The KMS simply never got the equivalent, so what looks like a deliberate stance ("KMS must be in a TEE") is an omission.

The failure is also badly misleading. Without the guest agent the KMS reports Failed to get quote, and a CVM booting against it reports:

Failed to get app keys from KMS: Failed to get app key: Failed to validate attestation:
Failed to verify the attestation report: failed to verify TDX quote:
ISV enclave report signature is invalid

which reads as "the KMS rejected this CVM's quote". It is the opposite — the CVM is rejecting the KMS's quote (the direction is now named explicitly by #829).

Fix

Add [core] attest_rpc_cert, default true.

It sits next to enforce_self_authorization, which is the KMS's own existing knob for the same situation — [core], no prefix, positive default, and documented as "Set false only for local dev/testing where the KMS runs outside a TEE and cannot reach a guest agent socket."

Deliberately not named insecure_*. Every existing insecure_* in the repo makes some party accept something it would otherwise reject: insecure_no_auth serves the admin API unauthenticated, insecure_allow_external_trust_anchors widens the trust roots, the gateway's insecure_skip_attestation drops peer verification in wavekv_sync.rs, insecure_enable_debug_rpc exposes an RPC that lets a caller claim any app id. This switch does none of that — it makes the KMS assert less about itself, and relaxes no verification on either side. Copying the gateway's name would also have been actively misleading here: in a KMS, "skip attestation" reads as "stop verifying the quotes CVMs present", which is exactly what must never happen.

The two directions are cleanly separable in the KMS, and only the first is touched:

KMS attests itself app_attest at onboard_service.rs — this PR gates only the call that embeds it in the RPC certificate
KMS verifies CVMs main_service::ensure_app_attestation_allowedattestation_verifier — untouched, still gates all key release

The blast radius is bounded by behaviour that already exists:

  • guests accept an unattested KMS certificate (if let Some(att) in system_setup.rs) but then do not extend mr-kms, so a remote verifier can still tell;
  • another KMS refuses to onboard from one — bail!("Source KMS certificate does not contain attestation").

Also in this change:

  • a startup warn! when it is off, stating the consequence rather than shouting "insecure";
  • an actionable error when it is off and no guest agent is reachable, naming the switch;
  • update_certs now logs that it reissued the certificate. It rewrites the RPC certificate on every start, which silently replaces a hand-placed one — I lost time to exactly that while investigating, because the success path said nothing;
  • docs restored under the new name rather than deleted, and #609 in the public report log annotated to record that quote_enabled was retired;
  • the full-stack suite's forbidden-settings audit extended to catch attest_rpc_cert = false and insecure_skip_attestation = true in rendered manifests;
  • tools/dev-stack.sh fixed on both halves: the new switch for KMS, and [core.debug] insecure_skip_attestation = true for the gateway in place of the dead run_in_dstack.

The quote_enabled references under test-suites/full-stack-compose/ are left alone: that suite deploys KMS 0.5.8 alongside current KMS for upgrade coverage, and its own comment already says # Required by KMS 0.5.8. Current KMS ignores this retired field.

Verification

Fail-closed by default, outside a TEE, with no [core.debug] section at all:

Error: Failed to generate keys
Caused by:
    0: failed to get a quote for the KMS RPC certificate. The KMS attests itself through
       the dstack guest agent, so it must run inside a dstack CVM. For local development
       set attest_rpc_cert = false
    1: client error (Connect)
    2: No such file or directory (os error 2)

With the switch on, same host, no TEE:

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
INFO  Onboarding
INFO  Reissued the KMS RPC certificate for kms.1022.dstack.org
INFO  Starting KMS
INFO  endpoint=https://127.0.0.1:13901 (TCP + mTLS)

The certificate it produces carries one dstack extension (special_usage) instead of two — the attestation extension is absent:

$ openssl x509 -in certs/rpc.crt -noout -text | grep -c '1.3.6.1.4.1'
1

Separately, on a TDX host, a real CVM booted successfully against a KMS running outside the TEE with an unattested certificate and registered with the gateway over WireGuard, which is what established that mr-kms is genuinely optional on the guest side.

cargo test -p dstack-kms 38 passed (2 new: the default must be attested, and a config written before this key existed must still parse onto the attested side). cargo fmt and cargo clippy -p dstack-kms clean.

Copilot AI review requested due to automatic review settings July 28, 2026 03:10

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@kvinwang
kvinwang force-pushed the fix/kms-unattested-rpc-cert branch from 46eb149 to 07e1f3a Compare July 28, 2026 03:14
@kvinwang kvinwang changed the title feat(kms): allow running outside a TEE with an unattested RPC certificate feat(kms): make RPC certificate attestation configurable Jul 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants