61c89a7efa
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EPZ4GJ8L5Jqf8UiPwbn1kt
80 lines
5.2 KiB
Markdown
80 lines
5.2 KiB
Markdown
# REPORT — felhom-agent v0.44.0: privileged-capability self-probe
|
|
|
|
**Date:** 2026-06-29 · **Class:** Implementation (agent self-health, slice 1) · **Baseline:**
|
|
`main` @ `1958204` (v0.43.0) → **v0.44.0** @ `25024d9`. Companion hub: felhom.eu **v0.19.0**.
|
|
|
|
## What & why
|
|
|
|
A non-root agent that can't run a privileged command it depends on is **degraded** and must say so —
|
|
at cutover, not days later. Five casualties of the 2026-06-28 root→non-root migration (leaf/token/
|
|
lxc-info/make-private/controller-swap) went undetected until user-visible breakage. This adds a
|
|
self-check that catches the class at BUILD time (CI) and RUNTIME (startup + each hub-report cycle),
|
|
serve-degraded + loud. (The controller↔agent channel check is a separate later task.)
|
|
|
|
## `internal/capability` — manifest + probe
|
|
|
|
- **Manifest** (`Manifest()`): the required `(binary, representative-arg)` vectors, seeded from the
|
|
2026-06-29 sudoers audit (the OK + CLOSED rows). The SURFACED/DEFERRED rows are **excluded**:
|
|
general `pct exec * ` (controller-swap, arbitrary exec — an open operator decision), `pct create`
|
|
(golden build, no daemon caller), `mount UUID=…` (legacy), `sensors -j` (callerless + uninstalled).
|
|
- **Critical** (hub alerts on these): `guest-init-pid` (lxc-info), the intermediary mount surface
|
|
(`parent-self-bind`, `parent-make-shared`, `parent-make-private`, `drive-bind`, `drive-umount`),
|
|
and the disk format/inspect gate (`disk-blkid`, `disk-lsblk`, `disk-mkfs-ext4`). The rest
|
|
(provision/dnsmasq/guesthook/mount-units/smart/lvs) are non-critical (degrade reported, no alert).
|
|
- **Probe** (`Prober.Probe`): per entry, `sudo -n -l -- <binary> <reprArgs>` — a sudo policy LIST
|
|
(exit 0 ⇔ permitted), run via a **DIRECT** runner so it never executes the command (safe for
|
|
mkfs/pct entries) — plus an `os.Stat` existence check. Maps to `ok` / `degraded` ("sudo policy
|
|
denied" | "binary not found"). A total sudo failure (drop-in missing) collapses to ONE aggregate
|
|
signal. **Never** blocks startup, panics, or errors (serve-degraded).
|
|
|
|
## Build-time gate (the headline) + red-proof
|
|
|
|
`manifest_test.go` parses `configs/felhom-agent.sudoers`, glob→regex translates each grant, and
|
|
asserts **every manifest vector is covered** — exactly what would have caught the dropped
|
|
`lxc-info`/`make-private` lines in CI. **Red-proof** (`TestRedProof_DroppedGrantFailsCheck`): with
|
|
the `lxc-info` line removed from an in-memory copy, the check FAILS for `guest-init-pid`, and passes
|
|
on the real file. Demonstrated: ran with the line dropped → **FAIL**; restored → **PASS** (not
|
|
hollow).
|
|
|
|
## Runtime wiring + report
|
|
|
|
`Probe` runs once at startup (`logCapabilities`: INFO `capabilities self-check N/N ok` + an ERROR per
|
|
degraded naming the gated feature) and on every collect (`Collector.SetCapabilityProber`); the
|
|
snapshot rides the report as the new non-nil `HostReport.Capabilities []capability.Status`. Golden +
|
|
contract test updated; the cross-repo hub golden mirrors `capabilities: []`.
|
|
|
|
## Tests (all green: `go build/vet/test ./...`)
|
|
|
|
- `TestManifestCoveredBySudoers` (manifest ⊆ sudoers, 40 vectors) + `TestRedProof_...`.
|
|
- Probe: `AllOK`, `OneDenied` (critical degraded, sibling ok, serve-degraded), `BinaryMissing`,
|
|
`SudoUnavailableAggregates`, `NilRunnerNoPanic`. Asserted the probe never executes a non-`-l` call.
|
|
|
|
## Live validation (felhom-pve, guest 9201) — deployed + verified
|
|
|
|
- **Deploy:** built `0.44.0` on 180 (sha `675b711e…`), shipped 180→local→felhom-pve (sha verified
|
|
identical), backed up the prior binary (`felhom-agent.bak-0.43.0`), `install` + `systemctl restart`.
|
|
`systemctl is-active` = **active**.
|
|
- **OK-path (live):** startup log `capabilities self-check ok=40 total=40 degraded=0` — N/N ok, a
|
|
live proof the probe agrees with the post-audit-complete sudoers. Hub (v0.19.0) logged
|
|
`Host capability checker initialized: 1 ok, 0 degraded`.
|
|
- **Degraded detection (live, non-intrusive):** on the real host, a granted vector
|
|
`sudo -n -l -- /usr/bin/lxc-info -n 9201 -p -H` → exit 0 (ok); a bogus vector → exit 1 (degraded) —
|
|
proving the probe's grant discrimination without mutating the freshly-certified live sudoers.
|
|
|
|
## NOT yet live-validated (deferred — see §16 forward note)
|
|
|
|
- **Full live agent→hub→operator-email degraded transition.** The hub alert fires only on a
|
|
**Critical** capability degraded, which would require removing a Critical grant on the live host
|
|
(forbidden — would re-break drives) or a separate test host, and would send a real operator email.
|
|
It is **fully covered by unit tests** (`hub` `TestHostCapabilityChecker`: one event on ok→degraded,
|
|
none steady, recovered, non-critical ignored, old-agent ok). Deferred to a scratch-host exercise.
|
|
- **Build-test in CI:** runs in `go test ./...`; no CI runner is wired in this repo yet (the test is
|
|
the gate, run on every local/build-server `go test`).
|
|
|
|
## Forward note (NOT this task)
|
|
|
|
The manifest is hand-curated; the build-test locks **manifest ⊆ sudoers** but **code → manifest**
|
|
drift (a new `runner.Run` call not added to the manifest) is not auto-caught — a future static check
|
|
of `runner.Run` call sites against the manifest would close that. The controller↔agent channel check
|
|
is the next self-health slice.
|