v0.88.0: controller-driven escrow ceremony — --output=json machine mode (escrowCeremony extraction, text mode byte-identical), the ONE fixed argv (escrow.CeremonyArgs, shared by exec+manifest+FELHOM_ESCROW sudoers, pin-tested), localapi ceremony job (single-flight, 60s) + one-shot in-memory R claim (10min TTL, unclaimed_void) + preflight; escrow-ceremony capability (Critical, pbs_dr-gated)
This commit is contained in:
@@ -1,3 +1,39 @@
|
||||
## v0.88.0 — controller-driven escrow ceremony: --output=json + localapi job + one-shot R claim (2026-07-13)
|
||||
|
||||
The agent half of the customer-facing recovery-code wizard (controller v0.127.0; every mechanism
|
||||
validated by felhom.eu SPIKE-controller-escrow-2026-07-13 — PTY-under-no-TTY, fixed-argv sudoers
|
||||
5/5 refusals, R pipe round-trip, env_reset, 2.3–2.4 s timings). Operator ruling F1 (2026-07-13):
|
||||
R transiting the CF tunnel once at display is an accepted, documented risk (threat-model paragraph
|
||||
in RUNBOOK-escrow-ceremony.md).
|
||||
|
||||
- **`--output=json` machine mode** (`cmd/felhom-agent/main.go`): `runSelftestEscrowCreate`'s body
|
||||
extracted into the shared `escrowCeremony()` core; text mode stays BYTE-IDENTICAL (banner, R
|
||||
block, exit codes 0/1/2, upload-fail-after-R order). json mode emits ONE
|
||||
`escrow.CeremonyOutput` object on stdout (version 1: recovery_code, key_fingerprint,
|
||||
entropy_bits, blob/identity sizes, restic_pw_sealed, uploaded), every human line to stderr, no
|
||||
partial JSON on failure; `--offline`/`--paperkey` are refused in json mode (print-oriented).
|
||||
- **The ONE fixed argv** (`internal/escrow/ceremony.go`): `escrow.CeremonyBinary` +
|
||||
`escrow.CeremonyArgs()` — the single source shared by the localapi exec, the capability
|
||||
manifest entry, and (byte-identically) the new `FELHOM_ESCROW` sudoers alias
|
||||
(`configs/felhom-agent.sudoers`). `TestEscrowCeremonyArgvPinned` +
|
||||
`TestManifestCoveredBySudoers` transitively lock runner == manifest == sudoers; never build the
|
||||
argv with flag helpers, never normalize `--`→`-` (spike §2.2).
|
||||
- **localapi ceremony endpoints** (`internal/localapi/escrow_ceremony.go`, `withGuest`-wrapped):
|
||||
`POST /escrow/ceremony` (single-flight 409; detached job, 60 s timeout; `sudo -n` + the fixed
|
||||
argv; stdout parsed then zeroed — SECRET-BEARING, never logged), `GET /escrow/ceremony/status`
|
||||
(non-secret summary + stderr-tail failure detail ≤500; R structurally absent from the job
|
||||
struct), `POST /escrow/ceremony/claim` (ONE-SHOT: 200 `{recovery_code}` once → holder zeroed;
|
||||
410 on re-claim; **TTL 10 min** → `unclaimed_void`, active AfterFunc belt + lazy check),
|
||||
`GET /escrow/preflight` (storage id, DR tier, age, hub target, staged-secret informational,
|
||||
`sudo -n -l` grant list-probe). Crash-safety is IN-MEMORY BY DESIGN — an agent restart loses R
|
||||
safely (re-run supersedes); no journal, deliberately.
|
||||
- **Capability** `escrow-ceremony` (Critical, `GatedBy: pbs_dr` EXPLICIT — non-pbsdr name by
|
||||
decision): list-mode probe of the shared argv; inactive (never red) while the DR tier is off.
|
||||
- Tests: one-shot claim + double-claim 410, TTL void + zeroed holder, R-substring absent from
|
||||
every status/snapshot payload (incl. the serialized job struct), single-flight, supersede on
|
||||
re-run, failure taxonomy (exit/unparseable/version), preflight truth table, argv pin. §10
|
||||
red-proofs demonstrated (see felhom.eu REPORT).
|
||||
|
||||
## v0.87.0 — SystemDisks device-mapper walk: legacy-boot hosts get a working drive wizard (IA finding 2, MEDIUM) (2026-07-13)
|
||||
|
||||
On a legacy-boot PVE (LVM root, no mounted ESP) `SystemDisks` resolved NOTHING — `wholeDiskOf`
|
||||
|
||||
Reference in New Issue
Block a user