#!/bin/bash # SPDX-Version: 3.0 # SPDX-CreationInfo: 2025-06-17; WEIDNER, Marc S.; # SPDX-ExternalRef: GIT https://git.coresecret.dev/msw/CISS.debian.installer.git # SPDX-FileContributor: WEIDNER, Marc S.; Centurion Intelligence Consulting Agency # SPDX-FileCopyrightText: 2024-2025; WEIDNER, Marc S.; # SPDX-FileType: SOURCE # SPDX-License-Identifier: EUPL-1.2 OR LicenseRef-CCLA-1.0 # SPDX-LicenseComment: This file is part of the CISS.debian.installer.secure framework. # SPDX-PackageName: CISS.debian.installer # SPDX-Security-Contact: security@coresecret.eu guard_sourcing ####################################### # Hardening 'fail2ban'. # Globals: # ARY_ALLOW_IPV4 # ARY_ALLOW_IPV6 # TARGET # VAR_FINAL_FQDN # VAR_FINAL_IPV4 # VAR_FINAL_IPV6 # VAR_LINK_IPV6 # VAR_PROVIDER # VAR_SSH_PORT # Arguments: # None # Returns: # 0: on success ####################################### hardening_fail2ban() { ### Declare Arrays, HashMaps, and Variables. declare -r var_logfile="/root/.ciss/cdi/log/4420_hardening_fail2ban.log" chroot_logger "${TARGET}${var_logfile}" mkdir -p "${TARGET}/root/.ciss/cdi/backup/etc/fail2ban/jail.d" cp "${TARGET}/etc/fail2ban/fail2ban.conf" "${TARGET}/root/.ciss/cdi/backup/etc/fail2ban/fail2ban.conf.bak" mv "${TARGET}/etc/fail2ban/jail.d/defaults-debian.conf" "${TARGET}/root/.ciss/cdi/backup/etc/fail2ban/jail.d/defaults-debian.conf.bak" # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1024305 insert_header "${TARGET}/etc/fail2ban/fail2ban.local" insert_comments "${TARGET}/etc/fail2ban/fail2ban.local" cat << 'EOF' >> "${TARGET}/etc/fail2ban/fail2ban.local" [DEFAULT] allowipv6 = auto EOF insert_header "${TARGET}/etc/fail2ban/jail.d/ciss-default.conf" insert_comments "${TARGET}/etc/fail2ban/jail.d/ciss-default.conf" if [[ "${#ARY_ALLOW_IPV4[@]}" -gt 0 ]]; then ### fail2ban ufw aggressive mode, one attempt for jumphost configuration. cat << EOF >> "${TARGET}/etc/fail2ban/jail.d/ciss-default.conf" [DEFAULT] usedns = yes ignoreip = 127.0.0.0/8 ::1 # ${VAR_FINAL_FQDN} ${VAR_FINAL_IPV4} EOF if [[ "${VAR_LINK_IPV6}" == "true" ]]; then cat << EOF >> "${TARGET}/etc/fail2ban/jail.d/ciss-default.conf" ${VAR_FINAL_IPV6}/64 EOF fi cat << EOF >> "${TARGET}/etc/fail2ban/jail.d/ciss-default.conf" # Jumphost ${ARY_ALLOW_IPV4[*]} EOF if [[ "${VAR_LINK_IPV6}" == "true" ]]; then cat << EOF >> "${TARGET}/etc/fail2ban/jail.d/ciss-default.conf" ${ARY_ALLOW_IPV6[*]} EOF fi cat << EOF >> "${TARGET}/etc/fail2ban/jail.d/ciss-default.conf" maxretry = 8 findtime = 24h bantime = 24h [sshd] enabled = true backend = systemd filter = sshd mode = normal port = ${VAR_SSH_PORT} protocol = tcp logpath = /var/log/auth.log maxretry = 3 findtime = 24h bantime = 24h # # ufw aggressive approach: # Any valid client communicating with our server should be going directly to the service ports opened in ufw (ssh, 80, ...). # Any client touching other ports is treated as malicious and therefore should be blocked access to ALL ports after 1 attempt. # [ufw] enabled = true filter = ciss.ufw action = iptables-allports logpath = /var/log/ufw.log maxretry = 1 findtime = 24h bantime = 24h protocol = tcp,udp # vim: number et ts=2 sw=2 sts=2 ai tw=128 ft=conf EOF else ### fail2ban ufw aggressive mode, 32 attempts for NO jumphost configuration. cat << EOF >> "${TARGET}/etc/fail2ban/jail.d/ciss-default.conf" [DEFAULT] usedns = yes ignoreip = 127.0.0.0/8 ::1 # ${VAR_FINAL_FQDN} ${VAR_FINAL_IPV4} EOF if [[ "${VAR_LINK_IPV6}" == "true" ]]; then cat << EOF >> "${TARGET}/etc/fail2ban/jail.d/ciss-default.conf" ${VAR_FINAL_IPV6}/64 EOF fi cat << EOF >> "${TARGET}/etc/fail2ban/jail.d/ciss-default.conf" maxretry = 8 findtime = 24h bantime = 24h [sshd] enabled = true backend = systemd filter = sshd mode = normal port = ${VAR_SSH_PORT} protocol = tcp logpath = /var/log/auth.log maxretry = 3 findtime = 24h bantime = 24h # # ufw aggressive approach: # Any valid client communicating with our server should be going directly to the service ports opened in ufw (ssh, 80, ...). # Any client touching other ports is treated as malicious and therefore should be blocked access to ALL ports after 8 attempts. # [ufw] enabled = true filter = ciss.ufw action = iptables-allports logpath = /var/log/ufw.log maxretry = 8 findtime = 24h bantime = 24h protocol = tcp,udp # vim: number et ts=2 sw=2 sts=2 ai tw=128 ft=conf EOF fi ### Provider Hetzner needs special ignoreip rules. if [[ "${VAR_PROVIDER}" == "hetzner" ]]; then sed -i '0,/^maxretry/{s/^maxretry/# Hetzner Intern\n 172.31.1.1\/16\n&/}' "${TARGET}/etc/fail2ban/jail.d/ciss-default.conf" fi insert_header "${TARGET}/etc/fail2ban/filter.d/ciss.ufw.conf" insert_comments "${TARGET}/etc/fail2ban/filter.d/ciss.ufw.conf" cat << EOF >> "${TARGET}/etc/fail2ban/filter.d/ciss.ufw.conf" [Definition] failregex = ^.*UFW BLOCK.* SRC= .*DPT=\d+ .* ignoreregex = EOF # Hardening of fail2ban systemd: https://wiki.archlinux.org/title/fail2ban#Service_hardening # The 'CapabilityBoundingSet' parameters 'CAP_DAC_READ_SEARCH' will allow fail2ban full read access to every directory and # file. "CAP_NET_ADMIN" and "CAP_NET_RAW" allow fail2ban to operate on any firewall that has a command-line shell interface. # By using 'ProtectSystem=strict' the filesystem hierarchy will only be read-only; 'ReadWritePaths' allows Fail2ban to have # write access on required paths. mkdir -p "${TARGET}/etc/systemd/system/fail2ban.service.d" mkdir -p "${TARGET}/var/log/fail2ban" insert_header "${TARGET}/etc/systemd/system/fail2ban.service.d/override.conf" insert_comments "${TARGET}/etc/systemd/system/fail2ban.service.d/override.conf" cat << EOF >> "${TARGET}/etc/systemd/system/fail2ban.service.d/override.conf" [Service] PrivateDevices=yes PrivateTmp=yes ProtectHome=read-only ProtectSystem=strict ReadWritePaths=-/var/run/fail2ban ReadWritePaths=-/var/lib/fail2ban ReadWritePaths=-/var/log/fail2ban ReadWritePaths=-/var/spool/postfix/maildrop ReadWritePaths=-/run/xtables.lock CapabilityBoundingSet=CAP_AUDIT_READ CAP_DAC_READ_SEARCH CAP_NET_ADMIN CAP_NET_RAW ProtectClock=true ProtectHostname=true EOF cat << 'EOF' >> "${TARGET}/etc/fail2ban/fail2ban.local" [Definition] logtarget = /var/log/fail2ban/fail2ban.log # vim: number et ts=2 sw=2 sts=2 ai tw=128 ft=conf EOF ### Logrotate must be updated too. mkdir -p "${TARGET}/root/.ciss/cdi/backup/etc/logrotate.d" cp "${TARGET}/etc/logrotate.d/fail2ban" "${TARGET}/root/.ciss/cdi/backup/etc/logrotate.d/fail2ban.bak" sed -i 's/\/var\/log\/fail2ban.log/\/var\/log\/fail2ban\/fail2ban.log/1' "${TARGET}/etc/logrotate.d/fail2ban" touch "${TARGET}/var/log/fail2ban/fail2ban.log" chmod 640 "${TARGET}/var/log/fail2ban/fail2ban.log" if [[ ! -f "${TARGET}/var/log/ufw.log" ]]; then install -d -m 0755 "${TARGET}/var/log" : >| "${TARGET}/var/log/ufw.log" chmod 0640 "${TARGET}/var/log/ufw.log" fi ### Merge / Dump-Parse via 'fail2ban-client -d'. All '*.conf', '*.local', and 'jail.*'-files are read, inherited, and merged. ### Syntax, path, and key errors result in a non-zero exit. chroot_script "${TARGET}" " fail2ban-client -d >> ${var_logfile} && echo "OK: config parsed" >> ${var_logfile} || echo "ERROR: config invalid" >> ${var_logfile} " guard_dir && return 0 } ### Prevents accidental 'unset -f'. # shellcheck disable=SC2034 readonly -f hardening_fail2ban # vim: number et ts=2 sw=2 sts=2 ai tw=128 ft=sh