Den Nginx-Proxy-Manager mit Fail2Ban absichern!

Brute-Force-Attacken stellen bei öffentlich erreichbaren Servern ein großes Ärgernis dar. Sie verbrauchen unnötig Hardware-Ressourcen und können auch noch zum Kompromittieren des Systems führen. Beides wollen wir natürlich auf alle Fälle vermeiden. Deshalb habe ich mir einmal Gedanken darüber gemacht, wie man diesen Reverse-Proxy in Form eines Docker-Containers absichern kann.

Auf dem Hostsystem läuft bereits Fail2Ban, überwacht derzeit aber nur den SSH-Dienst. Die Funktionsweise ist dabei recht einfach. Das Programm prüft die Logs anhand von Regex-Ausdrücken und sperrt bei einem Treffer die betroffenen IPs über eine Firewall-Regel aus. In meinem Fall erledigt den finalen Schritt dann die auf Debian-Systemen häufig genutzte Firewall UFW.

Doch wie kommt man an die Logs des Nginx-Proxy-Managers und was muss man bei Fail2Ban eigentlich konfigurieren? In beiden Fällen nicht besonders viel, das kann ich an dieser Stelle schon mal vorwegnehmen. Und um das Ganze so ressourcenschonend wie nur möglich zu machen, wird auch kein weiterer Container gebraucht. Lediglich ein ganz kleines bisschen Programmieren ist nötig.

Schritt 1 – Fail2Ban erfolgreich auf dem Docker-Host installieren:

Die erste Stufe ist nicht selten auch die einfachste. Einspielen lässt sich die Software mit jedem gängigen Paketmanager. Da ich dieses Tutorial nur auf einem Debian-System getestet habe, werde ich mich auf den dort genutzten App-Manager Aptidude konzentrieren. Mit Yum oder Zypper ist der Schritt aber ähnlich einfach:

apt update
apt install fail2ban

Installiert ist Fail2Ban damit schon mal erfolgreich. Doch noch arbeitet der Dienst nicht. Initialisieren und in den Autostart einfügen lässt er sich wie folgt:

systemctl enable --now fail2ban

Schritt 2 – SSH absichern ist nicht optional, sondern ein Muss:

Beginnen werden wir mit der Grundsicherung des Servers. Hier gehört ganz klar die Härtung von SSH dazu. Dafür müssen wir die Konfiguration aber etwas anpassen. Die meisten Anleitungen empfehlen dafür das Anlegen einer lokalen Konfiguration mit dem Namen jail.local. Das machen wir jetzt auch:

cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

Und weil wir natürlich nicht alles von Grund auf neu schreiben möchten, haben wir die bestehende Konfiguration geklont. Hier muss dann nicht besonders viel gemacht werden. Es genügt fast schon unter [sshd] ein enabled = true einzufügen. Allerdings solltest du Fail2Ban noch mitteilen, welche Firewall du benutzt. In meinem Fall mache ich das im gleichen Jail mit banaction = ufw.

Also Butter bei die Fische. Öffne die Datei /etc/fail2ban/jail.local einfach mit einem Texteditor deiner Wahl. Und wenn du schon mal dabei bist, dann passe doch gleich noch die Werte unter dem Punkt [Default] an. Damit du einen Ansatzpunkt hast, habe ich mal ein paar von ihnen inklusive einer Erklärung aufgelistet:

# Hier lassen sich IP-Adressen festlegen, die nicht gebannt werden:
ignoreip = 127.0.0.1/8 ::1 

# Wie lange eine IP geblockt werden soll, wird hier konfiguriert:
bantime  = 1d/1h/1m

# Über welchen Zeitraum sollen die Anmeldeversuche gezählt werden:
findtime  = 1d/1h/1m

# Wie viele Fehlversuche beim Login sind noch im Rahmen:
maxretry = 5

Schritt 3 – Die Konfiguration des Nginx Proxy Managers anpassen:

Fail2Ban-installieren

