docs: v0.78.0 REPORT — DELETE stage-secret deployed felhom-pve (56/56), live-validated
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
@@ -1,46 +1,29 @@
|
||||
# REPORT — agent v0.77.0: fork-4 escrow the offsite restic repo password under R
|
||||
# REPORT — agent v0.78.0: DELETE /escrow/stage-secret (fork-4 hygiene) — DEPLOYED
|
||||
|
||||
**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`).
|
||||
**Date:** 2026-07-09 · **Class:** implementation (agent, small). **Baseline:** v0.77.0 → **v0.78.0**, live
|
||||
on felhom-pve (`felhom-agent 0.78.0`, capabilities self-check **56/56, degraded=0**). Part of the offsite
|
||||
hardening bundle: pairs with controller v0.107.0 (calls this on every `EscrowState` flip to `escrowed`) and
|
||||
hub v0.39.0 (F4/F2/F5).
|
||||
|
||||
## 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`.
|
||||
|
||||
## 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`.
|
||||
`internal/localapi`: **`DELETE /escrow/stage-secret`** (withGuest, bearer-authed) — removes the staged
|
||||
offsite restic repo password (+ any stale `.tmp` partial). **Idempotent:** an absent file is a clean 200
|
||||
`{removed:false}`. Closes the fork-4 hygiene gap where a confirm WITHOUT a fresh ceremony (the password
|
||||
already escrowed — the live e2e's Option-A close) left the transient 0600 staging file behind indefinitely
|
||||
(only the escrow-create ceremony wiped it before).
|
||||
|
||||
## 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.
|
||||
`TestWipeStagedEscrowSecret`: stage → wipe (EFFECT: file GONE) → idempotent re-wipe (`removed:false`) →
|
||||
401 unauthenticated. Full suite green (`go build && go vet && go test ./...`).
|
||||
|
||||
## 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
|
||||
The endpoint was exercised for the one-off cleanup: the lingering
|
||||
`/var/lib/felhom-agent/escrow-stage/restic_repo_password` (left by the e2e) was wiped through the
|
||||
controller container over the pinned local-API channel → `{"removed":true}`, file verified GONE on
|
||||
felhom-pve; an immediate re-wipe returned `{"removed":false}` (idempotency proven live). No secret value
|
||||
was ever printed or logged.
|
||||
|
||||
## 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.
|
||||
## Deploy
|
||||
Standard flow: built on 180 (`-X main.version=0.78.0`), scp'd via local → felhom-pve, `.bak-0.77.0` kept,
|
||||
`install -m0755` + restart. `configs/` unchanged → no sudoers/wrapper shipping needed. NOT published to
|
||||
Gitea (no Day-0 vouch change; the Day-0 manifest still vouches 0.76.0).
|
||||
|
||||
Reference in New Issue
Block a user