Umzug des ReverseProxy
Der Grund
Als ich diesen Artikel begonnen habe, lief der ReverseProxy als Jail auf einem FreeBSD-Host. Leider stößt der Rechner mittlerweile öfters an seine Grenzen, da ich auf Sicherheit setze und MariaDB, WordPress, Nextcloud, Mailserver, Webmailer und GitHub in eigene Jails gesteckt habe. Damit hat auch jedes Jail seine eigene php-Engine und eigenen Webserver. Also Leistungsfresser pur.
Ich will also den Host entlasten. Mal abgesehen von der MariaDB, die mit Abstand den größten RAM-Fingerprint hat, gibt es da noch den ReverseProxy, der mit Abstand den größten Rechen- und Bandbreitenbedarf hat.
Also soll der ReverseProxy vom FreeBSD-System (XigmaNAS) auf ein eigens dafür eingerichtetet Debian-basiertes System (Raspberry Pi 4) umziehen.
Mit diesem Artikel möchte ich euch die Möglichkeit geben, mich bei dem Umzug zu begleiten, falls ihr ähnliches vorhaben solltet.
Inhalt
Der Umfang
Um wirklich eine spürbare Entlastung zu haben, muss auch Fail2Ban zukünftig direkt auf dem ReverseProxy laufen. Sonst würde der Proxy die Anfragen einfach an die Jails weiterleiten und dem FreeBSD-Host wäre nicht geholfen, da er weiter so viele Anfragen abarbeiten müsste.
Also müssen umziehen:
Ebenso müssen die nachgelagerten Server ihre Logdateien an den ReverseProxy senden, sonst bekommt dessen Fail2Ban ja nichts von Angriffen auf die Server mit. Das erledige ich mit syslog-ng(8).
Klingt trivial, oder? But, it isn’t.
Warum? Ganz einfach aus zwei Gründen:
- Damit Fail2Ban direkt auf dem Proxy die Anfragen, die als „missbräuchlich“ (engl. abuse) eingestuft werden, auch blockieren kann, muss er die Logdateien der angegriffenen Systeme lesen können. Das kann Fail2Ban aber nicht, da die Systeme physikalisch auf einem anderen Gerät laufen. Auf dem XigmaNAS-Host war das einfach, denn dort lief Fail2Ban auf dem Host und der kann in den Jails Daten lesen, also auch die Logs.
- Die Verzeichnisstruktur von FreeBSD ist grundlegend anders. Alle Verweise, die auf absolute Verzeichnisnamen zeigen, müssen angepasst werden. Und das sind bei meinen ganzen Subdomains ganz schön viele…
Die Vorbereitung
Als erstes habe ich mich per WinSCP (manchmal nehme ich auch den Bitvise SSH Client, da ich hier schnell mal eine Konsole aufmachen kann) in den XigmaNAS-Host eingewählt und die Ordner /usr/local/etc/letsencrypt
und /usr/local/etc/nginx
aus dem Jail des ReverseProxy geholt.
Anschließend werden nginx, CertBot und Fail2Ban auf das neue System gebracht, um die Verzeichnisstruktur aufzubauen. Würde man zuerst die Daten aufspielen und dann erst installieren, könnte die Installation wichtige Konfigurationen der vorhandenen Daten überschreiben.
apt-get update apt-get upgrade apt-get install python3-acme python3-certbot python3-mock python3-openssl python3-pkg-resources python3-pyparsing python3-zope.interface apt-get install nginx certbot python-certbot-nginx syslog-ng
Vorsicht, an dieser Stelle folge ich nicht der Anleitung von CertBot, denn ich will keine neuen Zertifikate erstellen. Ich habe ja schon welche, und diese werde ich in einem späteren Schritt einfügen.
Ebenso wird fail2ban erst später installiert, da ich hier nicht die veraltete Version aus den Debian-Paketen haben will (dort aktuell 0.9.x), sondern mit der (tages-)aktuellen Version in GitHub arbeite.
Daten kopieren, Pfade anpassen
Nun werden die Daten, die ich vom alten Server geholt habe, auf den Raspberry geschoben. Auch müssen einige Verweise in den Konfigurationsdateien angepasst werden.
nginx
Daten kopieren
Hier gibt es erhebliche Unterschiede in der Ordnerstruktur. Während nginx unter FreeBSD einfach alle Dateien als aktiv erkennt, die auf die Maske /usr/local/etc/nginx/conf.d/*.conf
passen, ist es unter Debian usus, die Dateien im Verzeichnis /etc/nginx/sites-available/
abzulegen und per symlink in /etc/nginx/sites-enabled/
dorthin zu verweisen.
Praktisch ist es, dass eine frische Installation von nginx beide Versionen erkennt. Theoretisch könnte man die Daten 1:1 kopieren und fertig. Ich habe mich aber entschieden, dem Debian-usus zu folgen.
Da ich nur eine IPv4 besitze, habe ich – der Übersichtlichkeit halber – eine Unterstruktur angelegt, da ich Dienste als http
, mail
und stream
darüber laufen lasse. Meine alte nginx.conf
enthält daher folgendes:
http { ... include /usr/local/etc/nginx/conf.d/http/*.conf; } stream { ... include /usr/local/etc/nginx/conf.d/stream/*.conf; } mail { include /usr/local/etc/nginx/conf.d/mail/*.conf; }
Für die korrekte Funktion von nginx muss ich auch weiterhin http, mail und stream trennen, da die Dateien in verschiedenen Abschnitten der local.conf geladen werden. Daher behalte ich diese Ordnerstruktur bei und kopiere die Daten aus /usr/local/etc/nginx/conf.d/
einfach nach /etc/nginx/sites-available/
, so dass es später diese Struktur gibt:
/etc/nginx/sites-available/http/ /etc/nginx/sites-available/mail/ /etc/nginx/sites-available/stream/
Die gleiche Struktur bekommt auch /etc/nginx/sites-enabled/
verpasst.
Dateien anpassen
Wie ihr gesehen habt, benutzen FreeBSD und nginx unterschiedliche Verzeichnisstrukturen. Die Verzeichnisse in den Konfigurationsdateien enthalten leider absolute Verweise. Daher müssen die Daten hier noch angepasst werden, sonst verweigert nginx den Start, da die Verzeichnisse, bzw. die referenzierten Dateien, nicht gefunden werden.
Das lässt sich per Hand machen, was eine sehr mühevolle Arbeit darstellt, oder man lässt die Maschine die Daten automatisch ändern. Aufgrund der Menge der Fundorte von absoluten Verzeichnisverweisen habe ich mich für die maschinelle Version entschieden.
Dazu nutze ich den „stream editor“
sed(1). (Wenn ihr die Dateien unter FreeBSD manipulieren wollt, schaut euch diesen Artikel an, da sed
unter FreeBSD anders funktioniert.)
Nun soll aus /usr/local/etc/nginx/conf.d/http/*.conf
die Zeichenfolge /etc/nginx/sites-enabled/
werden. Der Befehl dazu lautet:
sed -i 's/suchen/ersetzen/g' dateiname
Und da ist er, der fiese Fallstrick! Man beachte, dass der Befehlstrenner nicht im zu suchenden oder zu ersetzenden Text enthalten sein darf. Hier steht es etwas ausführlicher.
Ich habe mich für einen abweichenden Trenner entschieden, da ich keine Lust auf die Maskierung hatte. Also:
sed -i 's%/usr/local/etc/nginx/conf.d/%/etc/nginx/sites-enabled/%g' /etc/nginx/nginx.conf
Da ich meine alte nginx.conf ansonsten weiterverwenden will, werden nun noch die Verzeichnisse für die SSL-Zertifikate angepasst.
sed -i 's%/usr/local/etc/letsencrypt/%/etc/letsencrypt/%g' /etc/nginx/sites-available/*.conf
Wie das sonst so mit letsencrypt läuft, steht unter der nächsten Überschrift.
Ich inkludiere noch weitere Konfigurationen um allen Seiten einheitliche Header zu geben und die Haupt-Konfiguration nicht aufzublähen. Auch da müssen die Pfade angepasst werden.
sed -i 's%/etc/nginx/conf.d/includes/%/etc/nginx/includes/%g' /etc/nginx/sites-available/http/*.conf
Ein Test ist an dieser Stelle noch nicht möglich, da erst noch die Installation von letsencrypt angepasst werden muss. Im Verzeichnis /etc/letsencrypt/live/
liegt noch nichts, was nginx verwenden kann, ein Test bzw. ein Start von nginx würde noch zu folgendem Fehler führen:
# nginx -t nginx: [emerg] BIO_new_file("/usr/local/etc/letsencrypt/live/www.beispiel.de/fullchain.pem") failed (SSL: error:02001002:system library:fopen:No such file or directory:fopen('/usr/local/etc/letsencrypt/live/beispiel.de/fullchain.pem','r') error:2006D080:BIO routines:BIO_new_file:no such file) nginx: configuration file /etc/nginx/nginx.conf test failed
CertBot / letsencrypt
Daten kopieren
Die Daten lassen sich sehr einfach auf den neuen Server bringen. Einfach alles, was bei FreeBSD in /usr/local/etc/letsencrypt/ war unter Debian in den Ordner /etc/letsencrypt/ kopieren.
Pfade anpassen
Wieder wird sed(1)
benutzt, um die Pfade zu korrigieren. Im folgenden Codeschnipsel wechsel ich in den Ordner der Konfiguartionsdateien und passe die Pfadstruktur an Debian an:
cd /etc/letsencrypt/renewal/ sed -i 's%/usr/local/etc/%/etc/%g' *.conf
Das war es auch schon. Jetzt kommt der arbeitsintensive Teil…
Symlinks wieder aufbauen
Dicker Fallstrick! Eine 1:1 Kopie von letsencrypt funktioniert hier nicht.
Hier gibt es mit Abstand nun die meiste Arbeit. CertBot arbeitet mit symbolischen Links (symlinks). Diese lassen sich erstens nicht per SSH auf den „Zwischenwirt“ holen und zweitens würden sie eh auf der FreeBSD-Verzeichnisstruktur aufbauen.
Daher heißt es nun: Symlinks wieder aufbauen! Das geschieht mit ln(1)
. Für den folgenden Code nehme ich an, die Domain heißt www.beispiel.de und es ist der siebte Schlüssel, der aktuell ist. Welcher Schlüssel aktuell ist, kann man sehen, wenn man das Verzeichnis /etc/letsencrypt/archive/www.beispiel.de/
anzeigt. Die höchste Zahl ist der aktuelle Schlüssel.
# ls -l /etc/letsencrypt/archive/www.beispiel.de/ total 112K -rw-r--r-- 1 root 2260 Jan 23 2019 cert1.pem -rw-r--r-- 1 root 2260 Apr 4 2019 cert2.pem -rw-r--r-- 1 root 2260 Jun 6 2019 cert3.pem -rw-r--r-- 1 root 2260 Sep 3 2019 cert4.pem -rw-r--r-- 1 root 2260 Nov 3 2019 cert5.pem -rw-r--r-- 1 root 2260 Jan 21 15:24 cert6.pem -rw-r--r-- 1 root 2260 Mar 21 17:06 cert7.pem -rw-r--r-- 1 root 1647 Jan 23 2019 chain1.pem -rw-r--r-- 1 root 1647 Apr 4 2019 chain2.pem -rw-r--r-- 1 root 1647 Jun 6 2019 chain3.pem -rw-r--r-- 1 root 1647 Sep 3 2019 chain4.pem -rw-r--r-- 1 root 1647 Nov 3 2019 chain5.pem -rw-r--r-- 1 root 1647 Jan 21 15:24 chain6.pem -rw-r--r-- 1 root 1647 Mar 21 17:06 chain7.pem -rw-r--r-- 1 root 3907 Jan 23 2019 fullchain1.pem -rw-r--r-- 1 root 3907 Apr 4 2019 fullchain2.pem -rw-r--r-- 1 root 3907 Jun 6 2019 fullchain3.pem -rw-r--r-- 1 root 3907 Sep 3 2019 fullchain4.pem -rw-r--r-- 1 root 3907 Nov 3 2019 fullchain5.pem -rw-r--r-- 1 root 3907 Jan 21 15:24 fullchain6.pem -rw-r--r-- 1 root 3907 Mar 21 17:06 fullchain7.pem -rw-r--r-- 1 root 3268 Jan 23 2019 privkey1.pem -rw-r--r-- 1 root 3272 Apr 4 2019 privkey2.pem -rw-r--r-- 1 root 3272 Jun 6 2019 privkey3.pem -rw-r--r-- 1 root 3272 Sep 3 2019 privkey4.pem -rw-r--r-- 1 root 3272 Nov 3 2019 privkey5.pem -rw-r--r-- 1 root 3272 Jan 21 15:24 privkey6.pem -rw-r--r-- 1 root 3272 Mar 21 17:06 privkey7.pem
Dann würde der Befehl für diese Domain lauten:
ln -s /etc/letsencrypt/archive/www.beispiel.de/cert7.pem /etc/letsencrypt/live/www.beispiel.de/cert.pem ln -s /etc/letsencrypt/archive/www.beispiel.de/chain7.pem /etc/letsencrypt/live/www.beispiel.de/chain.pem ln -s /etc/letsencrypt/archive/www.beispiel.de/fullchain7.pem /etc/letsencrypt/live/www.beispiel.de/fullchain.pem ln -s /etc/letsencrypt/archive/www.beispiel.de/privkey7.pem /etc/letsencrypt/live/www.beispiel.de/privkey.pem
Vieeeel Handarbeit wenn der ReverseProxy viele (Sub-)Domains bedient. Aber eine kleine Formel in OpenOffice Calc (oder wahlweise Excel) hilft da ungemein und macht da einen recht flott zu erledigenden copy&paste-Marathon draus.
nginx: Test und Start
Bevor die Installation von CertBot getestet werden kann, muss nginx laufen. Da im vorigen Schritt die erforderlichen Symlinks aufgebaut wurden, sollte der Test von nginx erfolgreich sein.
# nginx -t nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful
Jawoll, das passt, keine Fehler. Also wird jetzt für den nächsten Schritt nginx gestartet.
systemctl start nginx
CertBot: Test und Cronjob einrichten
Ist die Symlink-Orgie vorbei, kommt nun der Test, ob alles soweit funktionert. Dazu wird CertBot mit der Option –dry-run ausgeführt, deren Aufgabe es ist, einen Zertifikatstausch zu simulieren.
# certbot --nginx --dry-run renew Saving debug log to /var/log/letsencrypt/letsencrypt.log - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Processing /etc/letsencrypt/renewal/www.beispiel.de.conf - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Cert not due for renewal, but simulating renewal for dry run Plugins selected: Authenticator nginx, Installer nginx Renewing an existing certificate Reusing existing private key from /etc/letsencrypt/live/www.beispiel.de/privkey.pem. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - new certificate deployed with reload of nginx server; fullchain is /etc/letsencrypt/live/www.beispiel.de/fullchain.pem - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ... ... - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ** DRY RUN: simulating 'certbot renew' close to cert expiry ** (The test certificates below have not been saved.) Congratulations, all renewals succeeded. The following certs have been renewed: /etc/letsencrypt/live/www.beispiel.de/fullchain.pem (success) ... ... ** DRY RUN: simulating 'certbot renew' close to cert expiry ** (The test certificates above have not been saved.) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Da das fehlerfrei durchgelaufen ist, kann nun der cronjob eingerichtet werden, der regelmäßig CertBot aufruft, damit wir nicht mehr dran denken müssen, wann Zertifikate zu erneuern sind.
Der Server von LetsEncrypt soll nicht überlastet werden. Um das zu verhindern wird ein kleines Script /usr/local/renew.sh
mit folgendem Inhalt angelegt:
#!/bin/sh python -c 'import random; import time; time.sleep(random.random() * 1800)' certbot renew --nginx
Anschließend wird das Script noch ausführbar gemacht.
chmod u+x /usr/local/renew.sh
Dieses Script startet einen Zufallstimer von 30 Minuten. Wenn der abgelaufen ist, wird CertBot ausgeführt. Mit dem Befehl crontab -e
wird der Crontab-Editor aufgerufen. Dort wird folgende Zeile angelegt:
0 5,17 * * * /bin/sh /usr/local/renew.sh >/dev/null 2>&1
Diese Zeile bedeutet übersetzt: Führe täglich um 5 und 17 Uhr den Befehl /bin/sh /usr/local/renew.sh
aus. Dabei werden keine Ausgaben erstellt.
Besonderheit: Mailserver
Die Mailserver nutzen die Zertifikate selbst, aber die Zertifikate werden zentral vom ReverseProxy verwaltet. Wie bekommen nun die Mailserver die neuen Zertifikate?
Dafür nutze ich in CertBot einen sog. „Hook“, der ausgeführt wird, wenn für die Domain ein neues Zertifikat bezogen wurde. Der Hook kopiert das neue Zertifikat per SSH auf die Mailserver und führt anschließend einen Neustart der Dienste aus.
syslog-ng
Die nachgelagerten Server werden so eingerichtet, dass sie ihre Logs an den ReverseProxy senden. In der /etc/syslog.conf
der nachgelagerten Server setze ich dafür:
*.* @ip.des.proxy
Nach einem Neustart von syslog
sendet dieser Server damit sein syslog per UDP-Port 514 an den ReverseProxy. Hier werden die Daten mit der folgenden Konfig aufgefangen und in eine Logdatei geschrieben, die im nächsten Abschnitt von Fail2Ban ausgelesen wird. Die Datei, die das steuert, ist die /etc/syslog-ng/conf.d/external.conf
. Die kann auch anders heißen, aber hütet euch, die Datei /etc/syslog-ng.conf
zu ändern, denn bei einem Update kann auch diese Datei überschrieben werden!
source s_network { syslog(ip(interne.IP.v4) port(514) transport("udp")); }; destination d_external { file("/var/log/external/${HOST}.log"); }; log { source(s_network); destination(d_external); };
Das Makro ${HOST}
sorgt dafür, dass pro sendendem Host eine Datei geschrieben wird. Das sorgt für Übersichtlichkeit und gibt später in Fail2Ban weitere Filtermöglichkeiten.
So, das war es hier auch schon. Wenn syslog-ng noch nicht läuft, dann starten:
systemctl start syslog-ng
Oder, falls es schon läuft, einfach die Konfiguration neu einlesen:
systemctl reload syslog-ng
Fail2Ban
Wie oben geschrieben, will ich nicht die Standardinstallation von Fail2Ban verwenden, da diese Version noch keine rezidiven Filter unterstützt.
Installation
Ich lege ein temporäres Verzeichnis an, hole die aktuelle Version von Github, entpacke und installiere sie und kopiere zum Schluss noch die fail2ban.service
an den richtigen Ort (aus irgendwelchen Gründen hat es der Installer nicht selbst gemacht). Fail2Ban kann auch direkt schon gestartet werden, um schon mal einen Grundschutz zu haben.
mkdir /tmp/fail2ban cd /tmp/fail2ban wget https://github.com/fail2ban/fail2ban/archive/0.11.1.tar.gz -I /tmp/ tar -xvf /tmp/0.11.1.tar.gz -C /tmp/ cd /tmp/fail2ban-0.11.1 sudo python setup.py install cp /opt/fail2ban-github/fail2ban/build/fail2ban.service /etc/systemd/system/fail2ban.service service fail2ban start
Konfiguration
Die Konfiguration von Fail2Ban geschieht ausschließlich in der /etc/fail2ban/jail.local
. Warum? Dies ist die einzige Datei, die von einem Update nicht überschrieben wird. Natürlich kann man auch direkt die jail.conf bearbeiten, aber bei einem Update werden die Änderungen auf Standartwerte zurückgesetzt. Hier ein Auszug aus meiner jail.local:
[DEFAULT] dbpurgeage = 6w ignoreip = 127.0.0.1/8, interne-IPv4.0/24 externe-IPv6::/62 interne-IPv6::/64 ::ffff:127.0.0.0/104 ::1/128 ignorecommand = bantime = 1m findtime = 7d maxretry = 5 backend = auto usedns = warn logencoding = auto enabled = false bantime.increment = true bantime.maxtime = 10w #bantime.factor = 1 #bantime.formula = ban.Time * (1<<(ban.Count if ban.Count<20 else 20)) * banFactor #bantime.formula = ban.Time * math.exp(float(ban.Count+1)*banFactor)/math.exp(1*banFactor) #bantime.multipliers = 1 2 4 8 16 32 64 #bantime.multipliers = 1 5 30 60 300 720 1440 2880 bantime.overalljails = true destemail = XXX sender = XXX mta = sendmail protocol = tcp chain = INPUT #port = 0:65535 fail2ban_agent = Fail2Ban/%(fail2ban_version)s action = %(action_)s %(action_abuseipdb)s[abuseipdb_category="18"] [sshd] enabled = true mode = aggressive action = %(action_)s %(action_abuseipdb)s[abuseipdb_category="18,22"] maxretry = 2 logpath = %(sshd_log)s [sshd-server7] enabled = true mode = aggressive filter = sshd[mode=%(mode)s] action = %(action_)s maxretry = 2 logpath = %(server7_log)s port = %(sshd/port)s [sshd-server11] enabled = true mode = aggressive filter = sshd[mode=%(mode)s] action = %(action_)s maxretry = 2 logpath = %(server11_log)s port = %(sshd/port)s [sshd-server36] enabled = true mode = aggressive filter = sshd[mode=%(mode)s] action = %(action_)s maxretry = 2 logpath = %(server36_log)s port = %(sshd/port)s ...
Die vom ReverseProxy betreuten Server senden ihre Logdateien an den Proxy. Mit einer angepassten /etc/fail2ban/paths-override.local
werden die von syslog-ng
aufgefangenen Logmeldungen für Fail2Ban nutzbar gemacht:
# Overrides [INCLUDES] before = paths-common.conf [DEFAULT] external_log = /var/log/external/external.* #xigmanas server7_log = /var/log/external/*server.7.* #mysql server11_log = /var/log/external/*server.11.* ...
Ist die Konfiguration entsprechend der lokalen Bedürfnisse angepasst, werden die Konfig-Dateien neu geladen. Da der Dienst ja schon oben mit einer Grundkonfiguration gestartet wurde, reicht:
fail2ban-client reload
Sollte Fail2Ban noch nicht laufen, gibt der Befehl einen Fehler aus. Dann muss der Dienst gestartet werden:
service fail2ban start
Fertig!
So, nun laufen CertBot, nginx, Fail2Ban und syslog-ng auf einem eigenen Server. Der Host auf dem die Jails laufen ist nun spürbar entlastet.
Sollte ich was vergessen haben, oder etwas unklar sein, einfach unten in die Kommentare!
Entdecke mehr von Ruhnke.Cloud
Subscribe to get the latest posts sent to your email.