Arg viel müssen wir hier nicht machen. Ich selbst nutze für alle meine Docker-Container die Konfigurationsdatei docker-compose.yml. Diese erweitere ich nun um eine kleine Zeile. Ziel ist es, die im Container vorhanden Logs unter /data/logs lokal auf meinem Host verfügbar zu machen. Dafür habe ich einfach das hier unter dem Punkt Volumes eingefügt:

- ./logs:/data/logs 

Wechsle ich jetzt in den neuen Ordner logs, sehe ich für jeden Proxy-Host ein Error- sowie ein Access-Log. Damit mein Fail2Ban-Filter später nur eine Log-Datei durchsuchen muss, braucht es also noch ein kleines Shell-Script. Wie ich dabei vorgegangen bin, soll der Code-Block zeigen:

# Eine Datei für das Shell-Skript erstellen:
touch /usr/local/bin/npm_create_log_file.sh

# Das Recht Executable für Eigentümer, Gruppe und Welt setzen:
chmod 777 /usr/local/bin/npm_create_log_file.sh

# Die Datei mit vim öffnen, den Inhalt einkopieren und anpassen:
vim /usr/local/bin/npm_create_log_file.sh

#!/bin/bash

# Ordner auf dem Host für die NPM-Logs:
local_logs='/npm/logs'

find $local_logs -name '*error.log' | xargs cat > $local_logs/all_proxy_hosts.log

# Crontab öffnen und die folgende Zeile einkopieren:
crontab -e
*/5  *  *  *  * /usr/local/bin/npm_create_log_file.sh

Schritt 4 – Fail2Ban um ein Jail samt Filter erweitern:

Endlich kommen wir ans Eingemachte. Die Rede ist natürlich von der Erstellung des Jails und dem dazu gehörenden Filter. Letzterer funktioniert mithilfe von Regular Expressions. Kurz ausgedrückt handelt es sich dabei um eine Folge von Zeichen, die ein Suchmuster angibt. Das klingt jetzt etwas kryptisch. Im Prinzip schreiben wir einen kleinen Code, der auf einen String im Error-Log matcht.

Wir wollen immer, wenn es eine Zeile mit einer Error-Warnung in eckigen Klamern gibt, die IP-Adresse für Fail2Ban ermitteln. Wir brauchen also eine Regex-Meldung in der die IP-Adresse durch die Fail2Ban spezifische Variable <ADDR> ersetzt wird. Das lässt sich relativ einfach umsetzen. Für den ersten groben Entwurf habe ich mein Logfile in das Webtool regex101 einkopiert.

Auf der rechten Seite erhält man dann Erklärungen zum erstellten Ausdruck und einen netten Debugger gibt es auch noch. Beachten sollte man, dass <ADDR> nur im Fail2Ban-Universum existiert. Ich habe also versucht, auf das erste Oktett der IP-Adresse zu matchen. Da hier aus Platzgründen keine Einführung in Regular Expressions stattfinden kann, folgt auch schon das fertige Ergebnis:

\[error\].*client:\s<ADDR>

Wie man sieht, habe ich das Oktett der IP durch die Variable <ADDR> ersetzt. Jetzt muss noch eine Datei für den Fail2Ban-Filter erstellt werden und etwas Inhalt wäre sicherlich auch nicht verkehrt. Wie das geht, steht unten geschrieben:

# Datei für den Filter anlegen:
touch /etc/fail2ban/filter.d/npm.conf

# Folgenden Inhalt reinkopieren:
[Definition]
failregex = \[error\].*client:\s<ADDR>

Noch haben wir es nicht ganz geschafft. Es gibt nämlich noch kein Jail, welches den Filter verwendet. Wir könnten hierfür einen Abschnitt in der jail.local definieren. Aber das wäre ja etwas langweilig und ich bin auch kein Fan von riesigen Konfigurationsdateien. Wir gehen daher etwas anders vor:

# Eine Datei für die Jail-Definition anlegen:
touch /etc/fail2ban/jail.d/npm.local

