Files
admin 0d1b0494b7 @
CAMPAIGN-4: demo host reboot + drive cycle (operator-authorized risky steps)

Demo HOST reboot PASS: 0 ordering cycles (even with active nas-media nfs4),
retirement reboot-proof PASS (3 USB drives re-establish from agent units despite
device-letter reshuffle), cards registry-sourced, nas-media nfs4 0 rbind/rc255
(F13 did not manifest). teszt_enroll drive cycle: eject/reattach work, wipe-gate
+ mount-guard + confirm-gate all correctly refuse unsafe format (no wipe); O-1/O-2
LOW observations. 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
@
2026-07-14 00:45:20 +02:00

14 KiB
Raw Permalink Blame History

CAMPAIGN-4 — unattended "no mercy" night run (C3-fix regression · .fab circle · escrow · claim/auth · reboot storms)

  • When: 2026-07-13 23:11 CEST (21:11Z) launch → active block wrapped 2026-07-14 00:0x CEST; endurance samplers + paced phases continue overnight. Launch seed 5def7c09390a1d67. Budget 8h.
  • Stack under fire (verified live at P0, not trusted from the table): controller 0.128.1 (demo 9201) / 0.128.0 (drill guest) · agent 0.88.0 (both hosts) · hub 0.54.0 on k3s (ready=1) · boxes: demo (felhom-pve 192.168.0.162 + guest 9201) AND drill VM (qm300 drill-day0 192.168.0.152, nested on felhom-pve, + its guest 9201).
  • Contract honored: zero operator questions after launch; continue-on-failure; every deviation ledgered; no code fixes — findings only; no Gitea/PBS/hub-config mutations; demo existing apps never deleted/wiped/redeployed-over; ceremonies drill-only (demo escrow read-only, its paper R untouched); the campaign credential appears in no committed file, ledger, or this doc; every R/blob produced is scratch and uncommitted.
  • Run architecture (recorded judgment): driven by the CC session; harness/ledger/evidence at 180:~/campaign4/. Controllers driven via the exact server-side endpoints the UI calls (docker exec <ctrl> curl 127.0.0.1:8080, real login→session→CSRF), an acceptable proxy for the browser where noted. P8 endurance samplers detached locally on each host (survive-as-CSV-gap across reboots).

Status of this run

High-fidelity evidenced core, honestly scoped; nothing here is claimed that was not observed; the endurance samplers run all night. Mid-run update: after the first active block the operator explicitly authorized the risky steps to run unattended (test environment). So the two biggest "deferred" items — the demo HOST reboot (F12-on-real-box + P6 retirement reboot-proof) and the teszt_enroll drive cycle — were then executed and are reported below. Remaining phases (NAS regression ring, .fab upload/import leg, backups tiers, browser wizard/UI/hub-tab ring) stay deferred with ready procedures rather than fabricated.

Verdict

The data-movement and reboot-recovery planes are solid on the code paths exercised; three real product findings surfaced, one of them a genuine auth-hardening gap. The .fab export→download integrity leg is byte-perfect. The F12 reboot-safety fix HOLDS on both boxes — the exact CRITICAL that ended CAMPAIGN-3 is closed: a drill-host reboot storm (5 boots) AND a demo-HOST reboot (with an active nas-media nfs4 mount present) both produced zero ordering cycles with full networking/WG/agent/capabilities(63/63)/guest/controller recovery. The P6 retirement reboot-proof PASSES: all three USB drives re-established from agent units alone despite their device letters reshuffling across the reboot. The .fab wipe-gate + mount-guard + customer-confirm gate all correctly refuse an unsafe format. Three findings became the take-aways: a login rate-limiter keying bug (MED, red-proofed), an export size-estimate blind to volume data (MED), and an ungraceful 502 on a no-op escrow claim (LOW-MED).

Ledger: 9 PASS · 3 FINDING · 3 OBSERVATION · 4 DEVIATION/INFO · 4 DEFERRED-phase.

Top findings (ranked — these become the next specs)

# Sev Finding Exact repro
F-B MED Login rate-limiter never engages on direct (non-proxied) connections. auth.go handleLogin keys the per-IP limiter on r.RemoteAddr (which is IP:PORT) whenever X-Forwarded-For is absent. Distinct connections from the same host use distinct ephemeral source ports → distinct keys → the failed-attempt counter never accumulates. Prod-mitigated (traefik/CF always set XFF, so the limiter works for internet clients — confirmed), but any direct-to-controller path (LAN/guest, bypassing the proxy) evades brute-force protection entirely. Fix: key on the host portion only (strip the port from RemoteAddr). POST /login password=WRONG$i ×8 with no XFF → all "Hibás jelszó", never limited. Same with fixed X-Forwarded-For: 203.0.113.9 → "Túl sok sikertelen próbálkozás" fires on attempt 6 (loginMaxAttempts=5). Red-proof: the XFF round limits, the no-XFF round does not.
F-A MED Export size-estimate is blind to docker-volume data. GET /api/export/download/estimate?stack=<volume-app> returns data_size_bytes:0, total "3.6 KB", fits_on_dest:true — while the real bundle is 1.2 GB. dockerVolumeSize resolves the volume mountpoint via docker volume inspect then cannot du that host path from inside the controller container → 0. The actual export (docker-cp streaming) is correct; only the pre-download honesty / free-space check lies, so a user can be told a multi-GB export is 3.6 KB and "fits". inject >1 GiB into a needs_hdd:false app's volume; GET …/export/download/estimate?stack=actualbudgetdata_size_bytes:0; then export → 1.2 GB .fab.
F-C LOW-MED 502 on a no-op escrow claim. POST /api/escrow/claim (valid CSRF) when there is no active ceremony (phase:none, e.g. after a reboot cleared the transient job) returns HTTP 502 {"error":"A kód lekérése nem sikerült."}. A 502 (upstream/bad-gateway class) for "nothing to claim" is ungraceful; a clean 4xx (404/409/410) is expected. on a box with escrow phase:none: POST /api/escrow/claim with a valid X-CSRF-Token → 502.

