Kontakt

Carsten Rieger IT Services
Am Danglfeld 8 | 83132 Pittenhart
Telefon: 08624.9009794
E-Mail: info@c-rieger.de

Nextcloud mit HAProxy 2.5 und SSL Terminierung

Um die Last auf mehrere Nextcloud Instanzen zu verteilen schalten wir einen Loadbalancer (HAProxy) vor die Nextcloud Webserver. Zudem verschlüsseln wir die Kommunikation mit SSL sowohl nach extern, als auch nach intern.

In diesem Beispiel nutzen wir folgende Ubuntu 20 Server:

  • haproxy – 192.168.2.205
  • nc1 – 192.168.2.206
  • nc2 – 192.168.2.207

Zuerst passen wir die hosts-Datei auf allen Servern an:

sudo -s
nano /etc/hosts
# Server: haproxy
127.0.0.1 localhost
127.0.1.1 haproxy
...
192.168.2.205 haproxy
192.168.2.206 nc1
192.168.2.207 nc2
# Server: nc1
127.0.0.1 localhost
127.0.1.1 nc1
...
192.168.2.205 haproxy
192.168.2.206 nc1
192.168.2.207 nc2
# Server: nc2
127.0.0.1 localhost
127.0.1.1 nc2
...
192.168.2.205 haproxy
192.168.2.206 nc1
192.168.2.207 nc2

Nun aktualisieren wir den haproxy-Server und installieren den HAProxy-Loadbalancer:

apt update && apt upgrade -y
apt-get install --no-install-recommends software-properties-common
add-apt-repository ppa:vbernat/haproxy-2.5
apt install haproxy=2.5.\*

Um sich den Status des Loabalancers „live“ ansehen zu können nutzen wir das Tool HATOP:

wget http://archive.ubuntu.com/ubuntu/pool/universe/h/hatop/hatop_0.8.0-1.1_all.deb
dpkg -i hatop*.deb

Ruft man nach Fertigstellung des HAProxys das HATop-Tool wie folgt auf,

hatop -s /run/haproxy/admin.sock

so erhält man eine „Live“-Sicht, auf den Loadbalancer.

Am Beispiel von self signed SSL-Zertifikaten verschlüsseln wir sowohl die Kommunikation zum Loadbalancer, als auch die Kommunikation zwischen dem Loadbalancer und den Nextcloud-Instanzen. Um das realisieren zu können installieren wir

apt install ssl-cert

und generieren uns neue self-signed SSL_Zertifikate

make-ssl-cert generate-default-snakeoil -y

Um diese Zertifikate im HAProxy nutzen zu können, fügen wir das Zertifikat und den PrivatKey in eine Datei zusammen:

cat /etc/ssl/private/ssl-cert-snakeoil.key /etc/ssl/certs/ssl-cert-snakeoil.pem > /etc/haproxy/server.pem

Zudem generieren wir uns ein dhparam.pem-File, um auch den Schlüsselaustausch selbst abzusichern:

openssl dhparam -dsaparam -out /etc/haproxy/dhparam.pem 4096

Die Vorbereitungen sind nun abgeschlossen, so dass wir den HAProxy (Layer6 / http-Mode) konfigurieren können. Beginnen wir mit einer Sicherungskopie der Standardkonfiguration.

cd /etc/haproxy
cp haproxy.cfg haproxy.cfg.bak

Öffnen Sie dann die Konfigurationsdatei und ersetzen den kompletten Inhalt mit dem nachfolgenden Block:

global
	log /dev/log	local0
	log /dev/log	local1 notice
	chroot /var/lib/haproxy
	stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
	stats timeout 30s
	user haproxy
	group haproxy
	daemon

	# Default SSL material locations
	ca-base /etc/ssl/certs
	crt-base /etc/ssl/private

	ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
    	ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
    	ssl-default-bind-options prefer-client-ciphers no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets

    	ssl-default-server-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
    	ssl-default-server-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
    	ssl-default-server-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets
	ssl-dh-param-file /etc/ssl/certs/dhparam.pem

defaults
	log	global
	mode	http
	option	httplog
	option	dontlognull
        timeout connect 5000
        timeout client  50000
        timeout server  50000
	errorfile 400 /etc/haproxy/errors/400.http
	errorfile 403 /etc/haproxy/errors/403.http
	errorfile 408 /etc/haproxy/errors/408.http
	errorfile 500 /etc/haproxy/errors/500.http
	errorfile 502 /etc/haproxy/errors/502.http
	errorfile 503 /etc/haproxy/errors/503.http
	errorfile 504 /etc/haproxy/errors/504.http

listen  stats
	bind 	:8443 ssl crt /etc/haproxy/server.pem alpn h2,http/1.1 ssl-min-ver TLSv1.2
        maxconn 5
        mode  	http
        stats 	enable
	stats show-legends
	stats hide-version
        stats refresh 30s
        stats show-node
        stats uri  /

