Files
dashboards/deploy/nut-sudoers
T
ducoterra 87042f8855 Add AGENTS.md and align the docs with the code
The brief asked for an AGENTS.md capturing the overall plan and it never
landed, while two files already pointed readers at it. It now holds the
invariants (read-only by construction, never invent a number,
unconfigured is not broken, no CDN, no guessed metric names), the locked
decisions with their reasons, the verified fleet facts, the commands, and
the four known gaps.

Also corrected the docs that had drifted:
- upsd config lives in /etc/ups on driveripper, not /etc/nut, and the
  real blocker is that upsd binds nothing on 3493 — with the sudo rules
  still needed written down in deploy/nut-sudoers.
- .env.example told operators to set `LISTEN 0.0.0.0` and `actions = -`;
  both are worse than needed. Loopback + ssh forward, and per-command
  instcmds instead of granting load.off.
- nut.py claimed the command path is gated three times and that the
  confirmation asks for the UPS name; it is gated four times and asks
  for DANGER.
- .gitignore described a tracked .agents/ roadmap that does not exist.

LLM-Generated: true
2026-10-16 18:54:35 -04:00

56 lines
3.1 KiB
Plaintext

# driveripper-ops sudo rules for the UPS dashboard work
#
# Install as /etc/sudoers.d/dashboards-nut (visudo -cf it first):
#
# sudo visudo -cf - <<'EOF' ... EOF # syntax check
# install -m 0440 deploy/nut-sudoers /tmp/dashboards-nut
# sudo mv /tmp/dashboards-nut /etc/sudoers.d/dashboards-nut
#
# Keep it scoped: paths are absolute (sudoers uses `secure_path`), arguments are wildcards
# only where the command is read-only or idempotent, and nothing here allows a shell,
# `EDITOR=`, `dnf install` of arbitrary code, or `upsd -c` with an attacker-chosen file.
# ---- already present on driveripper (this is what the dashboard setup used) ---------- #
# (root) NOPASSWD: /usr/bin/tee /etc/ups/* # write upsd.conf / upsd.users
# (root) NOPASSWD: /usr/bin/cp /etc/ups/* /home/ops/* # back up a config before editing
# (root) NOPASSWD: /usr/bin/systemctl start|restart|reload|enable *
# (root) NOPASSWD: /usr/bin/udevadm * # USB device discovery/replug
# (root) NOPASSWD: /usr/bin/firewall-cmd * # only if upsd must serve the LAN
# (root) NOPASSWD: /usr/bin/dnf install * # nut, nut-client, nut-dev
# ---- still needed: diagnose why upsd runs but binds nothing -------------------------- #
# Reading is the blocker. `upsd` is active, both drivers are up, and 3493 has no listener,
# but /etc/ups/* is 0640 root:nut and the journal needs group membership:
# $ sudo cat /etc/ups/upsd.conf -> Permission denied
# $ journalctl -u nut-server -n 50 -> No entries (not in systemd-journal/wheel)
# $ upsc -l -> Error: Connection failure: Connection refused
Cmnd_Alias NUT_DIAGNOSE = \
/usr/bin/cat /etc/ups/*, \
/usr/bin/systemctl status *, \
/usr/bin/journalctl -u nut-server *, /usr/bin/journalctl -u nut-driver@*, \
/usr/bin/journalctl -u nut*, \
/usr/sbin/ss -ltnp, \
/usr/sbin/getenforce, \
/usr/bin/ausearch *, /usr/sbin/sealert -l *, \
/usr/sbin/semanage port -l, \
/usr/bin/upsdrvctl status, /usr/bin/upsdrvctl status *, \
/usr/bin/upsc *, \
/usr/bin/lsusb
# ---- still needed: apply the fix and verify ------------------------------------------ #
# `tee /etc/ups/*` already writes configs; these cover the reload path, SELinux booleans,
# and a bounded cleanup of the .bak files `tee`-based edits leave behind.
Cmnd_Alias NUT_APPLY = \
/usr/bin/tee /etc/ups/upsd.conf, /usr/bin/tee /etc/ups/upsd.users, \
/usr/bin/tee /etc/ups/ups.conf, /usr/bin/tee /etc/ups/nut.conf, \
/usr/bin/rm -f /etc/ups/*.bak-*, \
/usr/bin/upsdrvctl restart, /usr/bin/upsdrvctl restart *, \
/usr/sbin/setsebool -P allow_upsd_tcp_server 1, \
/usr/sbin/setsebool -P nis_enabled 0
# NOTE: deliberately NOT allowed, so an agent cannot power-cycle anything by accident:
# /usr/bin/upscmd (any form) — the dashboard's own gated POST is the only command path
# systemctl stop * — stopping nut-server would blind the UPS view
# editors, shells, `sh -c` — no escape hatches