15397468df
Per-test results + companion red-proof, deployed v0.14.0 pod verification, live throwaway enroll/reuse/401/404/400 + Bearer-auth check, cleanup confirmed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TtXesNa2LGbMmE4DNL6SE7
87 lines
5.7 KiB
Markdown
87 lines
5.7 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).
|
|
|
|
---
|
|
|
|
## hub v0.14.0 — Passphrase-authed host enrollment (Day-0 option C) — 2026-06-26
|
|
|
|
**Task:** Implement option C from `documentation/audits/SPIKE-day0-firstboot-handshake-2026-06-26.md`:
|
|
a passphrase-authed, mint-once-reuse host-enrollment endpoint so Day-0 carries **one** secret (the
|
|
retrieval passphrase) and the global operator key never enters the field deploy path. Single repo
|
|
(`felhom.eu`, `hub/`).
|
|
|
|
### Confirmed baseline
|
|
- felhom.eu (hub) `main` @ `230980f` (= origin/main, verified by `git fetch`), version `v0.13.1`
|
|
(ldflags-injected; no source constant) → target `v0.14.0`.
|
|
- Reconfirmed every relied-on symbol live: `handleConfigRetrieve` (auth pattern), `handleAdminCreateHost`
|
|
(mint block — left untouched), `UpsertHost` (`ON CONFLICT(host_id)`), `GetHostByAPIKey`/`GetHost`/
|
|
`scanHost`/`hostSelectCols`, `idx_hosts_customer`, `configgen.RandomHex`.
|
|
|
|
### Files changed
|
|
| File | Change |
|
|
|------|--------|
|
|
| `hub/internal/store/store.go` | **+** `Store.GetHostByCustomer` (SELECT … WHERE customer_id=? ORDER BY updated_at DESC LIMIT 1; nil-on-not-found; uses `idx_hosts_customer`) |
|
|
| `hub/internal/store/host_test.go` | **+** `TestGetHostByCustomer` (none→nil / one / two→most-recent / other-customer) |
|
|
| `hub/internal/api/handler.go` | **+** route `POST /host-enroll` (exact-match, beside `/host-report`); **+** `handleHostEnroll` (passphrase auth → mint-once-reuse). `handleConfigRetrieve` + `handleAdminCreateHost` **unchanged** |
|
|
| `hub/internal/api/host_enroll_test.go` | **+** 5 scenario tests (mint/reuse/401-no-mint/404/400) |
|
|
| `hub/CHANGELOG.md` | **+** v0.14.0 entry (newest on top) |
|
|
| `hub/README.md` | **+** "Host Enrollment (Day-0, option C)" section |
|
|
| `documentation/audits/SPIKE-day0-firstboot-handshake-2026-06-26.md` | **+** "c-pref SHIPPED in v0.14.0" pointer in §5 |
|
|
|
|
### Commits
|
|
- `8098237` — `hub v0.14.0: passphrase-authed host enrollment (Day-0 option C)` (code + tests + CHANGELOG + README + spike pointer).
|
|
- `<this REPORT commit>` — REPORT.md overwrite.
|
|
|
|
### Tests (temp-sqlite stores; no network)
|
|
- **Per-scenario (all PASS):**
|
|
- A — first enroll mints → `201`, `host_id=c1-<6hex>`, `api_key=64hex`, exactly 1 host row, `GetHostByAPIKey(minted)` resolves.
|
|
- B — second enroll **reuses** → `200`, creds **byte-for-byte identical**, still exactly 1 host row.
|
|
- C — wrong passphrase → `401`, host-row count **unchanged at 0** (auth-before-mint proven).
|
|
- D — unknown customer → `404`, no mint.
|
|
- E — missing header → `401`; missing `customer_id` → `400`.
|
|
- store `TestGetHostByCustomer` — none→nil, one→hit, two→most-recently-updated.
|
|
- **Companion red-proof (§10):** temporarily disabled the reuse branch (always-mint) → Scenario B
|
|
**FAILED** as required (`second status = 201, want 200` — second call returned a *new* `host_id`
|
|
`c1-0d44c6`, not the first). Reverted to mint-once-reuse → green. This proves the idempotency test is
|
|
load-bearing, not hollow.
|
|
- **Test count:** api package 29 → **34** (+5); store package 11 → **12** (+1).
|
|
- **Green gate:** `go build ./... && go vet ./... && go test ./...` — all `ok` (api, configgen, monitor,
|
|
store), no failures.
|
|
|
|
### Deploy + pod verification
|
|
- `./build.sh 0.14.0 --push` on the build box (auto-pulls `8098237`) → image
|
|
`gitea.dooplex.hu/admin/felhom-hub:0.14.0` pushed (digest `sha256:541cdb54…`, 24M).
|
|
- `kubectl set image deploy/hub` → rollout succeeded; pod `hub-6c7d8bcc9-454n9` `1/1 Running`.
|
|
- Startup log: `[INFO] felhom-hub 0.14.0 starting`; deployed image confirmed `…:0.14.0`.
|
|
|
|
### Live verification against the real endpoint (throwaway `enroll-spike-2026-06-26`)
|
|
Curl-against-real-endpoint (the exact call the future host-bootstrap script makes). Secrets stayed
|
|
resident on the build box; only statuses / non-secret host_id / identical-bytes booleans surfaced.
|
|
|
|
| Check | Result |
|
|
|-------|--------|
|
|
| 1. first enroll (correct passphrase) | **201**, `host_id=enroll-spike-2026-06-26-c641d7`, api_key 64-hex |
|
|
| 2. second enroll (idempotent) | **200**, host_id identical YES, api_key identical YES, **exactly 1 host row** |
|
|
| 3. wrong passphrase | **401** |
|
|
| 4. unknown customer | **404** |
|
|
| 5. missing customer_id | **400** |
|
|
| 6. **Bearer-auth proof** — `GET /hosts/{host_id}/desired-state` with `Authorization: Bearer <enrolled key>` | **200** (the enrolled credential is a real, working host key end-to-end) |
|
|
|
|
### Cleanup (throwaway discipline, mirroring the spike)
|
|
- Deleted throwaway `customer_configs` / `hosts` / `host_reports` rows → counts **0/0/0** (verified).
|
|
- Real `demo-felhom` customer confirmed **still present / untouched**.
|
|
- Uninstalled temporary `sqlite` from the hub pod (`apk del`); removed all `/tmp/enroll-spike*` scratch.
|
|
- The global key was read out-of-band into a shell var only; **no secret** written to disk, any
|
|
committed file, or this report.
|
|
|
|
### Observations / out-of-scope (not acted on)
|
|
- `POST /admin/hosts` is still **PROVISIONAL** (`handler.go:595`) and is left **as the operator/global
|
|
escape hatch** — its removal/lock-down is a separate cutover decision, deliberately not done here.
|
|
- `GET /config/{id}` was **not** modified (no host fields, no JSON, raw-YAML body intact) — the in-guest
|
|
controller pull is unaffected.
|
|
- The host-bootstrap script (Day-0 wiring) is the **next** task — not started here. `host-enroll`'s
|
|
mint-once-reuse is the idempotency primitive that step will depend on.
|
|
- Under option A, `admin/hosts` re-mint still orphans a prior host (no idempotency there); option C
|
|
fixes this structurally for the enroll path.
|