Files
felhom-agent/REPORT.md
T

8.8 KiB

REPORT — Agent sudoers completeness audit + redeploy (restore multi-drive)

Date: 2026-06-29 · Class: Risky/supervised (host-root allowlist edit + live redeploy) · Binary: unchanged (no version bump) · Baseline: felhom-agent main @ c3020ee → this change 55888e5.

What & why

The 2026-06-28 root→non-root agent cutover left configs/felhom-agent.sudoers incomplete: several commands the agent shells via sudo -n were never granted. The headline casualty — lxc-info — broke guestInitPID → GuestSeesMount → bound_under_parent, so every external drive was reported absent, and the controller drive-gate stopped each drive's apps (the "attach one, the other drops" flapping; audit felhom.eu/documentation/audits/SPIKE-multidrive-mutual-exclusion-2026-06-29.md). This pass audited every privileged command against the allowlist and closed all read-only/ fixed-vector gaps in one go; arbitrary-execution gaps were surfaced, not added.

Audit table — every privileged command vector vs the allowlist

runner prepends sudo -n <cmd> <args>; the allowlist matches path + arg vector.

Command vector (shape) Call site Alias Status
lxc-info -n [0-9]* -p -H intermediary.go:256 guestInitPID FELHOM_INTERMEDIARY GAP → CLOSED
mount --make-private /mnt/felhom-drives intermediary.go:110 EnsureSharedParent FELHOM_INTERMEDIARY GAP → CLOSED
systemctl restart dnsmasq lanresolver restartDnsmasq FELHOM_DNSMASQ GAP → CLOSED
pct set [0-9]* -onboot 1 backhalf.go:162 (F3 autostart) FELHOM_PROVISION GAP → CLOSED
pct reboot [0-9]* guestbind.go:100 RebootGuest (disks.go:448) FELHOM_GUESTHOOK GAP → CLOSED
mount --make-shared /mnt/felhom-drives intermediary.go:113 FELHOM_INTERMEDIARY OK
mount --bind /mnt/felhom-drives /mnt/felhom-drives intermediary.go:107 FELHOM_INTERMEDIARY OK
mount --bind /mnt/*/felhom-data /mnt/felhom-drives/* intermediary.go:204 FELHOM_INTERMEDIARY OK
umount /mnt/felhom-drives/* intermediary.go:199,340 FELHOM_INTERMEDIARY OK
mkdir -p /mnt/felhom-drives[/*] , /mnt/*/felhom-data intermediary/backhalf FELHOM_INTERMEDIARY OK
chown 100000:100000 /mnt/*/felhom-data intermediary.go:181 FELHOM_INTERMEDIARY OK
install -m 0755/0644 -- /tmp/felhom-shared-parent.{sh,service} … intermediary.go:136,144 FELHOM_INTERMEDIARY OK
systemctl enable felhom-shared-parent.service intermediary.go:150 FELHOM_INTERMEDIARY OK
systemctl daemon-reload intermediary.go:147 FELHOM_MOUNT OK
pct set [0-9]* -mp8 /mnt/felhom-drives* backhalf.go:184 FELHOM_INTERMEDIARY OK
pct set [0-9]* -mp[0-9]* /var/lib/felhom-agent/guests/* backhalf.go:152 FELHOM_PROVISION OK
chown -R 100000:100000 /var/lib/felhom-agent/guests/* backhalf.go:145 FELHOM_PROVISION OK
install -m 0755 -- /tmp/felhom-guest-hook.sh /var/lib/vz/snippets/… guesthook install.go:45 FELHOM_GUESTHOOK OK
pct set [0-9]* --hookscript local:snippets/felhom-guest-hook.sh guesthook install.go:55 FELHOM_GUESTHOOK OK
pct set [0-9]* --delete mp[0-9]* decommission/eject FELHOM_GUESTHOOK OK
smartctl -a -j /dev/<disk> privileged.go:150 SMART (observe.go:90) FELHOM_DISK OK
lvs --reportformat json … storage FELHOM_DISK OK
blkid -p -o export /dev/* , lsblk -J -o … /dev/* format FELHOM_FORMAT OK
mkfs.ext4 -F /dev/* , mkfs.xfs -f /dev/* format FELHOM_FORMAT OK
apt-get install -y -q dnsmasq lanresolver:107 FELHOM_DNSMASQ OK
install -m <mode> /tmp/felhom-resolver-*.conf /etc/dnsmasq.d/felhom-*.conf lanresolver:266 FELHOM_DNSMASQ OK
systemctl enable --now dnsmasq , reload dnsmasq lanresolver:117 FELHOM_DNSMASQ OK
rm -f /etc/dnsmasq.d/felhom-*.conf lanresolver:178 FELHOM_DNSMASQ OK
pct exec [0-9]* -- ip -4 -o addr show dev eth0 lanresolver:187 FELHOM_DNSMASQ OK
pct exec [0-9]* -- docker exec felhom-controller cat …/controller.yaml lanresolver:213 FELHOM_DNSMASQ OK
sensors -j privileged.go:169 Sensors GAP — DEFERRED (no caller + lm-sensors not installed; path unverifiable)
mount UUID=<uuid> <target> (+mkdir -p) privileged.go:139 MountUSBByUUID no daemon caller (legacy) — not added
pct create <vmid> … --features nesting=1,keyctl=1 … privileged.go:124 CreateGoldenLXC SURFACED (maintenance/broad; no daemon caller)
pct exec [0-9]* -- {cat,docker,bash -c "<interp>",systemctl} … controllerswap.go GuestExec SECURITY FORK — SURFACED, NOT added

Note: guest_attached=false in the /disks report is not a sudoers gap — GuestConfig is a Proxmox API call (query.go:51), and in the intermediary model GuestAttached (legacy per-mp bind detection) is expected-false; the gate keys on BoundUnderParent, the fixed signal.

Sudoers diff (additive)

  • FELHOM_INTERMEDIARY: + mount --make-private /mnt/felhom-drives, + lxc-info -n [0-9]* -p -H
  • FELHOM_DNSMASQ: + systemctl restart dnsmasq
  • FELHOM_PROVISION: + pct set [0-9]* -onboot 1
  • FELHOM_GUESTHOOK: + pct reboot [0-9]*

visudo -cf clean (staged file + whole /etc/sudoers after install).

§5.5 — surfaced for operator decision (NOT executed)

GuestExec general pct exec <vmid> -- … (controller-swap self-update / Phase-2 managed updates). The five vectors it runs:

  1. cat <controllerImageFile> (read /etc/felhom-controller-image)
  2. docker image inspect <image>
  3. bash -c "printf '%s\n' '<image>' > <controllerImageFile>" ← interpolated write (arbitrary)
  4. systemctl restart <bootstrapUnit>
  5. docker inspect -f <fmt> <controllerContainer>

Granting pct exec [0-9]* -- * = arbitrary root execution in any guest; not added. Consequence: controller-swap / managed auto-update is currently BROKEN under the non-root agent (manual bootstrap-mechanism deploys still work). Operator decision needed: narrow per-vector grants (hard — #3 interpolates the image string and uses bash -c), or run the swap through a different trust path (e.g. a guest-side agent-authenticated endpoint). Not blocking day-to-day; surfaced for follow-up.

Also surfaced: pct create (golden build — maintenance, broad args, no daemon caller) and mount UUID=… (MountUSBByUUID, unreferenced/legacy — current USB mounts use systemd .mount units via FELHOM_MOUNT).

Live validation (felhom-pve, guest 9201) — ALL PASS

  • Deploy: staged → visudo -cf OK → install -m 0440 -o root -g root /etc/sudoers.d/felhom-agentvisudo -cf /etc/sudoers OK. No agent restart (sudo reads the drop-in per call; guestInitPID runs lxc-info fresh each call).
  • sudo -n probes (as felhom-agent): lxc-info -n 9201 -p -H → returns PID 2137736 (was "a password is required"); systemctl restart dnsmasq, pct reboot 9201, pct set 9201 -onboot 1 → ALLOWED; mount --make-private /mnt/felhom-drives → matched (probed via sudo -l, not executed — would churn the live parent).
  • Agent report flipped: /disks now reports bound_under_parent=true for both felhom-usb and felhom-flash (was false/false).
  • Gate quiesced: controller logged [gate] drive RETURNED …/felhom-usb — re-attached + restarted gate-stopped apps and the same for felhom-flash; nextcloud auto-restarted and held Up (healthy); zero drive ABSENT / disconnect / stop events across the following ~2 min.
  • Real UI (claude-in-chrome, /settings): both drives show Aktív concurrently — felhom-usb (915.8 GB, HD710 PRO, "1 alkalmazás használja") + felhom-flash (117.1 GB, Flash Drive). The multi-drive mutual-exclusion symptom is gone.

Notes left for follow-up

  • Live host-side "doubling" (each submount under both the parent and root, because the live /mnt/felhom-drives is in root's peer group shared:1) is left as-is — re-running make-private now would churn the peer group and orphan the working guest slave (the very failure EnsureSharedParent guards against). The new grant lets the next clean host boot establish the parent correctly via felhom-shared-parent.sh; verify the parent has its own peer group after that boot. Latent/harmless today (the guest sees both drives).
  • 03-host-agent.md carries only a high-level root-boundary narrative (§51), no line-level allowlist table — nothing to sync. The sudoers file + CHANGELOG + this REPORT are the record.
  • Durable countermeasure (separate task): an agent capability self-probe at startup (does each critical sudo -n vector resolve?) → report denials to the hub. Would have caught all of these at cutover.