11 KiB
Problemstellung: Layer-Trennung (L3 vs. L7)
Layer 7 (Application Layer) – Backend
Fail2Ban arbeitet auf Layer 7:
- Analysiert: HTTP-Requests (Logs)
- Erkennt: Muster (Bots, Scans, SQL-Injection)
- Entscheidet: „Bösartig / nicht bösartig“
Dieser Prozess erfolgt auf dem Backend (clientserver), da nur dort:
- Vollständige Request-Daten vorliegen.
- Die Applikationslogik sichtbar ist.
Layer 3 (Network Layer) – Proxy
Das eigentliche Blocking erfolgt auf Layer 3/4:
- IP-Adresse wird geblockt.
- Protokollunabhängig.
- Umsetzung: Via
iptables/ipset(Kernel).
Dieser Prozess erfolgt optimal auf dem Proxy (proxyserver), da:
- Dies der erste Eintrittspunkt in das Netzwerk ist.
- Der Traffic dort noch nicht verteilt wurde.
Technisches Problem ohne Trennung
Wenn das Blocking ausschließlich auf dem Backend erfolgt:
- Paket trifft auf dem Proxy ein.
- Proxy akzeptiert die Verbindung.
- Proxy leitet den Request an das Backend weiter.
- Backend erkennt den Angriff (die Client-IP steht zwar in der Log, aber der eigentliche Verbindungspeer des Backends ist der Proxy).
- Ein lokaler Block auf dem Backend ist wirkungslos: In der Firewall des Backends ist die Client-IP nie der Quelladresse – der gesamte Traffic kommt von der Proxy-IP. Würde man stattdessen die Proxy-IP bannen, wären alle Clients gleichzeitig blockiert.
Daraus resultierende Probleme:
- Der Proxy verarbeitet weiterhin Requests des Angreifers.
- Verbindungen werden trotzdem aufgebaut.
- Last entsteht bereits vor dem eigentlichen Block.
Konsequenz: Die Layer 7 Entscheidung erfolgt zu spät im Netzwerkpfad.
Lösung: Layer-Trennung
Architektur
- Layer 7 (Erkennung): Backend
- Layer 3 (Blockierung): Proxy
Ablauf mit Proxy-Blocking
- Angreifer sendet Request.
- Proxy leitet diesen initial weiter.
- Backend erkennt den Angriff via Fail2Ban.
- Backend meldet die IP an den Proxy.
- Proxy trägt die IP in die Firewall ein.
- Alle weiteren Pakete werden bereits am Proxy verworfen.
Effekt
- Kein Durchreichen mehr zum Backend.
- Keine HTTP-Verarbeitung für geblockte IPs.
- Minimierung der Applikationslast.
- Zentrale Kontrolle am Gateway.
Technischer Vergleich
| Ebene | Aufgabe | Ort |
|---|---|---|
| Layer 7 | Analyse (Regex, Logs) | Backend |
| Layer 3/4 | Blocking (IP) | Proxy |
Notwendigkeit der Trennung
- Fail2Ban benötigt Kontext (L7), der nur im Backend verfügbar ist.
- Firewall benötigt Performance (L3), was nur am Einstiegspunkt effizient ist.
Diese Kombination liefert:
- Hohe Erkennungsqualität.
- Minimale Systemlast.
Fazit
Blocking auf dem Backend ist funktional, aber ineffizient. Blocking auf dem Proxy ist effizient, benötigt jedoch eine externe Entscheidungslogik.
Die Lösung: Erkennung auf Layer 7 (Backend) und Durchsetzung auf Layer 3 (Proxy).
Fail2Ban: Proxy Forwarding via rsyslog (Client → Proxy)
Diese Dokumentation beschreibt die aktuelle Implementation (Stand: Okt 2026).
Die frühere Lösung – pro Ban eine SSH-Verbindung vom Client zum Proxy mit
fail2ban-client set recidive banip <ip> – wurde verworfen:
- SSH-Storms beim fail2ban-Neustart: Beim Restart feuern alle Jails ihre „Restore Ban“-Listen nach → dutzende SSH-Sessionen in wenigen Sekunden.
- Lockout-Risiko: Der Proxy bannt die IP auf allen Ports (inkl. 22) → der SSH-Transport blockiert sich selbst.
- Skalierung: Bei mehreren Backend-VMs summiert sich der SSH-Aufwand.
Die neue Lösung nutzt keinen SSH-Zugriff mehr: Ban-/Unban-Ereignisse werden aus dem fail2ban-Log per rsyslog (UDP 514) an den Proxy gesendet, wo ein einzelnes Jail die Regeln in die Firewall schreibt.
1. Architektur
clientserver (L7 – Erkennung) proxyserver (L3 – Enforcement)
───────────────────────────── ──────────────────────────────
fail2ban-Jails (Apache, sshd, ...)
→ /var/log/fail2ban.log
→ rsyslog imfile
→ UDP 514 ──────────────────────────────────► rsyslog imudp
→ /var/log/f2bexport.log
→ fail2ban-Jail „remote-bans“
→ iptables/ipset DROP (allports)
Unban-Zeilen:
f2b-unban.sh (systemd-Service, tail auf
/var/log/f2bexport.log)
→ fail2ban-client set remote-bans unbanip <ip>
Merkmale:
- Kein SSH, keine Verbindung pro Ban – nur Log-Forwarding.
- Ein einziges Jail auf dem Proxy, egal wieviele Jails/VMs dahinter liegen.
banaction = …-allports→ der Proxy selbst ist auf allen Ports geschützt (inkl. SSH), ohne dass normale Nutzer tangiert werden.- Unban-Ereignisse werden gespiegelt, die Ban-Liste bleibt synchron.
2. Backend (clientserver / Apache)
2.1 mod_remoteip (echte Client-IP in den Logs)
Ohne mod_remoteip loggt Apache die Proxy-IP als Requester → fail2ban würde
die Proxy-IP bannen → der Proxy bannt sich selbst (alle Upstreams down →
504/502). Deshalb muss Apache die Client-IP aus X-Forwarded-For nehmen:
Datei: /etc/apache2/conf-available/remoteip.conf (siehe remoteip.conf
in diesem Repo)
RemoteIPHeader X-Forwarded-For
# Vertrauen zu Proxy (Public IPv6 & internes IPv4)
RemoteIPInternalProxy 2a01:4f8:130:516e::160
RemoteIPInternalProxy 10.8.1.1
# HTTPS-Status für PHP (verhindert Redirect-Loops)
SetEnvIf X-Forwarded-Proto "https" HTTPS=on
a2enmod remoteip
a2enconf remoteip
systemctl restart apache2
Check: In der Apache-Accesslog muss die echte Client-IP stehen, nicht die Proxy-IP.
2.2 fail2ban
- Normale Jails, keine Custom-Actions nötig.
- Die
NOTICE … Ban <ip>/NOTICE … Unban <ip>-Zeilen in/var/log/fail2ban.logsind die einzige Schnittstelle zum Proxy. - Alte „forward“-Actions (SSH-basiert) entfernen oder auf
/bin/trueentschärfen.
2.3 rsyslog-Forwarding
Datei: /etc/rsyslog.d/40-to-proxy.conf
module(load="imfile")
input(type="imfile" File="/var/log/fail2ban.log" Tag="f2bexport")
if $programname == "f2bexport" then {
action(type="omfwd" target="2a01:4f8:130:516e::160" port="514" protocol="udp")
stop
}
systemctl restart rsyslog
Hinweis: Beim ersten Start liest imfile die Datei komplett von Anfang →
die gesamte Ban-Historie wird einmalig gesendet → der Proxy baut damit sofort
den vollständigen Spiegel der aktuellen Ban-Liste auf.
3. Proxy (proxyserver)
3.1 rsyslog-Empfang
Datei: /etc/rsyslog.d/40-f2bexport.conf
module(load="imudp")
input(type="imudp" port="514")
if $programname == "f2bexport" then {
action(type="omfile" file="/var/log/f2bexport.log")
stop
}
systemctl restart rsyslog
Test: Vom Client logger -n <proxy-ip> -P 514 -t fail2ban "TEST" → Zeile muss
in /var/log/f2bexport.log landen.
3.2 Firewall (Proxmox)
UDP 514 (v4 + v6) von der Client-VM zum Proxy erlauben.
3.3 fail2ban-Jail „remote-bans“
Achtung (fail2ban ≥ 1.0): Die Host-Gruppe im Regex heißt
fidbzw. man verwendet den Platzhalter<HOST>.(?P<ip>…)wird in 1.0.x abgelehnt („No failure-id group“).
Filter: /etc/fail2ban/filter.d/remote-bans.conf
[Definition]
failregex = ^.*fail2ban\.actions\s+\[\d+\]:\s+NOTICE\s+\[[^\]]+\] (?:Restore )?Ban <HOST>\s*$
ignoreregex = ^.*fail2ban\.actions\s+\[\d+\]:\s+NOTICE\s+\[[^\]]+\] Unban <HOST>\s*$
failregexerfasstBanundRestore Ban(Neustart des Client).- Unban-Zeilen werden vom Jail ignoriert (fail2ban hat kein
unbanregex); das eigentliche Unbanning erledigt der Watcher aus Abschnitt 4.
Jail: /etc/fail2ban/jail.d/remote-bans.local
[remote-bans]
enabled = true
backend = polling
logpath = /var/log/f2bexport.log
maxretry = 1
findtime = 86400
bantime = 604800
banaction = iptables-ipset-proto6-allports
ignoreip = 127.0.0.1/8 ::1 10.8.0.0/16 46.4.5.60 2a01:4f8:130:516e::160 2a01:4f8:130:516e::55 88.133.6.20 2a05:fa40:17a:4b00::/64
bantime= das Maximum der Bantimes der Client-Jails (7 Tage). Früheres Unbanning auf dem Client kommt per Unban-Zeile trotzdem an.ignoreipmuss mindestens enthalten: die eigene Proxy-IP (v4+v6), die Client-/Proxy-IPs im internen Netz und die Admin-IPs – sonst bannt sich der Proxy selbst und alle Upstream-Verbindungen brechen ab (Symptom: 504/502 auf allen proxiedomains).
4. Unban-Watcher (Proxy)
fail2ban kennt kein unbanregex → ein kleines systemd-Service tailt das
Export-Log und unbant via fail2ban-client:
/usr/local/bin/f2b-unban.sh:
#!/usr/bin/env bash
set -u
LOG=/var/log/f2bexport.log
touch "$LOG"
tail -n0 -F "$LOG" | while IFS= read -r line; do
[[ "$line" == *"Unban "* ]] || continue
ip="${line##*Unban }"
ip="${ip%%[[:space:]]*}"
[[ -n "$ip" ]] || continue
echo "unban: $ip"
fail2ban-client set remote-bans unbanip "$ip" 2>/dev/null || true
done
chmod +x /usr/local/bin/f2b-unban.sh
/etc/systemd/system/f2b-unban.service:
[Unit]
Description=fail2ban remote unban watcher
After=network.target fail2ban.service
[Service]
ExecStart=/usr/local/bin/f2b-unban.sh
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable --now f2b-unban
5. Funktionstest
# Client:
fail2ban-client set sshd banip 1.2.3.4
# Proxy (nach wenigen Sekunden):
fail2ban-client status remote-bans # → 1.2.3.4 in der Banned-Liste
# Client:
fail2ban-client set sshd unbanip 1.2.3.4
# Proxy:
fail2ban-client status remote-bans # → IP wieder weg
journalctl -u f2b-unban --no-pager | tail # → "unban: 1.2.3.4"
6. Skalierung auf weitere VMs
Pro zusätzlicher Backend-VM:
- Backend:
mod_remoteip(2.1) + rsyslog-Forward (2.3, Ziel = Proxy-IP). - Proxy: nichts – das bestehende
remote-bans-Jail sammelt alle Ban-/Unban-Zeilen aller Clients in/var/log/f2bexport.log.
Ein Jail, ein Log, ein Watcher – egal wieviele VMs melden.
7. Fehlerbilder / Lessons Learned
| Symptom | Ursache |
|---|---|
| 504/502 auf allen Proxiedomains | Proxy hat sich selbst gebannt (eigene IP in remote-bans, allports) → Upstream-Timeouts. Abhilfe: Proxy-IP in ignoreip. |
| SSH zum Proxy abgestürzt nach fail2ban-Start | Alter Mechanismus: „Restore Ban“-SSH-Storm + allports-Ban des SSH-Transports. Wird durch die rsyslog-Lösung beseitigt. |
No failure-id group in '…' |
fail2ban ≥ 1.0: (?P<ip>) ungültig → <HOST> (bzw. (?P<fid>…)). |
interpolation key 'jailname' not valid |
fail2ban 1.0.2 erlaubt in Actions nicht alle Platzhalter (u. a. kein jailname). |
Jail findet 0 Treffer, fail2ban-regex matcht |
Jail liest ab Dateiende (nur neue Zeilen); fail2ban-regex testet die ganze Datei. |
| Bans erscheinen nicht | UDP 514 blockiert (PVE-Firewall) oder programname-Filter passt nicht (f2bexport vs. fail2ban). |