# felhom-agent sudoers allowlist — the NARROW host-root surface (slice 5 Phase B, doc 03 §3/§7). # # Install as a drop-in: /etc/sudoers.d/felhom-agent (mode 0440, root:root), validated with # `visudo -cf`. The agent runs as the non-root `felhom-agent` service user and shells out via # `sudo -n` with FIXED argument vectors (no shell). The fine-grained validation is done IN # the agent BEFORE exec (internal/storage/validate.go): UUIDs against a strict hex regex, # mount paths confined+traversal-checked, SMART devices whitelisted to raw disks, LVM names # charset-checked. These sudoers wildcards are the COARSE allowlist; the agent is the fine # gate, so a wildcard can never be abused by a value the agent didn't already validate. # # Binary paths MUST match the agent config (privileged.systemctl/install/smartctl/lvs). Adjust # for your distro (Debian/PVE shown). A missing/declined entry degrades the agent with a # warning (SMART→UNKNOWN, mount→logged error), it does not crash. Cmnd_Alias FELHOM_MOUNT = \ /usr/bin/install -o root -g root -m 0644 -- /var/lib/felhom-agent/units/* /etc/systemd/system/*.mount, \ /usr/bin/systemctl daemon-reload, \ /usr/bin/systemctl enable --now -- *.mount, \ /usr/bin/systemctl disable -- *.mount, \ /usr/bin/systemctl stop -- *.mount Cmnd_Alias FELHOM_DISK = \ /usr/sbin/smartctl -a -j /dev/sd[a-z]*, \ /usr/sbin/smartctl -a -j /dev/nvme[0-9]*n[0-9]*, \ /usr/sbin/smartctl -a -j /dev/vd[a-z]*, \ /usr/sbin/smartctl -a -j /dev/hd[a-z]*, \ /usr/sbin/lvs --reportformat json --units b -o lv_name,data_percent,metadata_percent -- * # Provisioning back-half (slice 8A, doc 03 §6): populate a guest's bootstrap config mount # host-side (internal/provision). These are host-root ops the API token cannot do — a bind mount # is root@pam-only, and the chown maps the 0600 bootstrap.json to the unprivileged-LXC guest-root # (uid/gid 100000, spike gotcha 1). The host dir is AGENT-OWNED state under /var/lib/felhom-agent/ # (the wildcard only ever names a path the agent itself created), and the bootstrap file the agent # writes there is the only thing these touch. ':' is escaped per sudoers grammar. Cmnd_Alias FELHOM_PROVISION = \ /usr/bin/chown -R 100000\:100000 /var/lib/felhom-agent/guests/*, \ /usr/sbin/pct set [0-9]* -mp[0-9]* /var/lib/felhom-agent/guests/* # Disk inspection + format (slice 8C). blkid/lsblk read the device's data-bearing evidence (the # agent decides data-bearing-ness from THIS, never the caller's claim); mkfs.* formats a device the # agent already classified blank (a data-bearing format is refused pending an operator signature). # The agent fine-validates the device path (ValidateBlockDevice: raw disk / partition under /dev # only) + fstype before any exec — the wildcard is the coarse allowlist, the agent is the fine gate. Cmnd_Alias FELHOM_FORMAT = \ /usr/sbin/blkid -p -o export /dev/*, \ /usr/bin/lsblk -J -o NAME,FSTYPE,PTTYPE,MOUNTPOINT /dev/*, \ /usr/sbin/mkfs.ext4 -F /dev/*, \ /usr/sbin/mkfs.xfs -f /dev/* # LAN split-horizon resolver (internal/lanresolver): the agent manages a host-side dnsmasq that # answers *. with each guest's live LAN IP. install only ever writes felhom-*.conf # drop-ins (from agent-written /tmp temp files); the two `pct exec` reads are FIXED command vectors # (the guest's eth0 IPv4 + the controller's pulled controller.yaml for the domain) — NOT a general # `pct exec`. systemctl is scoped to the dnsmasq unit only. The agent never edits /etc/resolv.conf. Cmnd_Alias FELHOM_DNSMASQ = \ /usr/bin/apt-get install -y -q dnsmasq, \ /usr/bin/install -m 0644 /tmp/felhom-resolver-*.conf /etc/dnsmasq.d/felhom-*.conf, \ /usr/bin/systemctl enable --now dnsmasq, \ /usr/bin/systemctl reload dnsmasq, \ /usr/bin/rm -f /etc/dnsmasq.d/felhom-*.conf, \ /usr/sbin/pct exec [0-9]* -- ip -4 -o addr show dev eth0, \ /usr/sbin/pct exec [0-9]* -- docker exec felhom-controller cat /opt/docker/felhom-controller/controller.yaml # Guest mountpoint lifecycle (intermediary-mount re-architecture + C1 net). The pre-start self-heal hook # wrapper is installed once into the PVE snippets dir (from an agent-written /tmp file) and registered # per-guest; decommission/eject DELETE the dead mountpoint slot so a missing bind source can't brick the # guest at next boot (the B3 C1 fix). The agent fine-validates the vmid (numeric) + slot (mp[0-9]+) and # the snippet path is fixed — the wildcards are the coarse allowlist. Cmnd_Alias FELHOM_GUESTHOOK = \ /usr/bin/install -m 0755 -- /tmp/felhom-guest-hook.sh /var/lib/vz/snippets/felhom-guest-hook.sh, \ /usr/sbin/pct set [0-9]* --hookscript local\:snippets/felhom-guest-hook.sh, \ /usr/sbin/pct set [0-9]* --delete mp[0-9]* # Intermediary mount model (the drive hot-swap re-architecture). The agent keeps a SHARED host parent # /mnt/felhom-drives (self-bind + make-shared + a boot-persistence systemd unit) and binds/unbinds each # drive's felhom-data namespace UNDERNEATH it so the change propagates into the running guest live (no # pct, no reboot). The agent fine-validates the drive name + confines paths before any exec; the trailing # `*` (matching the comma-laden mp spec) mirrors the existing FELHOM_PROVISION pattern. Cmnd_Alias FELHOM_INTERMEDIARY = \ /usr/bin/mkdir -p /mnt/felhom-drives, \ /usr/bin/mkdir -p /mnt/felhom-drives/*, \ /usr/bin/mkdir -p /mnt/*/felhom-data, \ /usr/bin/chown 100000\:100000 /mnt/*/felhom-data, \ /usr/bin/mount --bind /mnt/felhom-drives /mnt/felhom-drives, \ /usr/bin/mount --make-shared /mnt/felhom-drives, \ /usr/bin/mount --bind /mnt/*/felhom-data /mnt/felhom-drives/*, \ /usr/bin/umount /mnt/felhom-drives/*, \ /usr/bin/install -m 0755 -- /tmp/felhom-shared-parent.sh /usr/local/sbin/felhom-shared-parent.sh, \ /usr/bin/install -m 0644 -- /tmp/felhom-shared-parent.service /etc/systemd/system/felhom-shared-parent.service, \ /usr/bin/systemctl enable felhom-shared-parent.service, \ /usr/sbin/pct set [0-9]* -mp8 /mnt/felhom-drives* felhom-agent ALL=(root) NOPASSWD: FELHOM_MOUNT, FELHOM_DISK, FELHOM_PROVISION, FELHOM_FORMAT, FELHOM_DNSMASQ, FELHOM_GUESTHOOK, FELHOM_INTERMEDIARY