52 KiB
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 ; 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 <user>"
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 "$@"