Skip to content

Commit ebc7a21

Browse files
h4x3rotabclaude
andcommitted
docs(consul-postgres-ha): Phase 0 attestation findings — dstack-verifier sidecar wins
Verified end-to-end against the dstack repo's fixture quote: - `dstack-verifier` (image `dstacktee/dstack-verifier:latest`) is published and ready. Single `POST /verify` with the quote + event_log + vm_config (all in `GetQuoteResponse` from the SDK) returns structured fields including `app_info.app_id`, `compose_hash`, `instance_id`, `mrtd`, `tcb_status`, `key_provider_info` — extracted, no client-side RTMR3 parsing. - Verification cost: ~800 ms – 1 s per call (5 trials, first includes one PCCS cache miss). Cache-worthy for re-attest cycles within token TTL. - Payload sizes: ~17 KB POST body, ~1.5 KB response — easy HTTP. - No Rust/C dependency in the broker. `dcap-qvl` Go bindings not needed. The broker shrinks to ~50 LoC of HTTP-pass-through + an `app_id ∈ allowlist` check. Image-digest pinning (Phase 2 in the brief) becomes free — `compose_hash` is in the same response. Open questions section trimmed: items 1 ("dcap-qvl vs sidecar") and 2 ("verification cost") are resolved. Six remaining items re-numbered. Phase 0 section in the implementation phases is now DONE-tagged with the remaining operational tasks (single live-CVM attest-and-dump smoke + decide whether dstack-verifier runs as the same container's supervised process or a sibling compose service). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 568fc59 commit ebc7a21

1 file changed

Lines changed: 48 additions & 20 deletions

File tree

consul-postgres-ha/design/attestation-admission.md

Lines changed: 48 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -258,19 +258,44 @@ before "get Connect cert":
258258
- Periodic re-attestation independent of token TTL.
259259
- Cross-cluster federation. Single dstack cluster only.
260260

261+
## Phase 0 findings (verified 2026-05-14)
262+
263+
- **Verification path: `dstack-verifier` HTTP sidecar.** Image
264+
`dstacktee/dstack-verifier:latest` (published, ~30 MB pulled).
265+
Single `POST /verify` returns structured `is_valid`,
266+
`quote_verified`, `event_log_verified`, `os_image_hash_verified`,
267+
`tcb_status`, and `app_info.{app_id, compose_hash, instance_id,
268+
mrtd, rtmr0..3, key_provider_info, ...}`. App_id arrives extracted —
269+
the broker doesn't parse RTMR3 event_log entries itself.
270+
- **No Rust/C dependency in the broker.** We considered `dcap-qvl`
271+
Go bindings; the sidecar approach is dramatically simpler.
272+
- **Verification cost: ~800 ms – 1 s per call** (5 trials against
273+
the dstack repo's fixture quote, first run includes one PCCS
274+
cache miss). Within budget for a sidecar-startup-time cost.
275+
Cache-worthy for re-attest cycles within token TTL.
276+
- **Payload sizes**: quote 5 KB hex, event_log ~3.4 KB JSON, total
277+
POST body ~17 KB, response 1.5 KB. All easy HTTP.
278+
- **`GetQuoteResponse` already carries `Quote`, `EventLog`,
279+
`ReportData`, `VmConfig`** — every field `dstack-verifier`
280+
wants. The sidecar passes the entire response through to the
281+
broker, which forwards to dstack-verifier as-is.
282+
283+
The broker shrinks to ~50 LoC of HTTP-pass-through plus an
284+
`app_id ∈ allowlist` check.
285+
261286
## Implementation phases
262287

263-
### Phase 0 — investigation & plumbing (~1 day)
288+
### Phase 0 — investigation (DONE)
289+
290+
Findings above. The remaining Phase 0 tasks are operational:
264291

265-
- Confirm `dcap-qvl`'s Go binding (or whether the broker shells out
266-
to a `dstack-verifier` sidecar). Measure verification cost: a
267-
budget of 10 ms per attest is fine; 200 ms is a concern.
268-
- Wire a stubbed `/v1/admission/challenge` + `/v1/admission/attest`
269-
inside `mesh-sidecar` on coordinator role only. Broker always
270-
returns 403; existing flow is unaffected.
271-
- Verify Go SDK `GetQuote(reportData)` works end-to-end on a live
272-
dstack CVM; capture an example quote + event_log for offline
273-
parsing tests.
292+
- Build a tiny attest-and-dump program; run on one fresh
293+
`tdx.small` CVM; confirm a live quote with our own
294+
`app_id` extracts cleanly. ~30 min wall clock.
295+
- Run `dstack-verifier` co-located with `mesh-sidecar` on
296+
coordinators. Decide: same container (one more supervised
297+
process in `entrypoint.sh`) or sibling docker-compose service.
298+
Lean toward sibling — it has its own image and lifecycle.
274299

275300
### Phase 1 — broker enforces; tokens gate Consul access (~1 week)
276301

@@ -304,38 +329,41 @@ before "get Connect cert":
304329

305330
## Open questions
306331

307-
1. **`dcap-qvl` Go bindings or HTTP sidecar?** `dcap-qvl` is Rust;
308-
does it have a Go binding good enough to use, or does the
309-
broker just `POST` to a co-located `dstack-verifier` service?
310-
The latter adds a process but isolates the C/Rust dependency.
311-
Phase 0 should pick.
332+
Phase 0 resolved the first two big ones (verifier path + cost).
333+
Remaining:
312334

313-
2. **Where does `admission-policy.hcl` live at runtime?** Options:
335+
1. **Where does `admission-policy.hcl` live at runtime?** Options:
314336
(a) rendered into the broker's image at build time (immutable,
315337
redeploy to change), (b) read from Consul KV (mutable, rotated
316338
live), (c) read from a file mounted from the host. (a) is
317339
simplest; (b) supports live rotation; (c) is awkward for dstack.
318340

319-
3. **Token lifetime tradeoffs.** Short TTL (10 min) means tight
341+
2. **Token lifetime tradeoffs.** Short TTL (10 min) means tight
320342
revocation but more broker traffic. Long TTL (8 h) means a
321343
compromised quote / replayed nonce buys longer access. 1 h is
322344
the proposed default; should it be configurable per-identity?
323345

324-
4. **Genesis CAS race.** Three coordinators race to write
346+
3. **Genesis CAS race.** Three coordinators race to write
325347
`cluster/<name>/secrets/initialized`. CAS is the right
326348
primitive but the loser needs to fall through to "read what
327349
the winner wrote." Sketch the loop and confirm correctness.
328350

329-
5. **Broker HA.** One broker per coordinator. If a worker can't
351+
4. **Broker HA.** One broker per coordinator. If a worker can't
330352
reach any of the three, it can't attest. Probably fine
331353
(degrades to "new joiners can't join, existing tokens keep
332354
working") but worth being explicit.
333355

334-
6. **Signaling broker auth.** Out of scope here but worth noting:
356+
5. **Signaling broker auth.** Out of scope here but worth noting:
335357
once we have attestation-issued identity keys, the signaling
336358
broker should require ICE auth messages to be signed by that
337359
identity. Different design doc.
338360

361+
6. **dstack-verifier image pinning.** Use
362+
`dstacktee/dstack-verifier:latest` for now; production deploy
363+
should pin a digest. Image build provenance not yet verified —
364+
worth checking if there's Sigstore attestation on the upstream
365+
tag.
366+
339367
## Success criteria
340368

341369
- [ ] Every workload's Consul Connect leaf cert chains back, via

0 commit comments

Comments
 (0)