3457415117
Recovery-mode toggle (global key, bounded auto-expiry) gates re-enroll + restore-directive serving. Re-enroll rotates the agent<->hub credential to the new box (old key revoked); returns the opaque escrow blobs + non-secret directive. Store gains recovery_mode_until + identity_blob + directive_json. Hub holds no usable secret + no Cloudflare write-power (operator-side rotation). Doc 03 §9: slice 10 CLOSED. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
50 lines
2.5 KiB
Markdown
50 lines
2.5 KiB
Markdown
# felhom.eu — task reports
|
|
|
|
> **Overwrite** this file with a summary of the most recent task only (uniform with the other repos; not cumulative). The cumulative hub history lives in [hub/CHANGELOG.md](hub/CHANGELOG.md).
|
|
|
|
---
|
|
|
|
# REPORT — Slice 10D (hub half): DR capstone — recovery mode + re-enroll + directive serving (hub v0.11.0) (2026-06-10)
|
|
|
|
## Type
|
|
|
|
TASK (CC-implemented). The hub half of the slice-10 DR capstone (closes slice 10). Pairs with
|
|
`felhom-agent` v0.18.0 (identity escrow + restore-mode consumption).
|
|
|
|
## What changed (hub)
|
|
|
|
The hub ORCHESTRATES recovery but holds **no usable secret and no Cloudflare write-power** — a
|
|
compromised hub can at most hand out **opaque** blobs (they need `R`, which the hub never has) + rotate
|
|
its own per-host credential. It cannot hijack a customer's tunnel (the destructive rotation is the
|
|
operator's job).
|
|
|
|
### API
|
|
- **`PUT/DELETE /admin/hosts/{id}/recovery-mode`** (global key) — arm/disable recovery mode with a
|
|
bounded TTL (clamped [60s, 4h], default 30m → **auto-expires**). Directive + re-enroll are served
|
|
ONLY while active.
|
|
- **`POST /hosts/{id}/re-enroll`** — gated ONLY on recovery mode (the lost box has no old key). Rotates
|
|
the host's API key to the new box's key (**old box revoked**) + returns the directive + opaque blobs.
|
|
- **`GET /hosts/{id}/restore-directive`** (re-enrolled key, recovery-gated) — re-fetch.
|
|
- The slice-7 escrow upload now also accepts the **identity blob** + **non-secret directive** (additive).
|
|
|
|
### Store
|
|
- `hosts.recovery_mode_until`; `host_escrow.identity_blob` + `directive_json`. Methods:
|
|
`SetRecoveryMode`/`ClearRecoveryMode`, `RotateHostAPIKey`, `SaveHostDRBundle`/`GetHostDRBundle`.
|
|
|
|
## Tests (green)
|
|
- re-enroll refused without recovery mode (403); recovery-arm is global-key-only; re-enroll **rotates +
|
|
revokes** (old key→401, new key→200); directive served only in recovery mode + **expires**; clear
|
|
disables re-enroll.
|
|
|
|
## Docs
|
|
- Doc 03 §9 (10D done → **SLICE 10 CLOSED**) + the host-loss DR flow with the **operator-side rotation**
|
|
model (hub orchestrates + read-only verifies; the operator deletes the stale connector + rotates the
|
|
tunnel/PBS token from a trusted environment).
|
|
|
|
## Deferred (non-blocking, per the locked model)
|
|
- The Config DR/Recovery **web UI** (functional today via the recovery-mode admin API) + a small
|
|
operator rotation CLI. **No Cloudflare write-credential is in the hub by design.**
|
|
|
|
## Pending
|
|
- Build + deploy hub v0.11.0 + agent v0.18.0; run the operator-in-the-loop DR drill (throwaway identity).
|