# Folgenden Inhalt reinkopieren:
[npm]
backend = auto
enabled = true
port = 80,443
protocol = tcp
filter = npm
maxretry = 3
bantime = 48h
findtime = 400m
logpath = /npm/logs/all_proxy_hosts.log
banaction = ufw

Die benutzen Parameter dürften selbsterklärend sein. Wichtig bei der Konfiguration eines selbst erstellten Jails ist die Angabe des Filters sowie der korrekten Log-Datei. Da ich außerdem nicht die Firewall iptables benutze, muss ich noch auf die von mir eingesetzte ufw verweisen. Das war es auch schon mit der grundlegenden Einrichtung.

Schritt 5 – Schutzmechanismen müssen einfach funktionieren:

Heute haben wir eine ganze Menge an unterschiedlichen Konfigurationen in Fail2Ban vorgenommen. Bevor wir jetzt den Dienst einfach neu starten und das Beste hoffen, treffen wir lieber noch ein paar Sicherheitsvorkehrungen. Wir öffnen noch eine 2. SSH-Session und stellen sicher, dass wir im absoluten Notfall auch noch über die Konsole auf die Maschine kommen.

Bei einem Cloud-Server ist dies recht einfach in der Weboberfläche des Hosters möglich. Bei einem lokalen Server hingegen muss entweder ein Bildschirm vorhanden sein oder man hat die komfortable Möglichkeit eine Fernwartungsschnitttelle nutzen zu können. Ansonsten kann der Support sicherlich auch ein Dongle für das Remote-Management anstöpseln.

Jetzt ist die Zeit gekommen, wo es ernst wird. Zuerst gilt es zu schauen, ob Fail2Ban mit dem von uns erstellten Filter arbeiten kann. Schließlich muss das Tool mithilfe des Regex-Ausdrucks eine IP-Adresse in den Logdateien finden und diese dann im Zusammenspiel mit der Firewall blockieren.

Testen lässt sich das ganz einfach mit den folgenden zwei Befehlen:

# Filter / Regular Expression mit Fail2Ban testen:
fail2ban-regex -v /npm/logs/all_proxy_hosts.log /etc/fail2ban/filter.d/npm.conf

# Prüfen, ob es passende Firewall-Rules gibt:
ufw status verbose

Manchmal will der Dienst aber selber nicht starten. Dann liegt der Fehler nicht selten in der von uns erstellten Datei jail.local. Ein paar gute Ansatzpunkte zum Debugging erhält man mit den folgenden Kommandos:

# Den Fail2Ban-Dienst neustarten:
systemctl restart fail2ban

# Den Fail2ban-Dienst stoppen:
systemctl stop fail2ban

# Den Fail2ban-Dienst bei Problemen aus dem Autostart entfernen:
systemctl disable fail2ban

# Fehlermeldungen im Journalctl auslesen, und zwar in Echtzeit:
journalctl -u fail2ban -f

# Den Status von Fail2Ban kontrollieren:
fail2ban-client status

# Prüfen, ob das NPM-Jail IPs blockt und FW-Regeln angelegt werden:
fail2ban-client status npm
ufw status verbose

# Eigene WAN-IP entbannen:
fail2ban-client set <Jail-Name> unbanip <WAN-IP-Adresse>

# Regex-Ausdruck / Filter mit fail2ban testen:
fail2ban-regex -v /npm/logs/all_proxy_hosts.log /etc/fail2ban/filter.d/npm.conf

Von Fabian Wüst

Er ist leidenschaftlicher Open-Source-Benutzer und ein begeisterter Technologie-Enthusiast. Als kreativer Kopf hinter Homelabtopia bringt Fabian hier seine umfangreiche Erfahrung als Linux-Admin ein. Um sicherzustellen, dass du aus seinen Beiträgen den größtmöglichen Nutzen ziehen kannst, führt er ausgiebige Tests durch und errichtet dafür immense Setups.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert