feat(desudo): init.sh installs the SCOPED sudoers by default — kill NOPASSWD:ALL

Replace the NOPASSWD: ALL drop-in with a validated, scoped grant:
  - (dockerinstall) NOPASSWD:SETENV: ALL   (data plane; rootless-confined)
  - (root) NOPASSWD: the 5 root-owned /usr/local/sbin/libreportal-* helpers
    + a fixed system-binary allowlist (systemctl/ufw/ufw-docker/nft/sysctl/
    loginctl/service)
No bash/su/tee/cp/chmod/chown/sed/mv/rm/install — none of the
root-equivalent primitives. Also: drop '-G sudo' from the manager useradd
(privileges come from the user-specific drop-in, not group membership),
and defensively remove legacy broad grants on re-run (a NOPASSWD: ALL line
appended to the main /etc/sudoers + sudo-group membership).

Validated live end-to-end as the manager: app lifecycle, webui generate,
ownership reconcile, ssh/dns/socket/svc helpers, task service, data-plane
drop (incl. -E for backups) all denial-free; sudo bash / sudo cat shadow /
arbitrary sudo chown all denied.

Residual (still raw runSystem file-primitives, denied under the scoped
grant until they get helpers / docker-exec rework): owncloud/adguard/
crowdsec app-config edits, wireguard-standalone, restic/kopia binary
self-install. These are opt-in/deferred features.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
This commit is contained in:
librelad 2026-05-24 18:48:16 +01:00
parent 12476b507a
commit c9e6afea79

43
init.sh
View File

@ -686,24 +686,53 @@ initUsers()
if id "$sudo_user_name" &>/dev/null; then if id "$sudo_user_name" &>/dev/null; then
isSuccessful "User $sudo_user_name already exists." isSuccessful "User $sudo_user_name already exists."
else else
sudo useradd -s /bin/bash -d "/home/$sudo_user_name" -m -G sudo "$sudo_user_name" 2>/dev/null # No -G sudo: the manager's privileges come from the scoped /etc/sudoers.d
# drop-in below (user-specific), not blanket sudo-group membership.
sudo useradd -s /bin/bash -d "/home/$sudo_user_name" -m "$sudo_user_name" 2>/dev/null
isNotice "Setting password for $sudo_user_name user." isNotice "Setting password for $sudo_user_name user."
echo "$sudo_user_name:$param2" | sudo chpasswd echo "$sudo_user_name:$param2" | sudo chpasswd
sudo usermod -aG docker "$sudo_user_name" sudo usermod -aG docker "$sudo_user_name"
sudo systemctl restart docker sudo systemctl restart docker
isSuccessful "User $sudo_user_name created successfully." isSuccessful "User $sudo_user_name created successfully."
fi fi
# Manager-user sudo lives in a validated /etc/sudoers.d drop-in, not appended # Manager-user sudo: a SCOPED, validated /etc/sudoers.d drop-in (NOT
# to /etc/sudoers — a malformed line in the main file locks out sudo entirely. # NOPASSWD: ALL, and never appended to /etc/sudoers — a malformed main file
# The grant is broad for now; this single drop-in is what gets tightened to a # locks out sudo entirely). Under Model A the runtime runs AS this user and
# scoped command allowlist once the runtime no longer needs broad root. # reaches root ONLY via: the unprivileged docker-install user (data plane,
# confined by rootless), the root-owned /usr/local/sbin/libreportal-* helpers
# (each a fixed, self-validated op the manager can't modify), and a fixed set
# of system binaries. Deliberately excluded: bash/su and tee/cp/chmod/chown/
# sed/mv/rm/install (each root-equivalent). See scripts/system/libreportal-*.
local sudoers_dropin="/etc/sudoers.d/${sudo_user_name}" local sudoers_dropin="/etc/sudoers.d/${sudo_user_name}"
local install_user="${CFG_DOCKER_INSTALL_USER:-dockerinstall}"
local sudoers_tmp local sudoers_tmp
sudoers_tmp=$(mktemp) sudoers_tmp=$(mktemp)
printf '%s ALL=(ALL) NOPASSWD: ALL\n' "$sudo_user_name" > "$sudoers_tmp" cat > "$sudoers_tmp" <<EOF
# Scoped least-privilege grant for the LibrePortal manager. Generated by init.sh.
Cmnd_Alias LP_HELPERS = /usr/local/sbin/libreportal-ownership, \\
/usr/local/sbin/libreportal-dns, \\
/usr/local/sbin/libreportal-ssh-access, \\
/usr/local/sbin/libreportal-socket, \\
/usr/local/sbin/libreportal-svc
Cmnd_Alias LP_SYSTEM = /usr/bin/systemctl, /usr/sbin/ufw, /usr/local/bin/ufw-docker, \\
/usr/sbin/nft, /usr/sbin/sysctl, /sbin/sysctl, \\
/usr/bin/loginctl, /usr/sbin/service
${sudo_user_name} ALL=(${install_user}) NOPASSWD:SETENV: ALL
${sudo_user_name} ALL=(root) NOPASSWD: LP_HELPERS, LP_SYSTEM
EOF
if sudo visudo -cf "$sudoers_tmp" >/dev/null 2>&1; then if sudo visudo -cf "$sudoers_tmp" >/dev/null 2>&1; then
sudo install -m 0440 -o root -g root "$sudoers_tmp" "$sudoers_dropin" sudo install -m 0440 -o root -g root "$sudoers_tmp" "$sudoers_dropin"
isSuccessful "Configured passwordless sudo for $sudo_user_name (/etc/sudoers.d/${sudo_user_name})." # Defensive cleanup of legacy broad grants from older installs: a
# NOPASSWD: ALL line appended to the main /etc/sudoers, plus sudo-group
# membership (the scoped drop-in is user-specific; the group isn't needed).
if sudo grep -qE "^${sudo_user_name}[[:space:]]+ALL=\(ALL\)[[:space:]]+NOPASSWD:[[:space:]]+ALL" /etc/sudoers 2>/dev/null; then
local main_tmp; main_tmp=$(mktemp)
sudo grep -vE "^${sudo_user_name}[[:space:]]+ALL=\(ALL\)[[:space:]]+NOPASSWD:[[:space:]]+ALL$" /etc/sudoers > "$main_tmp"
sudo visudo -cf "$main_tmp" >/dev/null 2>&1 && sudo cp "$main_tmp" /etc/sudoers
rm -f "$main_tmp"
fi
sudo gpasswd -d "$sudo_user_name" sudo >/dev/null 2>&1 || true
isSuccessful "Configured scoped passwordless sudo for $sudo_user_name (/etc/sudoers.d/${sudo_user_name})."
else else
isError "Refusing to install an invalid sudoers drop-in for $sudo_user_name." isError "Refusing to install an invalid sudoers drop-in for $sudo_user_name."
fi fi