From aa3ad2f716e42e525930708e81edd14ab3e8b0b7 Mon Sep 17 00:00:00 2001 From: Lutz Date: Fri, 12 Jun 2026 14:41:31 +0200 Subject: [PATCH] K-114: SSH root login prompt with sshd drop-in in the install path prompt_lxc_config asks 'SSH-Root-Login erlauben? [Y/n]' (env-presettable via SSH_ROOT_LOGIN, validated, normalized to yes|no). The bootstrap passes the value into the container; configure_ssh_root_login writes /etc/ssh/sshd_config.d/zz-root-login.conf (yes -> PermitRootLogin yes, no -> prohibit-password) and reloads sshd. --- install/authentik-install.sh | 1 + install/devpi-install.sh | 1 + install/nexus-db-install.sh | 1 + install/nexus-install.sh | 1 + install/webapp-install.sh | 1 + lib/build.func | 25 +++++++++++++++++++++- lib/install.func | 30 +++++++++++++++++++++++++++ tests/test_validation.sh | 40 ++++++++++++++++++++++++++++++++---- 8 files changed, 95 insertions(+), 5 deletions(-) diff --git a/install/authentik-install.sh b/install/authentik-install.sh index a77aa33..a02665b 100644 --- a/install/authentik-install.sh +++ b/install/authentik-install.sh @@ -29,6 +29,7 @@ CRED_FILE="/root/authentik.credentials" # ── base packages + Docker ──────────────────────────────────────────────────── setup_base_apt ca-certificates curl +configure_ssh_root_login if ! command -v docker >/dev/null 2>&1; then msg_info "Installing Docker (get.docker.com)..." curl -fsSL https://get.docker.com | sh >/dev/null diff --git a/install/devpi-install.sh b/install/devpi-install.sh index 2c27f41..382b5ed 100644 --- a/install/devpi-install.sh +++ b/install/devpi-install.sh @@ -19,6 +19,7 @@ DEVPI_PORT="3141" # ── packages + user + dirs ─────────────────────────────────────────────────── setup_base_apt python3 python3-venv python3-pip +configure_ssh_root_login create_system_user "$DEVPI_USER" "$DEVPI_HOME" mkdir -p "$DEVPI_DATA" diff --git a/install/nexus-db-install.sh b/install/nexus-db-install.sh index 20af92d..9bf3f77 100755 --- a/install/nexus-db-install.sh +++ b/install/nexus-db-install.sh @@ -36,6 +36,7 @@ CRED_FILE="/root/nexus-db.credentials" # ── packages: PGDG repo + PostgreSQL 16 + pgvector ──────────────────────────── setup_base_apt curl ca-certificates gnupg lsb-release +configure_ssh_root_login if [[ ! -f /etc/apt/sources.list.d/pgdg.sources ]] && [[ ! -f /etc/apt/sources.list.d/pgdg.list ]]; then msg_info "Adding PGDG apt repo..." diff --git a/install/nexus-install.sh b/install/nexus-install.sh index ce83cf1..60da47f 100644 --- a/install/nexus-install.sh +++ b/install/nexus-install.sh @@ -46,6 +46,7 @@ run_user() { runuser -u "$APP_USER" -- env HOME="$APP_HOME" "$@"; } # ── packages: git + rsync + sudo ; Node.js via NodeSource ───────────────────── setup_base_apt git rsync sudo +configure_ssh_root_login NODE_HAVE="$(command -v node >/dev/null 2>&1 && node -v | sed -E 's/^v([0-9]+).*/\1/' || echo 0)" if [[ "$NODE_HAVE" != "$NODE_MAJOR" ]]; then diff --git a/install/webapp-install.sh b/install/webapp-install.sh index 9f243d4..45a1eeb 100644 --- a/install/webapp-install.sh +++ b/install/webapp-install.sh @@ -44,6 +44,7 @@ run_user() { runuser -u "$APP_USER" -- env HOME="$APP_HOME" "$@"; } # ── packages: git + rsync + sudo ; Node.js via NodeSource ──────────────────── setup_base_apt git rsync sudo +configure_ssh_root_login NODE_HAVE="$(command -v node >/dev/null 2>&1 && node -v | sed -E 's/^v([0-9]+).*/\1/' || echo 0)" if [[ "$NODE_HAVE" != "$NODE_MAJOR" ]]; then diff --git a/lib/build.func b/lib/build.func index f54ebc5..4a72257 100644 --- a/lib/build.func +++ b/lib/build.func @@ -147,6 +147,16 @@ is_cidr() { is_ipcfg() { [[ "$1" == "dhcp" ]] || is_cidr "$1"; } +# Ja/Nein-Antworten (Prompts wie "… erlauben? [Y/n]"). Akzeptiert +# deutsch/englisch, normalize_yesno macht daraus kanonisch yes|no. +is_yesno() { is_clean_ascii "$1" && [[ "${1,,}" =~ ^(y|yes|j|ja|n|no|nein)$ ]]; } +normalize_yesno() { + case "${1,,}" in + y|yes|j|ja) printf 'yes' ;; + *) printf 'no' ;; + esac +} + # Space/comma-separated list of IPv4s (DNS prompt). Gesamtstring zuerst # prüfen — die Wort-Splittung würde eingebettete Newlines sonst verstecken. is_ipv4_list() { @@ -311,6 +321,17 @@ prompt_lxc_config() { else msg_warn "No network profile for VLAN ${VLAN_TAG:-none}; DHCP DNS will be inherited." fi + + # SSH-Root-Login (Default: ja, Homelab-Komfort). Umgesetzt wird das im + # Install-Pfad per sshd-Drop-in (configure_ssh_root_login, lib/install.func); + # bootstrap_install_script reicht den normalisierten Wert in den Container. + if [[ -z "${SSH_ROOT_LOGIN:-}" ]]; then + prompt_validated SSH_ROOT_LOGIN "SSH-Root-Login erlauben? [Y/n]: " is_yesno "y" + else + require_valid SSH_ROOT_LOGIN is_yesno "SSH root login (y/n)" + fi + SSH_ROOT_LOGIN="$(normalize_yesno "$SSH_ROOT_LOGIN")" + echo " → SSH root login: $SSH_ROOT_LOGIN" } # ── template ───────────────────────────────────────────────────────────────── @@ -408,7 +429,9 @@ bootstrap_install_script() { pct exec "$CTID" -- bash -c "apt-get update -qq && apt-get install -y -qq curl ca-certificates >/dev/null" msg_info "Running installer ($url)..." - pct exec "$CTID" -- bash -c "curl -fsSL '$url' -o /root/${APP}-install.sh && bash /root/${APP}-install.sh" + # SSH_ROOT_LOGIN ist durch normalize_yesno kanonisch yes|no — als Env in + # den Container durchreichen (configure_ssh_root_login wertet es aus). + pct exec "$CTID" -- bash -c "curl -fsSL '$url' -o /root/${APP}-install.sh && SSH_ROOT_LOGIN='${SSH_ROOT_LOGIN:-yes}' bash /root/${APP}-install.sh" } # ── summary ────────────────────────────────────────────────────────────────── diff --git a/lib/install.func b/lib/install.func index b4fc48f..8894be8 100644 --- a/lib/install.func +++ b/lib/install.func @@ -38,6 +38,36 @@ apt_cleanup() { apt-get autoclean -qq >/dev/null || true } +# ── ssh ────────────────────────────────────────────────────────────────────── +# SSH-Root-Login gemäß Host-Prompt (prompt_lxc_config setzt SSH_ROOT_LOGIN, +# bootstrap_install_script reicht es als Env durch; Default: yes). +# yes → PermitRootLogin yes (Passwort-Login mit dem generierten Root-Passwort) +# no → PermitRootLogin prohibit-password (Debian-Default, nur SSH-Key) +# Umsetzung als Drop-in, damit Paket-Updates von sshd_config nicht kollidieren. +configure_ssh_root_login() { + local choice="${SSH_ROOT_LOGIN:-yes}" value + case "$choice" in + yes) value="yes" ;; + no) value="prohibit-password" ;; + *) msg_err "SSH_ROOT_LOGIN must be yes|no, got: '$choice'"; return 1 ;; + esac + + if [[ ! -d /etc/ssh/sshd_config.d ]]; then + if [[ "$choice" == "no" ]]; then + msg_warn "openssh-server not installed — nothing to configure (root login stays off)" + return 0 + fi + msg_info "Installing openssh-server..." + apt-get install -y -qq openssh-server >/dev/null + fi + + msg_info "Configuring SSH root login: PermitRootLogin $value" + printf 'PermitRootLogin %s\n' "$value" >/etc/ssh/sshd_config.d/zz-root-login.conf + systemctl reload ssh 2>/dev/null || systemctl restart ssh 2>/dev/null \ + || msg_warn "ssh.service not active yet — config applies on first start" + msg_ok "SSH root login: $choice" +} + # ── users / dirs ───────────────────────────────────────────────────────────── create_system_user() { local user="$1" home="$2" diff --git a/tests/test_validation.sh b/tests/test_validation.sh index 08838fe..0a0ca90 100755 --- a/tests/test_validation.sh +++ b/tests/test_validation.sh @@ -77,6 +77,38 @@ assert_false "is_hostname lehnt 'a b' ab" is_hostname "a b" assert_true "is_token akzeptiert local-lvm" is_token "local-lvm" assert_false "is_token lehnt 'a;b' ab" is_token "a;b" +# ── Ja/Nein (SSH-Root-Login-Prompt) ────────────────────────────────────────── +assert_true "is_yesno akzeptiert y" is_yesno "y" +assert_true "is_yesno akzeptiert Ja" is_yesno "Ja" +assert_true "is_yesno akzeptiert NO" is_yesno "NO" +assert_true "is_yesno akzeptiert nein" is_yesno "nein" +assert_false "is_yesno lehnt 'maybe' ab" is_yesno "maybe" +assert_false "is_yesno lehnt leeren Wert ab" is_yesno "" +[[ "$(normalize_yesno "J")" == "yes" && "$(normalize_yesno "nein")" == "no" ]] \ + && ok "normalize_yesno kanonisiert J→yes, nein→no" \ + || nok "normalize_yesno kanonisiert J→yes, nein→no" + +# SSH-Root-Login-Prompt: leere Eingabe = Default Y → normalisiert yes; +# explizites 'n' → no. +ssh_default="$( + printf '\n' | { + SSH_ROOT_LOGIN="" + prompt_validated SSH_ROOT_LOGIN "SSH-Root-Login erlauben? [Y/n]: " is_yesno "y" >/dev/null 2>&1 + normalize_yesno "$SSH_ROOT_LOGIN" + } +)" +[[ "$ssh_default" == "yes" ]] && ok "SSH-Root-Login: leere Eingabe → Default yes" \ + || nok "SSH-Root-Login Default (got: '$ssh_default')" +ssh_no="$( + printf 'n\n' | { + SSH_ROOT_LOGIN="" + prompt_validated SSH_ROOT_LOGIN "SSH-Root-Login erlauben? [Y/n]: " is_yesno "y" >/dev/null 2>&1 + normalize_yesno "$SSH_ROOT_LOGIN" + } +)" +[[ "$ssh_no" == "no" ]] && ok "SSH-Root-Login: 'n' → no" \ + || nok "SSH-Root-Login 'n' (got: '$ssh_no')" + # ── require_valid: env-Werte werden sanitisiert + geprüft ──────────────────── CHECKVAL=$' 7\r' require_valid CHECKVAL is_uint "Testwert" && [[ "$CHECKVAL" == "7" ]] \ @@ -145,12 +177,12 @@ ctid_out="$( smoke_out="$( env CTID=999 CT_HOSTNAME=smoke DISK_SIZE=8 CORES=2 RAM=1024 BRIDGE=vmbr0 \ VLAN_TAG=20 TEMPLATE_STORAGE=local ROOTFS_STORAGE=local-lvm \ - IPCFG=10.11.20.99/24 GATEWAY=10.11.20.1 NAMESERVER="" \ + IPCFG=10.11.20.99/24 GATEWAY=10.11.20.1 NAMESERVER="" SSH_ROOT_LOGIN=J \ NET_PROFILES_FILE="$REPO_ROOT/lib/networks.conf" \ - bash -c "source '$REPO_ROOT/lib/build.func' && prompt_lxc_config >/dev/null && echo SMOKE-OK" + bash -c "source '$REPO_ROOT/lib/build.func' && prompt_lxc_config >/dev/null && echo SMOKE-OK:\$SSH_ROOT_LOGIN" )" || true -[[ "$smoke_out" == *SMOKE-OK* ]] \ - && ok "prompt_lxc_config Dry-Run mit validen env-Werten läuft durch" \ +[[ "$smoke_out" == *SMOKE-OK:yes* ]] \ + && ok "prompt_lxc_config Dry-Run mit validen env-Werten läuft durch (SSH_ROOT_LOGIN J→yes)" \ || nok "prompt_lxc_config Dry-Run (got: '$smoke_out')" smoke_bad="$(