Files
IPV4-proxyserver-zu-IPV6-on…/README-Fail2ban.md
T
2026-10-20 15:28:16 +02:00

11 KiB
Raw Blame History

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:

  1. Paket trifft auf dem Proxy ein.
  2. Proxy akzeptiert die Verbindung.
  3. Proxy leitet den Request an das Backend weiter.
  4. Backend erkennt den Angriff (die Client-IP steht zwar in der Log, aber der eigentliche Verbindungspeer des Backends ist der Proxy).
  5. 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

  1. Angreifer sendet Request.
  2. Proxy leitet diesen initial weiter.
  3. Backend erkennt den Angriff via Fail2Ban.
  4. Backend meldet die IP an den Proxy.
  5. Proxy trägt die IP in die Firewall ein.
  6. 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.log sind die einzige Schnittstelle zum Proxy.
  • Alte „forward“-Actions (SSH-basiert) entfernen oder auf /bin/true entschä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 fid bzw. 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*$
  • failregex erfasst Ban und Restore 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.
  • ignoreip muss 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:

  1. Backend: mod_remoteip (2.1) + rsyslog-Forward (2.3, Ziel = Proxy-IP).
  2. 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).