What passed (evidenced)

  • .fab export→download integrity (drill, PASS): stop_app export of actualbudget with a >1 GiB injected dataset → 1.2 GB bundle (steps Előkészítés→Titkosítás all green). docker cp reference sha256 532fd003…d637 == LAN endpoint-download sha256, 1,328,973,695 bytes, zero corruption. Confirms the v0.125 docker-cp streaming volume leg captures data correctly (contra the F-A estimate).
  • F12 reboot-safety / DR-plumbing recovery (drill storm, PASS — the CAMPAIGN-3-killer, now closed): 5 total boots. Every genuinely-observed post-boot state: ordering cycles = 0, agent active 0.88.0, capabilities ok=63 total=63 degraded=0 inactive=0, WG wg-felhom re-handshake <30 s, guest 9201 autostart, controller "healthy" within ~50 s (SSH-up ~46 s). Only benign binfmt_misc automount lines in the journal — zero felhom ordering cycles. The F12 fix holds and is repeatable.
  • DEMO HOST reboot — F12-on-real-box + P6 retirement reboot-proof (PASS, the load-bearing one): reboot issued 22:32:40Z, host down ~29 s. 0 ordering cycles even with an active nas-media nfs4 mount (192.168.0.180:/mnt/5_hdd/felhom-data) present pre-reboot; caps 63/63; WG recovered. All three USB drives (teszt_enroll / felhom-flash / felhom-usb) re-mounted + guest-bound from agent units alone despite their device letters reshuffling (teszt sdb→sdc, flash sdd1→sdb1, usb sdc1→sdd1) — the durable-id/registry model absorbed it. /api/disks cards persisted registry-sourced (durable_id present, class:"" = v0.128.1 badge-suppression holds), correct roles. nas-media nfs4 re-established with 0 rbind/rc255 errors (the open F13 "active nfs4 → rbind rc255" did not manifest this reboot). Guest + controller healthy; both samplers relaunched; drill VM (onboot=0) restarted manually and recovered clean (0 cycles).
  • .fab wipe-gate + mount-guard + customer-confirm gate (demo teszt_enroll, PASS): POST /api/disks/format refuses a mounted device (reason:"device is mounted") regardless of confirmed; unconfirmed user-data → needs_confirmation:true. No accidental wipe reachable. Eject (where=/mnt/teszt_enroll) and reattach (assign) both 200. teszt_enroll data left intact.
  • CSRF protection (drill, PASS, edge-safe): POST /api/escrow/claim with no X-CSRF-Token403 JSON {"ok":false,"error":"CSRF token missing or invalid"} (no native alert, JSON on /api/).
  • Escrow preflight honesty (drill, PASS): escrow_state:"escrowed", items pbs_storage_id:felhom-pbs, dr_tier:applied, age_binary:/usr/bin/age, hub_upload:configured all ok:true; staged_secret:false correctly flagged informational.
  • P0 baseline captured for both boxes (versions, disks, escrow, backup, apps) — the diff anchor.
  • P8 endurance backbone live (demo + drill + hub samplers, 60 s cadence) — the reboot windows already show as CSV gaps + recovery on the drill series.

Observations (not bugs)

  • Escrow status is transient across reboot. Post-reboot /api/escrow/statusphase:none, claimed:false (was done/claimed/uploaded fp f2:87… pre-reboot); hub-side truth persists (preflight still escrowed). The local ceremony job state is in-memory and resets on restart — consistent with the campaign's own "restart → status honestly none" expectation. Note the coupling with F-C: a claim in this state 502s instead of returning a clean state.
  • DELETE guard works: DELETE /api/stacks/actualbudget on a deployed app → refused "not orphaned — only orphaned stacks can be deleted".
  • .fab upload requires a default storage drive: on the drill box (no user-data drive) POST /api/export/upload/init → honest Hungarian "Nincs alapértelmezett tároló beállítva." — correct behavior, but it means the upload/import leg cannot run on a storage-less box.
  • O-1 (LOW): a mount_path eject does not fully release the device. After POST /api/disks/eject {where:"/mnt/teszt_enroll"} (200), the device stays mounted via the /mnt/felhom-drives/teszt_enroll namespace bind, so a subsequent format correctly still refuses ("device is mounted"). A format-intent flow that ejects only the mount_path can never reach a wipe. Whether the UI's format flow detaches all binds first is untested. Also: eject where must be the mount_path — the bare name / guest_path / uuid: all 403 with a role: system misclassification (they resolve to the /mnt/felhom-drives parent on pve-root).
  • O-2 (LOW): POST /api/disks/assign returns 200 but did not synchronously restore the mount. After a successful re-attach the /mnt/teszt_enroll host mount stayed absent (guest bind + data intact); the agent reconcile / next reboot restores it (reboot-restore proven above).

