diff --git a/scripts/audit.sh b/scripts/audit.sh index 0317644..a4d293b 100755 --- a/scripts/audit.sh +++ b/scripts/audit.sh @@ -76,6 +76,23 @@ else audit_fail "no named @pve user found - root@pam is the only account" fi +# --- passwordless sudo for pvesh/qm/pct --- +# The nixos flake's create-proxmox-resource.sh runs pvesh/qm/pct over +# non-interactive SSH, so the admin user needs NOPASSWD for these tools. +SUDO_OK=0 +for f in /etc/sudoers.d/*-proxmox; do + [ -f "$f" ] || continue + if grep -qE 'NOPASSWD:.*pvesh' "$f" && grep -qE 'NOPASSWD:.*\bqm\b' "$f" && grep -qE 'NOPASSWD:.*\bpct\b' "$f"; then + SUDO_OK=1 + break + fi +done +if [ "$SUDO_OK" -eq 1 ]; then + audit_pass "admin user has NOPASSWD sudo for pvesh/qm/pct" +else + audit_fail "no sudoers file grants NOPASSWD for pvesh/qm/pct (run setup-admin-sudo.sh )" +fi + # --- time sync --- if timedatectl show -p NTPSynchronized --value 2>/dev/null | grep -qx 'yes'; then audit_pass "clock is NTP-synchronized" diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh index bdb6279..f50861e 100755 --- a/scripts/bootstrap.sh +++ b/scripts/bootstrap.sh @@ -41,5 +41,6 @@ echo "=== 5/5: disable subscription nag (cosmetic) ===" echo echo "=== Base hardening applied. Remaining manual/deliberate steps: ===" echo " - ${SCRIPT_DIR}/create-admin-user.sh " +echo " - ${SCRIPT_DIR}/setup-admin-sudo.sh (passwordless sudo for pvesh/qm/pct)" echo " - Enable 2FA/TOTP for that user and root@pam via the web UI" echo " - ${SCRIPT_DIR}/audit.sh (verify everything above)" diff --git a/scripts/setup-admin-sudo.sh b/scripts/setup-admin-sudo.sh new file mode 100755 index 0000000..2087a9c --- /dev/null +++ b/scripts/setup-admin-sudo.sh @@ -0,0 +1,49 @@ +#!/bin/bash +# Grant a named admin user passwordless sudo for Proxmox management tools +# (pvesh, qm, pct) so that scripts in the nixos flake repo can run these +# over non-interactive SSH without a TTY for password entry. +# +# Idempotent - safe to re-run (rewrites if paths have changed). Run as +# root on the PVE host. +# +# Usage: ./setup-admin-sudo.sh +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=lib/common.sh +source "${SCRIPT_DIR}/lib/common.sh" +require_root + +USERNAME="${1:-}" +if [ -z "$USERNAME" ]; then + echo "Usage: $0 " >&2 + exit 1 +fi + +# Resolve actual binary paths at script time -- they differ across Proxmox +# versions (pvesh moved from /usr/sbin to /usr/bin in PVE 8.x) and the +# sudoers rule must match the real path or sudo will fall back to +# prompting for a password. +resolve_bin() { + command -v "$1" 2>/dev/null || { echo "ERROR: $1 not found on PATH" >&2; exit 1; } +} + +PVESH="$(resolve_bin pvesh)" +QM="$(resolve_bin qm)" +PCT="$(resolve_bin pct)" + +SUDOERS_FILE="/etc/sudoers.d/${USERNAME}-proxmox" +CONTENT="${USERNAME} ALL=(root) NOPASSWD: ${PVESH}, ${QM}, ${PCT}" + +write_if_changed "$SUDOERS_FILE" "$CONTENT" + +# visudo -c validates the file we just wrote before we walk away. +if visudo -c -f "$SUDOERS_FILE" >/dev/null 2>&1; then + chmod 0440 "$SUDOERS_FILE" + echo "Sudoers rule for ${USERNAME} is valid and in place." + echo " ${CONTENT}" +else + echo "ERROR: sudoers validation failed -- removing bad file." >&2 + rm -f "$SUDOERS_FILE" + exit 1 +fi