diff --git a/tools/harden.sh b/tools/harden.sh index 0dbcc1d..fda5d1c 100755 --- a/tools/harden.sh +++ b/tools/harden.sh @@ -1,7 +1,7 @@ #!/bin/bash # LXS - Server Hardening -# Description: Apply baseline security hardening (UFW + fail2ban + SSH key-only + unattended-upgrades) +# Description: Apply baseline security hardening (UFW + fail2ban + unattended-upgrades) # Author: LXS # Date: 2025 @@ -22,7 +22,6 @@ set -u DO_UFW=1 DO_FAIL2BAN=1 -DO_SSH=1 DO_UNATTENDED=1 ASSUME_YES=0 @@ -30,7 +29,6 @@ for arg in "$@"; do case "$arg" in --no-ufw) DO_UFW=0 ;; --no-fail2ban) DO_FAIL2BAN=0 ;; - --no-ssh) DO_SSH=0 ;; --no-unattended) DO_UNATTENDED=0 ;; -y|--yes) ASSUME_YES=1 ;; -h|--help) @@ -40,7 +38,6 @@ Usage: harden.sh [options] Options: --no-ufw Skip UFW firewall setup --no-fail2ban Skip fail2ban setup - --no-ssh Skip SSH key-only enforcement --no-unattended Skip unattended-upgrades setup -y, --yes Skip confirmation prompt -h, --help Show this help @@ -60,17 +57,25 @@ done require_debian_ubuntu || exit 1 -SSH_PORT=$(awk '/^[[:space:]]*Port[[:space:]]+/ {print $2; exit}' /etc/ssh/sshd_config 2>/dev/null) +# Read the effective Port from sshd_config + any drop-in under sshd_config.d/ +sshd_effective_port() { + local files=(/etc/ssh/sshd_config) + if compgen -G "/etc/ssh/sshd_config.d/*.conf" >/dev/null; then + files+=(/etc/ssh/sshd_config.d/*.conf) + fi + awk '/^[[:space:]]*Port[[:space:]]+/ {print $2}' "${files[@]}" 2>/dev/null | tail -1 +} + +SSH_PORT=$(sshd_effective_port) SSH_PORT=${SSH_PORT:-22} echo -e "${WHITE}${BOLD}LXS Server Hardening${NC}" -echo -e "${GRAY}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" +show_separator echo "The following actions will be applied to this server:" [ $DO_UFW -eq 1 ] && echo " • UFW firewall: default deny incoming, allow SSH on port ${SSH_PORT}" [ $DO_FAIL2BAN -eq 1 ] && echo " • fail2ban: enable sshd jail (bantime 1h, maxretry 5)" -[ $DO_SSH -eq 1 ] && echo " • SSH: disable password auth (key-only), prohibit-password root login" [ $DO_UNATTENDED -eq 1 ] && echo " • unattended-upgrades: enable automatic security updates" -echo -e "${GRAY}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" +show_separator if [ $ASSUME_YES -ne 1 ]; then read -r -p "Proceed? [y/N] " reply @@ -80,7 +85,8 @@ if [ $ASSUME_YES -ne 1 ]; then esac fi -export DEBIAN_FRONTEND=noninteractive +apt_noninteractive +wait_for_apt || exit 1 # ═══════════════════════════════════════════════════════════════════════════ # UFW @@ -121,55 +127,6 @@ EOF ok "fail2ban enabled (sshd jail active)" } -# ═══════════════════════════════════════════════════════════════════════════ -# SSH key-only -# ═══════════════════════════════════════════════════════════════════════════ - -has_authorized_key() { - local user_home - for user_home in /root "/home/${SUDO_USER:-}"; do - [ -z "$user_home" ] && continue - [ "$user_home" = "/home/" ] && continue - if [ -s "${user_home}/.ssh/authorized_keys" ]; then - return 0 - fi - done - return 1 -} - -setup_ssh() { - info "Enforcing SSH key-only authentication..." - if ! has_authorized_key; then - err "No authorized_keys found for root or ${SUDO_USER:-current user}." - err "Refusing to disable password auth — you would lock yourself out." - err "Add a public key first, then re-run: harden.sh --no-ufw --no-fail2ban --no-unattended" - return 1 - fi - - local cfg=/etc/ssh/sshd_config - cp -n "$cfg" "${cfg}.lxs-backup" - - sed -i -E \ - -e 's/^[#[:space:]]*PasswordAuthentication[[:space:]]+.*/PasswordAuthentication no/' \ - -e 's/^[#[:space:]]*ChallengeResponseAuthentication[[:space:]]+.*/ChallengeResponseAuthentication no/' \ - -e 's/^[#[:space:]]*KbdInteractiveAuthentication[[:space:]]+.*/KbdInteractiveAuthentication no/' \ - -e 's/^[#[:space:]]*PermitRootLogin[[:space:]]+.*/PermitRootLogin prohibit-password/' \ - "$cfg" - - grep -q '^PasswordAuthentication ' "$cfg" || echo 'PasswordAuthentication no' >> "$cfg" - grep -q '^PermitRootLogin ' "$cfg" || echo 'PermitRootLogin prohibit-password' >> "$cfg" - - if sshd -t 2>/tmp/lxs_sshd_test; then - systemctl reload ssh 2>/dev/null || systemctl reload sshd - ok "SSH configured for key-only auth (backup: ${cfg}.lxs-backup)" - else - err "sshd config test failed; restoring backup." - cp "${cfg}.lxs-backup" "$cfg" - cat /tmp/lxs_sshd_test >&2 - return 1 - fi -} - # ═══════════════════════════════════════════════════════════════════════════ # unattended-upgrades # ═══════════════════════════════════════════════════════════════════════════ @@ -192,7 +149,6 @@ EOF [ $DO_UFW -eq 1 ] && setup_ufw [ $DO_FAIL2BAN -eq 1 ] && setup_fail2ban -[ $DO_SSH -eq 1 ] && setup_ssh || true [ $DO_UNATTENDED -eq 1 ] && setup_unattended # ═══════════════════════════════════════════════════════════════════════════ @@ -201,11 +157,9 @@ EOF echo "" echo -e "${WHITE}${BOLD}Summary${NC}" -echo -e "${GRAY}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" +show_separator command -v ufw >/dev/null && echo "UFW: $(ufw status | head -1)" systemctl is-active fail2ban >/dev/null 2>&1 && echo "fail2ban: active" || echo "fail2ban: inactive" -echo "SSH password auth: $(grep -E '^PasswordAuthentication' /etc/ssh/sshd_config 2>/dev/null | awk '{print $2}' || echo unknown)" -echo "SSH root login: $(grep -E '^PermitRootLogin' /etc/ssh/sshd_config 2>/dev/null | awk '{print $2}' || echo unknown)" systemctl is-active unattended-upgrades >/dev/null 2>&1 && echo "unattended-upgrades: active" || echo "unattended-upgrades: inactive" echo "" ok "Hardening complete."