diff --git a/INSTALL.md b/INSTALL.md index 6199535..73e3562 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -77,6 +77,19 @@ a2enconf remoteip systemctl restart apache2 ``` +### 3.2 Fail2Ban-Forwarding (Client → Proxy) + +Die zentrale Ban-Weiterleitung vom Client an den Proxy ist in **README-Fail2ban.md** +dokumentiert (rsyslog UDP 514, ein einziges `remote-bans`-Jail auf dem Proxy, **kein SSH**). + +Zusätzlich zu 3.1 (mod_remoteip) muss auf dem Client laufen: +* rsyslog-Forwarding von `/var/log/fail2ban.log` an den Proxy (UDP 514, `imfile`), +* fail2ban mit normalen Jails (keine Custom-Actions). + +Auf dem Proxy: rsyslog-Empfang (`imudp`) nach `/var/log/f2bexport.log`, +fail2ban-Jail `remote-bans` (allports-Ban, `ignoreip` inkl. eigener Proxy-IP!) und +der Unban-Watcher (`f2b-unban.service`). + --- ## 4. ISPConfig Integration (Apache Direktiven Beispiel Gitea) diff --git a/README-Fail2ban.md b/README-Fail2ban.md index 1be14ea..39992e5 100644 --- a/README-Fail2ban.md +++ b/README-Fail2ban.md @@ -18,7 +18,7 @@ Das eigentliche Blocking erfolgt auf Layer 3/4: * **IP-Adresse** wird geblockt. * **Protokollunabhängig.** -* **Umsetzung:** Via `nftables` (Kernel). +* **Umsetzung:** Via `iptables`/`ipset` (Kernel). Dieser Prozess erfolgt optimal auf dem Proxy (**proxyserver**), da: * Dies der erste Eintrittspunkt in das Netzwerk ist. @@ -32,8 +32,12 @@ 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. -5. Backend blockt die IP lokal. +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. @@ -55,7 +59,7 @@ Wenn das Blocking ausschließlich auf dem Backend erfolgt: 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 `nftables` ein. +5. Proxy trägt die IP in die Firewall ein. 6. Alle weiteren Pakete werden bereits am Proxy verworfen. ### Effekt @@ -89,60 +93,246 @@ Blocking auf dem Backend ist funktional, aber ineffizient. Blocking auf dem Prox **Die Lösung:** Erkennung auf Layer 7 (Backend) und Durchsetzung auf Layer 3 (Proxy). --- -# Fail2Ban: Proxy Forwarding Configuration (Client) -Diese Dokumentation beschreibt die Konfiguration des Backend-Clients (**clientserver**), um erkannte Angriffe auf Layer 7 an den Proxy (**proxyserver**) weiterzureichen, damit diese dort auf Layer 3 blockiert werden können. +# Fail2Ban: Proxy Forwarding via rsyslog (Client → Proxy) -## 1. Voraussetzungen +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 ` – wurde verworfen: -* **SSH-Zugriff:** Vom Client (clientserver) zum Proxy muss eine Verbindung bestehen. -* **SSH-Key Authentifizierung:** Muss passwortlos (Key-basiert) eingerichtet sein. -* **SSH-Port:** Muss in der SSH-Config des Root-Users hinterlegt sein. -* **Fail2Ban:** Muss auf beiden Systemen installiert und aktiv sein. +* **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. -## 2. SSH Konfiguration -Konfiguration für den schnellen, passwortlosen Zugriff auf den Proxy. +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. -**Datei:** `/root/.ssh/config` -```ssh -Host proxy - HostName proxy.myserver.com - User root - Port 22 - IdentityFile /root/.ssh/clientserverproxy - IdentitiesOnly yes +## 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 +``` + +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. --- -# Fail2Ban: Proxy-Setup (Zentraler Bann über Reverse Proxy) +## 2. Backend (clientserver / Apache) -## Ziel -Implementierung einer zentralen IP-Blockierung am Reverse Proxy, während die Angriffserkennung dezentral auf den Backend-Systemen erfolgt. +### 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: -### Architektur -* **Backend (Client):** Erkennt Angriffe mittels Fail2Ban-Filtern (L7). -* **Proxy (Enforcement):** Setzt Firewall-Regeln via `nftables` um (L3). -* **Kommunikation:** Erfolgt über den Befehl `fail2ban-client set recidive banip `. +**Datei:** `/etc/apache2/conf-available/remoteip.conf` (siehe `remoteip.conf` +in diesem Repo) +```apache +RemoteIPHeader X-Forwarded-For ---- +# Vertrauen zu Proxy (Public IPv6 & internes IPv4) +RemoteIPInternalProxy 2a01:4f8:130:516e::160 +RemoteIPInternalProxy 10.8.1.1 -## Problemstellung -Beim Einsatz eines Reverse Proxys ergeben sich folgende Hürden: -* **Logs:** Enthalten zwar die Client-IP (via `X-Forwarded-For`), aber... -* **Firewall:** Die Backend-Firewall sieht nur die IP des Proxys. - -**Ergebnis:** Fail2Ban erkennt den Angriff zwar korrekt, ein lokaler Block auf dem Backend bleibt jedoch wirkungslos, da der Traffic weiterhin über den Proxy fließen kann. - ---- - -## Lösung -Konsequente Trennung von **Detection** (Backend) und **Enforcement** (Proxy). - ---- - -## Proxy-Konfiguration - -### 1. Installation +# HTTPS-Status für PHP (verhindert Redirect-Loops) +SetEnvIf X-Forwarded-Proto "https" HTTPS=on +``` ```bash -apt install fail2ban +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 ` / `NOTICE … Unban `-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 +} +``` +```bash +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 +} +``` +```bash +systemctl restart rsyslog +``` +Test: Vom Client `logger -n -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 ``. +> `(?P…)` 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 \s*$ +ignoreregex = ^.*fail2ban\.actions\s+\[\d+\]:\s+NOTICE\s+\[[^\]]+\] Unban \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`:** +```bash +#!/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 +``` +```bash +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 +``` +```bash +systemctl daemon-reload +systemctl enable --now f2b-unban +``` + +--- + +## 5. Funktionstest + +```bash +# 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)` ungültig → `` (bzw. `(?P…)`). | +| `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`). |