frontend NEXTCLOUD
        mode    http
        bind    :80
        bind    :443 ssl crt /etc/haproxy/server.pem alpn h2,http/1.1 ssl-min-ver TLSv1.2
        maxconn 20000
        acl url_discovery path /.well-known/caldav /.well-known/carddav
        http-request redirect location /remote.php/dav/ code 301 if url_discovery
        redirect scheme https code 301 if !{ ssl_fc }
        http-response set-header Strict-Transport-Security max-age=63072000
        acl is_certbot path_beg /.well-known/acme-challenge/
        use_backend LetsEncrypt if is_certbot    
        default_backend NEXTCLOUD

backend NEXTCLOUD
        balance leastconn 
        cookie SERVERID insert indirect nocache
        option httpchk GET /login
        http-check expect rstatus [2-3][0-9][0-9]
        server server1 192.168.2.206:443 check maxconn 10000 ssl verify none ca-file /etc/haproxy/server.pem cookie nc1
        server server2 192.168.2.207:443 check maxconn 10000 ssl verify none ca-file /etc/haproxy/server.pem cookie nc2

backend LetsEncrypt
        mode http
        server certbot 127.0.0.1:9080

Zwei Alternativen aus dem Enterprise-Umfeld sehen wie folgt aus:

Beispiel 1: Layer 6 / http-mode mit http-Healthcheck:

global
        log /dev/log    local0
        log /dev/log    local1 notice
        chroot /var/lib/haproxy
        stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
        stats timeout 30s
        user haproxy
        group haproxy
        daemon
        ca-base /etc/ssl/certs
        crt-base /etc/ssl/private
        ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
        ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
        ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
        tune.ssl.cachesize 1000000
        ssl-dh-param-file /etc/haproxy/dhparam.pem

defaults
        log	global
	mode	http
	option	httplog
	option	dontlognull
        timeout connect 5000
        timeout client  50000
        timeout server  50000
	errorfile 400 /etc/haproxy/errors/400.http
	errorfile 403 /etc/haproxy/errors/403.http
	errorfile 408 /etc/haproxy/errors/408.http
	errorfile 500 /etc/haproxy/errors/500.http
	errorfile 502 /etc/haproxy/errors/502.http
	errorfile 503 /etc/haproxy/errors/503.http
	errorfile 504 /etc/haproxy/errors/504.http

frontend Statistiken
        bind   *:8443 ssl crt /etc/haproxy/server.pem alpn h2,http/1.1 ssl-min-ver TLSv1.2
        mode   http
        option httplog
        maxconn 5
        stats enable
        stats show-legends
        stats hide-version
        stats refresh 60s
        stats show-node
        stats uri /

frontend NEXTCLOUD
	mode    http
	bind    :80
	bind    :443 ssl crt /etc/haproxy/server.pem alpn h2,http/1.1 ssl-min-ver TLSv1.2
	acl url_discovery path /.well-known/caldav /.well-known/carddav
	http-request redirect location /remote.php/dav/ code 301 if url_discovery
	redirect scheme https code 301 if !{ ssl_fc }
	http-response set-header Strict-Transport-Security max-age=63072000
	acl is_certbot path_beg /.well-known/acme-challenge/
	use_backend LetsEncrypt if is_certbot    
	default_backend NEXTCLOUD

backend NEXTCLOUD
        mode http
        fullconn 20000
        balance leastconn
        stick-table type ip size 128m expire 2h
        stick on src
	option forwardfor
	option httpchk GET /login
        http-check expect rstatus [2-3][0-9][0-9]
        server NC1 192.168.2.206:443 weight 1 inter 5s downinter 20s rise 4 fall 2 check ssl verify none ca-file /etc/haproxy/server.pem on-marked-down shutdown-sessions maxconn 10000
        server NC2 192.168.2.207:443 weight 1 inter 5s downinter 20s rise 4 fall 2 check ssl verify none ca-file /etc/haproxy/server.pem on-marked-down shutdown-sessions maxconn 10000

backend LetsEncrypt
       mode http
       server certbot 127.0.0.1:9080
# (c) Carsten Rieger IT-Services, v. 220102

Beispiel 2: Layer 4 / tcp-mode mit http-Healthcheck:

global
        log /dev/log    local0
        log /dev/log    local1 notice
        chroot /var/lib/haproxy
        stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
        stats timeout 30s
        user haproxy
        group haproxy
        daemon
        ca-base /etc/ssl/certs
        crt-base /etc/ssl/private
        ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
        ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
        ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
        tune.ssl.cachesize 1000000
        ssl-dh-param-file /etc/haproxy/dhparam.pem

defaults
        log     global
        mode    tcp
        log     global
        option  tcplog
        option  dontlognull
        option  dontlognull
        timeout connect 5000
        timeout client  50000
        timeout server  50000
        errorfile 400 /etc/haproxy/errors/400.http
        errorfile 403 /etc/haproxy/errors/403.http
        errorfile 408 /etc/haproxy/errors/408.http
        errorfile 500 /etc/haproxy/errors/500.http
        errorfile 502 /etc/haproxy/errors/502.http
        errorfile 503 /etc/haproxy/errors/503.http
        errorfile 504 /etc/haproxy/errors/504.http

