HAProxy mit Lets Encrypt Zertifikat und SSL Terminierung

Da HAProxy ein wirklich dankbarer Helfer in vielen Lagen ist, sollen an dieser Stelle ein paar Erkenntnisse zur weiteren Verwendung dokumentiert werden.

Lets Encrypt HAProxy Setup

Laufen Services in K8s oder Docker und stellen diese ein Webinterface o.ä. bereit, das abgesichert werden soll, ist dies nicht immer ganz einfach. In der Regel kann man nicht einfach in den entsprechenden Container springen und dort den certbot installieren – selbst wenn doch, wäre er dann auch recht bald wieder weg. Das selbe gilt für die vom Certbot erzeugten Zertifikate.

Außerdem sind die Container vielleicht nicht direkt vom Internet aus erreichbar, oder hören innerhalb von K8s oder Docker Swarm auf andere Namen und IP Adressen. Es ist daher gängig Zertifikate auf dem Host abzulegen und die Pfade als Parameter beim Start des Containers zu übergeben. Das funktioniert aber nur dann mit überschaubarem Aufwand, wenn man ein passendes Zertifikat mit einer langen Laufzeit erworben hat. Mit Lets Encrypt Zertifikaten kann sowas hingegen zu einer Dauerbeschäftigung werden, was wenig Sinn macht.

Ein deutlich einfacherer Weg kann es daher sein, Services im internen Netz unverschlüsselt bereitzustellen und den Datenverkehr nur dann zu verschlüsseln, wenn er das interne Netzwerk verlässt. Hier kommt HAProxy ins Spiel. Das Ziel ist es, alle Services an das interne Netz zu binden und diese für Anfragen von außen nur über den HAProxy verfügbar zu machen.

In diesem Fall ist es ausreichend, wenn die SSL Verschlüsselung beim HAProxy endet. Folglich muss nur dieser ein valides Zertifikat haben. Im Hintergrund kann alles hoch- und runterskalieren, oder von einer Maschine zur anderen wandern, ohne irgendwelche Seiteneffekte bez. Zertifikatsgültigkeit zu verursachen.

Allerdings läuft auf dem HAProxy erstmal kein Webserver, weswegen ein paar kleine Tricks notwendig werden.

Certbot einrichten und Zertifikate erstellen

apt -y install bc git
mkdir -p /etc/haproxy/cert
mkdir -p /opt/letsencrypt
git clone https://github.com/letsencrypt/letsencrypt /opt/letsencrypt

Nun die haproxy.conf editieren um folgende Einträge (sinngemäß) zu integrieren:

frontend skgmCache
        bind 116.202.109.112:80
        mode http
        acl lets_encrypt path_beg /.well-known/acme-challenge/ # <- Diesen Pfad versucht lets encrypt bei der Verifizierung zu erreichen
        use_backend lets_encrypt if lets_encrypt # <- Wenn dieser Pfad abgefragt wird, soll ein anderes Backend genutzt werden 
        default_backend cache

backend cache
        mode http
        http-request set-header X-Forwarded-For %[src]
        balance leastconn
        option tcp-check
        server CacheBE01.skgm.de 116.202.108.28:80 check fall 3 rise 2
        server CacheBE02.skgm.de 78.46.194.99:80 check fall 3 rise 2

backend lets_encrypt # <- Dieses Backend existiert nur temporär während der Certbot läuft
        mode http
        server local localhost:60001

Den HAProxy durchstarten.

systemctl restart haproxy

Nun den Certbot laufen lassen (Domain anpassen):

cd /opt/letsencrypt/
./letsencrypt-auto certonly --domains CHANGEME.NOW.XXX --renew-by-default --http-01-port 60001 --agree-tos
cat /etc/letsencrypt/live/CHANGEME.NOW.XXX/fullchain.pem /etc/letsencrypt/live/CHANGEME.NOW.XXX/privkey.pem > /etc/haproxy/cert/CHANGEME.NOW.XXX.pem

In einem Script um einen zyklischen renew zu triggern, kann das gleiche Vorgehen genutzt werden.

Erneut die haproxy.conf editieren und folgenden Eintrag (sinngemäß) integrieren:

frontend skgmCacheSecure
        bind 116.202.109.112:443 ssl crt /etc/haproxy/cert/
        mode http
        http-request set-header X-Forwarded-For %[src]
        reqadd X-Forwarded-Proto:\ https
        option http-server-close
        default_backend cache

Ggfs. (fast immer) macht es nun Sinn, den Eintrag für das unverschlüsselte Frontend jetzt anzupassen, damit die dahinterliegenden Services nur noch verschlüsselt über das Internet kontaktiert werden können.

Service Config Beispiele für haproxy.conf

Generischer Webservice mit SSL Terminierung am Proxy

frontend skgmCache
        bind 116.202.109.112:80
        mode http
        acl lets_encrypt path_beg /.well-known/acme-challenge/
        use_backend lets_encrypt if lets_encrypt

frontend skgmCacheSecure
        bind 116.202.109.112:443 ssl crt /etc/haproxy/cert/
        mode http
        http-request set-header X-Forwarded-For %[src]
        reqadd X-Forwarded-Proto:\ https
        option http-server-close
        default_backend cache

backend cache
        mode http
        http-request set-header X-Forwarded-For %[src]
        balance leastconn
        option tcp-check
        server CacheBE01.skgm.de 116.202.108.28:80 check fall 3 rise 2
        server CacheBE02.skgm.de 78.46.194.99:80 check fall 3 rise 2

backend lets_encrypt
        mode http
        server local localhost:60001

Related Post

HardlinksHardlinks

Vereinfacht: Hardlinks sind Zeiger auf den I-Node eines Files. D.h. die Rechte aller Hardlinks sind identisch (genau genommen ist auch der erste Zeiger auf den I-Node, der beim Anlegen eines