docs: v0.108.0 README (auto-confirm) + REPORT (deployed, Scenario E live)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
@@ -1,43 +1,40 @@
|
||||
# REPORT — controller v0.107.0: offsite hardening (key-auth-first + staged-secret wipe) — DEPLOYED
|
||||
# REPORT — controller v0.108.0: SLICE 3 hub-verified escrow auto-confirm — DEPLOYED
|
||||
|
||||
**Date:** 2026-07-09 · **Class:** implementation. Baseline v0.106.1 → **v0.107.0** (live on guest 9201,
|
||||
`Up (healthy)`, clean logs). Part of the offsite hardening bundle: pairs with **hub v0.39.0** (F4 re-issue +
|
||||
F2 scan retry + F5 save UX) and **agent v0.78.0** (`DELETE /escrow/stage-secret`) — both also deployed live.
|
||||
**Date:** 2026-07-09 · **Class:** implementation. Baseline v0.107.0 → **v0.108.0** (live on 9201,
|
||||
`Up (healthy)`). The chain pairs: **agent v0.79.0** (ceremony uploads `restic_pw_sha256`) + **hub v0.40.0**
|
||||
(stores it; report ACK serves `escrow:{identity_blob_present, restic_pw_sha256, created_at}`) — both live.
|
||||
|
||||
## What shipped (controller)
|
||||
- **Key-auth-first bridge** (`internal/offsiteapply`): new `KeyAuthProber` seam + `SFTPKeyAuthProber` (probes
|
||||
the already-installed key against the descriptor target, pinned to the freshly-scanned known_hosts,
|
||||
BatchMode + 20s budget). On a descriptor change where the key still authenticates: re-pin + reconfigure
|
||||
+ marker — **no consume, no install**. Kills the stale-descriptor consume-404 loop (seen twice in the live
|
||||
e2e) and shrinks the F4 blast radius to genuinely-fresh guests. The probe NEVER weakens identity: scan +
|
||||
fingerprint verify still precede it, and a mismatch refuses before any probe (test-asserted with a
|
||||
panicking prober). Fresh guests fall through to the unchanged full path.
|
||||
- **Staged-secret wipe:** `agentapi.WipeStagedEscrowSecret` (DELETE, agent ≥ v0.78.0); the confirm-escrow
|
||||
handler wipes the agent-staged repo password on every flip to `escrowed` — best-effort (failure logs a
|
||||
loud "NOT wiped" ERROR, never fails the confirm; re-confirm retries). Seam-injectable (`wipeStagedEscrowFn`).
|
||||
`report.EscrowAutoConfirmer` — runs on every report ACK; flips offbox `EscrowState` pending→escrowed ONLY
|
||||
when `sha256(local repo_password) == restic_pw_sha256` from the ACK. Verifies the RIGHT fact: not "a blob
|
||||
exists" but "the blob covers the CURRENT key" (a stale blob — re-provision, inject, drive history — must
|
||||
never truthfully-claim; that would re-open the exact un-recoverable-ciphertext gap fork-4 closed).
|
||||
- Match → flip + wipe the agent-staged secret (the v0.107.0 DELETE path; best-effort, loud on failure).
|
||||
- Mismatch → stays pending + LOUD warn naming the fix ("run the escrow ceremony"), deduped per distinct
|
||||
hash (not per 15-min cycle).
|
||||
- No row / NULL hash / hash-without-identity-blob / no local password file → silently pending (fail-closed).
|
||||
- Non-pending → total no-op — **auto-UN-confirm does not exist**.
|
||||
- `backup.HashResticPassword` (canonical: sha256 hex of the TRIMMED string) + `Manager.OffboxRepoPasswordHash`;
|
||||
**pinned cross-repo test vector** (same in felhom-agent) so the two hashers can never drift silently.
|
||||
- Manual `POST /backup/offbox/confirm-escrow` → documented **deprecated fallback** (legacy hash-less blobs).
|
||||
|
||||
## Tests + companion red-proofs (ALL-GREEN, both red-proofs fired)
|
||||
- `TestBridge_KeyAuthFirstSkipsConsume` — probe-success: consumer PANICS if called; enabler gets the
|
||||
EXISTING key + fresh pin; marker updated. **Red-proof:** deleted the key-auth-first block → the panicking
|
||||
consumer fired → FAIL. Reverted.
|
||||
- `TestBridge_FreshGuestFallsThroughToFullPath` — probe-fail: consume+install exactly once with the
|
||||
GENERATED key. **Red-proof:** made probe-fail return early → `cons=0 inst=0` → FAIL. Reverted.
|
||||
- `TestBridge_HostKeyMismatchRefuses` — now also proves the probe never runs on a failed identity check.
|
||||
- `TestOffboxWeb_ConfirmWipesStagedSecret` — confirm wipes exactly once; a failing wipe still confirms +
|
||||
logs "NOT wiped".
|
||||
## Tests + companion red-proof (ALL-GREEN, red-proof fired)
|
||||
Scenarios: A match→flip+wipe (+repeat ACK no-op); B stale-blob stays pending + warn-once-per-hash (+new
|
||||
hash warns again); C all silent-pending cases; E never acts outside pending (even a mismatch on an escrowed
|
||||
target is silent); wipe-failure keeps the confirm. **Red-proof:** modeled the blob-present-only check → the
|
||||
stale-blob AND hash-less scenarios flipped when they must not → both tests FAILED. Reverted.
|
||||
Pinned vector + trim convention asserted in `internal/backup` (mirrors the agent's).
|
||||
|
||||
## Live validation (this deploy)
|
||||
- v0.107.0 on 9201 healthy; the bridge no-ops on the unchanged descriptor (marker match — no probe needed).
|
||||
- **One-off cleanup executed via the NEW endpoint:** the staged file left behind by the e2e's Option-A
|
||||
confirm (`/var/lib/felhom-agent/escrow-stage/restic_repo_password`) was wiped through the controller
|
||||
container → agent `DELETE /escrow/stage-secret` → `{"removed":true}`; the file is GONE on felhom-pve, and
|
||||
an idempotent re-wipe returned `{"removed":false}`. Token handled in a shell var only.
|
||||
## Live state check (deployed, no operator action)
|
||||
Hub 0.40.0 Synced/Healthy; migration applied (`host_escrow.restic_pw_sha256` present; all 3 legacy rows
|
||||
NULL — demo included). Controller 0.108.0 healthy; the startup report went through the new hub and the
|
||||
demo's `escrowed` state produced **zero `escrow-confirm` log lines** — Scenario E proven live for free.
|
||||
Agent 0.79.0 on felhom-pve (56/56 capabilities).
|
||||
|
||||
## NOT exercised live
|
||||
Hub F4 re-issue (would reset a real credential — the demo's working offsite must not be touched; a live
|
||||
exercise needs a throwaway customer under supervision). Unit-tested with the exactly-1-scope red-proof.
|
||||
F5 (submit-disable JS) ships in the hub template — visible on the next operator save.
|
||||
## NOT yet live-exercised
|
||||
The full auto-confirm chain (fresh enable → ceremony records the hash → ACK → auto-flip) — first real
|
||||
offsite enable proves it (Peti onboarding runbook). Unit red-proofs carry the logic per the task.
|
||||
|
||||
## NEXT
|
||||
SLICE 3 (escrow auto-confirm — inherits the wipe-on-escrowed), SLICE 4 (soft-quota), NEW-box restore
|
||||
round-trip → retire the old-box archive, Peti onboarding runbook.
|
||||
SLICE 4 (soft-quota on `quota_gb`), Peti onboarding runbook (will live-exercise auto-confirm + F4),
|
||||
NEW-box restore round-trip → retire the old-box archive.
|
||||
|
||||
+11
-2
@@ -730,8 +730,17 @@ not just those with HDD data. Non-HDD apps can configure destination, method, an
|
||||
> **DR:** recover R → the escrow yields the password → `POST /backup/offbox/inject-password {password}`
|
||||
> pre-places it 0600 → configure offbox → restore. The SFTP access key is **regenerated** at DR (a fresh
|
||||
> sub-account key), NOT escrowed; the DR recipe carries only the non-secret `offsite_restic`
|
||||
> coordinates (`DRResticCoord`). *(A hub-verified auto-confirm + customer-self-serve enable + fresh-R UX
|
||||
> are the provisioning task, deferred.)*
|
||||
> coordinates (`DRResticCoord`).
|
||||
> - **Hub-verified auto-confirm (v0.108.0, SLICE 3; pairs with agent v0.79.0 + hub v0.40.0).** The report
|
||||
> ACK carries `escrow:{identity_blob_present, restic_pw_sha256, created_at}` (the hash is recorded at
|
||||
> ceremony time — non-reversible sha256 of the sealed password, safe to serve). `report.EscrowAutoConfirmer`
|
||||
> flips `EscrowState` pending→escrowed ONLY when `sha256(local repo_password)` **matches** — i.e. the
|
||||
> stored escrow provably covers the CURRENT key; blob-presence alone never confirms (a stale blob would
|
||||
> re-open the fork-4 gap). Mismatch → stays pending + a loud warn naming the ceremony (deduped per hash);
|
||||
> no hash / no row / no local file → silently pending; **never un-confirms**. On flip it wipes the
|
||||
> agent-staged secret. The canonical hasher (`backup.HashResticPassword`, trimmed-string sha256) is pinned
|
||||
> by a cross-repo test vector against the agent's. The manual `POST /backup/offbox/confirm-escrow` is a
|
||||
> **deprecated fallback** for legacy hash-less blobs (e.g. the demo's).
|
||||
> - **Injection guard** (`ValidateOffboxTarget`): host/user/repo must not start with `-` (ssh
|
||||
> option-injection) or carry metacharacters/traversal; `OffboxConfigured` fails closed on an invalid
|
||||
> target. Image: `restic` + `openssh-client` (re-added; restic's sftp backend shells out to `ssh`).
|
||||
|
||||
Reference in New Issue
Block a user