docs: the offsite key is proven recoverable (R-199 closed, R-200 half, R-201 scoped)
gates / gates (push) Successful in 8s
gates / gates (push) Successful in 8s
This commit is contained in:
+35
@@ -17,6 +17,41 @@
|
||||
|
||||
## Standing rulings
|
||||
|
||||
**S-29 — a box may fetch its OWN sealed recovery blob with its OWN credential; the operator-driven DR
|
||||
path is a separate thing and stays gated (2026-08-04, R-199; hub v0.94.0 + agent v0.125.0 + controller
|
||||
v0.195.0).**
|
||||
|
||||
**The decision, so it is never re-litigated from scratch.** Self-service recovery needs the box to
|
||||
obtain its own sealed package. The only retrieval that existed was operator-gated behind recovery mode
|
||||
and the global key (`dr.go`), which cannot serve a customer-present flow. `GET /hosts/{id}/escrow` now
|
||||
serves that ONE object to its authenticated owner.
|
||||
|
||||
- **Why it is safe:** the blob is opaque; the hub has never held R and has no decrypt path; a wrong
|
||||
code fails closed at age's scrypt KDF (measured live — `exit status 1`, no plaintext, nothing
|
||||
written); the caller already authenticates as this host for its report, desired state, WG
|
||||
registration and PBS token, so this adds no new identity, only a new object.
|
||||
- **What it changes, which is the whole of the trade:** obtaining the blob used to require the
|
||||
OPERATOR to arm recovery mode. It now needs only that box's credential. **Fewer parties are
|
||||
required.** The mitigation is that the capability is AUDITED, not silent: every retrieval raises
|
||||
`escrow_blob_served` (warning, operator-only), written before the bytes leave.
|
||||
- **The override is one constant.** `api.escrowSelfServiceRetrieval` — false re-imposes the
|
||||
recovery-mode requirement on this endpoint and changes nothing else. Do not scatter the condition.
|
||||
- **`handleReEnroll` / `handleGetRestoreDirective` are UNTOUCHED** and must stay so: they rotate the
|
||||
host API key and serve the K-escrow and the directive as well. Different object, different gate.
|
||||
Pinned by `TestEscrowGet_OperatorDRPathUnchanged`.
|
||||
|
||||
**Facts a future session needs before touching this area:**
|
||||
- **The unseal lives in the AGENT and must stay there.** `age` is an agent runtime dependency
|
||||
(`/usr/bin/age`, present on both demo hosts) and is deliberately absent from the controller image;
|
||||
the blob is host-scoped and its only writer is the agent under the per-host key.
|
||||
- **The agent returns ONE field.** Not the tunnel token, not the PBS token, not the WG key — the
|
||||
controller is a trust tier down. Narrowing cost nothing and is not recoverable later.
|
||||
- **R's rules:** in memory for one call, cleared on every path, never on disk, never in argv, never
|
||||
logged, never echoed. The controller CLI reads it from STDIN for exactly that reason. Verified live:
|
||||
0 log lines, 0 files, 0 leftover staging dirs, with a positive control proving the search worked.
|
||||
- **The chain today: links 1–8 walked, 9–11 not.** The KEY comes back. Nothing installs it, reopens a
|
||||
repository with it, or restores a file — R-200's remaining half and R-201.
|
||||
|
||||
**S-28 — the escrow retention now covers the OFF-SITE data key, and customer-present recovery is the
|
||||
accepted design, which makes that retention load-bearing (2026-08-04, R-198/R-197; hub v0.93.0).**
|
||||
|
||||
|
||||
Reference in New Issue
Block a user