Dies hier soll weniger Ein Blogbeitrag über Fail2ban werden als mehr eine Notiz an mich selbst, damit ich die Dinge, die ich eingestellt habe, nicht vergesse.
Worum geht es?
Mailcow über Docker bringt ein eigenes Fail2ban mit sich. In der GUI von Mailcow lässt sich einstellen, wie Fail2ban sich verhalten soll. Leider können manche Dinge so immer nur "übergeordnet" verwaltet werden. Beispiel: Die Anzahl Versuche bis zur Sperrung oder der Zeitraum in dem Versuche aufgezeichnet werden. Ich habe Fail2ban sehr streng eingestellt und sperre nach dem dritten Fehlversuch.
Allerdings störte mich, dass immer wieder unzählige Anmeldeversuche mit Fantasienamen auf den Server einprasselten. Ich habe mir eine Funktion gewünscht, die ähnlich wie bei manchen WordPress-Sicherheitsplugins bei unbekannten Benutzernamen sofort sperren. Dies lässt sich aber meines Wissens nicht mit den Mailcow-Bordmitteln realisieren. Außer man bannt generell beim ersten Fehlversuch.
Meine Fail2ban-Konfiguration ist sicher nicht für jeden geeignet aber auf meinem Mailserver kenne ich die Benutzernamen und so wollte ich sozusagen mit einer "Positivliste" arbeiten.
Voraussetzungen
Mailcow-Logs werden primär als Docker-Container-Ausgaben (stdout/stderr) generiert und hauptsächlich über den JSON-Treiber von Docker verarbeitet, wobei die letzten 10.000 Zeilen persistent in Redis für die Web-UI gespeichert werden. Daher müssen wir dafür sorgen, dass Fail2ban die Logfiles überhaupt zum Lesen bekommt. Dies funktioniert laut Mailcow-Website über die Datei docker-compose.override.yml, die wir in /opt/mailcow-dockerized/ erstellen. Ich mache das mit dem Editor nano. Funktionier aber mit vi oder vim ebenso.
Die docker-compose.override.yml
Wir öffnen die docker-compose.override.yml mit dem Editor unserer Wahl und geben folgendes ein:
services:
postfix-mailcow:
logging:
driver: "journald"
dovecot-mailcow:
logging:
driver: "journald"Hier findet ihr die Mailcow-Dokumentation zum Umstellen der Logfiles: https://docs.mailcow.email/de/post_installation/firststeps-logging/
Nun werden die Logs für Postfix und Dovecot auch über den journald ausgegeben und können mit journalctl abgerufen werden. So kann auch Fail2ban auf dem Host (nicht Fail2ban von Mailcow) auf die Logs zugreifen und auswerten. Fail2ban ist auf dem Host zusätzlich installiert, damit auch die ssh-Zugriffe überwacht werden können und dies nicht aus dem Mailcow-Container heraus geschehen muss.
Der Fail2ban-Filter für Dovecot
Als nächstes brauchen wir einen Fail2ban-Filter um den Regex einzugeben, mit dem in den Logs nach den entsprechenden Zeilen gesucht werden kann. Die Fail2ban-Filter liegen im Verzeichnis /etc/fail2ban/filter.d
Wir schauen uns erst einen Logeintrag von Dovecot an. Eine typische Dovecot Logzeile sieht wie folgt aus:
Mar 21 17:56:34 mxsrv1 9e6d079edff6[916]: Mar 21 17:56:34 9e6d079edff6 dovecot: auth-worker(18103): conn unix:auth-worker (pid=135,uid=401): auth-worker<112561>: lua(test@schoenes-vom-starnberger-see.de,49.247.24.89): Password mismatch (SHA1 of given password: 7f5865)
In diesem Beispiel sieht man, dass versucht wird, sich mit dem Benutzernamen test@schoenes-vom-starnberger-see.de einzuloggen. Einen Benutzer test gibt es nicht. Die Idee ist nun, eine "Positiv-Liste" mit zulässigen Benutzernamen im Filter als Regex umzusetzen. Dies ist natürlich nur möglich, wenn erstens klar ist, welche Benutzer es genau gibt. Oft ist dies nicht möglich, weil Benutzer selbst Adressen bzw. Logins erstellen können. In meinem Fall habe ich die Kontrolle darüber und kann mit einer Positivliste arbeiten.
Die Stellen im Logeintrag, die uns interessieren sind lua und Password mismatch
[Definition]
loglevel = DEBUG
failregex = ^.*lua\((?!(anton@|archiv@|office@|carla@|susi@|mike@|wyny@))[^@]+@.*?,<HOST>.*\): Password mismatch
ignoreregex =Fail2ban soll bei der Suche mit dem String lua beginnen. dafür steht das ^.*lua am Anfang des Regex. Danach wird mit \ die Klammer ( escaped. Diese Klammer steht im Logfile. Die zweite Klammer nach lua\( umklammert den Lookahaed. Wir verwenden einen negativen Lookahead (?!(anton@ … Das bedeutet „das folgende darf nicht übereinstimmen. Es darf also kein Benutzer mit Namen „anton“ oder „office“ etc. sein. Hier muss die Liste der Mailbenutzer gepflegt werden. Das funktioniert, wie schon geschrieben, nur, wenn man genau sagen kann, welche Mailbenutzer es gibt. Da sich hier alle Benutzer mit einer kompletten E-Mail-Adresse anmelden sollte hinter dem Benutzernamen noch das „@” stehen. Damit verhindern wir falsch positive Treffer. Mit dem Regex [^@]+ schließen wir das @ zuerst aus. Dadurch wird verhindert, dass auch Namen wie anton123 oder antonio „durchgelassen“ werden. Weil wir im ersten Schritt das @ ausschließen müssen wir es im zweiten Schritt wieder hinzufügen. Darum lautet der Regex [^@]+@
Nun kommen wir zu dem Teil nach dem „@”. Dieser lautet .*?,. Der Punkt am Ende gehört zum Regex und kein Satzzeichen! Der „.“ steht für beliebige Zeichen, der „*“ für „beliebig oft“. Das Fragezeichen danach steht für „lazy“ und sagt dem Regex „nimm beliebige Zeichen aber so wenige wie möglich“. Dies geschieht, bis der nächste Teil passt. In diesem Fall ist der nächste Teil das Komma zwischen der E-Mail-Adresse und der IP-Adresse. Die IP-Adresse kommt direkt nach dem Komma. Darum bauen wir hier <HOST> ein und sagen damit dem Regex, dass dies die Adresse des „Angreifers“ ist, die gesperrt werden soll. Danach kommt dann die schließende Klammer (escaped) sowie ein Doppelpunkt und ein Leerzeichen, bevor der String „Password mismatch“ beginnt.
Nach was wird nun genau gesucht?
Wir suche also in allen Logzeilen nach jenen, die irgendwo „lua“ stehen haben und auf das eine geöffnete Klammer folgt. Dann prüfen wir, ob der Loginname in der Logzeile einem zulässigen Benutzer entspricht. Zusätzlich müssen dan ein @-Zeichen und einige weitere Zeichen folgen, die Klammer geschlossen werden und dahinter ein Doppelpunkt sowie ein Leerzeichen folgen. Darauf muss dann wiederum „Password mismatch“ folgen. So erkennen wir, dass jemand versucht sich mit einem nicht vorhandenen Benutzernamen einzuloggen und können die IP-Adresse (<HOST>) sperren.
Die jail-Datei und ihre Parameter
Bei Fail2ban liegen die Jail-Dateien in /etc/fail2ban/jail.d/. Man kann sie aber auch in die Datei jail.local einbauen. Hierzu muss diese Datei erst erstellt werden. Die Datei jail.conf ist tabu. Diese kann als Vorlage verwendet werden aber bei einem Update o. Ä. kann es sein, dass diese Datei überschrieben wird und eure eigenen Konfigurationen weg sind. Darum immer Jail-Konfigurationen in jail.local oder in /etc/fail2ban/jail.d/ als einzelne Jail-Dateien ablegen.
[dovecot-lua]
enabled = true
filter = mc-baduser-lua
backend = systemd
journalmatch = _SYSTEMD_UNIT=docker.service
maxretry = 1
findtime = 86400
bantime = 86400
banaction = iptables-allports[blocktype=DROP]Wir haben nun also zwei Dateien für Fail2ban erstellt. Einen Filter und einen Jail:
- /etc/fail2ban/filter.d/mc-baduser-lua.conf
- /etc/fail2ban/jail.d/mc-baduser-lua-filter.local
Beginnen wir oben. [dovecot-lua] ist der Name des Jails. Zeile 2 (enabled = true) aktiviert den Jail. Dann kommt der Filter (filter = mc-dovecot-lua), dessen Name mit dem Dateinamen der Filterdatei übereinstimmen muss (das .conf kann weggelassen werden).
