Skip to content

Commit bc0e0fd

Browse files
committed
docs: show that a KDS caching mirror works, with a reference implementation
Section 9.4 already said a mirror would drop in behind `amd_kds`. Someone reading that still has to discover the three things that decide whether the proxy is correct, and each was found the hard way: AMD 403s an unexpected Host header, a cached 429 converts a rate limit into an outage, and every KDS response carries `Cache-Control: no-cache` so a plain reverse proxy caches nothing at all. Thirty lines of Worker are cheaper to read than that list in prose. Measured rather than asserted: four cold-process verifications of one real attestation pass through the mirror where the second fails going direct, and a KMS pointed at it released keys to an AMD guest end to end. Also notes why the TTL should be bounded even though a VCEK is valid for seven years -- dstack checks no CRL and no validity dates, so an unbounded cache would freeze that gap rather than merely inherit it.
1 parent f19c423 commit bc0e0fd

1 file changed

Lines changed: 53 additions & 4 deletions

File tree

docs/rc-testing-runbook.md

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,7 @@ certificate** — the onboarded KMS re-issues its own cert, so the PEM differs
429429
while `openssl x509 -pubkey` is identical. A KMS that bootstrapped independently
430430
differs in both.
431431

432-
### 9.4 KDS is on the critical path, and stays there
432+
### 9.4 KDS is on the critical path
433433

434434
Verification needs the ASK and VCEK certificates. They come from
435435
`https://kdsintf.amd.com`, a **single global endpoint with no mirror**, rate
@@ -446,9 +446,58 @@ them ~25s apart.
446446
Caching is in-process only. `AmdKdsClient` keeps a `moka` cache of 16 CA chains
447447
and 1024 VCEKs, capacity-bounded, no TTL, no persistence — warm for the life of
448448
one KMS or verifier process and cold in every one-shot `dstack-verifier --verify`.
449-
There is no KDS cache service in this repo; `[core.attestation.urls] amd_kds`
450-
would accept a mirror, but nothing implements one. (Compare `nvidia-attest-proxy`,
451-
the persistent on-disk cache the NVIDIA collateral got and AMD did not.)
449+
There is no KDS cache service in this repo — compare `nvidia-attest-proxy`, the
450+
persistent on-disk cache the NVIDIA collateral got and AMD did not — but
451+
`[core.attestation.urls] amd_kds` accepts any KDS-compatible base URL, and putting
452+
a caching proxy there works. Four consecutive cold-process `dstack-verifier
453+
--verify` runs of one attestation pass through a mirror; the same sequence run
454+
directly against KDS fails on the second. A KMS configured the same way released
455+
keys to an AMD guest end to end.
456+
457+
Three things decide whether such a proxy is correct, and all three are easy to get
458+
wrong:
459+
460+
```js
461+
export default {
462+
async fetch(request, env, ctx) {
463+
const url = new URL(request.url);
464+
if (request.method !== "GET" || !url.pathname.startsWith("/vcek/v1/")) {
465+
return new Response("not found", { status: 404 });
466+
}
467+
const cache = caches.default;
468+
const key = new Request(url.toString()); // query string carries the TCB SPLs
469+
const hit = await cache.match(key);
470+
if (hit) return hit;
471+
472+
// 1. AMD answers 403 to an unexpected Host header, so do not forward it.
473+
const upstream = await fetch("https://kdsintf.amd.com" + url.pathname + url.search,
474+
{ headers: { accept: "*/*" } });
475+
const body = await upstream.arrayBuffer();
476+
477+
// 2. Only success is cacheable. A cached 429 turns a rate limit into an outage.
478+
if (!upstream.ok) {
479+
return new Response(body, { status: upstream.status,
480+
headers: { "cache-control": "no-store" } });
481+
}
482+
483+
// 3. Every KDS response says `Cache-Control: no-cache`; override it.
484+
const res = new Response(body, { headers: {
485+
"content-type": "application/octet-stream",
486+
"cache-control": "public, max-age=2592000",
487+
}});
488+
ctx.waitUntil(cache.put(key, res.clone()));
489+
return res;
490+
},
491+
};
492+
```
493+
494+
Caching is safe here because the collateral is signature-verified downstream and
495+
the ARK is compiled in, so a mirror can withhold or replay genuine certificates
496+
but cannot forge one. Two fetches of one VCEK URL return different bytes — AMD
497+
re-issues rather than serving a fixed object — but any valid copy is as good as
498+
another, and a VCEK is valid for seven years. Bound the TTL anyway: dstack checks
499+
no CRL and no certificate validity dates, and an unbounded cache would make that
500+
permanent rather than merely current.
452501

453502
Do not plan on serving the certificates from the host instead. The verifier side
454503
supports it — `normalize_kernel_cert_table` reads ASK and VCEK from the SNP

0 commit comments

Comments
 (0)