Archived
add Linux admin user setup as part of node hardening
New script setup-linux-admin-user.sh creates the Linux system user, installs an SSH authorized key, and adds the user to the sudo group. Integrated into bootstrap.sh before harden-ssh.sh so key-based access is in place before password authentication is disabled. bootstrap.sh now accepts ADMIN_USER and ADMIN_SSH_KEY env vars to run user setup and setup-admin-sudo.sh automatically at the right point. audit.sh checks that at least one non-root user has an authorized key. docs/04-security-hardening.md updated with the new steps and ordering. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -69,6 +69,22 @@ if [ -f /var/run/reboot-required ]; then
|
||||
audit_warn "a reboot is pending (/var/run/reboot-required) - schedule one"
|
||||
fi
|
||||
|
||||
# --- Linux admin user with SSH key (for non-root SSH login) ---
|
||||
LINUX_ADMIN_OK=0
|
||||
for auth_file in /home/*/.ssh/authorized_keys; do
|
||||
[ -f "$auth_file" ] || continue
|
||||
# Must have at least one non-comment, non-empty key line.
|
||||
if grep -qE '^(ssh-rsa|ssh-ed25519|ecdsa-sha2-nistp[0-9]+) ' "$auth_file" 2>/dev/null; then
|
||||
LINUX_ADMIN_OK=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ "$LINUX_ADMIN_OK" -eq 1 ]; then
|
||||
audit_pass "a non-root Linux user has an SSH authorized key"
|
||||
else
|
||||
audit_fail "no non-root Linux user has an authorized SSH key (run setup-linux-admin-user.sh)"
|
||||
fi
|
||||
|
||||
# --- named admin user (not just root@pam) ---
|
||||
if pveum user list --output-format json 2>/dev/null | grep -q '"userid":"[^"]*@pve"'; then
|
||||
audit_pass "a named @pve admin user exists (root@pam is not the only account)"
|
||||
|
||||
+36
-15
@@ -2,11 +2,15 @@
|
||||
# Stage 1 base config + hardening, end to end, for a single fresh PVE host.
|
||||
# Runs the individual scripts in order. Idempotent - safe to re-run.
|
||||
#
|
||||
# Does NOT create the named admin user (needs a username decision) - run
|
||||
# create-admin-user.sh separately afterwards. Run audit.sh at the end to
|
||||
# verify.
|
||||
# If ADMIN_USER and ADMIN_SSH_KEY are set, a Linux system user is created
|
||||
# with SSH key access and sudo before SSH hardening runs - so key-based
|
||||
# login is in place before password auth is disabled. If they are not set,
|
||||
# a reminder is printed at the end to run setup-linux-admin-user.sh manually
|
||||
# (but do this BEFORE disconnecting, since password auth will be disabled).
|
||||
#
|
||||
# Usage: MGMT_CIDR=192.168.2.0/24 ./bootstrap.sh
|
||||
# Usage:
|
||||
# MGMT_CIDR=192.168.2.0/24 ./bootstrap.sh
|
||||
# MGMT_CIDR=192.168.2.0/24 ADMIN_USER=wayne ADMIN_SSH_KEY="ssh-ed25519 ..." ./bootstrap.sh
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
@@ -19,28 +23,45 @@ if [ -z "${MGMT_CIDR:-}" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=== 1/5: remove enterprise repos, switch to no-subscription ==="
|
||||
STEP=0
|
||||
next_step() { STEP=$((STEP + 1)); echo; echo "=== ${STEP}: $* ==="; }
|
||||
|
||||
next_step "remove enterprise repos, switch to no-subscription"
|
||||
"${SCRIPT_DIR}/switch-to-no-subscription-repo.sh"
|
||||
|
||||
echo
|
||||
echo "=== 2/5: SSH hardening (key-only root login + fail2ban) ==="
|
||||
# Create the Linux admin user before SSH hardening so that authorized_keys
|
||||
# is in place before password auth is disabled.
|
||||
if [ -n "${ADMIN_USER:-}" ] && [ -n "${ADMIN_SSH_KEY:-}" ]; then
|
||||
next_step "Linux admin user '${ADMIN_USER}' + SSH key + sudo group"
|
||||
"${SCRIPT_DIR}/setup-linux-admin-user.sh" "$ADMIN_USER" "$ADMIN_SSH_KEY"
|
||||
|
||||
next_step "passwordless sudo for pvesh/qm/pct (${ADMIN_USER})"
|
||||
"${SCRIPT_DIR}/setup-admin-sudo.sh" "$ADMIN_USER"
|
||||
else
|
||||
echo
|
||||
echo "WARNING: ADMIN_USER / ADMIN_SSH_KEY not set -- skipping Linux user setup."
|
||||
echo " Run setup-linux-admin-user.sh and setup-admin-sudo.sh BEFORE disconnecting"
|
||||
echo " from this session, since the next step disables password authentication."
|
||||
fi
|
||||
|
||||
next_step "SSH hardening (key-only root login + fail2ban)"
|
||||
"${SCRIPT_DIR}/harden-ssh.sh"
|
||||
|
||||
echo
|
||||
echo "=== 3/5: unattended security upgrades ==="
|
||||
next_step "unattended security upgrades"
|
||||
"${SCRIPT_DIR}/setup-unattended-upgrades.sh"
|
||||
|
||||
echo
|
||||
echo "=== 4/5: PVE firewall (mgmt-only SSH/8006) ==="
|
||||
next_step "PVE firewall (mgmt-only SSH/8006)"
|
||||
MGMT_CIDR="$MGMT_CIDR" "${SCRIPT_DIR}/deploy-firewall.sh"
|
||||
|
||||
echo
|
||||
echo "=== 5/5: disable subscription nag (cosmetic) ==="
|
||||
next_step "disable subscription nag (cosmetic)"
|
||||
"${SCRIPT_DIR}/disable-subscription-nag.sh"
|
||||
|
||||
echo
|
||||
echo "=== Base hardening applied. Remaining manual/deliberate steps: ==="
|
||||
echo " - ${SCRIPT_DIR}/create-admin-user.sh <username>"
|
||||
echo " - ${SCRIPT_DIR}/setup-admin-sudo.sh <username> (passwordless sudo for pvesh/qm/pct)"
|
||||
if [ -z "${ADMIN_USER:-}" ]; then
|
||||
echo " - ${SCRIPT_DIR}/setup-linux-admin-user.sh <username> <ssh-pubkey>"
|
||||
echo " - ${SCRIPT_DIR}/setup-admin-sudo.sh <username> (NOPASSWD for pvesh/qm/pct)"
|
||||
fi
|
||||
echo " - ${SCRIPT_DIR}/create-admin-user.sh <username> (PVE web UI account)"
|
||||
echo " - Enable 2FA/TOTP for that user and root@pam via the web UI"
|
||||
echo " - ${SCRIPT_DIR}/audit.sh (verify everything above)"
|
||||
|
||||
Executable
+82
@@ -0,0 +1,82 @@
|
||||
#!/bin/bash
|
||||
# Create a Linux system user for SSH access and sudo, and install their
|
||||
# authorized SSH public key. Run this before harden-ssh.sh so that
|
||||
# key-based access is in place before password authentication is disabled.
|
||||
#
|
||||
# Idempotent - safe to re-run. Run as root on the PVE host.
|
||||
#
|
||||
# Usage:
|
||||
# ./setup-linux-admin-user.sh <username> <ssh-public-key>
|
||||
# ./setup-linux-admin-user.sh <username> --key-file <path-to-.pub>
|
||||
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 <username> <ssh-public-key>" >&2
|
||||
echo " $0 <username> --key-file <path-to-.pub>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
shift
|
||||
SSH_KEY=""
|
||||
if [ "${1:-}" = "--key-file" ]; then
|
||||
KEY_FILE="${2:-}"
|
||||
[ -z "$KEY_FILE" ] && { echo "ERROR: --key-file requires a path." >&2; exit 1; }
|
||||
[ -f "$KEY_FILE" ] || { echo "ERROR: key file not found: $KEY_FILE" >&2; exit 1; }
|
||||
SSH_KEY="$(cat "$KEY_FILE")"
|
||||
else
|
||||
SSH_KEY="${1:-}"
|
||||
fi
|
||||
|
||||
if [ -z "$SSH_KEY" ]; then
|
||||
echo "ERROR: an SSH public key is required (key string or --key-file <path>)." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! echo "$SSH_KEY" | grep -qE '^(ssh-rsa|ssh-ed25519|ecdsa-sha2-nistp[0-9]+) [A-Za-z0-9+/=]'; then
|
||||
echo "ERROR: argument doesn't look like a valid SSH public key." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- create Linux user if missing ---
|
||||
if id "$USERNAME" >/dev/null 2>&1; then
|
||||
echo "User '${USERNAME}' already exists - skipping useradd."
|
||||
else
|
||||
useradd --create-home --shell /bin/bash "$USERNAME"
|
||||
echo "Created Linux user '${USERNAME}'."
|
||||
fi
|
||||
|
||||
# --- sudo group membership ---
|
||||
if id -nG "$USERNAME" | grep -qw sudo; then
|
||||
echo "User '${USERNAME}' is already in the sudo group."
|
||||
else
|
||||
usermod --append --groups sudo "$USERNAME"
|
||||
echo "Added '${USERNAME}' to the sudo group."
|
||||
fi
|
||||
|
||||
# --- install authorized SSH key ---
|
||||
HOME_DIR="$(getent passwd "$USERNAME" | cut -d: -f6)"
|
||||
SSH_DIR="${HOME_DIR}/.ssh"
|
||||
AUTH_FILE="${SSH_DIR}/authorized_keys"
|
||||
|
||||
mkdir -p "$SSH_DIR"
|
||||
chmod 700 "$SSH_DIR"
|
||||
touch "$AUTH_FILE"
|
||||
chmod 600 "$AUTH_FILE"
|
||||
chown -R "${USERNAME}:${USERNAME}" "$SSH_DIR"
|
||||
|
||||
if grep -qF "$SSH_KEY" "$AUTH_FILE" 2>/dev/null; then
|
||||
echo "SSH key is already present in ${AUTH_FILE}."
|
||||
else
|
||||
echo "$SSH_KEY" >> "$AUTH_FILE"
|
||||
echo "Installed SSH key in ${AUTH_FILE}."
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "Linux user '${USERNAME}' is ready for SSH key-based login with sudo access."
|
||||
echo "Next: run setup-admin-sudo.sh ${USERNAME} to grant NOPASSWD for pvesh/qm/pct."
|
||||
Reference in New Issue
Block a user