hub v0.54.0: change operator login password from the Configuration UI

Adds a "Login password" card on /configuration. The password was previously
settable only via the hub-config ConfigMap (auth.password_hash) + redeploy.

- store: hub_settings key operator_password_hash + Get/SetOperatorPasswordHash
- server: passwordHash field -> configPasswordHash (seed); new
  effectivePasswordHash() (DB override wins, else seed) is now the single
  source for the CSRF gate, RequireAuth, and handleLogin
- POST /configuration/password (handleChangePassword): requires current
  password, 8-72 byte new + confirm, bcrypt cost 10, persists DB override;
  existing sessions kept valid; ConfigMap stays the break-glass reset path
- UI: current/new/confirm form + inline mismatch pre-check + 6 flashes
- tests + red-proofs: override precedence, happy-path via handleLogin,
  wrong-current rejection, mismatch/too-short/no-op, template render
- docs: CHANGELOG, README (auth+config), REUSE, REPORT

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LbMm4T7Ayzs1unB9pN6Uqd
@
This commit is contained in:
2026-07-13 22:46:49 +02:00
parent ed271fadb2
commit a1d045079f
8 changed files with 440 additions and 32 deletions
+53 -21
View File
@@ -2,26 +2,58 @@
> **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); the scripts history lives in [scripts/CHANGELOG.md](scripts/CHANGELOG.md).
## Controller-driven escrow ceremony — felhom.eu leg (installer + docs) — 2026-07-13
## Hub v0.54.0 — operator login password changeable from the UI — 2026-07-13
Companion commits to felhom-agent **v0.88.0** (`1c3a3ef`) + felhom-controller **v0.127.0**
(`08a966b`) — the customer-facing recovery-code wizard. felhom.eu commit `375cb08`.
### What & why
- **host-install v1.16.0:** the `FELHOM_ESCROW` sudoers alias ships via the EXISTING canonical
sudoers fetch (no new step; header documents it). Hub `hostInstallVersion` synced to 1.16.0 in
the same commit (`hostinstall_gates.py` green; hub green gate run; **no hub deploy** — the
const rides the next hub train, display-only lag).
- **RUNBOOK-escrow-ceremony.md rewritten:** the controller wizard is the PRIMARY path; the CLI is
the operator fallback (text mode unchanged); **F1 threat-model paragraph** (R transits the CF
tunnel once at reveal — accepted 2026-07-13, same trust class as claim code/login password;
agent→controller leg never leaves the box; LAN-direct delivery PARKED); stale-blob warning +
supersede/void semantics documented; CLI staged-secret rule spelled out (a CLI run without the
staged secret mints a hash-less blob → the new Scenario-F warning).
- Deploy + live validation evidence: felhom-agent/REPORT.md + felhom-controller/REPORT.md
(agents 0.88.0 on demo host + drill VM, 63/63 capabilities; controllers 0.127.0 on both guests;
Scenario F fired live on BOTH boxes' hash-less blobs; the drill blob repaired —
`restic_pw_sha256` now covers the local password; one-shot claim + 410 proven endpoint-exact).
- **Operator follow-ups:** (1) one supervised wizard pass with Viktor's drill login (the full
browser leg incl. re-auth + reveal — CC cannot type the customer-owned password), ideally also
on the demo box to clear ITS legacy stale warning; (2) publish agent 0.88.0 + vouch in the
Day-0 manifest at the next publish train (deployed hosts got direct deploys).
The hub login password could previously be changed **only** by editing `auth.password_hash` in the
`hub-config` ConfigMap and redeploying — no in-app path existed (operator hit this wall). Added a
**Configuration → Login password** card that changes the password at runtime, persisted in the DB,
with the ConfigMap kept as the break-glass reset path.
### Design (matches the controller-version-floor precedence pattern)
- **Store** (`internal/store/store.go`): new `hub_settings` key `operator_password_hash` with
`GetOperatorPasswordHash()` / `SetOperatorPasswordHash()` (thin wrappers over the existing
`getSetting`/`setSetting`). No schema change.
- **Server** (`internal/web/server.go`): the static `Server.passwordHash` field is renamed
`configPasswordHash` (the hub.yaml SEED). New `effectivePasswordHash()` = **DB override wins, else
config seed** — and it is now the single source for every auth check (CSRF gate, `RequireAuth`
session + Basic-Auth paths, `handleLogin`).
- **Handler** `POST /configuration/password` (`handleChangePassword`): requires the current password
(verified against the effective hash), new password 872 bytes, matching confirmation, rejects a
no-op. On success bcrypts (cost 10) and persists the override. Existing sessions stay valid; CSRF
enforced by the central `ServeHTTP` gate; no secret logged.
- **UI** (`templates/configuration.html`): current/new/confirm fields, inline client-side mismatch
pre-check, six flash outcomes.
### Recovery posture (operator's explicit choices)
- Requires the **current** password to change it (blocks a walk-up attacker on an open session).
- ConfigMap `auth.password_hash` remains the **break-glass fallback** — blank the DB row (or edit the
manifest + redeploy) to reset a forgotten password.
### Tests & red-proofs (`internal/web/change_password_test.go`)
- `TestEffectivePasswordHash_DBOverrideWins` — override wins; clearing falls back to the seed.
- `TestChangePassword_HappyPath` — end-to-end through `handleLogin` (new works, old dead).
- `TestChangePassword_WrongCurrentRejected` — security anchor: no override written.
- `TestChangePassword_ValidationRejections` — mismatch / too-short / no-op refused, no override.
- `TestConfigurationPage_RendersPasswordCard` — form renders through the production template.
- Red-proofs verified: dropping the current-password check → WrongCurrentRejected fails; breaking the
override precedence → precedence + happy-path login assertions fail.
### Gates
- `go build ./... && go vet ./... && go test ./...` (hub): green.
- `python scripts/hub_confirm_gate.py`: green (no native confirm/prompt in templates).
### Docs
- `hub/CHANGELOG.md` (v0.54.0), `hub/README.md` (Authentication + Configuration sections),
`REUSE.md` (`effectivePasswordHash`, `Get/SetOperatorPasswordHash` rows).
### Deploy
Hub image built + pushed as `v0.54.0`; `manifests/hub.yaml` tag bumped; ArgoCD synced; verified live
on `hub.felhom.eu`.