controllerswap: stdin tee write + narrow FELHOM_CONTROLLERSWAP grants (non-root, v0.45.0)
writeImage drops bash -c/printf for GuestExecStdin(img+\n -> tee /etc/felhom-controller-image); new Runner.RunStdin/GuestExecStdin route stdin through the fenced sudo -n runner. 5 narrow, auditable sudoers grants (no general pct exec, no bash -c) + capability manifest entries (Critical) so the self-probe watches them and the build-test asserts coverage (companion red-proof). No controller change; swap orchestration/rollback/state unchanged. Spike GO. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EPZ4GJ8L5Jqf8UiPwbn1kt
This commit is contained in:
@@ -3,6 +3,35 @@
|
||||
All notable changes to **felhom-agent** are recorded here. Update on every code
|
||||
change that gets pushed.
|
||||
|
||||
## v0.45.0 — controller-swap under non-root: stdin `tee` write + narrow sudoers grants (Option A) (2026-06-29)
|
||||
|
||||
Restores fleet controller-swap / managed auto-update under the **non-root** agent — the one capability
|
||||
the 2026-06-29 sudoers audit deliberately left broken because the old write vector needed arbitrary
|
||||
in-guest execution. Mechanics spike-proven
|
||||
(`felhom.eu/documentation/audits/SPIKE-controllerswap-narrow-grants-2026-06-29.md`, GO). **No controller
|
||||
change** — the swap endpoint contract is unchanged; only the agent's internal write mechanism + the
|
||||
allowlist.
|
||||
|
||||
- **`writeImage` no longer shells out.** Was `GuestExec("bash","-c","printf '%s\n' '<img>' > <file>")`
|
||||
(the swap's only interpolated/shell vector). Now
|
||||
`GuestExecStdin(strings.NewReader(img+"\n"), "tee", "/etc/felhom-controller-image")` — the image ref
|
||||
is piped on **stdin** into an in-guest `tee`; no shell, no interpolation. The trailing `\n` keeps the
|
||||
on-disk bytes byte-identical to the golden's `printf '%s\n'`, and the bootstrap reads `IMAGE=$(cat …)`
|
||||
(newline-stripping), so the write is consumed identically. `ValidControllerImage` still gates upstream.
|
||||
- **New stdin seam (no fenced-runner bypass):** `proxmox.Runner.RunStdin` / `ExecRunner.RunStdin` (Run
|
||||
with `cmd.Stdin`), `GuestBinder.GuestExecStdin`, and `GuestExecutor.GuestExecStdin` — the swap routes
|
||||
stdin through the SAME `sudo -n` fenced runner as every other privileged op.
|
||||
- **`FELHOM_CONTROLLERSWAP` sudoers alias (5 narrow, auditable grants):** `cat <fixed file>`,
|
||||
`docker image inspect *`, `docker inspect -f *`, `systemctl restart <fixed unit>`,
|
||||
`tee <FIXED image file>`. **No general `pct exec`, no `bash -c`** — the spike's negative controls
|
||||
(arbitrary exec, `tee` to any other path, `docker rm`, `rm -rf`) stay denied. The 5 are added to the
|
||||
v0.44.0 **capability manifest** (Critical — a silently-broken fleet auto-update is operator-alert-worthy),
|
||||
so the self-probe watches them and the build-test asserts grant↔code coverage (companion red-proof:
|
||||
dropping the `tee` grant fails the gate — demonstrated red→green on the real file).
|
||||
- Existing swap tests (happy / rollback-on-unhealthy / image-absent / no-healthcheck / bad-image /
|
||||
single-flight) pass over the new write path; a new test asserts the write is stdin-`tee` with exact
|
||||
`image\n` and **no** shell vector. Version `0.44.0 → 0.45.0`.
|
||||
|
||||
## v0.44.0 — privileged-capability self-probe (build-time manifest test + runtime probe + hub snapshot) (2026-06-29)
|
||||
|
||||
The agent now self-checks the `sudo -n` grants it depends on, so a missing allowlist entry (the
|
||||
|
||||
Reference in New Issue
Block a user