From c9e6afea79e9269f1b8483320ba011851f119902 Mon Sep 17 00:00:00 2001 From: librelad Date: Sun, 24 May 2026 18:48:16 +0100 Subject: [PATCH] =?UTF-8?q?feat(desudo):=20init.sh=20installs=20the=20SCOP?= =?UTF-8?q?ED=20sudoers=20by=20default=20=E2=80=94=20kill=20NOPASSWD:ALL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Signed-off-by: librelad --- init.sh | 43 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/init.sh b/init.sh index 10249b6..853ff5f 100755 --- a/init.sh +++ b/init.sh @@ -686,24 +686,53 @@ initUsers() if id "$sudo_user_name" &>/dev/null; then isSuccessful "User $sudo_user_name already exists." 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." echo "$sudo_user_name:$param2" | sudo chpasswd sudo usermod -aG docker "$sudo_user_name" sudo systemctl restart docker isSuccessful "User $sudo_user_name created successfully." fi - # Manager-user sudo lives in a validated /etc/sudoers.d drop-in, not appended - # to /etc/sudoers — a malformed line in the main file locks out sudo entirely. - # The grant is broad for now; this single drop-in is what gets tightened to a - # scoped command allowlist once the runtime no longer needs broad root. + # Manager-user sudo: a SCOPED, validated /etc/sudoers.d drop-in (NOT + # NOPASSWD: ALL, and never appended to /etc/sudoers — a malformed main file + # locks out sudo entirely). Under Model A the runtime runs AS this user and + # 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 install_user="${CFG_DOCKER_INSTALL_USER:-dockerinstall}" local sudoers_tmp sudoers_tmp=$(mktemp) - printf '%s ALL=(ALL) NOPASSWD: ALL\n' "$sudo_user_name" > "$sudoers_tmp" + cat > "$sudoers_tmp" </dev/null 2>&1; then 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 isError "Refusing to install an invalid sudoers drop-in for $sudo_user_name." fi