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:
2026-06-26 15:35:24 +02:00
parent 230980f7a8
commit 8098237ce1
7 changed files with 331 additions and 1 deletions
@@ -176,8 +176,15 @@ scope — the `customerID` (path param) and the `h.store` handle (which exposes
headers alongside the YAML body. The in-guest controller ignores them; the script reads them. Fewer
endpoints but couples two tiers' secrets into one response.
> **UPDATE 2026-06-26 — c-pref SHIPPED in hub v0.14.0.** The dedicated passphrase-authed endpoint
> landed as `POST /api/v1/host-enroll` (mint-once-reuse via the new `Store.GetHostByCustomer`); the
> proven `GET /config/{id}` and the global-key `POST /admin/hosts` escape hatch are unchanged. See the
> hub `CHANGELOG.md` v0.14.0 + `README.md` "Host Enrollment (Day-0, option C)". The route is the exact
> `path == "/host-enroll"` form (not `/hosts/enroll`, which would collide with the `/hosts/` per-host
> prefix routes).
**Recommendation:** ship **Option A now** (it is proven and needs nothing). Adopt **Option C, variant
(c-pref) — a dedicated passphrase-authed `hosts/enroll` endpoint, mint-once-reuse — at the production
(c-pref) — a dedicated passphrase-authed `host-enroll` endpoint, mint-once-reuse — at the production
cutover**, on the secret-hygiene argument: it removes the operator-tier global key from the field
deployment entirely and reduces the operator-carried secret set to one (the passphrase the customer
already has). It is a small, isolated change that does not touch the proven controller config-pull.