REPORT + CLAUDE: slice 8C Phase A (agent disk endpoints + data-bearing gate, v0.12.0)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -15,7 +15,7 @@
|
||||
- Module `gitea.dooplex.hu/admin/felhom-agent`; binary `felhom-agent` (`cmd/felhom-agent/`).
|
||||
- **Pure Go stdlib + `golang.org/x/crypto` only** — no web frameworks.
|
||||
- `go.mod` directive **go 1.25.0**; dep `golang.org/x/crypto v0.52.0` (declares go 1.25, will NOT build on Go 1.24). The **build server (192.168.0.180) runs go1.26.0** (upstream Go on PATH, backward-compatible). Build/run the agent there for live tests (same LAN as the demo host).
|
||||
- Version: `version` var in `cmd/felhom-agent/main.go`, overridable via `-ldflags "-X main.version=<v>"`; `--version` flag. **Current: v0.11.0** (slice 8B: `internal/localapi` `/backup/due` real **cadence** policy + `/backup/status` **phases** (idle|running|done|failed) + `POST /backup` single-flight job id — the app-consistent backup the controller's quiesce loop drives; `backup.backup_cadence_seconds`. Plus slice 8A: the per-guest **local-API server** `internal/localapi` — the doc 03 §6 authorization gate, 7 self-scoped endpoints, hashed per-guest token store, persisted self-signed leaf w/ stable pin; the **provisioning back-half** `internal/provision` — mint token → render `bootstrap.json` → write 0600 → `chown 100000:100000` → `pct set` bind mount, host-side/F3, no registry cred in guest; `--selftest=provision`; `build-golden.sh` now bakes the controller. Plus v0.9.0's PBS escrow. Runtime dep: `proxmox-backup-client`). Bump on meaningful changes + add a CHANGELOG entry.
|
||||
- Version: `version` var in `cmd/felhom-agent/main.go`, overridable via `-ldflags "-X main.version=<v>"`; `--version` flag. **Current: v0.12.0** (slice 8C Phase A: the `/disks` endpoints — `internal/localapi/disks.go` + `internal/storage` `mkfs` executor + `InspectDevice`/`DeviceProbe` data-bearing detection. **Security centerpiece**: `POST /disks/format` inspects the device itself; data-bearing → `ClassStorageWipe` → gate → `pending_signature` refused (caller's claim ignored); blank → benign `mkfs`. `FELHOM_FORMAT` sudoers. Controller de-privileging is `felhom-controller` v0.37.0. Plus slice 8B: `internal/localapi` `/backup/due` real **cadence** policy + `/backup/status` **phases** (idle|running|done|failed) + `POST /backup` single-flight job id — the app-consistent backup the controller's quiesce loop drives; `backup.backup_cadence_seconds`. Plus slice 8A: the per-guest **local-API server** `internal/localapi` — the doc 03 §6 authorization gate, 7 self-scoped endpoints, hashed per-guest token store, persisted self-signed leaf w/ stable pin; the **provisioning back-half** `internal/provision` — mint token → render `bootstrap.json` → write 0600 → `chown 100000:100000` → `pct set` bind mount, host-side/F3, no registry cred in guest; `--selftest=provision`; `build-golden.sh` now bakes the controller. Plus v0.9.0's PBS escrow. Runtime dep: `proxmox-backup-client`). Bump on meaningful changes + add a CHANGELOG entry.
|
||||
|
||||
## Layout
|
||||
|
||||
|
||||
@@ -1,51 +1,49 @@
|
||||
# REPORT — slice 8B (agent half): app-consistent backup policy + status phases (v0.11.0) (2026-06-10)
|
||||
# REPORT — slice 8C Phase A (agent half): disk endpoints + data-bearing classifier gate (v0.12.0) (2026-06-10)
|
||||
|
||||
> Overwrite-latest report (most recent significant work only). Cumulative history lives in
|
||||
> [CHANGELOG.md](CHANGELOG.md). Implements the host-agent half of `TASK — Slice 8B`. The in-guest
|
||||
> controller quiesce loop is in `felhom-controller` v0.36.0. No hub change.
|
||||
> Overwrite-latest report. Cumulative history: [CHANGELOG.md](CHANGELOG.md). Implements the
|
||||
> host-agent half of `TASK — Slice 8C`. The controller de-privileging + disk-subsystem retirement is
|
||||
> the `felhom-controller` half (v0.37.0). No hub change.
|
||||
|
||||
## Outcome
|
||||
|
||||
Turns the 8A thin backup stubs into the real policy the controller's quiesce loop drives. An
|
||||
agent-initiated vzdump is crash-consistent only (an LXC has no fsfreeze); the controller now stops
|
||||
its app stacks around `POST /backup` (8B) so the captured state is clean-shutdown-consistent —
|
||||
**validated live end-to-end** including the postgres clean-vs-crash-recovery restore contrast.
|
||||
The agent now owns disk execution. It serves the §6 disk-management endpoints the controller's UI
|
||||
drives, with the **8C security invariant**: the agent decides data-bearing-ness by **inspecting the
|
||||
actual device** (agent-internal evidence), NEVER from the caller's claim. A compromised controller
|
||||
asserting "this drive is blank" cannot wipe a data-bearing drive — **proven live** (a data-bearing
|
||||
device → refused `pending_signature`, `mkfs` never called, the device untouched).
|
||||
|
||||
## What landed (`internal/localapi`)
|
||||
## What landed
|
||||
|
||||
- **`GET /backup/due`** — real **cadence** policy (replaces the 8A "never backed up" stub): due when
|
||||
no **successful** backup is recorded OR the newest one is older than the agent-local cadence
|
||||
(`backup.backup_cadence_seconds`, default 24h). A successful `POST /backup` flips due to **false**
|
||||
for the window (the controller won't re-quiesce in a loop); a **failed** backup does not satisfy the
|
||||
cadence. Returns `age_seconds`.
|
||||
- **`GET /backup/status`** — real **phases** `idle | running | done | failed` + the job id, so the
|
||||
controller can poll a backup to completion. (The `snapshotted` phase is the 8B.2 hook.)
|
||||
- **`POST /backup`** — returns a **job id** + `running`; tracks the in-flight job and is
|
||||
**single-flight per guest** (a second POST while one runs returns the same job — no concurrent
|
||||
vzdump). On completion the job transitions done/failed and the result is recorded to the store.
|
||||
- Config: `backup.backup_cadence_seconds` + `BackupCadence()`; the local-API server takes the cadence.
|
||||
- All still self-scoped (token→guest); the snapshot/rollback/storage 8A surface is unchanged.
|
||||
- **`internal/storage`** — `SudoHostOps.Format(device, fstype)` (device-pinned `mkfs.ext4 -F` /
|
||||
`mkfs.xfs -f`, `ValidateBlockDevice`+`ValidateFSType`, narrow `FELHOM_FORMAT` sudoers).
|
||||
`SudoHostOps.InspectDevice(device)` → `DeviceProbe` (`blkid -p` signatures + `lsblk -J`
|
||||
partitions/mount). **`DeviceProbe.DataBearing()` is conservative**: any filesystem / partition
|
||||
table / partition / mount — OR a probe that did not read cleanly — is data-bearing (fail-safe).
|
||||
- **`internal/localapi` — the §6 disk endpoints**, self-scoped (token→guest; cross-guest 403):
|
||||
`GET /disks` (drives + data-bearing flag), `POST /disks/assign` (benign `EnsureMount`),
|
||||
`POST /disks/eject` (benign `Unmount` + dependent guests), `POST /disks/format`.
|
||||
**`/disks/format` is the centerpiece**: the agent inspects the device; blank → benign → `mkfs`;
|
||||
**data-bearing → `ClassStorageWipe` → the slice-4 gate → `pending_signature` (refused)**. The
|
||||
signed completion is slice 10. `storageGateAdapter` bridges to the gate (no new gate/crypto).
|
||||
|
||||
## Tests
|
||||
|
||||
`go test ./...` green; `-race` green on the build server (pure-Go parts). `/backup/due`: due when
|
||||
stale / no backup, **not due within the window after a success**, due again past the cadence, **a
|
||||
failed backup does not count**. `/backup/status`: running→done and running→failed (a gated fake
|
||||
observes the running phase). `POST /backup` single-flight (one vzdump for concurrent POSTs). Plus the
|
||||
8A self-scoping/auth suite unchanged.
|
||||
`go test ./...` green; `-race` green (build server). localapi security matrix: blank → mkfs called,
|
||||
gate not consulted; **data-bearing → 403, mkfs NEVER called**, gate consulted; ambiguous/unprobed →
|
||||
fail-safe destructive; gate-allows still refuses in 8C; assign/eject; cross-guest 403; bad
|
||||
device/fstype 400; unconfigured 503. storage: validators (whitelist + injection rejection),
|
||||
`InspectDevice` (blank/fs/parttable/mounted/failed-probe-fail-safe), `Format` invokes the right mkfs.
|
||||
|
||||
## Live validation (demo-felhom)
|
||||
|
||||
A provisioned guest (controller v0.36.0, postgres app stack) with a short test cadence: the
|
||||
controller's quiesce loop hit `GET /backup/due` (true) → quiesced → `POST /backup` → the agent ran a
|
||||
**real vzdump** (`local-api: backup job complete … archive=vzdump-lxc-8202-…`) → `GET /backup/status`
|
||||
`done` → unquiesce. `/backup/due` then correctly went **false** for the cadence window (no re-loop).
|
||||
The single-flight + phase transitions behaved against the live server. (The controller's legacy
|
||||
hub-report HTTP 401 is the frozen per-customer path, unrelated to 8B.) The standing demo agent was
|
||||
upgraded to **v0.11.0** (local-API live on `192.168.0.162:8443`); test guests torn down, cadence reset.
|
||||
A provisioned de-privileged controller (v0.37.0) drove the agent: `GET /disks` flagged the mounted
|
||||
`felhom-usb` (`/dev/sdb1`) **`data_bearing: true`** and the LVM/PBS/dir storages false. `POST
|
||||
/disks/format` on the mounted-vfat `/dev/sda2` → **403** `"device is data-bearing — format requires
|
||||
operator authorization (pending_signature)"`; agent log `refusing format of a data-bearing device …
|
||||
gate=pending_signature`; **`/dev/sda2` untouched**. Deployed to the standing demo service as v0.12.0.
|
||||
|
||||
## Deferred (stated, not built)
|
||||
|
||||
**8B.2** downtime optimization (`vzdump --mode snapshot` + a `snapshotted` status phase so the
|
||||
controller resumes at snapshot-taken) — needs snapshot-capable storage validated for LXC; the status
|
||||
phase enum already carries the hook. Hub-served per-guest backup policy → slice 10. No secrets committed.
|
||||
The operator-**signed completion** of a data-bearing wipe/format → **slice 10**. No spare blank
|
||||
`/dev/sd*` on the demo to live-exercise an actual `mkfs` (won't format real disks — covered by unit
|
||||
tests). No secrets committed.
|
||||
|
||||
Reference in New Issue
Block a user