#!/bin/bash # Firewall - v2.0 (2025) # Debian 13 / iptables / fail2ban / OpenVPN / Mosquitto / Nginx # ─── CONFIGURATION ──────────────────────────────────────────────────────────── ETH_IF="${ETH_IF:-eth0}" # valeur réelle fournie par .env (EnvironmentFile=, voir firewall.service) ; eth0 = filet de sécurité si absent VPN_IF="${VPN_IF:-tun0}" WG_IF="${WG_IF:-wg0}" LAN_NET="${LAN_NET:-}" # voir section ANTI-SPOOF plus bas FILTERS="/etc/firewall/firewall-filters.conf" BAN_FILE="/etc/firewall/ban_ip.txt" SYSCTL_FILE="/etc/sysctl.d/90-firewall.conf" LOG_LIMIT="5/min" LOG_BURST=10 IPT="iptables" IP6T="ip6tables" # ────────────────────────────────────────────────────────────────────────────── fw_sysctl() { cat > "$SYSCTL_FILE" << 'EOF' # Anti-spoofing (Reverse Path Filtering) net.ipv4.conf.all.rp_filter = 1 net.ipv4.conf.default.rp_filter = 1 # ICMP net.ipv4.icmp_echo_ignore_all = 0 net.ipv4.icmp_echo_ignore_broadcasts = 1 net.ipv4.icmp_ignore_bogus_error_responses = 1 # SYN cookies (protection SYN flood) net.ipv4.tcp_syncookies = 1 # Pas de redirections ICMP ni de source routing net.ipv4.conf.all.accept_redirects = 0 net.ipv4.conf.all.send_redirects = 0 net.ipv4.conf.all.accept_source_route = 0 net.ipv4.conf.all.log_martians = 1 # Forwarding activé pour OpenVPN net.ipv4.ip_forward = 1 # Timeouts anti-DoS (sans casser les perfs modernes) net.ipv4.tcp_fin_timeout = 15 net.ipv4.tcp_keepalive_time = 1800 # NE PAS désactiver window_scaling ni sack : indispensables sur réseaux modernes net.ipv4.tcp_window_scaling = 1 net.ipv4.tcp_sack = 1 net.ipv4.ip_default_ttl = 64 # IPv6 — désactivé (adapter si utilisé) net.ipv6.conf.all.disable_ipv6 = 1 net.ipv6.conf.default.disable_ipv6 = 1 EOF sysctl -p "$SYSCTL_FILE" -q echo " [sysctl appliqué depuis $SYSCTL_FILE]" } fw_sysctl_restore() { cat > "$SYSCTL_FILE" << 'EOF' net.ipv4.conf.all.rp_filter = 0 net.ipv4.conf.default.rp_filter = 0 net.ipv4.ip_forward = 0 net.ipv4.tcp_fin_timeout = 60 net.ipv4.tcp_keepalive_time = 7200 net.ipv4.tcp_window_scaling = 1 net.ipv4.tcp_sack = 1 net.ipv4.ip_default_ttl = 64 net.ipv6.conf.all.disable_ipv6 = 0 net.ipv6.conf.default.disable_ipv6 = 0 EOF sysctl -p "$SYSCTL_FILE" -q } case "$1" in start) echo " [sysctl]" fw_sysctl echo " [FLUSH]" $IPT -F $IPT -X $IPT -t nat -F $IPT -t nat -X $IPT -t mangle -F $IPT -t mangle -X echo " [POLITIQUES PAR DEFAUT : DROP]" $IPT -P INPUT DROP $IPT -P OUTPUT DROP $IPT -P FORWARD DROP # ─── CHAÎNES DE LOGGING ─────────────────────────────────────────────────── echo " [CHAÎNES LOG]" $IPT -N LOG_DROP $IPT -A LOG_DROP -m limit --limit $LOG_LIMIT --limit-burst $LOG_BURST \ -j LOG --log-prefix '[FW DROP] ' --log-level warning $IPT -A LOG_DROP -j DROP $IPT -N LOG_INVALID $IPT -A LOG_INVALID -m limit --limit $LOG_LIMIT --limit-burst $LOG_BURST \ -j LOG --log-prefix '[FW INVALID] ' --log-level warning $IPT -A LOG_INVALID -j DROP $IPT -N LOG_SPOOF $IPT -A LOG_SPOOF -m limit --limit $LOG_LIMIT --limit-burst $LOG_BURST \ -j LOG --log-prefix '[FW SPOOF] ' --log-level warning $IPT -A LOG_SPOOF -j DROP $IPT -N LOG_SCAN $IPT -A LOG_SCAN -m limit --limit $LOG_LIMIT --limit-burst $LOG_BURST \ -j LOG --log-prefix '[FW SCAN] ' --log-level warning $IPT -A LOG_SCAN -j DROP $IPT -N LOG_FLOOD $IPT -A LOG_FLOOD -m limit --limit $LOG_LIMIT --limit-burst $LOG_BURST \ -j LOG --log-prefix '[FW FLOOD] ' --log-level warning $IPT -A LOG_FLOOD -j DROP # ─── FAIL2BAN ───────────────────────────────────────────────────────────── # Pré-création des chaînes fail2ban pour éviter les erreurs si fw démarre # avant fail2ban. fail2ban les vide et les recrée à son démarrage. echo " [FAIL2BAN : pré-création des chaînes]" for chain in fail2ban-INPUT fail2ban-sshd fail2ban-nginx fail2ban-mosquitto; do $IPT -N $chain 2>/dev/null || true $IPT -F $chain 2>/dev/null || true $IPT -A $chain -j RETURN done $IPT -I INPUT -j fail2ban-INPUT # ─── LOOPBACK ───────────────────────────────────────────────────────────── echo " [LOOPBACK]" $IPT -A INPUT -i lo -j ACCEPT $IPT -A OUTPUT -o lo -j ACCEPT # Anti-spoof loopback depuis l'extérieur $IPT -A INPUT ! -i lo -s 127.0.0.0/8 -j LOG_SPOOF # ─── PAQUETS INVALIDES ──────────────────────────────────────────────────── echo " [PAQUETS INVALIDES]" $IPT -A INPUT -m conntrack --ctstate INVALID -j LOG_INVALID $IPT -A FORWARD -m conntrack --ctstate INVALID -j DROP $IPT -A OUTPUT -m conntrack --ctstate INVALID -j DROP # ─── CONNEXIONS ÉTABLIES ────────────────────────────────────────────────── echo " [CONNEXIONS ETABLIES/LIEES]" $IPT -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT $IPT -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT # ─── ICMP ───────────────────────────────────────────────────────────────── echo " [ICMP]" # Ping entrant : limité par source (hashlimit > limit : par IP) $IPT -A INPUT -p icmp --icmp-type echo-request \ -m hashlimit --hashlimit-name icmp-in --hashlimit-upto 5/s \ --hashlimit-burst 10 --hashlimit-mode srcip -j ACCEPT # Types ICMP utiles en entrée $IPT -A INPUT -p icmp --icmp-type echo-reply -j ACCEPT $IPT -A INPUT -p icmp --icmp-type destination-unreachable -j ACCEPT $IPT -A INPUT -p icmp --icmp-type time-exceeded -j ACCEPT $IPT -A OUTPUT -p icmp -j ACCEPT # ─── ANTI-SPOOF (martians) ──────────────────────────────────────────────── # Écrit pour une IP publique dédiée (VPS) : toute IP source privée vue # sur $ETH_IF y est forcément usurpée. Sur un noeud derrière une box # résidentielle, $ETH_IF est aussi l'unique interface LAN — du trafic # local légitime (192.168.x.x, parfois l'IP de la box elle-même via # son NAT loopback/hairpin) y arrive normalement et ne doit pas être # traité comme du spoofing. LAN_NET (vide par défaut, donc aucun # changement de comportement pour un VPS) exempte ce sous-réseau # avant la boucle ci-dessous — à ne définir que sur un noeud dans # cette situation (ex. LAN_NET=192.168.1.0/24 dans .env). if [ -n "$LAN_NET" ]; then echo " [ANTI-SPOOF : LAN_NET=$LAN_NET exempté (trafic local légitime)]" $IPT -A INPUT -i "$ETH_IF" -s "$LAN_NET" -j ACCEPT fi echo " [ANTI-SPOOF / MARTIANS]" for net in 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 \ 169.254.0.0/16 224.0.0.0/4 240.0.0.0/5 \ 0.0.0.0/8 127.0.0.0/8; do $IPT -A INPUT -i "$ETH_IF" -s $net -j LOG_SPOOF done # ─── FLAGS TCP INVALIDES ────────────────────────────────────────────────── echo " [FLAGS TCP INVALIDES]" $IPT -A INPUT -p tcp --tcp-flags ALL NONE -j LOG_SCAN # NULL scan $IPT -A INPUT -p tcp --tcp-flags ALL ALL -j LOG_SCAN # XMAS scan $IPT -A INPUT -p tcp --tcp-flags SYN,FIN SYN,FIN -j LOG_SCAN $IPT -A INPUT -p tcp --tcp-flags SYN,RST SYN,RST -j LOG_SCAN $IPT -A INPUT -p tcp --tcp-flags FIN,RST FIN,RST -j LOG_SCAN $IPT -A INPUT -p tcp --tcp-flags ACK,FIN FIN -j LOG_SCAN $IPT -A INPUT -p tcp --tcp-flags ACK,PSH PSH -j LOG_SCAN $IPT -A INPUT -p tcp --tcp-flags ACK,URG URG -j LOG_SCAN # ─── SYN FLOOD ──────────────────────────────────────────────────────────── echo " [SYN FLOOD]" $IPT -N syn-flood $IPT -A INPUT -i "$ETH_IF" -p tcp --syn -j syn-flood $IPT -A syn-flood -m limit --limit 25/s --limit-burst 50 -j RETURN $IPT -A syn-flood -j LOG_FLOOD # ─── FRAGMENTS ──────────────────────────────────────────────────────────── echo " [FRAGMENTS]" $IPT -A INPUT -i "$ETH_IF" -f -j LOG_DROP # ─── PORTS BRUITÉS : drop silencieux (évite de saturer les logs) ────────── echo " [DROP SILENCIEUX : NetBIOS / MSSQL / RPC / BitTorrent]" for port in 135 137 138 139 445 1433 1025 1026 1027; do $IPT -A INPUT -i "$ETH_IF" -p tcp --dport $port -j DROP $IPT -A INPUT -i "$ETH_IF" -p udp --dport $port -j DROP done $IPT -A INPUT -i "$ETH_IF" -p udp --dport 1434 -j DROP $IPT -A INPUT -i "$ETH_IF" -p tcp --dport 6881:6889 -j DROP $IPT -A INPUT -i "$ETH_IF" -p udp --dport 6881:6889 -j DROP # ─── RÈGLES SERVICES ────────────────────────────────────────────────────── echo " [FILTRAGE NET VERS SERVEUR]" # shellcheck disable=SC1090 # chemin résolu au déploiement (install.sh), pas statique [ -f "$FILTERS" ] && source "$FILTERS" # ─── SORTIE SERVEUR ─────────────────────────────────────────────────────── echo " [SORTIE SERVEUR VERS NET]" $IPT -A OUTPUT -o "$ETH_IF" -m conntrack --ctstate NEW,ESTABLISHED,RELATED -j ACCEPT # ─── BANS IP ────────────────────────────────────────────────────────────── if [ -f "$BAN_FILE" ]; then echo " [BANS IP]" # shellcheck disable=SC1090 # chemin résolu au déploiement (install.sh), pas statique . "$BAN_FILE" fi # ─── IPv6 ───────────────────────────────────────────────────────────────── echo " [IPv6 : blocage total]" $IP6T -F 2>/dev/null || true $IP6T -X 2>/dev/null || true $IP6T -P INPUT DROP 2>/dev/null || true $IP6T -P OUTPUT DROP 2>/dev/null || true $IP6T -P FORWARD DROP 2>/dev/null || true $IP6T -A INPUT -i lo -j ACCEPT 2>/dev/null || true $IP6T -A OUTPUT -o lo -j ACCEPT 2>/dev/null || true # ─── DROP FINAL ─────────────────────────────────────────────────────────── echo " [DROP FINAL]" $IPT -A FORWARD -j DROP $IPT -A INPUT -j LOG_DROP $IPT -A OUTPUT -j LOG_DROP echo " [FIREWALL ACTIF]" ;; status) echo "=== IPv4 filter ===" $IPT -vn -L echo "" echo "=== IPv4 nat ===" $IPT -vn -L -t nat echo "" echo "=== IPv6 ===" $IP6T -vn -L 2>/dev/null || echo "(ip6tables non disponible ou IPv6 désactivé)" ;; stop) echo " [ARRÊT FIREWALL]" for t in filter nat mangle raw; do $IPT -t $t -F 2>/dev/null $IPT -t $t -X 2>/dev/null for p in INPUT OUTPUT FORWARD PREROUTING POSTROUTING; do $IPT -t $t -P $p ACCEPT 2>/dev/null || true done done fw_sysctl_restore echo " [FIREWALL ARRÊTÉ - SYSTÈME OUVERT]" ;; reload) echo " [RELOAD]" $0 stop $0 start ;; *) echo "Usage: $0 {start|stop|reload|status}" exit 1 ;; esac exit 0