From 4449118783c2e09bf7a9eaca888e890f1183ebf3 Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Thu, 9 Jul 2026 15:19:31 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20v0.77.0=20REPORT=20=E2=80=94=20fork-4?= =?UTF-8?q?=20offsite=20password=20escrow;=20deployed=20felhom-pve=20(56/5?= =?UTF-8?q?6=20caps)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6 --- REPORT.md | 120 +++++++++++++++++------------------------------------- 1 file changed, 38 insertions(+), 82 deletions(-) diff --git a/REPORT.md b/REPORT.md index 7b66815..505b5b9 100644 --- a/REPORT.md +++ b/REPORT.md @@ -1,90 +1,46 @@ -# REPORT — v0.76.0: restore-test full-fidelity verification (GL-5b / G12) + publish +# REPORT — agent v0.77.0: fork-4 escrow the offsite restic repo password under R -**Date:** 2026-07-08 · **Class:** risky/supervised implementation + live run + deploy + publish. -**Baseline:** `main` @ `5a72a4b5` (v0.75.0, live) → `b1697874` + docs. Closes GL-5 finding #2's -mirror image (REPORT v0.75.0 observation: the restore-test boot-verified scratch guests WITHOUT -their storage mpN). STOP honored: no 9201 restore target, no `-keep`, no PBS writes, no manifest -change (operator's). - -## The operator values (Day-0 manifest — Configs → Day-0 artifacts) - -``` -AGENT_VERSION=0.76.0 -AGENT_SHA256=9828c5f75e719fb2e1fc3523f9c322b595a963ec1dcdf37ca42a96bedab7f50b -GOLDEN_VERSION=0.103.0 -GOLDEN_SHA256=8481e8a14e2aa0abe3831cc40e5aea4c32f2017e7561c15dca8a674df6a6026e -``` - -**0.75.0 was superseded UNPUBLISHED** (deliberate — one manifest bump, straight to 0.76.0). The -golden stays 0.103.0 (it ships the controller, not the agent — no re-bake). +**Date:** 2026-07-09 · **Class:** implementation (agent). **Baseline:** `main` @ `7f07393` (v0.76.0) → +`0c22b9b` (v0.77.0). Pairs with controller v0.105.0. Design validated by the custody spike +(`felhom.eu/documentation/audits/SPIKE-restic-password-custody-2026-07-09.md`, `febdc56`). ## What shipped +Makes the restic-offsite repo password recoverable at DR by riding the existing customer-recovery-code (R) +zero-knowledge escrow (age-under-R, in `IdentityBundle`). Additive; the PBS-K escrow path is untouched. +- `internal/escrow/identity.go`: `IdentityBundle.ResticRepoPassword` (rides `WrapIdentityBundle`/ + `UnwrapIdentityBundle`, self-verified by `escrow.Create`); `AttachResticPassword` (mirrors `AttachWGKey`); + `StagedResticPasswordPath` + `WipeStagedResticPassword`. +- `internal/localapi`: `POST /escrow/stage-secret` (`withGuest` + `scopedFromBody`) stages the + controller-pushed password 0600 (atomic tmp+rename), **never logged** (field name only), overwritten on + re-push. Stage path injectable via `Options.EscrowStagePath` (default `StagedResticPasswordPath`). +- `cmd/felhom-agent/main.go` (`runSelftestEscrowCreate`): auto-injects the staged password into the bundle + (mirrors the WG-key auto-inject) and wipes the staging file after a successful `escrow.Create`. -- Restore-test params now derive from the **archive's own embedded config** via - `ExtractArchiveConfig` + `drRestoreOverrides` — the same derivation DR bring-up uses, because the - ARCHIVE is the object under test (the old path read the live source guest's config: wrong object, - and its bind-only overrides tripped PVE's drop-unlisted-mountpoints rule). Unreadable config / - unknown bind topology → refuse up front, no partial-guest "verification". -- **Mount-parity assert** (step 2b, pre-start): restored mpN set vs the archive's — missing, - mispathed, undersized, or extra mpN fails the test naming the delta. `MountParity` + - `MountInventory` added to the result and the hub wire record (additive JSON keys; older hubs - ignore them). Teardown still fires on a parity fail (launch-proven). -- Dead code deleted WITH its tests: `bindMountOverrides`, `archiveVMID`, the two volid regexes — - the restore-test was their only caller (§5's delete-or-justify: deleted; no dead lookalike stays - reachable). `throwawayVolumeOverride`/`rootfsSizeGB`/`mountPathOf` live on under - `drRestoreOverrides`. -- **Scenario D:** `bringup.go` had ZERO line changes — provision/DR behavior byte-identical, all - GL-5 tests green, the §13 DR re-run trigger did not fire. +## Files changed (commit `0c22b9b`) +`internal/escrow/identity.go`, `internal/escrow/identity_test.go`, `internal/localapi/server.go`, +`internal/localapi/escrow_stage.go` (new), `internal/localapi/escrow_stage_test.go` (new), +`cmd/felhom-agent/main.go`, `CHANGELOG.md`, `CONTEXT.md`. ## Tests +Green gate `go build ./... && go vet ./... && go test ./...` on the build host (age present) — **all green**. +New tests: +- `TestIdentity_RoundTrip_CarriesResticPassword` — the bundle carries `ResticRepoPassword` byte-exact, the + blob does not leak the plaintext, and a wrong R fails closed (no bundle). +- `TestAttachResticPassword` — missing staged file → clean no-attach; present → trimmed value attached; + empty → error. +- `TestStageEscrowSecret_StagesScopesAndHidesValue` — the pushed secret is staged 0600, the ack is + `{ok,data.staged:true}` (no value echoed), a cross-guest push is 403 and does not overwrite the staged file. +- `TestStageEscrowSecret_NoSecretInLog` — the staged value never appears in a captured log buffer. +Note: `TestGenerateRecoveryCode_EntropyAndFormat` is the documented pre-existing flake (a hyphenated +wordlist word splits to 11) — re-run passes; unrelated to this change. -Scenario A (exact 5-param derivation from a 9201-shaped archive config; parity ok; 4-entry -inventory; scratch torn down), B (mp0 deleted from the restored config → FAIL naming `mp0 MISSING`, -guest never started, still torn down), C (extract-failure + unknown-topology refusals with zero -restore attempts), `mountParity` pure-function matrix (parity / missing / mispathed / undersized / -extra / trivially-empty). **Scenario-B red-proof** (run→fail→revert on the committed baseline): -with the 2b parity assert removed, the dropped-mount run PASSES silently — the test caught it -(FAIL), then reverted, all green. Full gate on Windows + 180/Linux: 23 packages ok. +## Deploy verification +Built `felhom-agent 0.77.0` (`go build -ldflags -X main.version=0.77.0`), installed on felhom-pve (prior +binary backed up to `felhom-agent.bak-0.76.0`), `systemctl restart felhom-agent` → `--version` = 0.77.0, +`systemctl is-active` = active, **capabilities self-check ok=56 total=56 degraded=0**, local-api listening +on 192.168.0.162:8443, no errors/panics. -## Live validation (felhom-pve) - -Deploy: backup `felhom-agent.bak-0.75.0` → install → restart: active, v0.76.0, zero errors, no -ce9d rebind. First run FAILED honestly — the GL-5-era archive had been rotated out by retention -and `ExtractArchiveConfig` refused up front ("volume does not exist"; exactly the fail-fast the -design wants). Re-run against the current newest archive -(`vzdump-lxc-9201-2026_07_08-09_38_15.tar.zst`, 6.5GB): - -- transcript: `full-fidelity restore params derived from the archive config … params=5`; -- pre-teardown `pct config 990000`: `mp0 …,mp=/var/lib/docker,backup=1,size=200G`, - `mp1 …,mp=/mnt/sys_drive,backup=1,size=50G`, `mp8`/`mp9` as 1G throwaways at the structural - paths, `rootfs …,size=32G`, no unusedN; -- record: `pass: true`, `mount_parity: "ok"`, `mount_inventory`: the 4 verified mpN, - `duration_seconds: 184.28`; -- scratch torn down; band clean. - -**Runtime, honestly:** 3m4s total on the LOCAL tier — cheaper than the ~7m the GL-5 DR contrast -suggested (that 7m23s included DR bring-up's longer boot/verify chain; the extraction cost itself -for this guest's actual content is ~2 extra minutes over a data-less restore). The PBS tier will -be slower (WAN + decrypt); its generous `RestoreTaskTimeout` already exists spec-side. No fidelity -knob added per §12 — the runtime is the verification. - -## Publish (GL-1 pattern) - -The exact deployed bytes (`readlink /proc/$(pidof felhom-agent)/exe` → `/usr/local/bin/felhom-agent`, -`--version` = 0.76.0) published via `publish-agent.sh 0.76.0`: PUT 201, round-trip sha match, then -an ANONYMOUS fetch re-verify — sha identical end-to-end -(`9828c5f7…f50b` on the box, on 180, and via the anonymous customer-path proxy). Creds sourced on -the build server, never echoed. - -## Observations - -- The pinned-archive rotation (first live run) is a nice property in production: a restore-test - against a pruned volid refuses loudly instead of "verifying" nothing. The daemon's scheduled - restore-test resolves the newest archive itself, so cadence runs are unaffected. -- 9201's local backups ran 3× this morning (08:52/09:07/09:38) — the local cadence is busy; not a - GL-5b concern, worth a glance at retention math someday. - -## NOT live-validated - -C7-class parity firing on REAL drift (needs a deliberately mangled restore — GL-6 family); the PBS -tier's full-fidelity runtime; hub-side rendering of the new record fields (additive, ignored today). +## NOT yet live-validated +The supervised escrow ceremony (enable → stage → `--selftest=escrow-create` with a real R → confirm → +gated run) is the **operator-run** follow-up — NOT run here. The stage endpoint is deployed and unit-proven. +No agent publish (`publish-agent.sh`) / Day-0 manifest bump was done — that is a separate operator step.