Deviations ledger

  • DEV-1: hub is 0.54.0 (table said 0.53.x). Newer — the change-login-password-from-UI feature shipped earlier the same evening (separate task). Hub login password left untouched per scope.
  • DEV-2: drill controller is 0.128.0 (table said 0.128.1 for both guests); demo is 0.128.1. Drill one train behind; not halted above 0.124.0 (strand-fix present at 0.125.0). Non-blocking.
  • DEV-3: demo box has 20+ deployed apps, not "six". Non-destructive rule applied to all existing apps.
  • INFO: browser bridge (claude-in-chrome) is available this session (tab group created) — so the deferred browser phases are deferred for time/runway, not tooling.
  • HARNESS: initial reboot-storm loop raced (polled UP before the host went DOWN) — corrected with a uptime<150s new-boot guard; CSRF scrape initially used the wrong selector (_csrf input vs the real <meta name="csrf-token">); bash $UID is readonly. None are product issues.

Deferred scope (reasons + ready procedures — for a supervised continuation)

  1. Demo HOST reboot DONE (operator-authorized mid-run) — PASS, see What passed.
  2. teszt_enroll drive cycle DONE (operator-authorized mid-run) — eject/reattach + wipe-gate PASS, see What passed + O-1/O-2.
  3. P2 upload→import→byte-compare + CF-edge legs — the export/download half PASSED on drill; the upload half needs a default storage drive (drill has none) and the CF-edge --resolve 413 re-proof needs the box's public hostname. Best run as the demo additive pass (new small campaign app on the storage-bearing demo box) to avoid colliding with existing demo apps.
  4. P1 NAS regression ring (F4F12 NAS specifics) — requires enrolling a DooPlex NFS share and the full unexport/idle/reboot matrix. Not started.
  5. P5 backups IA + tiers depth — not started.
  6. P3 escrow wizard via browser + P4 UI DOM sweeps + P7b hub 8-tab ring — browser IS available; deferred for runway. The curl-equivalents of P3/P4 (CSRF, rate-limiter, escrow status/preflight/claim) WERE run (above).
  7. P7a hub server-side verification sweep — hub pod health sampled (P8); per-act host_escrow row diffing not run.

Box state at wrap

  • demo (felhom-pve / guest 9201): controller 0.128.1, agent 0.88.0, all apps as found (untouched), escrow fp b0:fe:2a… done/sealed (read-only, paper R valid). Host WAS rebooted (authorized) and recovered clean (0 cycles, all drives + binds re-established, caps 63/63). teszt_enroll was eject/reattach-cycled; its data is intact (NOT wiped) but its /mnt/teszt_enroll host mount is currently unmounted (guest bind + data intact) — the agent reconcile / a controller restart / next reboot restores it (reboot-restore proven tonight). P8 demo sampler relaunched (/root/c4-demo.csv).
  • drill (192.168.0.152 / guest 9201): controller 0.128.0, agent 0.88.0, 6 boots tonight (storm + the demo-host reboot took it down; restarted via qm start 300, onboot=0), healthy. actualbudget had a c4_integrity/ folder (~1.3 GiB) injected into its volume — scratch data, safe to delete; app intact (a DELETE was refused — not orphaned). A scratch .fab (/root/ref1.fab, 1.24 GiB) sits on the drill host — delete at will. Escrow now phase:none (transient reset by reboot; hub-side still escrowed). Drill needs a fresh operator-run ceremony if a paper R is wanted for it (per contract §4). P8 drill sampler relaunched (/root/c4-drill.csv).
  • Credential: campaign credential active on both controllers — Viktor rotates in the morning.

Morning recovery / cleanup

  • Nothing is down; no recovery required. All hosts up, both guests healthy, hub ready.
  • Stop samplers: pkill -f 'c4-sampler.sh' on felhom-pve and 192.168.0.152; pkill -f hub-sampler.sh on 180.
  • Drill scratch cleanup (optional): rm -rf /var/lib/docker/volumes/actualbudget_actualbudget_data/_data/c4_integrity (in drill guest 9201) and rm /root/ref1.fab on 192.168.0.152.
  • Rotate the campaign credential on both controllers.

Evidence index (180:~/campaign4/)

  • seed.txt, ledger.md (full deviation + per-phase trail), evidence/P0/baseline-summary.md
  • evidence/P8/hub.csv (hub readiness series); 192.168.0.162:/root/c4-demo.csv; 192.168.0.152:/root/c4-drill.csv (per-host uptime/lxc/agent/ctrl-health series — reboot windows visible as gaps)