From c040c180e981616debec11aa91c6463f4cb91db8 Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Mon, 13 Jul 2026 19:24:17 +0200 Subject: [PATCH] docs: v0.88.0 REPORT (deploy + endpoint-exact live validation: 63/63 caps both hosts, ceremony ~4s, one-shot claim + 410, hub hash covering; red-proofs 1/2/3/6 run-fail-revert) + CONTEXT --- CONTEXT.md | 14 ++++++ REPORT.md | 143 +++++++++++++++++------------------------------------ 2 files changed, 58 insertions(+), 99 deletions(-) diff --git a/CONTEXT.md b/CONTEXT.md index 1e4f43d..24d2be4 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -5,6 +5,20 @@ ## Current +- **v0.88.0** (2026-07-13 eve) — **controller-driven escrow ceremony (agent half), LIVE on demo + host + drill VM (63/63 capabilities both).** `--output=json` machine mode (text mode + byte-identical; extraction into `escrowCeremony()`); the ONE fixed argv + (`escrow.CeremonyArgs()` — shared by the localapi exec + the `escrow-ceremony` capability + (Critical, pbs_dr-gated EXPLICIT) + the new `FELHOM_ESCROW` sudoers alias, three-way + pin-tested); localapi job endpoints (`POST /escrow/ceremony` single-flight 60 s, + status, ONE-SHOT claim → 410, 10-min TTL → `unclaimed_void`, `GET /escrow/preflight`). + R in-memory ONLY (never the job struct — snapshot-hygiene-tested; restart loses it safely). + Live-proven on drill endpoint-exact: stage → preflight all-green (live FELHOM_ESCROW + list-probe) → job ~4 s → hub blob `restic_pw_sha256` covering (repaired the spike's hash-less + blob) → claim 200 once → 410. Coupled: controller v0.127.0 (MinAgent 0.88.0 for the wizard). + **OPEN: publish 0.88.0 + Day-0 manifest vouch (operator) at the next train; deployed hosts got + direct deploys.** Details: REPORT.md + felhom.eu RUNBOOK-escrow-ceremony.md (F1 threat model). + - **v0.87.0** (2026-07-13) — **SystemDisks device-mapper walk (IA finding 2, MEDIUM): legacy-boot hosts get a working drive wizard.** Operator ruling (approved 2026-07-13, verbatim): *resolve device-mapper/raid parents — for the root filesystem's backing block diff --git a/REPORT.md b/REPORT.md index 5e0e515..91ab3af 100644 --- a/REPORT.md +++ b/REPORT.md @@ -1,110 +1,55 @@ -# REPORT — v0.87.0: SystemDisks device-mapper walk (IA finding 2, MEDIUM) +# REPORT — v0.88.0: controller-driven escrow ceremony (agent half) -**Date:** 2026-07-13 · **Version:** felhom-agent v0.87.0 (from v0.86.0, baseline `c20814e` confirmed) · Agent-only classification fix; MinAgent/controller coupling: none. **Safety direction:** the outcome made impossible is a root-backing disk classified candidate; the fixed failure is the opposite over-protection (legacy-boot → everything system → dead wizard). +**Date:** 2026-07-13 · **Version:** felhom-agent v0.88.0 (from v0.87.0, baseline `adf7882f` confirmed) · Coupled: controller v0.127.0 gates its wizard on agent ≥ 0.88.0 (passive version header, header-absent = older). Commit `1c3a3ef` on `main`. Spike basis: felhom.eu `SPIKE-controller-escrow-2026-07-13` (all five mechanisms GO; the production argv adds `--output=json`, a sudoers-semantics-neutral delta). -## §3 spike (live, BEFORE code — both transcripts) +## What shipped -- **Drill host** (root@192.168.0.152, the exact IA-failure shape): legacy boot — `/` from - `/dev/mapper/pve-root` → `readlink -f` → **/dev/dm-1** → `/sys/block/dm-1/slaves` = **sda3** → - `/sys/class/block/sda3/partition` exists → parent **sda**. NO /boot/efi mount (sda2 vfat - exists, unmounted — classic legacy shape). Bonus fact baked into the design: a PHYSICAL disk's - `slaves/` dir EXISTS and is EMPTY (`ls /sys/block/sda/slaves | wc -l` = 0) — so `hasDir` alone - is not resolution; the walk requires non-empty slaves to ground a virtual device. -- **felhom-pve** (regression baseline): `/` → dm-1 → slaves sda3 → sda AND `/boot/efi` → - /dev/sda2 → sda — ESP and walk agree on the same disk ⇒ post-fix output must be identical. - Pre-upgrade API captures saved (scratchpad `pve-disks-pre-0870.json`, `pve-cands-pre-0870.json`, - `drill-pre-0870.txt`): felhom-pve roles (felhom-usb/felhom-flash/raw-sdb user-data, local/lvm - system, pbs/offsite backup), candidates EMPTY on both hosts pre-fix. +- **`--output=json`** — `runSelftestEscrowCreate` extracted into the shared `escrowCeremony()` core + (`cmd/felhom-agent/main.go`); text mode byte-identical (banner/R/exit codes 0/1/2, incl. the + upload-fail-after-R print order); json mode = ONE `escrow.CeremonyOutput` (v1) on stdout, + humans → stderr, `--offline`/`--paperkey` refused (exit 2). +- **The ONE fixed argv** — `internal/escrow/ceremony.go` (`CeremonyBinary` + `CeremonyArgs()`): + shared by the localapi exec, the `escrow-ceremony` capability entry (Critical, `GatedBy: pbs_dr` + explicit), and byte-identically by the new `FELHOM_ESCROW` alias in + `configs/felhom-agent.sudoers`. `TestEscrowCeremonyArgvPinned` + `TestManifestCoveredBySudoers` + lock all three. +- **localapi endpoints** (`internal/localapi/escrow_ceremony.go`): `POST /escrow/ceremony` + (single-flight 409, detached, 60 s), `GET /escrow/ceremony/status` (non-secret; R structurally + absent), `POST /escrow/ceremony/claim` (ONE-SHOT; 410 after; 10-min TTL → `unclaimed_void`, + lazy check + AfterFunc belt), `GET /escrow/preflight` (6 items; staged-secret informational; + `sudo -n -l` grant list-probe). R in-memory only — restart loses it SAFELY (re-run supersedes). -No surprises → proceeded. +## Green gate + red-proofs (all run → fail → revert, outputs in the session record) -## Implementation (commit on main) +`go build ./... && go vet ./... && go test ./...` green before every commit. Test functions +668 → **678** (+9 localapi ceremony, +1 argv pin; 2 probe tests updated for the 4th gated +capability). Red-proofs: +1. one-shot wipe+latch removed → `TestEscrowCeremony_OneShotClaim` FAILED ("R holder survived the claim"). +2. TTL check neutered → `TestEscrowCeremony_TTLExpiryVoidsUnclaimedR` FAILED ("claim after TTL: got 200, want 410"). +3. R injected into the job struct → `TestEscrowCeremony_JobStructCannotCarryR` FAILED ("R leaked into serialized job struct"). +6. argv element mutated (`--upload`→`--upload-mutated`) → `TestEscrowCeremonyArgvPinned` AND + `TestManifestCoveredBySudoers` FAILED (the transitive three-way lock proven). +(4/5 are controller-side — see felhom-controller/REPORT.md.) -- `internal/storage/role.go`: `SystemDisks` now grounds every system mountpoint via - **`physicalDisksOf`** — symlink canonicalization → `wholeDiskOf` fast path (raw disks/ - partitions, unchanged) → **`walkSlaves`**: recursive `/sys/block//slaves` resolution for - virtual devices (dm-*, md*), partitions resolved by the existing regexes (`sda3 → /dev/sda`), - cycle/depth guard (visited set, cap 32). Per-branch conservatism (operator ruling): ANY - unresolvable slave — dangling entry, unrecognizable name (e.g. `md0p1`), virtual with empty - slaves — fails the WHOLE walk → `ok=false` → the UNCHANGED all-system fail-safe. Non-`/dev` - sources (ZFS dataset, NFS, overlay) are unwalkable → fail-safe (same as pre-fix). -- `internal/storage/hostread.go`: **`HostReader.BlockSlaves(name) ([]string, bool)`** — the one - new seam method (root-free; sysfs world-readable); `ProcHostReader` reads - `//slaves`; all four test fakes mirror it (storage `fakeHostReader` gains a - `slaves` map; localapi `fakeHostReader`/`uuidHostReader`/`f2HostReader` return nil,false). -- **Caller audit:** `handleDisks`, `roleForMountPath`, `deviceRole` (localapi/disks.go) and - `gatherClaimFacts` (storage/claim.go) — all consume `SystemDisks` through - `isSystemBacked`/`classifyClaim` fail-safes; NONE relies on all-system as a feature (finding: - none needed changes). Format/mkfs paths, data-bearing guards, wizard UI untouched. -- **Behavior deltas:** legacy-boot LVM → resolves, wizard lives; EFI(+LVM) → byte-identical; - md-raid root → BOTH members system. New PROTECTIVE delta: an ESP-resolving host whose root - topology cannot be grounded is now all-system (pre-fix trusted the ESP alone) — per the - ruling's conservatism; direction-safe. +## Deployed + live-validated (2026-07-13 evening) -## Tests + red-proofs (fake sysfs via the HostReader seam) +| Target | Result | +|---|---| +| demo host 192.168.0.162 | v0.88.0 + refreshed sudoers (`visudo -cf` gated), restart clean, **capabilities 63/63 ok, 0 degraded, 0 inactive** (DR on → `escrow-ceremony` ok) | +| drill VM (qm 300, 192.168.0.152) | same: v0.88.0, sudoers, **63/63 ok** | +| **Live ceremony (drill, endpoint-exact with the REAL guest token):** | stage-secret (real password, exact controller call) → preflight ALL GREEN (incl. the live `sudo_grant` list-probe of FELHOM_ESCROW) → `POST /escrow/ceremony` → done in ~4 s (2 polls @ 2 s) → status: `restic_pw_sealed:true, uploaded:true, claimable, TTL 598 s`, R absent from every payload → claim 1 = 200 (R: 10 words/79 chars, captured to a shell var, never printed/persisted) → **claim 2 = 410** → status `claimed:true, claimable:false` | +| Hub-side proof | `host_escrow` row `demo-vm-felhom-2f4b00`: updated `2026-07-13 17:10:57Z`, `restic_pw_sha256 = 24a887bafce4…` = sha256 of the guest's local repo password — **the spike's hash-less blob is REPAIRED through the shipped pipeline**; demo row untouched | -`role_walk_test.go` — fixtures mirror the §3 transcripts: legacy-LVM (dm→part→disk), md-raid -(md→2 disks, BOTH asserted system), EFI+raw, EFI+LVM (felhom-pve), nested dm-on-md; -dangling-slave (with a load-bearing RESOLVABLE sibling branch so a skip-mutation yields a -plausible partial set); cycle; virtual-with-empty-slaves. **Signature test -`TestSystemDisks_WalkTopologies`** asserts the root-backing physical disk(s) are ALWAYS in the -system set + the scratch disk never is + role-level consequences — never weaken. +Method note: the wizard's agent half was validated by invoking the EXACT endpoints the controller +invokes, with the real pinned-channel token (browser leg: see the controller REPORT — the drill +box's dashboard password is customer-owned/Viktor's, so the typed re-auth is operator-gated). -Red-proofs (mutate → fail → revert, verbatim): -- **A (pre-fix resolver, no walk):** `TestSystemDisks_LegacyBoot_WizardLives` FAILS — - "legacy-boot topology must resolve (the all-system fail-safe is the ERROR case…)" — the exact - dead-wizard live shape; the signature table fails on legacy/md/nested too. -- **B (walk returns the dm node):** `SIGNATURE VIOLATION: root-backing disk /dev/sda missing - from system set map[/dev/mapper/pve-root:true]` (+ md0 / nested variants naming both disks). -- **D (per-branch conservatism removed):** `TestSystemDisks_DanglingSlave_FailSafe` FAILS — - scratch classified candidate while the walk is incomplete (asserted through the REAL - `sysKnown=false` → `isSystemBacked`/`RoleForRawDevice` path, not a lookalike). - -Full suite: `go test ./...` green (all packages; recovery-code flake did not fire). - -## §13 deploy + regression (DONE) + the IA replay (agent half) - -- Built on 180 (`sha256 2447d4a3…dc7a`); deployed **drill host first** (0.86.0 → 0.87.0, - `.bak-0.86.0` kept, service active, clean journal), then **felhom-pve** (0.86.0 → 0.87.0, - `.bak-0.86.0` kept, capabilities self-check **62/62 ok, degraded=0, inactive=0**). -- **13.3 regression (felhom-pve, the §3 pre-captures vs post):** `/disks` roles BYTE-IDENTICAL - (all 7 targets: felhom-usb/felhom-flash/raw-sdb user-data, local/local-lvm system, - felhom-pbs/felhom-offsite backup) and `/disks/candidates` identical (empty pre and post). -- **The exact IA-failure replay (drill, API level — the endpoint the wizard UI invokes):** - hot-added a 5 GB scsi disk to qm 300 (`local-lvm:vm-300-disk-1`, appears as /dev/sdb in the - drill PVE) → `GET /disks/candidates` now returns - `initialize: [{device:/dev/sdb, 5 GiB, QEMU HARDDISK, data_bearing:false}]` — pre-fix the - same call returned empty (drill-pre-0870.txt). The root disk sda is NOT offered (safety - direction). **The scratch disk is left ATTACHED for the supervised UI leg.** -- Published to Gitea: `AGENT_VERSION=0.87.0`, `AGENT_SHA256=2447d4a345115db0b380779f251750f3d8c48ba9e89aeaac162fd8c74ce8dc7a` - (creds from the 180 docker-config, out-of-band). felhom.eu disposition note: - `documentation/audits/DISPOSITION-ia-finding2-systemdisks-2026-07-13.md` (commit `74fa61c`). - -## §13.2 wizard live leg — COMPLETE (operator session + GO; finished same day) - -- Wizard **OFFERED** the hot-added 5 GB disk (Üres — formázható) on the legacy-boot drill box — - the exact pre-fix dead end. Operator enrolled + formatted through the REAL UI flow - (mount name enroll-test, ext4, default): "A meghajtó sikeresen inicializálva és regisztrálva: - /mnt/felhom-drives/enroll-test"; storage page showed the drive Active/Default, role - Felhasználói adat, uuid durable-id. (First attempt hit the controller-side CSRF bug on - claimed-box wizard pages → fixed as controller v0.126.3, unrelated to the agent.) -- **Teardown (leave as found):** decommission attempted through the UI → correctly REFUSED by - the controller's M1 guard (last usable drive; the refusal surfaced badly → controller - v0.126.4 fixed the 502-through-Cloudflare + native-alert classes). Teardown completed at - pipeline level: agent `/disks/decommission` (guest token) → ok (logical retire per design; - the raw mount/unit intentionally stays for re-enroll — removed manually as the "physical - removal" step), controller registry entry removed (0 storage paths — the pre-enroll state), - mount unit stopped + file removed, `qm disk unlink 300 --idlist scsi1 --force` (LV - vm-300-disk-1 destroyed). Verified: /proc/mounts clean, candidates empty, drill gate back ON. -- **Day-0 manifest**: vouched by the operator — agent **0.87.0**, sha `2447d4a3…dc7a` - (hub Configuration screenshot; golden 0.120.0 + MinAgent 0.81.0 unchanged). +**NOT yet live-validated:** behavior on a real customer box (Peti); the CLI text mode on a host +(unit-locked byte-identical; not re-run live); Day-0 publish/vouch of 0.88.0 (operator follow-up — +the deployed hosts got it via direct deploy). ## Observations - -- No `SystemDisks` caller relied on the all-system behavior as a feature (audited: disks.go ×3, - claim.go ×1) — the fix is purely widening resolution. -- md-raid fixture lesson: partitions of an md array (`md0p1`) are NOT covered by the partition - regexes — such a slave fails the walk → all-system (documented in code; acceptable, exotic). -- A physical disk's `slaves/` dir exists and is empty (live-probed) — any future sysfs-walking - code must treat "dir present" ≠ "virtual device". -- Pre-existing `gofmt -l` hits (capability/probe.go, escrow/*.go) — not touched, not mine. +- The v0.86.0 "3 inactive" shape on no-DR boxes becomes "4 inactive" with `escrow-ceremony` — + hub chip counts shift by one; no hub change needed (generic rendering). +- `sudo -n -l` preflight probing from the daemon works exactly as the capability prober does — + no drop-in beyond the canonical sudoers needed.