From d4641c221d4e81fbf1eecda093104588736fd726 Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Sun, 12 Jul 2026 18:56:29 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20v0.122.0=20customer-claim=20gate=20?= =?UTF-8?q?=E2=80=94=20REPORT=20+=20CONTEXT?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CONTEXT.md | 17 ++++++- REPORT.md | 129 +++++++++++++++++------------------------------------ 2 files changed, 56 insertions(+), 90 deletions(-) diff --git a/CONTEXT.md b/CONTEXT.md index 3a5b07d..260dabb 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -7,7 +7,22 @@ > > Ask Claude Code: "Please update CONTEXT.md with what we did today" -Last updated: 2026-07-12 (v0.121.0 — backups page truth pass) +Last updated: 2026-07-12 (v0.122.0 — customer-claim password gate) + +> **2026-07-12 — v0.122.0: CUSTOMER-CLAIM PASSWORD GATE (closes DRILL-day0-vm F-4/F-5). MinAgent +> 0.81.0 unchanged. Requires hub v0.50.0.** The dashboard password is CUSTOMER-OWNED via a one-time +> claim code the hub emails to the registered address — the "no password → open dashboard" default +> is GONE. Unclaimed box (code hash delivered, no password) → serves ONLY `/claim`; every other route +> → claim page (302) or 401 (API). A set password disables the gate (auth wins). Reset rides the same +> code engine (login "Elfelejtett jelszó"). Legacy-open (no password + no hash) → red transition +> banner until the hub delivers a hash. `internal/web/claim.go` (gate + pages + HMAC pre-auth CSRF + +> 5-try/15-min lockout → `claim_lockout` event), `report/claim_sync.go` (ACK cache, idempotent by +> generation), settings `Claimed`/`ClaimCode*`/`ClaimConsumedGeneration`, `config.web.claim_code_*` +> (hub-baked), `--print-reset-code` root hatch. Gate-coverage signature test + 4 red-proofs. +> **LIVE-PROVEN on drill guest 9201 (0.122.0): gate ON via the real edge (/ → 302 claim page, /api → +> 401), code emailed to demo-vm-felhom's registered address.** Floor raise 0.120→0.122 = operator's +> LAST step (supervised). Details: felhom.eu/documentation/audits/DRILL-day0-vm-2026-07-12.md (F-4/F-5 +> RESOLVED). > **2026-07-12 — v0.121.0: BACKUPS PAGE TRUTH PASS. MinAgent 0.81.0 unchanged. Controller-only, no > agent-API change.** Pure UI/data-plumbing on `/backups`; no backup-engine behavior change. Fixes the diff --git a/REPORT.md b/REPORT.md index 3d270ea..95285e6 100644 --- a/REPORT.md +++ b/REPORT.md @@ -1,98 +1,49 @@ -# REPORT — v0.121.0: backups page truth pass +# REPORT — v0.122.0: customer-claim password gate (closes DRILL-day0-vm F-4/F-5) -**Date:** 2026-07-12 · **Version:** controller v0.121.0 (from v0.120.0) · **MinAgent:** 0.81.0 (UNCHANGED) -· **Scope:** controller-only; no agent-API change, no backup-engine behavior change · **Deployed:** guest 9201. - -**Confirmed baseline:** `felhom-controller` @ `92d670e8a6` (main), v0.120.0 → v0.121.0. - -## Problem - -On the live demo (`/backups`, v0.120.0) the page contradicted itself: a dead "Részletek" card claimed -"Nincs 2. szintű mentés konfigurálva" (the never-set `.Tier2DriveGroups` field) while six successful -Tier-2 runs showed per-app above it; every per-app "3. mentés" row said "Hamarosan — B2/S3/SFTP" while -the off-box tier was live (Storage Box, 6 snapshots); and an embedded-SQLite-only box rendered -"Adatbázis mentve: 0" / "Nem található adatbázis mentés." as if backups were failing. +**Date:** 2026-07-12 · **Version:** controller v0.122.0 (from v0.121.0) · **MinAgent:** 0.81.0 (UNCHANGED) · **Requires:** hub v0.50.0 ## What shipped -1. **Dead "Részletek" card removed** — operator-approved as redundant (per-app rows + Adatbázisok - section carry the truth). Removes the last uses of `Tier2DriveGroups`, `ResticPassword`, the - `restic-pw` element, and the `toggleTier`/`toggleResticPw`/`copyResticPw` JS. -2. **Per-app "3. mentés" row → real off-box state** — four-state row driven by the pure `tier3State` - (configured→toggle→escrow precedence): `unconfigured` / `off` / `escrow_pending` / `active`. Off-box - status is repo-global (single `LastRun`) — no fabricated per-app run time. "hamarosan" gone. -3. **SQLite-honest DB messaging** — pure `dbSectionState(discovered, dumps)` → `dumps`/`pending`/ - `embedded`. Embedded-only box: stat card "–" + "beágyazott DB-k a kötetmentésben"; section - "…beágyazott adatbázist használnak (pl. SQLite)…". Discovered-not-dumped: "…az első ütemezett mentés - éjjel fut le." -4. **Dead/raw display fields fixed** — `Tier1LastRun`/`Tier1LastStatus` populated from - `ListRestorePoints` (correct per-drive resolution; no time for unit-less apps); Tier-1/Tier-2 labels - via `timeAgoStr` (relative), confirm() dialog keeps the precise timestamp. -5. **Terminology split** — off-box section retitled "Távoli mentés (3. mentés) — titkosított, offsite" - (+ `#offbox-section` anchor); whole-guest PBS stat card relabeled "Távoli rendszermentés". -6. **Deploy page** — app backup card gains "Mentési beállítások →" → `/stacks//backup`. +The dashboard password is now **customer-owned**, set through a one-time claim code the hub emails +to the registered address. The old "no password → open dashboard" default is gone: a fresh box +(claim-code hash delivered, no password) serves ONLY the claim page — every other route answers the +claim page (`302 → /claim`) or `401` (API). This closes the DRILL-day0-vm F-4 exposure (every Day-0 +dashboard was open on the internet, with the full deploy API = RCE-equivalent) and F-5 (the +unauthenticated geo toggle is now behind the gate too). -## Files +- **`internal/web/claim.go`** — `claimGateActive()` / `effectiveClaimCode()` (ACK-cached settings + beats the config bake by generation), the claim page (`GET /claim`), submit (`POST /claim`: + verify code → set own password via `SetPasswordHash` → mark claimed → consume generation → + session), and `POST /claim/request-new-code` (forwards to the hub reset-request). Code checks: + bcrypt match AND generation-not-consumed (single-use) AND ≤ 72 h. Per-source + global limiter (5 + → 15-min lockout) raising the allowlisted `claim_lockout` event. Pre-auth CSRF = HMAC over + `web.session_secret` (fixes CTRL-007). Min password 12. +- **Gate wiring** (`auth.go`/`csrf.go`/`server.go`/`cmd`): gate atop `RequireAuth`; a SET password + disables it (auth wins — claimed boxes never regress). `/claim*` + `/static/*` reachable pre-auth. + Legacy-open (no password, no hash) → red transition banner (`layout.html`). Login "Elfelejtett + jelszó" link. +- **`internal/report/claim_sync.go`** — caches the ACK `claim {hash, generation}` into settings.json + idempotently by generation (one-way: newer advances; same/older/nil never rewrites; hub outage + never clears). Report carries `claimed` (set-only). Hub bakes `web.claim_code_*` for gate-from- + first-boot. +- **`--print-reset-code`** root escape hatch (one-time local code above cached/baked/consumed gen). -- **Created:** `controller/internal/web/backup_page_state.go` (pure `dbSectionState`, `tier3State`); - `controller/internal/web/backup_page_state_test.go` (9 tests). -- **Modified:** `controller/internal/web/handlers.go` (`AppBackupRow` +`OffboxEnabled`/`Tier3State`; - `buildAppBackupRows` Tier-1 + Tier-3 plumbing; `backupsHandler` `DBSectionState`); - `controller/internal/web/templates/backups.html` (Részletek removed, Tier-3 four-state row, section - rename+anchor, terminology fix, DB messaging, `timeAgoStr` wraps); `controller/internal/web/templates/deploy.html` - (settings link); `CHANGELOG.md`, `REPORT.md`, `CONTEXT.md`, `controller/README.md`. +## Tests + red-proofs -## Tests +- Gate-coverage signature test (`TestClaimGate_EveryRouteGated`): every route → claim page/401, a + deploy POST reaches no handler. Plus happy-path / reuse-refused / expired / lockout+window-reopen + (fake clock), legacy-open vs password-set, claim-sync idempotence. +- **Four §10 red-proofs proven** (mutate → FAIL → revert → green): (1) gate skip-line, (2) single-use + generation — hub `RotateClaimCode` bump AND controller consumed-generation check, (3) reset + non-DoS (clearing claimed → fail), (4) rate-limiter (both scopes disabled → fail). +- Green gate: `go build ./... && go vet ./...` clean; all touched packages pass. Pre-existing + env-sensitive `internal/backup` volume-dump failures are unrelated (fail identically on the clean + baseline — no tar/zstd toolchain on the dev box). -- **New (9, all green):** `TestDBSectionState` (table), `TestTier3State` (table, all 4 states + - precedence), `TestBuildAppBackupRows_OffboxMapping` (Scenario A), `_EscrowPendingPrecedence` - (Scenario B), `_Tier1FromRestorePoints` (Scenario D — unit→RFC3339, no-unit→""), - `TestBackupsTemplate_Tier3Live` (Scenario A + removal assertions), `_Tier3EscrowPending` (Scenario B), - `_DBMessaging` (Scenario C embedded/pending), `_Tier2RelativeTime` (Scenario D). -- **Test count (internal/web):** 108 → 117 test funcs. -- **Companion red-proofs (run → FAIL → revert), all verified:** - 1. Re-insert "hamarosan elérhető" into the rendered Tier-3 `off` block → `TestBackupsTemplate_Tier3Live` - FAILED on the no-"hamarosan" assertion → reverted. ✓ - 2. Hardcode `row.OffboxEnabled = false` in `buildAppBackupRows` → `TestBuildAppBackupRows_OffboxMapping` - FAILED (calibre OffboxEnabled:false, Tier3State:off) → reverted. ✓ - 3. `dbSectionState` drops the `discovered` branch → `TestDBSectionState` FAILED on `(2,0)` want "pending" - → reverted. ✓ - 4. Drop the `row.Tier1LastRun = pts[0].Time` assignment → `TestBuildAppBackupRows_Tier1FromRestorePoints` - FAILED (hasunit Tier1LastRun "") → reverted. ✓ -- **Green gate:** `go build ./...` + `go vet ./internal/web/` + `go test ./internal/web/` all green. - Template gates `template_id_gate.py` + `emoji_gate.py` OK. (Pre-existing, unrelated: `internal/backup` - `TestDumpAppVolumes_*` fail in this environment — docker-dependent; confirmed failing on the clean - `92d670e` baseline too, and I did not touch `internal/backup`.) +## Live validation (drill guest 9201 on qm 300, controller 0.122.0, pre-floor) -## Deploy & live validation - -- **Built + pushed** on 180: `gitea.dooplex.hu/admin/felhom-controller:0.121.0` - (digest `sha256:0478d42a…`, 145M). **Deployed** to guest 9201 via the bootstrap flow. -- **`docker ps`:** `gitea.dooplex.hu/admin/felhom-controller:0.121.0 Up (healthy)`; - log: `Event pushed: controller_started (info) — Controller elindult (0.121.0)`. Version badge on - the page footer reads `0.121.0`. -- **Live UI validation (claude-in-chrome, `https://felhom.demo-felhom.eu`):** - - (a) **no "hamarosan" anywhere** on `/backups` — confirmed (page text + expanded rows). ✓ - - (b) **Calibre-Web Tier-3 row** = "Sikeres" + "restic → u629488-sub1.your-storagebox.de" + - "Utolsó: 13 órája" + "Helyreállítási egység, titkosítva". ✓ - - (c) **Radarr (untoggled) Tier-3** = "Kikapcsolva" + "Ez az alkalmazás nincs kijelölve távoli - mentésre" + "Bekapcsolás"; clicking it jumped the URL to `…/backups#offbox-section`. ✓ - - (d) **"Részletek" card gone.** ✓ - - (e) **DB messaging** — stat card "Adatbázis mentve / beágyazott DB-k a kötetmentésben"; Adatbázisok - section "…beágyazott adatbázist használnak (pl. SQLite)…". ✓ - - (f) **Relative times** — Tier-1 "Utolsó: most ✓", Tier-2 "Utolsó: 33 perce Sikeres" (Calibre) / - "3 órája" (Radarr) — no raw RFC3339 in the visible labels. ✓ - - (g) **Deploy page** (`/stacks/calibre-web/deploy`) shows "Mentési állapot → · Mentési beállítások →"; - the new link opens `/stacks/calibre-web/backup` = "2. mentés beállítása" tier-2 config page. ✓ - - Method: claude-in-chrome (server-side render exercised end-to-end; no pipeline bypass). Per §13, - no restore/format/offbox toggle button was pressed on the live demo. - -## Observations (documented, NOT acted on) - -- **Orphaned style.css classes** left by the Részletek removal (not cleaned per task §9.2): - `details-tier` / `details-tier-header` / `details-tier-body`, `repo-encryption*`, `restic-pw-field`, - `drive-detail-card` / `drive-detail-header`, `tier-empty-state`, `repo-info-row` / `repo-info-rows`, - `repo-tier-title`, `repo-card`, `repo-label` / `repo-value`. Candidates for a later CSS sweep. -- The mojibake drive label ("TĂ˝rhely (felhom-usb)" / "Tárhely (felhom-usb)") is a DATA repair, not a - code fix — out of scope here. -- `felhom.eu/documentation/controller/backup-architecture.md` off-box refresh is a separate queued doc task. +Deployed 0.122.0 → the ACK delivered the claim hash → gate flipped ON within a report cycle. Proven +via the REAL Cloudflare edge (`curl --resolve`): `/` → 302 → "A szerver beállítása" claim page; +`/claim` → 200; `/api/host-metrics` → 401. Controller log: `claim-sync: hub claim code cached +(generation 1)`. Hub log: claim code emailed to the registered address of demo-vm-felhom. The +customer-side claim + reset + the floor raise are the operator's supervised steps.