diff --git a/init.sh b/init.sh index d51b873..4fd7624 100755 --- a/init.sh +++ b/init.sh @@ -698,16 +698,42 @@ initUsers() sudo systemctl restart docker isSuccessful "User $sudo_user_name created successfully." fi - # 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/lib/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-*. + # Install-phase sudo: the heavy install runs AS this user (see the handoff in + # completeInitMessage) and needs BROAD root — useradd for the docker-install + # user, rootless setup, apt, sysctl, etc. So grant a temporary validated + # NOPASSWD: ALL drop-in now (never appended to /etc/sudoers — a malformed main + # file locks out sudo entirely); completeInitMessage calls initScopedSudoers + # to tighten it to the scoped RUNTIME allowlist once the install succeeds. 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" + if sudo visudo -cf "$sudoers_tmp" >/dev/null 2>&1; then + sudo install -m 0440 -o root -g root "$sudoers_tmp" "$sudoers_dropin" + isSuccessful "Configured install-phase sudo for $sudo_user_name (tightened after install)." + else + isError "Refusing to install an invalid sudoers drop-in for $sudo_user_name." + fi + rm -f "$sudoers_tmp" + + initRootHelpers +} + +# Tighten the manager's sudo from the install-phase NOPASSWD: ALL down to the +# scoped RUNTIME allowlist. Called AFTER the (manager-run) install phase, which +# needs the broad root this deliberately withholds. The runtime then reaches root +# ONLY via: the unprivileged docker-install user (data plane, rootless-confined), +# the root-owned /usr/local/lib/libreportal/ helpers (each a fixed, self-validated +# op the manager can't modify), and a fixed system-binary set. Excluded: +# bash/su + tee/cp/chmod/chown/sed/mv/rm/install (each root-equivalent). Also +# clears legacy broad grants (a NOPASSWD: ALL in the main /etc/sudoers, sudo-group +# membership). See FOOTPRINT.md. +initScopedSudoers() +{ + local sudoers_dropin="/etc/sudoers.d/${sudo_user_name}" + local install_user + install_user=$(grep -h '^CFG_DOCKER_INSTALL_USER=' "$configs_dir/general/general_docker_install" 2>/dev/null | head -1 | cut -d= -f2 | awk '{print $1}') + install_user="${install_user:-${CFG_DOCKER_INSTALL_USER:-dockerinstall}}" local sudoers_tmp sudoers_tmp=$(mktemp) cat > "$sudoers_tmp" </dev/null 2>&1; then sudo install -m 0440 -o root -g root "$sudoers_tmp" "$sudoers_dropin" - # 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). + # Clear legacy broad grants from older installs. 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" @@ -737,13 +761,11 @@ EOF 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})." + isSuccessful "Tightened $sudo_user_name sudo to the scoped runtime allowlist." else - isError "Refusing to install an invalid sudoers drop-in for $sudo_user_name." + isError "Invalid scoped sudoers — left install-phase sudo in place for $sudo_user_name." fi rm -f "$sudoers_tmp" - - initRootHelpers } # Install the root-owned privilege helpers. Under Model A the runtime runs AS the @@ -1260,7 +1282,9 @@ completeInitMessage() # Switch to libreportal user and run the install command if sudo -u "$sudo_user_name" LIBREPORTAL_SKIP_LOGO=1 bash -c "libreportal run install"; then - : + # Install done — tighten the manager's broad install-phase sudo down to + # the scoped runtime allowlist. + initScopedSudoers else echo "" echo "⚠️ LibrePortal installation encountered issues."