HAProxy, Nginx, LXD und Let’s Encrypt
By zefanja
In meinem letzten Beitrag habe ich beschrieben, wie man verschiedene Webserver, die in einem LXD Container laufen, von außen über einen Reverse-Proxy (in unserem Fall HAProxy) erreichbar machen kann. Diese Setup läuft aber nur über HTTP (Port 80) und damit über einen unverschlüsselte Verbindung. Heutzutage ist es unabdingbar, dass man seine Website auch verschlüsselt. Deswegen möchte ich heute das Setup erweitern, sodass die Webserver über eine verschlüsselte Verbindung erreichbar sind. HAProxy wird dabei SSL/TLS Termination Proxy agieren, d.h. wir müssen nur an einer Stelle alle unsere Zertifikate verwalten und nicht auf jedem einzelnen Webserver selbst. Der Vorteil ist, dass den Webservern Arbeit durch den Proxy abgenommen wird, allerdings muss man wissen, dass die Kommunikation zwischen HAProxy und den Webservern unverschlüsselt erfolgt. Dieses private Netz sollte als sicher angesehen werden. In unserem Fall ist das das Subnet von LXD, in dem sich die Container befinden. Folgende Grafik veranschaulicht den Prozess:
Container überprüfen
Zuerst überprüfen wir, ob alle unsere Container laufen und eine IP haben. Das können wir mit dem bekannten lxc list
Befehl machen:
$ lxc list
+---------+---------+-----------------------+------+------------+-----------+
| NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS |
+---------+---------+-----------------------+------+------------+-----------+
| haproxy | RUNNING | 10.10.10.10 (eth0) | | PERSISTENT | 0 |
+---------+---------+-----------------------+------+------------+-----------+
| web1 | RUNNING | 10.10.10.100 (eth0) | | PERSISTENT | 0 |
+---------+---------+-----------------------+------+------------+-----------+
| web2 | RUNNING | 10.10.10.200 (eth0) | | PERSISTENT | 0 |
+---------+---------+-----------------------+------+------------+-----------+
Alle unsere Container sind online und haben eine IP-Adresse bekommen. Weiterhin sollten die DNS-Einträge unserer beiden Webserver auf die öffentliche IP des Servers zeigen. Dies können wir z.B. mit dem host
-Befehl überprüfen:
$ host web2.example.com
web1.example.com has address 1.2.3.4
Weiterleitung für HTTPS einrichten
Bisher wird nur Traffic, der auf Port 80 auf unserem Server ankommt an den HAProxy Container weitergeleitet. Für HTTPS brauchen wir aber noch eine Weiterleitung für Port 443. Diese können wir mit einer ähnlichen iptables Regel einrichten:
$ sudo iptables -t nat -I PREROUTING -i eth0 -p TCP -d server_ip/32 --dport 443 -j DNAT --to-destination haproxy_ip:443
Diese Regel ist nach einem Neustart wieder weg. Um sie dauerhaft einzurichten, kann man z.B. das Paket iptables-persistent installieren.
HAProxy anpassen
Als nächsten müssen wir den HAProxy Konfiguration anpassen. Dazu loggen wir uns als root
in dem Container ein:
$ lxc exec haproxy bash
Nun können wir die Konfigurationsdatei (/etc/haproxy/haproxy.cfg) öffnen (im HAProxy-Container) und anpassen.
$ nano /etc/haproxy/haproxy.cfg
Damit unser SSL Setup möglichst sicher ist, fügen wir folgende Zeilen im global
Abschnitt hinzu. Ich habe Sie mit dem Mozilla SSL Configuration Generator erzeugt:
global
...
# set default parameters to the modern configuration
ssl-default-bind-ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256
ssl-default-bind-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
ssl-default-server-ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256
ssl-default-server-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
Im defaults
Block fügen wir folgende Zeilen hinzu:
defaults
...
option forwardfor
option http-server-close
...
Nun sind einige Anpassungen im frontend www_frontend
Abschnitt notwendig:
frontend www_frontend
# Http
bind *:80
# Https mit Ordner für Certifikate
bind *:443 ssl crt /etc/haproxy/certs/
# allen Traffic nach https weiterleiten
redirect scheme https if !{ ssl_fc }
# TLS Termination. HAProxy informiert hier unseren Webserver darüber.
reqadd X-Forwarded-Proto:\ https
# Unterscheide zwischen sicheren und unsicheren Requests (wird in den nächsten zwei Zeilen verwendet).
acl secure dst_port eq 443
# Markiere alle Cookies als sicher, wenn sie über SSL gesendet werden.
rsprep ^Set-Cookie:\ (.*) Set-Cookie:\ \1;\ Secure if secure
# Füge den HSTS-Header mit einem maximalen Alter von 1 Jahr hinzu.
rspadd Strict-Transport-Security:\ max-age=31536000 if secure
In in den Backend Abschnitten müssen wir keine Änderungen vornehmen.
Let’s Encrypt Zertifikate einrichten
Nun brauchen wir für die verschlüsselte Kommunikation noch ein Zertifikat für unsere Webserver. Let’s Encrypt bietet kostenfreie Zertifikate schon seit einigen Jahren an und ab diesem Jahr sogar Wildcard-Zertifikate.
Damit wir die Zertifikate einrichten können, brauchen wir den Let’s Encrypt Client, den wir wie folgt installieren (im HAProxy Container):
$ sudo apt install letsencrypt
Wir generieren ein Zertifikat, welches für alle unsere Domains gültig ist:
$ letsencrypt certonly -d example.com -d web2.example.com
Wenn alles geklappt hat, haben wir nun ein gültiges Zertifikat für unsere beiden Webserver. Jetzt müssen wir nur noch HAProxy die richtigen Zertifikate zur Verfügung stellen. Dazu müssen wir die Certificate Chain und den Private Key zusammenfügen, damit HAProxy etwas damit anfangen kann. Wir legen im ersten Schritt das Zielverzeichnis an und fügen dann die Zertifikate und den Key zusammen:
$ mkdir -p /etc/haproxy/certs/
$ DOMAIN='example.com' sudo -E bash -c 'cat /etc/letsencrypt/live/$DOMAIN/fullchain.pem /etc/letsencrypt/live/$DOMAIN/privkey.pem > /etc/haproxy/certs/$DOMAIN.pem'
Zum Schluss starten wir den HAProxy neu:
$ systemctl restart haproxy
Testen
Wir sind fast am Ziel und möchten unsere Domains noch testen, ob auch alles richtig konfiguriert wurde. Zuerst rufen wir die Seiten in einem Webbrowser auf. Es sollte neben der Adresse ein grünes Schloss erscheinen (im Firefox). Weiterhin sollten wir noch einen SSL Server Test durchführen. Das Ergebnis lässt sich sehen:
Fazit
Mit wenigen Schritten haben wir ein Setup geschaffen, in dem wir mehrere Webseiten nach außen hin erreichbar machen können. Das Ganze lässt sich leicht erweitern und skalieren. Besonders angenehm finde ich die Verwaltung der Zertifikate an einer Stelle und nicht auf jedem Webserver einzeln. LXD und Let’s Encrypt, sowie HAProxy sind für mich eine der großen persönlichen Entdeckungen der letzten zwei Jahre.
Was sind für dich die größten Herausforderung bei der Bereitstellung von Webservern?