frontend Statistiken
        bind   *:8443 ssl crt /etc/haproxy/server.pem alpn h2,http/1.1 ssl-min-ver TLSv1.2
        mode   http
        option httplog
        maxconn 5
        stats enable
        stats show-legends
        stats hide-version
        stats refresh 60s
        stats show-node
        stats uri /

frontend NEXTCLOUD
        bind *:443
        maxconn 20000
        mode tcp
        option tcplog
        tcp-request inspect-delay 5s
        tcp-request content accept if { req_ssl_hello_type 1 }
        default_backend NEXTCLOUD

backend NEXTCLOUD
        mode tcp
        fullconn 20000
        balance leastconn
        stick-table type ip size 100m expire 2h
        stick on src
	option httpchk GET /login
        http-check expect rstatus [2-3][0-9][0-9]
        server server1 192.168.2.206:443  weight 1 inter 5s downinter 20s rise 4 fall 2 check check-ssl verify none on-marked-down shutdown-sessions maxconn 10000
        server server2 192.168.2.207:443  weight 1 inter 5s downinter 20s rise 4 fall 2 check check-ssl verify none on-marked-down shutdown-sessions maxconn 10000
# (c) Carsten Rieger IT-Services, v. 220102

Nach dem Speichern der Konfigurationsdatei, gefolgt von einem Neustart des HAProxys

service haproxy restart

können Sie die Status-Seite des Loadbalancers bereits aufrufen (https://192.168.2.205:8443):

Vorausgesetzt, Sie haben die Nextcloud-Instanzen (Backends) bereits aufgesetzt (Lesen Sie mehr in diesem Artikel), so können Sie diese „lastverteilt“/“gebalanced“ über https://192.168.2.205 erreichen.

Überprüfen Sie sowohl in der Nextcloud, als auch in der Browserkonsole das Balancingverhalten. Dazu sehen Sie sich zum Einen die System-Einstellungen der Nextcloud an und vollziehen dort visuell nach, auf welchem Node (Knoten/Server) Sie sich befinden. Zum Anderen sehen Sie sich das Pendant, also den vom HAProxy gesetzten Cookie, in der Browserkonsole an. Beide spiegeln im Beispiel den Server nc1 wider.

Sie befinden sich auf dem Sever nc1 – Sie finden in der Browserkonsole den gesetzten Cookie nc1 (SERVERID=nc1)

Möchten Sie Let’s Encrypt Zertifikate nutzen, so installieren Sie die Let’s Encrypt Software certbot (URL):

apt install snapd
snap install core && snap refresh core
apt remove certbot

snap install --classic certbot
ln -s /snap/bin/certbot /usr/bin/certbot

Mittels des Kommandozeilentools certbot requestieren wir die SSL Zertifikate

certbot certonly --standalone --preferred-challenges http --http-01-address 127.0.0.1 --http-01-port 9080 -d ihre.domain.de --email ihre@domain.de --agree-tos --non-interactive

und nutzen dann ein Skript um alle Zertifikate zu konsolidieren:

nano /etc/haproxy/le-certificates.sh
#!/bin/bash
for CERTIFICATE in `find /etc/letsencrypt/live/* -type d`; do
CERTIFICATE=`basename $CERTIFICATE`
cat /etc/letsencrypt/live/$CERTIFICATE/fullchain.pem /etc/letsencrypt/live/$CERTIFICATE/privkey.pem > /etc/haproxy/server.pem
done
exit 0

Nun machen wir das Skript noch ausführbar

chmod +x /etc/haproxy/le-certificates.sh

und führen es aus.

/etc/haproxy/le-certificates.sh

Nach einem Neustart des HAProxy-Services

services haproxy restart

werden die neuen Let’s Encypt SSL-Zertifikate schon verwendet.

Um die Zertifikate automatisch zu erneuern benötigen wir ein weiteres Skript:

nano /etc/haproxy/le-renewal.sh
#!/bin/bash
certbot renew --standalone --preferred-challenges http --http-01-address 127.0.0.1 --http-01-port 9080 --post-hook "/etc/haproxy/le-certificates.sh && systemctl reload haproxy.service" --quiet
exit 0

Auch dieses Skript muss wieder ausführbar gemacht werden

chmod +x /etc/haproxy/le-renewal.sh

Nun legen wir noch den wöchentlichen Cronjob an

crontab -e
@weekly /etc/haproxy/le-renewal.sh > /dev/null

wodurch die Zertifikate wöchentlich auf Aktualisierungen geprüft werden und bei einer Aktualisierung auch der haproxy-Service neu gestartet wird.

Die Installation und Einrichtung des HAProxy-Loadbalancers wurde somit erfolgreich abgeschlossen. Über Ihre Unterstützung (diese wird ordnungsgemäß versteuert!) würden sich meine Frau, meine Zwillinge und ich sehr freuen. Vielen Dank!

© Carsten Rieger IT-Services