guest-agent: freeze the v0 API at v0.5.11 and add the versioned v1 API - #1116
Draft
kvinwang wants to merge 29 commits into
Draft
guest-agent: freeze the v0 API at v0.5.11 and add the versioned v1 API#1116kvinwang wants to merge 29 commits into
kvinwang wants to merge 29 commits into
Conversation
0.6.0 dropped the `Verify` RPC because verification needs no key material and no attestation, and the agent's verdict arrives over the socket unattested -- the reasoning still holds, and SDKs >= 0.6 verify locally. But dropping it broke every 0.5.x SDK against a 0.6 agent for a method those SDKs still call, and prpc answers an unknown method with a bare HTTP 400, so the caller cannot tell a removed method from a wrong socket. The unversioned surface is what 0.5.x clients speak; new API design belongs in `dstack.guest.v1`, not in breaking this one. Restore the RPC and its messages at the v0.5.11 field numbers, marked deprecated and legacy-only, and annotate the service as frozen: no renumbering, no removals, no semantic changes. Semantics are identical to 0.5.11, including k256 rejecting non-canonical high-S signatures outright.
`EmitEvent` is gone for good -- runtime RTMR3 events are system-owned in 0.6.0, so an app extending the measurement chain is no longer something this API should offer. Deleting the method outright is the wrong way to say that. prpc answers both "no such method" and "the handler failed" with HTTP 400 and drops the message, so a 0.5.x app that emits events gets a bare 400 that reads identically to a wrong socket path or a broken build, and its author has no way to learn why the events stopped being recorded. Keep the method and the args message on the unversioned service, and have the handler always bail with the reason. Nothing reaches `emit_runtime_event`; this is a deliberate error, not a code path.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Single PR for the dstack 0.6.0 guest-agent API: freezes the unversioned surface at exactly v0.5.11, introduces the versioned v1 API, and switches the four SDKs to mirror both surfaces. Supersedes #1113/#1114.
Problem
nexthad broken the unversioned paths that 0.5.x clients call (Verify/EmitEventdeleted,Worker.GetAttestationForAppKeyrenamed), while the unversioned API carried design debt it could not shed without further breakage:GetTlsKeynames a key what is really certificate issuance;GetKey'spurpose/algorithmare accepted but do not domain-separate the derived key (one 32-byte secret serves both curves);Sign/Verifyare pure computation granting no capabilityGetKeydoes not already grant;Inforeturns triple-nested JSON duplicating its own top-level fields; and the chain claimkeccak256("{purpose}:{hex(pubkey)}")lets a caller-chosenpurposemint nearly arbitrary signed strings.Fix
Every surface becomes a frozen v0 fossil plus a v1, selected by URL path only (no header negotiation):
/(alias),/v0: 9 v0.5.11 methods/v1: IssueCert, GetKey, Attest, AttestGpu, Info, Version/prpc(alias),/prpc/v0: Info, Version, GetAttestationForAppKey/prpc/v1: Info, Version, Healthv0:
Verifyrestored byte-identical to v0.5.11;EmitEventrestored as a deliberate always-failing stub (RTMR3 runtime events are system-owned; the message is the signal, since prpc reports handler failure and unknown method differently only in the body);GetAttestationForAppKeyrestored TDX-only. Never-released post-0.5.11 additions removed. A descriptor-digest test pins the frozen shape so even a wire-compatible field addition fails CI.v1 keeps only what needs the TEE — derivation, attestation, and KMS-backed cert issuance:
GetKey(domain, algorithm):HKDF-SHA256(salt="dstack-guest-v1", IKM=app_root_k256, info=LP("dstack-guest-v1-key")||LP(algorithm)||LP(domain))— length-prefixed encoding, per-algorithm keys, v1-specific salt so no v0 input can reproduce a v1 key. Chain claim binds(algorithm, domain, raw pubkey)and is structurally unforgeable from the v0 claim format (a v0 preimage's tail is lowercase hex; the v1 length prefix contains 0x00). Constants and vectors live inra_tls::guest_api_v1, shared by agent and tests.IssueCert: honest name for whatGetTlsKeydid — certificate issuance via KMSSignCert, generating a fresh key as the integrated one-step mode.Attest(sole CVM attestation entry; optional boot-time GPU evidence),AttestGpu(on-demand, 32-byte nonce), flat identity-and-configurationInfo(attestation data deliberately excluded — it belongs toAttest, quote-backed),Version./prpc/v1/Health. Identity forInfois decoded once at startup (an anonymous external caller can no longer drive hardware quote generation per request).SDKs (all four):
ClientV0mirrors the frozen surface exactly (v0.5.11 method set,verify()back,emit_event()surfaces the removal error);ClientV1mirrors the six v1 methods; the never-released localverify_signature/verify_signature_chainhelpers and their vectors are removed — the normative spec is the verification reference. Deprecated aliases keep existing code compiling. Versions unified at 0.6.0.Spec:
docs/guest-api-v1.md— byte-level KDF/claim encodings, verification steps, status-code/probe rules (measured: absent mount → 404 HTML, unknown method → 404 JSON, failed handler → 400 JSON), migration contract including that v1 keys deliberately differ from v0 keys for the same inputs.Verification
v0.5.11tag (comments stripped): frozenDstackGuest,Worker,Tappdidentical; 16/19 messages byte-identical, 3 differ byreservedonly../dstack/run-tests.sh(agent-backed simulator): 1235 passed, 0 failed.sdk/run-tests.sh: all four suites green (Rust 38, Go 51, Python 156, JS 131).a_v0_path_cannot_reproduce_a_v1_key; forgery test asserts the structural hex/0x00 property; frozen-descriptor test verified to catch a planted field addition; route tests prove/v0and unversioned are the same handler and each mount serves only its own surface.Known follow-ups (out of scope)
asUint8Array→toPkcs8Der, Gousage_server_authdefault, JS transport status checks).sdk/simulator/attestation.binwas captured from a different app-compose thanapp-compose.json(compose_hashmismatch, reproducible on v0 too) — needs a fixture refresh.