@
CAMPAIGN-4 findings doc (2026-07-13 night run, active block) Findings-only. Evidenced core: F12 reboot-safety HOLDS (drill storm, 0 ordering cycles, 63/63 caps, full recovery x5 boots); .fab export->download byte-integrity PASS. Three findings: F-B login rate-limiter keys on RemoteAddr:port without XFF (MED, red-proofed), F-A export estimate blind to docker-volume data (MED), F-C 502 on no-op escrow claim (LOW-MED). Remaining phases deferred with procedures. No credential/R/blob committed. 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:
@@ -2,58 +2,26 @@
|
||||
|
||||
> **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).
|
||||
|
||||
## Hub v0.54.0 — operator login password changeable from the UI — 2026-07-13
|
||||
## CAMPAIGN-4 — unattended "no mercy" night run — 2026-07-13 (active block)
|
||||
|
||||
### What & why
|
||||
Full report: [documentation/audits/CAMPAIGN-4-2026-07-13.md](documentation/audits/CAMPAIGN-4-2026-07-13.md). Launch seed `5def7c09390a1d67`. Findings-only (no code fixes).
|
||||
|
||||
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.
|
||||
### Verdict
|
||||
Data-movement + reboot-recovery planes are solid on the paths exercised; three real product findings surfaced. **The F12 reboot-safety fix (the CRITICAL that ended CAMPAIGN-3) HOLDS** — a drill-host reboot storm (5 boots) produced zero ordering cycles with full networking/WG/agent/capabilities(63/63)/guest/controller recovery every time. `.fab` export→download integrity is byte-perfect.
|
||||
|
||||
### Design (matches the controller-version-floor precedence pattern)
|
||||
### This is a partial, honestly-scoped campaign
|
||||
A single unattended session cannot faithfully run the full 8h/two-box/nine-phase destructive matrix without fabricating results; priority items were executed with real evidence, the rest is deferred with reasons + ready procedures. Nothing claimed was not observed. Endurance samplers run all night.
|
||||
|
||||
- **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 8–72 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.
|
||||
### Top findings
|
||||
- **F-B (MED, red-proofed):** login rate-limiter never engages on direct (non-proxied) connections — `handleLogin` keys on `RemoteAddr` (IP:PORT) when `X-Forwarded-For` is absent, so distinct ephemeral ports never accumulate the counter. Prod-mitigated (proxy always sets XFF; limit=5 confirmed with a fixed XFF); direct-to-controller paths evade brute-force protection.
|
||||
- **F-A (MED):** export size-estimate blind to docker-volume data — `download/estimate` reports `0 B`/"3.6 KB"/`fits_on_dest:true` while the real bundle is 1.2 GB; the pre-download honesty/free-space check is meaningless for volume-backed apps.
|
||||
- **F-C (LOW-MED):** `POST /api/escrow/claim` with no active ceremony returns HTTP 502 instead of a clean 4xx.
|
||||
|
||||
### Recovery posture (operator's explicit choices)
|
||||
### Passed
|
||||
`.fab` export→download byte-integrity (ref==LAN sha256, 1.24 GiB, zero corruption); F12 reboot storm (0 cycles, 63/63 caps, full recovery ×5 boots); CSRF 403-JSON on missing token; escrow preflight honesty; P0 baseline both boxes; P8 samplers live.
|
||||
|
||||
- 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.
|
||||
### Deferred (ready procedures in the audit doc)
|
||||
Demo host reboot (retirement reboot-proof; F12 already proven on identical-version drill), `.fab` upload/import + CF-edge legs (need a storage-bearing box), NAS regression ring, backups tiers, escrow wizard + UI DOM sweeps + hub 8-tab ring (browser available; deferred for runway).
|
||||
|
||||
### 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`.
|
||||
### Box state
|
||||
Nothing down. demo untouched (host not rebooted, apps intact, escrow read-only/paper-R valid). drill: 5 boots, healthy, scratch `c4_integrity/` + `/root/ref1.fab` (safe to delete); escrow reset to `phase:none` (transient) — needs a fresh ceremony if a paper R is wanted. **Campaign credential active on both controllers — Viktor rotates in the morning.** Evidence at `180:~/campaign4/`.
|
||||
|
||||
Reference in New Issue
Block a user