# Berechtigung geben chmod +x cis_level1_hardening.sh # Erst Dry-Run: sudo ./cis_level1_hardening.sh --dry-run # Dann produktiv: sudo ./cis_level1_hardening.sh # Quellcode #!/usr/bin/env bash #=============================================================================== # CIS Benchmark Level 1 - Server Hardening Script # Unterstützt: Red Hat Enterprise Linux 8 & 9 (und kompatible Derivate) # Version: 1.0 # Modus: Automatisch für unkritische Maßnahmen # Interaktiv (ja/nein) für kritische Maßnahmen #=============================================================================== set -euo pipefail # ── Farben & Formatierung ───────────────────────────────────────────────────── RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m' CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m' # ── Globale Variablen ───────────────────────────────────────────────────────── LOGFILE="/var/log/cis_hardening_$(date +%Y%m%d_%H%M%S).log" BACKUP_DIR="/root/cis_backup_$(date +%Y%m%d_%H%M%S)" RHEL_VERSION="" CHANGES_MADE=0 SKIPPED=0 ALREADY_OK=0 ERRORS=0 DRY_RUN=false # ── Hilfsfunktionen ────────────────────────────────────────────────────────── log() { echo -e "$(date '+%Y-%m-%d %H:%M:%S') $*" | tee -a "$LOGFILE"; } info() { log "${BLUE}[INFO]${NC} $*"; } ok() { log "${GREEN}[OK]${NC} $*"; ((ALREADY_OK++)) || true; } fixed() { log "${GREEN}[FIXED]${NC} $*"; ((CHANGES_MADE++)) || true; } warn() { log "${YELLOW}[WARN]${NC} $*"; } error() { log "${RED}[ERROR]${NC} $*"; ((ERRORS++)) || true; } skip() { log "${YELLOW}[SKIP]${NC} $*"; ((SKIPPED++)) || true; } section(){ echo "" | tee -a "$LOGFILE"; log "${BOLD}${CYAN}═══ $* ═══${NC}"; } ask_critical() { local desc="$1" echo -e "${YELLOW}[KRITISCH]${NC} ${BOLD}$desc${NC}" echo -en " Umsetzen? (j/n/info): " while true; do read -r answer case "${answer,,}" in j|ja|y|yes) return 0 ;; n|nein|no) skip "Übersprungen: $desc"; return 1 ;; i|info) echo -e " ${CYAN}$2${NC}"; echo -en " Umsetzen? (j/n): " ;; *) echo -en " Bitte j oder n eingeben: " ;; esac done } backup_file() { local file="$1" if [[ -f "$file" ]]; then local target="${BACKUP_DIR}${file}" mkdir -p "$(dirname "$target")" cp -a "$file" "$target" 2>/dev/null || true fi } sysctl_set() { local param="$1" value="$2" local current current=$(sysctl -n "$param" 2>/dev/null || echo "NICHT_VERFÜGBAR") if [[ "$current" == "$value" ]]; then ok "$param = $value (bereits gesetzt)" else if ! $DRY_RUN; then sysctl -w "${param}=${value}" >/dev/null 2>&1 || { error "sysctl $param fehlgeschlagen"; return; } grep -qxF "${param} = ${value}" /etc/sysctl.d/99-cis-hardening.conf 2>/dev/null || \ echo "${param} = ${value}" >> /etc/sysctl.d/99-cis-hardening.conf fi fixed "$param = $value" fi } ensure_config_line() { local file="$1" line="$2" desc="$3" backup_file "$file" if grep -qxF "$line" "$file" 2>/dev/null; then ok "$desc (bereits konfiguriert)" else if ! $DRY_RUN; then echo "$line" >> "$file" fi fixed "$desc" fi } disable_kernel_module() { local mod="$1" local conf="/etc/modprobe.d/cis-disable-${mod}.conf" if [[ -f "$conf" ]] && grep -q "install ${mod} /bin/false" "$conf" 2>/dev/null; then ok "Kernel-Modul $mod bereits deaktiviert" else if ! $DRY_RUN; then echo "install ${mod} /bin/false" > "$conf" echo "blacklist ${mod}" >> "$conf" modprobe -r "$mod" 2>/dev/null || true rmmod "$mod" 2>/dev/null || true fi fixed "Kernel-Modul $mod deaktiviert" fi } disable_service() { local svc="$1" desc="$2" if systemctl is-enabled "$svc" 2>/dev/null | grep -q "enabled"; then if ! $DRY_RUN; then systemctl stop "$svc" 2>/dev/null || true systemctl disable "$svc" 2>/dev/null || true fi fixed "$desc: $svc deaktiviert" else ok "$desc: $svc bereits deaktiviert/nicht vorhanden" fi } mask_service() { local svc="$1" desc="$2" if ! systemctl is-masked "$svc" 2>/dev/null | grep -q "masked"; then if ! $DRY_RUN; then systemctl stop "$svc" 2>/dev/null || true systemctl mask "$svc" 2>/dev/null || true fi fixed "$desc: $svc maskiert" else ok "$desc: $svc bereits maskiert" fi } remove_package() { local pkg="$1" desc="$2" if rpm -q "$pkg" >/dev/null 2>&1; then if ! $DRY_RUN; then dnf remove -y "$pkg" >/dev/null 2>&1 || { error "Konnte $pkg nicht entfernen"; return; } fi fixed "$desc: $pkg entfernt" else ok "$desc: $pkg nicht installiert" fi } # ── Voraussetzungen prüfen ─────────────────────────────────────────────────── preflight() { if [[ $EUID -ne 0 ]]; then echo -e "${RED}Dieses Script muss als root ausgeführt werden.${NC}" exit 1 fi if [[ "${1:-}" == "--dry-run" ]]; then DRY_RUN=true echo -e "${YELLOW}=== DRY-RUN MODUS – es werden keine Änderungen vorgenommen ===${NC}" fi if [[ -f /etc/redhat-release ]]; then RHEL_VERSION=$(rpm -E %{rhel} 2>/dev/null || grep -oP '(?<=release )\d' /etc/redhat-release) fi if [[ "$RHEL_VERSION" != "8" && "$RHEL_VERSION" != "9" ]]; then echo -e "${RED}Nicht unterstütztes System. RHEL 8 oder 9 erforderlich.${NC}" echo "Erkannt: $(cat /etc/redhat-release 2>/dev/null || echo 'unbekannt')" exit 1 fi mkdir -p "$BACKUP_DIR" touch "$LOGFILE" echo -e "${BOLD}${CYAN}" echo "╔══════════════════════════════════════════════════════════════╗" echo "║ CIS Level 1 Server Hardening – RHEL $RHEL_VERSION ║" echo "║ $(date '+%Y-%m-%d %H:%M:%S') ║" echo "║ Backup-Dir: $BACKUP_DIR ║" echo "║ Logfile: $LOGFILE ║" echo "╚══════════════════════════════════════════════════════════════╝" echo -e "${NC}" info "System: $(cat /etc/redhat-release)" info "Kernel: $(uname -r)" info "Backup-Verzeichnis: $BACKUP_DIR" echo "" echo -e "${YELLOW}Das Script wird jetzt CIS Level 1 Server Hardening durchführen.${NC}" echo -e "Unkritische Maßnahmen werden automatisch umgesetzt." echo -e "Kritische Maßnahmen erfordern Bestätigung." echo "" echo -en "Fortfahren? (j/n): " read -r confirm if [[ "${confirm,,}" != "j" && "${confirm,,}" != "ja" ]]; then echo "Abgebrochen." exit 0 fi } # ══════════════════════════════════════════════════════════════════════════════ # SEKTION 1: Initial Setup # ══════════════════════════════════════════════════════════════════════════════ harden_filesystem_modules() { section "1.1.1 – Dateisystem-Kernel-Module deaktivieren" local modules_safe=(cramfs freevxfs hfs hfsplus jffs2 udf) for mod in "${modules_safe[@]}"; do disable_kernel_module "$mod" done # squashfs – kritisch wegen Container if lsmod | grep -q squashfs || mount | grep -q squashfs; then warn "squashfs ist aktiv (Container/Snap). Wird NICHT deaktiviert." else disable_kernel_module "squashfs" fi # usb-storage – kritisch if ask_critical "1.1.1.8/10: USB-Storage Kernel-Modul deaktivieren" \ "USB-Speichergeräte werden komplett blockiert. Alternative: USBGuard für granulare Kontrolle."; then disable_kernel_module "usb-storage" fi } harden_filesystem_partitions() { section "1.1.2 – Dateisystem-Partitionen & Mountoptionen" # /tmp Mountoptionen prüfen/setzen if findmnt -n /tmp >/dev/null 2>&1; then local tmp_opts tmp_opts=$(findmnt -n -o OPTIONS /tmp) for opt in nosuid nodev noexec; do if echo "$tmp_opts" | grep -q "$opt"; then ok "/tmp hat $opt" else warn "/tmp fehlt $opt – manuelle Anpassung in /etc/fstab empfohlen" fi done else warn "/tmp ist keine separate Partition – empfohlen: tmpfs-Eintrag in /etc/fstab" fi # /dev/shm prüfen if findmnt -n /dev/shm >/dev/null 2>&1; then local shm_opts shm_opts=$(findmnt -n -o OPTIONS /dev/shm) for opt in nosuid nodev noexec; do if echo "$shm_opts" | grep -q "$opt"; then ok "/dev/shm hat $opt" else if ! $DRY_RUN; then mount -o remount,"$opt" /dev/shm 2>/dev/null || true # Persistenz in fstab if ! grep -q "/dev/shm" /etc/fstab; then echo "tmpfs /dev/shm tmpfs defaults,nosuid,nodev,noexec 0 0" >> /etc/fstab fi fi fixed "/dev/shm: $opt gesetzt" fi done fi # /home, /var, /var/tmp, /var/log, /var/log/audit – nur Status prüfen for mp in /home /var /var/tmp /var/log /var/log/audit; do if findmnt -n "$mp" >/dev/null 2>&1; then ok "$mp ist separate Partition" else warn "$mp ist KEINE separate Partition (empfohlen für CIS Compliance)" fi done } harden_package_management() { section "1.2 – Paketverwaltung" # gpgcheck global if grep -q "^gpgcheck=1" /etc/dnf/dnf.conf 2>/dev/null; then ok "gpgcheck ist global aktiviert" else backup_file /etc/dnf/dnf.conf if ! $DRY_RUN; then sed -i 's/^gpgcheck=.*/gpgcheck=1/' /etc/dnf/dnf.conf 2>/dev/null || \ echo "gpgcheck=1" >> /etc/dnf/dnf.conf fi fixed "gpgcheck global aktiviert" fi # Repo-gpgcheck local bad_repos bad_repos=$(grep -rl "^gpgcheck=0" /etc/yum.repos.d/ 2>/dev/null || true) if [[ -n "$bad_repos" ]]; then warn "Repos ohne gpgcheck gefunden: $bad_repos" else ok "Alle Repos haben gpgcheck aktiviert" fi } harden_selinux() { section "1.3 – SELinux / Mandatory Access Control" # SELinux Bootloader-Parameter local grub_selinux grub_selinux=$(grubby --info=ALL 2>/dev/null | grep -E "selinux=0|enforcing=0" || true) if [[ -n "$grub_selinux" ]]; then if ! $DRY_RUN; then grubby --update-kernel ALL --remove-args "selinux=0 enforcing=0" fi fixed "SELinux-Deaktivierung aus Bootloader entfernt" else ok "SELinux ist nicht im Bootloader deaktiviert" fi # SELinux Policy local current_policy current_policy=$(grep -E "^SELINUXTYPE=" /etc/selinux/config 2>/dev/null | cut -d= -f2 || echo "unknown") if [[ "$current_policy" == "targeted" ]]; then ok "SELinux Policy: targeted" else warn "SELinux Policy ist '$current_policy' – sollte 'targeted' sein" fi # SELinux Enforcing local current_mode current_mode=$(getenforce 2>/dev/null || echo "Disabled") if [[ "$current_mode" == "Enforcing" ]]; then ok "SELinux ist Enforcing" else if ask_critical "SELinux auf Enforcing setzen (aktuell: $current_mode)" \ "Relabeling dauert 30-60 Min beim nächsten Boot. Erst Permissive testen empfohlen. audit2why hilft bei Policy-Fehlern."; then backup_file /etc/selinux/config if ! $DRY_RUN; then sed -i 's/^SELINUX=.*/SELINUX=enforcing/' /etc/selinux/config if [[ "$current_mode" == "Permissive" ]]; then setenforce 1 2>/dev/null || warn "setenforce 1 fehlgeschlagen – wird nach Reboot aktiv" else warn "SELinux war Disabled – Reboot mit fixfiles -F onboot erforderlich" fixfiles -F onboot 2>/dev/null || true fi fi fixed "SELinux auf Enforcing konfiguriert" fi fi # setroubleshoot und mcstrans entfernen remove_package "setroubleshoot" "1.3.1.6: setroubleshoot" remove_package "mcstrans" "1.3.1.7: mcstrans" } harden_bootloader() { section "1.4 – Bootloader absichern" # Bootloader-Passwort if grep -q "^GRUB2_PASSWORD=" /boot/grub2/user.cfg 2>/dev/null; then ok "Bootloader-Passwort ist gesetzt" else warn "Bootloader-Passwort ist NICHT gesetzt – manuell setzen: grub2-setpassword" fi # Bootloader-Config Permissions local grub_cfg="/boot/grub2/grub.cfg" if [[ -f "$grub_cfg" ]]; then local perms perms=$(stat -c "%a" "$grub_cfg") if [[ "$perms" == "600" ]] || [[ "$perms" == "700" ]]; then ok "grub.cfg Berechtigungen: $perms" else if ! $DRY_RUN; then chmod 600 "$grub_cfg" chown root:root "$grub_cfg" fi fixed "grub.cfg Berechtigungen auf 600 gesetzt" fi fi } harden_process_hardening() { section "1.5 – Prozess-Härtung" # core dumps ensure_config_line "/etc/security/limits.d/cis-hardening.conf" "* hard core 0" "1.5.1: Core Dumps limitiert" sysctl_set "fs.suid_dumpable" "0" # ASLR sysctl_set "kernel.randomize_va_space" "2" # ptrace sysctl_set "kernel.yama.ptrace_scope" "1" } harden_crypto_policy() { section "1.6 – System-weite Crypto-Policy" local current_policy current_policy=$(update-crypto-policies --show 2>/dev/null || echo "UNKNOWN") if [[ "$current_policy" == "LEGACY" ]]; then if ask_critical "Crypto-Policy von LEGACY auf DEFAULT ändern" \ "Verbindungen zu Systemen mit TLS 1.0/1.1 oder schwachen Ciphern schlagen danach fehl."; then if ! $DRY_RUN; then update-crypto-policies --set DEFAULT >/dev/null 2>&1 fi fixed "Crypto-Policy auf DEFAULT gesetzt" fi elif [[ "$current_policy" == "DEFAULT" || "$current_policy" == "FUTURE" || "$current_policy" == "FIPS" ]]; then ok "Crypto-Policy: $current_policy" else warn "Crypto-Policy: $current_policy – prüfen ob angemessen" fi # CBC für SSH deaktivieren local cbc_rec="1.6.4" [[ "$RHEL_VERSION" == "9" ]] && cbc_rec="1.6.5" if sshd -T 2>/dev/null | grep -qi "aes.*cbc"; then if ask_critical "$cbc_rec: CBC-Cipher für SSH deaktivieren" \ "Ältere SSH-Clients unterstützen evtl. nur CBC. Prüfe vorher Verbindungen zu Legacy-Systemen."; then if ! $DRY_RUN; then update-crypto-policies --set "${current_policy}:NO-CBC" >/dev/null 2>&1 || \ warn "Subpolicy NO-CBC konnte nicht gesetzt werden" fi fixed "CBC-Cipher für SSH deaktiviert" fi else ok "$cbc_rec: Keine CBC-Cipher in SSH aktiv" fi } harden_banners() { section "1.7 – Login-Banner konfigurieren" local banner_text="Autorisierter Zugriff erforderlich. Alle Aktivitaeten werden protokolliert. Unbefugter Zugriff wird strafrechtlich verfolgt." for f in /etc/issue /etc/issue.net; do backup_file "$f" # Prüfe ob OS-Infos im Banner stehen if grep -qEi '\\r|\\s|\\m|\\v|kernel' "$f" 2>/dev/null; then if ! $DRY_RUN; then echo "$banner_text" > "$f" chmod 644 "$f" chown root:root "$f" fi fixed "$(basename $f): OS-Informationen entfernt, Banner gesetzt" elif [[ ! -s "$f" ]]; then if ! $DRY_RUN; then echo "$banner_text" > "$f" chmod 644 "$f" chown root:root "$f" fi fixed "$(basename $f): Banner gesetzt" else ok "$(basename $f): Banner vorhanden ohne OS-Infos" fi done # /etc/motd – keine OS-Infos if [[ -s /etc/motd ]] && grep -qEi '\\r|\\s|\\m|\\v|kernel' /etc/motd 2>/dev/null; then backup_file /etc/motd if ! $DRY_RUN; then > /etc/motd fi fixed "/etc/motd: OS-Informationen entfernt" else ok "/etc/motd: OK" fi } # ══════════════════════════════════════════════════════════════════════════════ # SEKTION 2: Dienste # ══════════════════════════════════════════════════════════════════════════════ harden_services() { section "2.1 – Server-Dienste deaktivieren" # Unkritische Dienste – immer deaktivieren/entfernen local safe_services=( "telnet-server:Telnet-Server" "ypserv:NIS-Server" "xinetd:xinetd" ) for entry in "${safe_services[@]}"; do local pkg="${entry%%:*}" desc="${entry##*:}" remove_package "$pkg" "2.1: $desc" done # Dienste die meist nicht benötigt werden aber interaktiv abfragen local critical_services=( "autofs:autofs:Automatisches Mounten von Wechselmedien und NFS-Shares wird deaktiviert." "avahi-daemon:avahi:mDNS/Bonjour Discovery wird deaktiviert. Kann Service-Discovery beeinträchtigen." "cups:cups:Drucken wird deaktiviert. Auf Servern meist unproblematisch." "vsftpd:vsftpd:FTP-Server wird entfernt. Alternative: SFTP über SSH." "samba:samba:Windows-Filesharing wird deaktiviert." "snmpd:net-snmp:SNMP-Monitoring wird deaktiviert. Alternative: Prometheus/node_exporter." "squid:squid:Web-Proxy wird deaktiviert." "rsync:rsync-daemon:rsync-Daemon wird deaktiviert. rsync als Client bleibt nutzbar." ) for entry in "${critical_services[@]}"; do IFS=':' read -r svc pkg impact <<< "$entry" if systemctl is-enabled "$svc" 2>/dev/null | grep -q "enabled" || rpm -q "$pkg" >/dev/null 2>&1; then if ask_critical "2.1: $svc deaktivieren/entfernen" "$impact"; then disable_service "$svc" "2.1" fi else ok "2.1: $svc nicht aktiv/installiert" fi done # NFS/rpcbind – besonders kritisch wegen libvirt section "2.1 – NFS/rpcbind (libvirt-Abhängigkeit)" if rpm -q nfs-utils >/dev/null 2>&1 || rpm -q rpcbind >/dev/null 2>&1; then local libvirt_installed=false rpm -q libvirt >/dev/null 2>&1 && libvirt_installed=true if $libvirt_installed; then warn "libvirt ist installiert – NFS/rpcbind werden als Abhängigkeit benötigt" warn "Empfehlung: nfs-server deaktivieren, Pakete NICHT entfernen" if ask_critical "NFS-Server-Dienst deaktivieren (Pakete bleiben für libvirt)" \ "nfs-server wird gestoppt, nfs-utils/rpcbind bleiben installiert für KVM/QEMU."; then disable_service "nfs-server" "2.1.9" fi else if ask_critical "NFS und rpcbind komplett entfernen" \ "Kein libvirt installiert. NFS-Mounts, NFS-Shares und RPC-basierte Dienste werden nicht mehr funktionieren."; then disable_service "nfs-server" "2.1.9" disable_service "rpcbind.socket" "2.1.12" disable_service "rpcbind" "2.1.12" fi fi else ok "2.1: NFS/rpcbind nicht installiert" fi # TFTP – kritisch wegen PXE if rpm -q tftp-server >/dev/null 2>&1; then if ask_critical "TFTP-Server entfernen" \ "TFTP wird für PXE/Netzwerk-Boot verwendet. Wenn PXE-Deployment genutzt wird: NICHT entfernen."; then remove_package "tftp-server" "2.1.16/17" fi else ok "2.1: TFTP-Server nicht installiert" fi # Web-Server prüfen for websrv in httpd nginx; do if rpm -q "$websrv" >/dev/null 2>&1; then if ask_critical "Web-Server $websrv deaktivieren" \ "Web-Services werden gestoppt. Wenn dieser Server Web-Dienste hostet: NICHT deaktivieren."; then disable_service "$websrv" "2.1.18/19" fi fi done } harden_client_services() { section "2.2 – Client-Dienste" remove_package "ypbind" "2.2.1: NIS-Client" remove_package "telnet" "2.2.4: Telnet-Client" # LDAP-Client if rpm -q openldap-clients >/dev/null 2>&1; then warn "2.2.2: openldap-clients installiert – prüfen ob benötigt" fi } harden_time_sync() { section "2.3 – Zeitsynchronisation" if systemctl is-enabled chronyd 2>/dev/null | grep -q "enabled"; then ok "chronyd ist aktiviert" elif systemctl is-enabled ntpd 2>/dev/null | grep -q "enabled"; then ok "ntpd ist aktiviert (chronyd bevorzugt)" else if ! $DRY_RUN; then dnf install -y chrony >/dev/null 2>&1 || true systemctl enable --now chronyd 2>/dev/null || true fi fixed "chronyd installiert und aktiviert" fi } harden_cron() { section "2.4 – Cron & at absichern" # crontab Berechtigungen for f in /etc/crontab; do if [[ -f "$f" ]]; then local perms owner perms=$(stat -c "%a" "$f") owner=$(stat -c "%U:%G" "$f") if [[ "$perms" != "600" || "$owner" != "root:root" ]]; then if ! $DRY_RUN; then chmod 600 "$f" chown root:root "$f" fi fixed "$(basename $f): Berechtigungen auf 600 root:root" else ok "$(basename $f): Berechtigungen OK" fi fi done for d in /etc/cron.d /etc/cron.daily /etc/cron.hourly /etc/cron.weekly /etc/cron.monthly; do if [[ -d "$d" ]]; then local perms perms=$(stat -c "%a" "$d") if [[ "$perms" != "700" ]]; then if ! $DRY_RUN; then chmod 700 "$d" chown root:root "$d" fi fixed "$d: Berechtigungen auf 700" else ok "$d: Berechtigungen OK" fi fi done # cron.allow / at.allow for f in /etc/cron.allow /etc/at.allow; do if [[ ! -f "$f" ]]; then if ! $DRY_RUN; then touch "$f" chmod 600 "$f" chown root:root "$f" echo "root" > "$f" fi fixed "$(basename $f) erstellt (nur root)" else ok "$(basename $f) existiert" fi done # .deny-Dateien entfernen for f in /etc/cron.deny /etc/at.deny; do if [[ -f "$f" ]]; then if ! $DRY_RUN; then backup_file "$f" rm -f "$f" fi fixed "$(basename $f) entfernt" fi done } # ══════════════════════════════════════════════════════════════════════════════ # SEKTION 3: Netzwerk # ══════════════════════════════════════════════════════════════════════════════ harden_network_devices() { section "3.1 – Netzwerk-Geräte" # WLAN – kritisch if nmcli radio wifi 2>/dev/null | grep -qi "enabled"; then if ask_critical "WLAN deaktivieren" \ "Wireless-Interfaces werden komplett deaktiviert. Nur auf Servern empfohlen, NICHT auf Laptops."; then if ! $DRY_RUN; then nmcli radio wifi off 2>/dev/null || true disable_kernel_module "cfg80211" fi fixed "WLAN deaktiviert" fi else ok "WLAN bereits deaktiviert oder nicht vorhanden" fi # Bluetooth if systemctl is-enabled bluetooth 2>/dev/null | grep -q "enabled"; then if ask_critical "Bluetooth deaktivieren" \ "Kabellose Peripherie funktioniert nicht mehr. Auf Servern empfohlen."; then disable_service "bluetooth" "3.1.3" disable_kernel_module "bluetooth" fi else ok "Bluetooth bereits deaktiviert oder nicht vorhanden" fi } harden_network_modules() { section "3.2 – Netzwerk-Kernel-Module" local net_modules=(dccp sctp tipc) for mod in "${net_modules[@]}"; do disable_kernel_module "$mod" done } harden_network_params() { section "3.3 – Netzwerk-Kernel-Parameter" # IP-Forwarding – kritisch local ip_fwd ip_fwd=$(sysctl -n net.ipv4.ip_forward 2>/dev/null) if [[ "$ip_fwd" == "1" ]]; then if ask_critical "IP-Forwarding deaktivieren (aktuell: aktiv)" \ "Router/NAT/VPN/Container-Hosts benötigen Forwarding. Cloud-VMs oft ebenfalls. NICHT deaktivieren wenn System routet."; then sysctl_set "net.ipv4.ip_forward" "0" sysctl_set "net.ipv6.conf.all.forwarding" "0" fi else sysctl_set "net.ipv4.ip_forward" "0" fi # Unkritische Netzwerk-Parameter – automatisch setzen info "Setze sichere Netzwerk-Parameter..." # ICMP Redirects sysctl_set "net.ipv4.conf.all.send_redirects" "0" sysctl_set "net.ipv4.conf.default.send_redirects" "0" sysctl_set "net.ipv4.conf.all.accept_redirects" "0" sysctl_set "net.ipv4.conf.default.accept_redirects" "0" sysctl_set "net.ipv6.conf.all.accept_redirects" "0" sysctl_set "net.ipv6.conf.default.accept_redirects" "0" # Source Routing sysctl_set "net.ipv4.conf.all.accept_source_route" "0" sysctl_set "net.ipv4.conf.default.accept_source_route" "0" sysctl_set "net.ipv6.conf.all.accept_source_route" "0" sysctl_set "net.ipv6.conf.default.accept_source_route" "0" # Logging Martians sysctl_set "net.ipv4.conf.all.log_martians" "1" sysctl_set "net.ipv4.conf.default.log_martians" "1" # ICMP Protection sysctl_set "net.ipv4.icmp_echo_ignore_broadcasts" "1" sysctl_set "net.ipv4.icmp_ignore_bogus_error_responses" "1" # SYN Cookies sysctl_set "net.ipv4.tcp_syncookies" "1" # Reverse Path Filtering local rp_current rp_current=$(sysctl -n net.ipv4.conf.all.rp_filter 2>/dev/null) if [[ "$rp_current" == "0" ]]; then if ask_critical "Reverse Path Filtering aktivieren" \ "Bricht asymmetrisches Routing. Wenn asymmetrisches Routing genutzt wird: rp_filter=2 (loose) statt 1 (strict)."; then sysctl_set "net.ipv4.conf.all.rp_filter" "1" sysctl_set "net.ipv4.conf.default.rp_filter" "1" fi else sysctl_set "net.ipv4.conf.all.rp_filter" "1" sysctl_set "net.ipv4.conf.default.rp_filter" "1" fi # IPv6 Router Advertisements sysctl_set "net.ipv6.conf.all.accept_ra" "0" sysctl_set "net.ipv6.conf.default.accept_ra" "0" } # ══════════════════════════════════════════════════════════════════════════════ # SEKTION 4: Firewall # ══════════════════════════════════════════════════════════════════════════════ harden_firewall() { section "4 – Host-basierte Firewall" if ask_critical "Firewall konfigurieren" \ "Drop-Policy ohne SSH-Regel sperrt Remote-Zugang! Script fügt SSH-Regel VOR Drop-Policy hinzu. Trotzdem: IPMI/iDRAC-Zugang als Fallback empfohlen."; then if [[ "$RHEL_VERSION" == "8" ]]; then # RHEL 8: firewalld if ! rpm -q firewalld >/dev/null 2>&1; then if ! $DRY_RUN; then dnf install -y firewalld >/dev/null 2>&1 fi fixed "firewalld installiert" fi if ! $DRY_RUN; then systemctl enable --now firewalld 2>/dev/null || true firewall-cmd --permanent --add-service=ssh 2>/dev/null || true firewall-cmd --reload 2>/dev/null || true fi fixed "firewalld aktiviert, SSH erlaubt" # Default Zone prüfen local zone zone=$(firewall-cmd --get-default-zone 2>/dev/null || echo "unknown") info "Default Firewall-Zone: $zone" if [[ "$zone" == "public" ]]; then ok "Default Zone ist 'public' (sinnvoller Default)" fi elif [[ "$RHEL_VERSION" == "9" ]]; then # RHEL 9: nftables (oder firewalld) if systemctl is-enabled firewalld 2>/dev/null | grep -q "enabled"; then info "firewalld ist aktiv (nutzt nftables als Backend)" if ! $DRY_RUN; then firewall-cmd --permanent --add-service=ssh 2>/dev/null || true firewall-cmd --reload 2>/dev/null || true fi fixed "SSH in firewalld erlaubt" else if ! rpm -q nftables >/dev/null 2>&1; then if ! $DRY_RUN; then dnf install -y nftables >/dev/null 2>&1 fi fi if ! $DRY_RUN; then # SSH-Regel ZUERST, dann Drop-Policy cat > /etc/nftables/cis-base.nft << 'NFTEOF' table inet filter { chain input { type filter hook input priority 0; policy drop; iif lo accept ct state established,related accept ct state invalid drop tcp dport 22 accept icmp type echo-request accept } chain forward { type filter hook forward priority 0; policy drop; } chain output { type filter hook output priority 0; policy accept; } } NFTEOF # In nftables.conf einbinden if ! grep -q "cis-base.nft" /etc/sysconfig/nftables.conf 2>/dev/null; then echo 'include "/etc/nftables/cis-base.nft"' >> /etc/sysconfig/nftables.conf fi systemctl enable --now nftables 2>/dev/null || true fi fixed "nftables mit Default Deny und SSH-Allow konfiguriert" fi fi fi } # ══════════════════════════════════════════════════════════════════════════════ # SEKTION 5: Zugriffskontrolle # ══════════════════════════════════════════════════════════════════════════════ harden_ssh() { section "5.1 – SSH-Server absichern" local ssh_conf="/etc/ssh/sshd_config.d/99-cis-hardening.conf" backup_file "$ssh_conf" if ! $DRY_RUN; then mkdir -p /etc/ssh/sshd_config.d cat > "$ssh_conf" << 'SSHEOF' # CIS Level 1 SSH Hardening PermitRootLogin no MaxAuthTries 4 MaxSessions 10 PermitEmptyPasswords no HostbasedAuthentication no IgnoreRhosts yes X11Forwarding no AllowTcpForwarding no ClientAliveInterval 300 ClientAliveCountMax 3 LoginGraceTime 60 Banner /etc/issue.net MaxStartups 10:30:60 PermitUserEnvironment no SSHEOF fi fixed "SSH-Härtung in $ssh_conf geschrieben" # SSH-Daemon neu laden if ! $DRY_RUN; then # Syntax-Check if sshd -t 2>/dev/null; then systemctl reload sshd 2>/dev/null || systemctl reload ssh 2>/dev/null || true ok "sshd Konfiguration validiert und neu geladen" else error "sshd Konfiguration fehlerhaft! Bitte manuell prüfen: sshd -t" # Rollback rm -f "$ssh_conf" warn "SSH-Konfiguration zurückgerollt" fi fi # SSH Private Key Berechtigungen find /etc/ssh -name 'ssh_host_*_key' -exec chmod 600 {} \; 2>/dev/null || true find /etc/ssh -name 'ssh_host_*_key.pub' -exec chmod 644 {} \; 2>/dev/null || true ok "SSH Host-Key Berechtigungen geprüft" } harden_sudo() { section "5.2 – sudo absichern" # use_pty if grep -rq "^Defaults.*use_pty" /etc/sudoers /etc/sudoers.d/ 2>/dev/null; then ok "sudo use_pty bereits konfiguriert" else if ! $DRY_RUN; then echo "Defaults use_pty" > /etc/sudoers.d/cis-use-pty chmod 440 /etc/sudoers.d/cis-use-pty # Syntax-Check if ! visudo -cf /etc/sudoers.d/cis-use-pty >/dev/null 2>&1; then rm -f /etc/sudoers.d/cis-use-pty error "sudo use_pty Syntax-Fehler – zurückgerollt" fi fi fixed "sudo use_pty konfiguriert" fi # sudo Logfile if grep -rq "^Defaults.*logfile=" /etc/sudoers /etc/sudoers.d/ 2>/dev/null; then ok "sudo Logfile bereits konfiguriert" else if ! $DRY_RUN; then echo 'Defaults logfile="/var/log/sudo.log"' > /etc/sudoers.d/cis-logfile chmod 440 /etc/sudoers.d/cis-logfile if ! visudo -cf /etc/sudoers.d/cis-logfile >/dev/null 2>&1; then rm -f /etc/sudoers.d/cis-logfile error "sudo logfile Syntax-Fehler – zurückgerollt" fi fi fixed "sudo Logfile konfiguriert" fi } harden_pam() { section "5.3 – PAM / Authentifizierung" # Passwort-Qualität local pwquality="/etc/security/pwquality.conf" if [[ -f "$pwquality" ]]; then backup_file "$pwquality" local settings=( "minlen = 14" "minclass = 4" "maxrepeat = 3" "maxclassrepeat = 3" "dcredit = -1" "ucredit = -1" "lcredit = -1" "ocredit = -1" ) for setting in "${settings[@]}"; do local key="${setting%% =*}" if grep -q "^${key}" "$pwquality" 2>/dev/null; then if ! $DRY_RUN; then sed -i "s/^${key}.*/${setting}/" "$pwquality" fi else if ! $DRY_RUN; then echo "$setting" >> "$pwquality" fi fi done fixed "Passwort-Qualitätsrichtlinien gesetzt (minlen=14, minclass=4)" fi # Faillock local faillock="/etc/security/faillock.conf" if [[ -f "$faillock" ]]; then backup_file "$faillock" local fl_settings=( "deny = 5" "unlock_time = 900" "fail_interval = 900" ) for setting in "${fl_settings[@]}"; do local key="${setting%% =*}" if grep -q "^${key}" "$faillock" 2>/dev/null; then if ! $DRY_RUN; then sed -i "s/^${key}.*/${setting}/" "$faillock" fi else if ! $DRY_RUN; then echo "$setting" >> "$faillock" fi fi done fixed "Account-Lockout konfiguriert (deny=5, unlock=900s)" fi } harden_password_policy() { section "5.4 – Passwort-Policy & User-Accounts" backup_file /etc/login.defs # PASS_MAX_DAYS local current_max current_max=$(grep "^PASS_MAX_DAYS" /etc/login.defs 2>/dev/null | awk '{print $2}') if [[ "${current_max:-99999}" -gt 365 ]]; then if ! $DRY_RUN; then sed -i 's/^PASS_MAX_DAYS.*/PASS_MAX_DAYS\t365/' /etc/login.defs fi fixed "PASS_MAX_DAYS auf 365 gesetzt" else ok "PASS_MAX_DAYS: ${current_max}" fi # PASS_MIN_DAYS local current_min current_min=$(grep "^PASS_MIN_DAYS" /etc/login.defs 2>/dev/null | awk '{print $2}') if [[ "${current_min:-0}" -lt 1 ]]; then if ! $DRY_RUN; then sed -i 's/^PASS_MIN_DAYS.*/PASS_MIN_DAYS\t1/' /etc/login.defs fi fixed "PASS_MIN_DAYS auf 1 gesetzt" else ok "PASS_MIN_DAYS: ${current_min}" fi # PASS_WARN_AGE local current_warn current_warn=$(grep "^PASS_WARN_AGE" /etc/login.defs 2>/dev/null | awk '{print $2}') if [[ "${current_warn:-0}" -lt 7 ]]; then if ! $DRY_RUN; then sed -i 's/^PASS_WARN_AGE.*/PASS_WARN_AGE\t7/' /etc/login.defs fi fixed "PASS_WARN_AGE auf 7 gesetzt" else ok "PASS_WARN_AGE: ${current_warn}" fi # UMASK local current_umask current_umask=$(grep "^UMASK" /etc/login.defs 2>/dev/null | awk '{print $2}') if [[ "$current_umask" != "027" ]]; then if ! $DRY_RUN; then sed -i 's/^UMASK.*/UMASK\t\t027/' /etc/login.defs fi fixed "UMASK auf 027 gesetzt" else ok "UMASK: $current_umask" fi # Inaktive Accounts sperren local inactive inactive=$(useradd -D 2>/dev/null | grep INACTIVE | cut -d= -f2) if [[ "${inactive}" == "-1" || -z "${inactive}" ]]; then if ! $DRY_RUN; then useradd -D -f 30 2>/dev/null || true fi fixed "Inaktive Accounts werden nach 30 Tagen gesperrt" else ok "INACTIVE: ${inactive} Tage" fi # Root-Zugang kontrollieren if ask_critical "Root-Account absichern (SSH-Root-Login ist bereits per SSH-Config deaktiviert)" \ "Prüft ob automatisierte Prozesse root ohne Auth nutzen. Sperrt ggf. direkten Root-Login."; then # Shell für root prüfen local root_shell root_shell=$(grep "^root:" /etc/passwd | cut -d: -f7) info "Root-Shell: $root_shell" # Wir sperren root nicht komplett – nur Info ok "Root-SSH ist über sshd_config deaktiviert" fi # System-Accounts prüfen info "Prüfe System-Accounts..." local bad_shells=0 while IFS=: read -r user _ uid _ _ _ shell; do if [[ "$uid" -lt 1000 && "$user" != "root" && "$shell" != "/sbin/nologin" && "$shell" != "/usr/sbin/nologin" && "$shell" != "/bin/false" ]]; then warn "System-Account $user (UID $uid) hat Login-Shell: $shell" ((bad_shells++)) || true fi done < /etc/passwd if [[ "$bad_shells" -eq 0 ]]; then ok "Alle System-Accounts haben nologin-Shell" fi # Default User Shell Timeout local timeout_conf="/etc/profile.d/cis-timeout.sh" if [[ ! -f "$timeout_conf" ]]; then if ! $DRY_RUN; then cat > "$timeout_conf" << 'TMEOF' # CIS: Shell Timeout nach 900 Sekunden Inaktivität readonly TMOUT=900 export TMOUT TMEOF chmod 644 "$timeout_conf" fi fixed "Shell-Timeout auf 900s gesetzt" else ok "Shell-Timeout bereits konfiguriert" fi } # ══════════════════════════════════════════════════════════════════════════════ # SEKTION 6: Logging & Auditing # ══════════════════════════════════════════════════════════════════════════════ harden_integrity() { section "6.1 – Integritätsprüfung (AIDE)" if rpm -q aide >/dev/null 2>&1; then ok "AIDE ist installiert" else if ! $DRY_RUN; then dnf install -y aide >/dev/null 2>&1 || { error "AIDE konnte nicht installiert werden"; return; } fi fixed "AIDE installiert" fi if [[ ! -f /var/lib/aide/aide.db.gz ]]; then info "AIDE-Datenbank wird initialisiert (kann einige Minuten dauern)..." if ! $DRY_RUN; then aide --init >/dev/null 2>&1 || { warn "AIDE init fehlgeschlagen"; return; } mv /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz 2>/dev/null || true fi fixed "AIDE-Datenbank initialisiert" else ok "AIDE-Datenbank existiert" fi # AIDE Cron if ! crontab -l 2>/dev/null | grep -q "aide.*--check"; then if ! $DRY_RUN; then echo "0 5 * * * /usr/sbin/aide --check --config /etc/aide.conf" | \ crontab -l 2>/dev/null | cat - | crontab - 2>/dev/null || \ echo "0 5 * * * root /usr/sbin/aide --check" >> /etc/crontab fi fixed "AIDE täglicher Check als Cronjob konfiguriert" else ok "AIDE Cronjob existiert" fi } harden_journald() { section "6.2 – journald konfigurieren" backup_file /etc/systemd/journald.conf # Persistente Logs if [[ ! -d /var/log/journal ]]; then if ! $DRY_RUN; then mkdir -p /var/log/journal systemd-tmpfiles --create --prefix /var/log/journal 2>/dev/null || true fi fixed "Persistente Journal-Logs aktiviert" else ok "Journal-Verzeichnis existiert" fi # Komprimierung und Speicherlimit local jconf="/etc/systemd/journald.conf" declare -A jparams=( ["Compress"]="yes" ["Storage"]="persistent" ["ForwardToSyslog"]="no" ) for key in "${!jparams[@]}"; do local val="${jparams[$key]}" if grep -q "^${key}=${val}" "$jconf" 2>/dev/null; then ok "journald $key=$val" else if ! $DRY_RUN; then sed -i "s/^#*${key}=.*/${key}=${val}/" "$jconf" 2>/dev/null || \ echo "${key}=${val}" >> "$jconf" fi fixed "journald $key=$val gesetzt" fi done if ! $DRY_RUN; then systemctl restart systemd-journald 2>/dev/null || true fi } harden_auditd() { section "6.3 – auditd konfigurieren" # auditd installiert/aktiviert if ! rpm -q audit >/dev/null 2>&1; then if ! $DRY_RUN; then dnf install -y audit >/dev/null 2>&1 || { error "auditd konnte nicht installiert werden"; return; } fi fixed "auditd installiert" fi if ! systemctl is-enabled auditd 2>/dev/null | grep -q "enabled"; then if ! $DRY_RUN; then systemctl enable --now auditd 2>/dev/null || true fi fixed "auditd aktiviert" else ok "auditd ist aktiviert" fi # Audit-Regeln – CIS Level 1 local rules_file="/etc/audit/rules.d/99-cis-level1.rules" if [[ ! -f "$rules_file" ]]; then if ! $DRY_RUN; then cat > "$rules_file" << 'AUDITEOF' ## CIS Level 1 Audit Rules # Datei-Integritaet -w /etc/passwd -p wa -k identity -w /etc/shadow -p wa -k identity -w /etc/group -p wa -k identity -w /etc/gshadow -p wa -k identity -w /etc/security/opasswd -p wa -k identity # Login-Events -w /var/log/lastlog -p wa -k logins -w /var/run/faillock -p wa -k logins -w /var/log/tallylog -p wa -k logins # sudo Konfiguration -w /etc/sudoers -p wa -k sudo_changes -w /etc/sudoers.d -p wa -k sudo_changes # Netzwerk-Konfiguration -w /etc/hosts -p wa -k network_changes -w /etc/sysconfig/network -p wa -k network_changes -w /etc/sysconfig/network-scripts -p wa -k network_changes # SELinux -w /etc/selinux -p wa -k selinux_changes # Zeitaenderungen -a always,exit -F arch=b64 -S adjtimex -S settimeofday -k time_change -a always,exit -F arch=b32 -S adjtimex -S settimeofday -S stime -k time_change -a always,exit -F arch=b64 -S clock_settime -k time_change -a always,exit -F arch=b32 -S clock_settime -k time_change -w /etc/localtime -p wa -k time_change # User/Group-Aenderungen -w /etc/login.defs -p wa -k login_changes -w /etc/securetty -p wa -k login_changes # Session-Tracking -w /var/run/utmp -p wa -k session -w /var/log/wtmp -p wa -k session -w /var/log/btmp -p wa -k session # Kernel-Module -w /sbin/insmod -p x -k kernel_modules -w /sbin/rmmod -p x -k kernel_modules -w /sbin/modprobe -p x -k kernel_modules -a always,exit -F arch=b64 -S init_module -S delete_module -k kernel_modules # MAC Policy -w /etc/selinux/ -p wa -k MAC_policy # Finalize – diese Regel muss am Ende stehen -e 2 AUDITEOF # Regeln laden augenrules --load 2>/dev/null || auditctl -R "$rules_file" 2>/dev/null || true fi fixed "CIS Level 1 Audit-Regeln konfiguriert" else ok "CIS Audit-Regeln existieren bereits" fi # Audit-Konfiguration local audit_conf="/etc/audit/auditd.conf" if [[ -f "$audit_conf" ]]; then backup_file "$audit_conf" # max_log_file_action if ! grep -q "^max_log_file_action = keep_logs" "$audit_conf"; then if ! $DRY_RUN; then sed -i 's/^max_log_file_action.*/max_log_file_action = keep_logs/' "$audit_conf" fi fixed "auditd max_log_file_action = keep_logs" else ok "auditd max_log_file_action OK" fi # space_left_action if ! grep -q "^space_left_action = email" "$audit_conf"; then if ! $DRY_RUN; then sed -i 's/^space_left_action.*/space_left_action = email/' "$audit_conf" fi fixed "auditd space_left_action = email" fi fi } harden_logfile_permissions() { section "6.2/6.3 – Logfile-Berechtigungen" # /var/log Berechtigungen find /var/log -type f -perm /g+wx,o+rwx -exec chmod g-wx,o-rwx {} + 2>/dev/null || true ok "Logfile-Berechtigungen geprüft und korrigiert" } # ══════════════════════════════════════════════════════════════════════════════ #SEKTION 7: System Maintenance # ══════════════════════════════════════════════════════════════════════════════ harden_system_files() { section "7.1 – Systemdatei-Berechtigungen" # Kritische Dateien declare -A file_perms=( ["/etc/passwd"]="644" ["/etc/shadow"]="000" ["/etc/group"]="644" ["/etc/gshadow"]="000" ["/etc/passwd-"]="644" ["/etc/shadow-"]="000" ["/etc/group-"]="644" ["/etc/gshadow-"]="000" ) for file in "${!file_perms[@]}"; do local expected="${file_perms[$file]}" if [[ -f "$file" ]]; then local current current=$(stat -c "%a" "$file") if [[ "$current" != "$expected" ]]; then if ! $DRY_RUN; then chmod "$expected" "$file" chown root:root "$file" fi fixed "$file: $current -> $expected" else ok "$file: $current" fi fi done # SUID/SGID Binaries prüfen (nur Warnung) info "Suche SUID/SGID-Binaries (nur Info)..." local suid_count suid_count=$(find / -xdev \( -perm -4000 -o -perm -2000 \) -type f 2>/dev/null | wc -l) info "SUID/SGID-Binaries gefunden: $suid_count (manuell prüfen empfohlen)" } harden_user_groups() { section "7.2 – User & Group Einstellungen" # Prüfe auf Accounts ohne Passwort local no_pw no_pw=$(awk -F: '($2 == "" ) { print $1 }' /etc/shadow 2>/dev/null || true) if [[ -n "$no_pw" ]]; then warn "Accounts OHNE Passwort gefunden: $no_pw" warn "Bitte manuell sperren: passwd -l " else ok "Keine Accounts ohne Passwort" fi # root UID 0 einzig local uid0_users uid0_users=$(awk -F: '($3 == 0 && $1 != "root") { print $1 }' /etc/passwd) if [[ -n "$uid0_users" ]]; then warn "Nicht-root Accounts mit UID 0: $uid0_users" else ok "Nur root hat UID 0" fi # root GID prüfen local root_gid root_gid=$(id -g root 2>/dev/null) if [[ "$root_gid" == "0" ]]; then ok "root GID ist 0" else warn "root GID ist $root_gid (sollte 0 sein)" fi # Prüfe PATH in /etc/profile if echo "$PATH" | grep -q "::"; then warn "PATH enthält leere Einträge (::)" elif echo "$PATH" | grep -q ":$"; then warn "PATH endet mit Doppelpunkt" elif echo "$PATH" | grep -q "\."; then warn "PATH enthält relativen Pfad (.)" else ok "Root-PATH enthält keine unsicheren Einträge" fi } # ══════════════════════════════════════════════════════════════════════════════ # ZUSAMMENFASSUNG # ══════════════════════════════════════════════════════════════════════════════ print_summary() { echo "" | tee -a "$LOGFILE" echo -e "${BOLD}${CYAN}╔══════════════════════════════════════════════════════════════╗${NC}" | tee -a "$LOGFILE" echo -e "${BOLD}${CYAN}║ ZUSAMMENFASSUNG ║${NC}" | tee -a "$LOGFILE" echo -e "${BOLD}${CYAN}╚══════════════════════════════════════════════════════════════╝${NC}" | tee -a "$LOGFILE" echo "" | tee -a "$LOGFILE" echo -e " ${GREEN}Bereits OK:${NC} $ALREADY_OK" | tee -a "$LOGFILE" echo -e " ${GREEN}Geändert/Fixed:${NC} $CHANGES_MADE" | tee -a "$LOGFILE" echo -e " ${YELLOW}Übersprungen:${NC} $SKIPPED" | tee -a "$LOGFILE" echo -e " ${RED}Fehler:${NC} $ERRORS" | tee -a "$LOGFILE" echo "" | tee -a "$LOGFILE" echo -e " Logfile: ${BOLD}$LOGFILE${NC}" | tee -a "$LOGFILE" echo -e " Backup-Dir: ${BOLD}$BACKUP_DIR${NC}" | tee -a "$LOGFILE" echo "" | tee -a "$LOGFILE" if [[ $CHANGES_MADE -gt 0 ]]; then echo -e " ${YELLOW}WICHTIG:${NC}" | tee -a "$LOGFILE" echo -e " - SSH-Verbindung in separater Session testen bevor Logout" | tee -a "$LOGFILE" echo -e " - Reboot empfohlen für Kernel-Modul- und sysctl-Änderungen" | tee -a "$LOGFILE" echo -e " - Rollback: Backup-Verzeichnis $BACKUP_DIR" | tee -a "$LOGFILE" fi if $DRY_RUN; then echo "" | tee -a "$LOGFILE" echo -e " ${YELLOW}=== DRY-RUN: Es wurden KEINE Änderungen vorgenommen ===${NC}" | tee -a "$LOGFILE" fi } # ══════════════════════════════════════════════════════════════════════════════ # MAIN # ══════════════════════════════════════════════════════════════════════════════ main() { preflight "${1:-}" # Sektion 1: Initial Setup harden_filesystem_modules harden_filesystem_partitions harden_package_management harden_selinux harden_bootloader harden_process_hardening harden_crypto_policy harden_banners # Sektion 2: Services harden_services harden_client_services harden_time_sync harden_cron # Sektion 3: Network harden_network_devices harden_network_modules harden_network_params # Sektion 4: Firewall harden_firewall # Sektion 5: Access Control harden_ssh harden_sudo harden_pam harden_password_policy # Sektion 6: Logging & Auditing harden_integrity harden_journald harden_auditd harden_logfile_permissions # Sektion 7: System Maintenance harden_system_files harden_user_groups print_summary } main "$@"