hub v0.14.0: passphrase-authed host enrollment (Day-0 option C)
New POST /api/v1/host-enroll (handleHostEnroll): X-Retrieval-Password authed,
body {customer_id} -> {host_id, api_key}. Mint-once-reuse (201 first, 200
reuse) so re-running the host-bootstrap never orphans a running agent's key;
auth checked before any mint. Backed by new Store.GetHostByCustomer
(ORDER BY updated_at DESC LIMIT 1, idx_hosts_customer).
GET /config/{id} and global-key POST /admin/hosts left untouched. Exact-match
route (path == "/host-enroll") to avoid the /hosts/ prefix collision.
Tests: host_enroll_test.go (mint/reuse/401-no-mint/404/400) + GetHostByCustomer
store test; companion red-proof verified always-mint fails the reuse assertion.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TtXesNa2LGbMmE4DNL6SE7
This commit is contained in:
@@ -1,5 +1,30 @@
|
||||
# Felhom Hub — Changelog
|
||||
|
||||
## v0.14.0 — Passphrase-authed host enrollment (Day-0 option C) (2026-06-26)
|
||||
|
||||
Adds the single-secret Day-0 host-enrollment path proven in
|
||||
`documentation/audits/SPIKE-day0-firstboot-handshake-2026-06-26.md` (option C). The operator /
|
||||
host-bootstrap script now carries **only the customer's retrieval passphrase** — the global operator
|
||||
key never enters the field deploy path.
|
||||
|
||||
- **New endpoint `POST /api/v1/host-enroll`** (`internal/api/handler.go`, `handleHostEnroll`):
|
||||
passphrase-authed (`X-Retrieval-Password` header, body `{customer_id}`), returns
|
||||
`{host_id, api_key}`. **Mint-once-reuse** — mints on first call (`201`), returns the existing
|
||||
credential byte-for-byte on every subsequent call (`200`), so re-running the bootstrap never orphans
|
||||
a running agent's key. Auth is checked **before** any mint (a wrong passphrase never writes a row):
|
||||
wrong/missing passphrase → `401`, unknown customer → `404`, missing `customer_id` → `400`. Mirrors
|
||||
`handleConfigRetrieve`'s auth pattern + `handleAdminCreateHost`'s mint block.
|
||||
- **New store method `Store.GetHostByCustomer`** (`internal/store/store.go`): `SELECT … FROM hosts
|
||||
WHERE customer_id = ? ORDER BY updated_at DESC LIMIT 1` (uses `idx_hosts_customer`), nil-on-not-found.
|
||||
Backs the reuse lookup. >1 host for a customer (not expected in Day-0) → most-recent wins, never a
|
||||
duplicate mint.
|
||||
- **Unchanged & deliberately untouched:** `GET /api/v1/config/{id}` (controller pull — same raw-YAML
|
||||
body) and `POST /api/v1/admin/hosts` (global-key operator escape hatch, still PROVISIONAL pending the
|
||||
cutover lock-down).
|
||||
- Tests: `internal/api/host_enroll_test.go` (mint/reuse/401-no-mint/404/400, +5) and a
|
||||
`GetHostByCustomer` store test (+1); companion red-proof confirmed an always-mint variant fails the
|
||||
reuse assertion.
|
||||
|
||||
## v0.13.1 — DR recipe v1 drive-shape sync: test-data + regression guard only (2026-06-16)
|
||||
|
||||
**No behavior change — redeploy optional.** Tracks the agent's v0.39.0 v1 host-half drive shape (which
|
||||
|
||||
Reference in New Issue
Block a user