Files
felhom-agent/REPORT.md
T
admin e4dfe5ccc7 slice 10D (agent): DR capstone — identity escrow + restore-mode consumption (v0.18.0)
Identity escrow wraps {tunnel_token,pbs_token} under the SAME R via age
(scrypt+ChaCha20-Poly1305), reusing the K-escrow pty; wrong R fails closed.
escrow.Create optionally emits the identity blob; escrow-create uploads it +
the non-secret directive; identity-consume recovers it (R by hand, never
logged). K-escrow + 10C Consume untouched. Closes slice 10 with hub v0.11.0;
operator-side rotation model (hub holds no Cloudflare write-power).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 09:48:36 +02:00

3.1 KiB

REPORT — slice 10D (agent half): DR capstone — identity escrow + restore-mode consumption (v0.18.0) (2026-06-10)

Overwrite-latest report. Cumulative history: CHANGELOG.md.

What was implemented

The agent half of the slice-10 DR capstone (closes slice 10), grounded by both 10-series spikes (escrow-consumption + identity-restore). The hub half (recovery-mode, re-enroll, directive serving) is hub v0.11.0. Rotation model (locked): the hub holds no Cloudflare write-power; the destructive tunnel/PBS rotation is the operator's step from a trusted environment (same spirit as 10B).

Identity escrow (internal/escrow/identity.go)

  • WrapIdentity/UnwrapIdentity (+ …Bundle) wrap the {tunnel_token, pbs_token} bundle under the SAME recovery code R via age (scrypt + ChaCha20-Poly1305 — vetted passphrase-AEAD, not hand-rolled), reusing the K-escrow pty mechanism (passphrase via the tty, data via files; R/tokens never logged). Same two-factor, zero-knowledge shape as the K-escrow; a wrong R fails closed (no bundle). age is a runtime dep for the identity path (like proxmox-backup-client for K).
  • escrow.Create gains an optional IdentityBundle → emits IdentityBlob under the same R (additive; the K-escrow + 10C Consume are byte-unchanged; self-verifies the identity round-trip).
  • --selftest=escrow-create -identity-bundle <f> -directive <f> (wrap + upload the identity blob + non-secret directive) and --selftest=identity-consume -blob <f> -keydest <f> (R via FELHOM_RECOVERY_CODE; tokens written 0600, never logged) — the real code, drillable.

Restore-mode orchestration (10D.3)

Composed from proven pieces: re-enroll (hub) → fetch directive (10A) → Consume K (10C, fingerprint -gated) + identity-consume (R by hand) → restore guests from PBS (restore-overwrite gated by 10B) → re-establish the tunnel (recovered token + reconstituted origin — proven in the 10D spike). The destructive cred rotation is the operator's step.

Tests

  • identity round-trips (wrap→unwrap byte-identical; blob is opaque ciphertext); wrong R fails closed + blob stays retryable; input validation. K-escrow/10C unchanged. go test ./... green (age integration tests gated to a host with the age CLI).

Versioning / docs

  • Version 0.17.0 → 0.18.0; CHANGELOG.md. Doc 03 §9 (10D done → slice 10 CLOSED) + the host-loss DR flow with the operator-side rotation model.

Deferred (non-blocking, per the locked model)

  • The hub Config DR/Recovery web UI (functional via the recovery-mode admin API today) + a small operator rotation CLI (the tunnel/PBS rotation + stale-connector delete is a documented operator procedure from the trusted environment — the hub never holds Cloudflare write-power).

Pending

  • The operator-in-the-loop DR drill (live, throwaway identity): host-loss → recovery mode → re-enroll (old key revoked) → R → consume → restore → re-establish → operator rotates/retires the old connector → only the new box serves. CC drives the [CC] steps; the operator does the trusted steps.