Eine IP Adresse, mehrere Webdienste und alle verwenden die Ports 80 und 443. Ein Reverse Proxy kann z.B. anhand von der URL verschiedene Webdienste an "einem Anschluss" zur Verfügung stellen.
Ausgangspunkt bei mir: ich habe einen Webdienst, der funktioniert und soll vorerst so verfügbar bleiben. Ich möchte aber noch ein paar Dinge dazu schalten. Der HAProxy ist wie der Name sagt: als Proxy entwickelt und nicht wie Apache oder nginx ein Webserver der auch Proxy kann. Für mich war es komplettes Neuland, ich will hier die gesamte Konfiguration beschreiben.
Mein Betriebssystem ist ein debian 12 LXC, die Installation von HAProxy ist erstmal simpel...
apt update
apt install haproxy
... es wird noch etwas aufwendig.
DNS und Router
Ich habe eine gehosteten Webauftritt domain.tld, dort hatte ich schon einen cname Eintrag auf cloud.domain.cloudns.cl. Der existierende Webdienst hat per cronjob die IPv4 und IPv6 Adresse diese Dienstes dort aktualisiert.
Achtung! Die dyndns Registrierung durch die Fritzbox ist für IPv6 an dieser Stelle unbrauchbar, bei IPv6 wird kein NAT gemacht, man braucht die IPv6 Adresse des Webdienstes.
Mit diesem Setup ändert sich das für den existierenden Webdienst: Der Proxy übersetzt die IP Adressen (v4 und v6), d.h. die Registrierung der IPv6 Adresse muss in Zukunft durch das OS vom HAProxy erfolgen. Der HAProxy soll der Endpunkt für alle Dienste sein und übersetzt intern.
Um flexibel zu sein, habe ich noch einen cname Eintrag *.home.domain.tld auf cloud.domain.cloudns.cl erzeugt. Damit kann man später cloud.home.domain.tld oder dienst1.home.domain.tld usw. verwenden ohne am DNS neu konfigurieren zu müssen.
Die alte Portfreigabe im Router muss, wenn der Proxy läuft, durch eine neue Freigabe ersetzt werden!
Test des HAProxy
Die Proxy Funktionalität wird durch frontend | backend Abschnitte konfiguriert. Die Abschnitte werden ähnlich wie Firewallregeln von oben nach unten abgearbeitet. Eine umfangreiche Doku gibt es hier.
Die Datei /etc/haproxy/haproxy.cfg wird um die hier gezeigten Abschnitte ergänzt!
Ein schneller Test kann mit dieser kurzen Konfiguration erfolgen:
frontend test-front
bind [::]:80
default_backend test-back
backend test-back
server localhost 0.0.0.0:8000
Nach dem restart des haproxy ( systemctl restart haproxy ) erzeugt man eine Webservice mit python:
python3 -m http.server --bind ::
Sowohl der Browser Zugriff über http://ip_des_proxy:8000 als auch http://ip_des_proxy sollte das aktuelle Directory listen.
Für den folgenden Abschnitt muss diese Testkonfiguration wieder gelöscht werden.
Man kann die Funktion des Proxy relativ umfangreich und im Detail testen, ohne dabei auf die DNS Auflösung angewiesen zu sein. Viele Beispiele findet man hier. Man kann nach dem folgenden Abschnitt erstmal testen, bevor man den Webdienst hinter den Proxy stellt.
HAProxy Konfiguration
Die 3 Frontend Definitionen sollen mehrere Funktionen abdecken, die eigentlich etwas konträr sind.
- HTTP mit Erkennung der acme_challenge für die Ausstellung und Erneuerung von letsencrypt Zertifikaten und Weiterleitung auf HTTPS
- SSL Passthrough (mode tcp) für Webdienste mit eigener SSL Terminierung.
- SSL Terminierung (mode http) am Proxy für alle weiteren Webdienste
Ich habe hier Konfigurationen aus Beiträgen des Users oezh , Paul und C.Rieger eingearbeitet. Alle haben mir sehr beim schrittweisen Verständnis von HAProxy geholfen!
Die Abschnitte verwenden ganz bewusst unterschiedliche (Syntax) Möglichkeiten von HAProxy zum Erkennen und Verzweigen:
- acl setzt eine Zustandsvariable auf true oder false,
- redirect verzweigt wenn die Bedingung erfüllt ist,
- use_ verzweigt zu einem backend wenn die Bedingung wahr ist,
- default_ ist quasi der letzte Ausweg.
Soll ein neues SSL Passthrough Backend installiert werden, muss die passende Proxy Konfiguration vorher erweitert und aktiviert werden um die initiale Installation der Zertifikate zu gewährleisten!
Wahrscheinlich fehlt hier noch etwas und muss im Laufe des Betriebes noch ergänzt werden.
frontend HTTP_ACME
mode http
bind [::]:80 v4v6
maxconn 200
acl acmerequest path_beg /.well-known/acme-challenge/
acl backend1_host hdr(host) -i cloud.domain.tld
acl backend2_host hdr(host) -i nc.home.domain.tld
acl backend3_host hdr(host) -i opencloud.domain.org opencloud-collabora.domain.org opencloud-wopiserver.domain.org
redirect scheme https code 301 if !acmerequest
use_backend ACME_backend1 if acmerequest backend1_host
use_backend ACME_backend2 if acmerequest backend2_host
# use_backend ACME_backend3 if acmerequest backend3_host
default_backend HTTP_certbot
frontend SSL_PassThrough
mode tcp
bind [::]:443 v4v6
tcp-request inspect-delay 5s
tcp-request content accept if { req_ssl_hello_type 1 }
# ssl passthrough backends
use_backend backend1 if { req_ssl_sni -i cloud.domain.tld }
use_backend backend2 if { req_ssl_sni -i nc.home.domain.tld }
# redirect for SSL termination
default_backend tcp_to_https
backend tcp_to_https
mode tcp
server haproxy-https 127.0.0.1:44443 check
frontend SSL_Termination
mode http
bind 127.0.0.1:44443 ssl crt /usr/local/ssl/certs/sites
#ssl crt /etc/haproxy/certs/ggg.hhh.com.pem crt /etc/haproxy/certs/iii.kkk.com.pem
use_backend backend4 if { hdr(host) -i home.domain.tld }
use_backend backend5 if { hdr(host) -i test.home.domain.tld }
# SSL Passthrough backends (every manage their own SSL termination)
backend backend1
mode tcp
server server1 192.168.90.160:443 check
backend backend2
mode tcp
server server2 192.168.90.90:443 check
# SSL Terminated by HAProxy Backends (plain http traffic between HAProxy and these backends)
backend backend4
mode http
server server4 192.168.90.75:8000 check
http-request set-header X-Forwarded-Port %[dst_port]
http-request add-header X-Forwarded-Proto https if { ssl_fc }
backend backend5
mode http
server server5 192.168.90.75:8000 check
http-request set-header X-Forwarded-Port %[dst_port]
http-request add-header X-Forwarded-Proto https if { ssl_fc }
# ACME / certbot backends for acme-challenge
backend ACME_backend1
mode http
fullconn 100
balance source
server nextcloud 192.168.90.160:80 check maxconn 100
backend ACME_backend2
mode http
fullconn 100
balance source
server nextcloud 192.168.90.90:80 check maxconn 100
backend HTTP_certbot
mode http
log global
# this server only runs when we are renewing a certificate
server localhost 0.0.0.0:54321
Die Anzahl Backends für SSL PassThrough bzw. Termination kann man quasi beliebig erweitern: im Abschnitt frontend muss die Zeile use_backend ... und die dazu passenden backend Abschnitte kopiert und angepasst werden.
Letsencrypt Zertifikate mit certbot
Ich wollte in dem debian System nicht extra snap installieren, deshalb habe ich mich für die pip Methode entschieden. Hier ist die offizielle Doku dazu und die Kurzform der Befehle.
apt update
apt install python3 python3-venv libaugeas0
python3 -m venv /opt/certbot/
/opt/certbot/bin/pip install --upgrade pip
/opt/certbot/bin/pip install certbot
ln -s /opt/certbot/bin/certbot /usr/bin/certbot
Um die Vorschläge von hier zu verwenden, habe ich die beiden Scripts auf meinem Github abgelegt. Das Script certbot-new erzeugt Zertifikate im standalone Modus und startet für die challenge response temporär einen Webservice auf Port 54321. Das hook Script kombiniert beide Zertifikate in die notwendige Form für HAProxy.
wget -N -P /usr/bin/ https://raw.githubusercontent.com/heinz-otto/scripts/master/Bash/{letsencrypt-reload-hook,certbot-new}
chmod +x /usr/bin/{letsencrypt-reload-hook,certbot-new}
Damit ist die Ausstellung und Erneuerung der Zertifikate angepasst an die Proxy Konfiguration simpel
certbot-new -d sub1.home.domain.tld
Die Erzeugung eines neuen Zertifikates erzeugt eine conf Datei im Pfad /etc/letsencrypt/renewal/ damit werden alle Parameter dort gespeichert, beim renew muss nichts angegeben werden.
Die Erneuerung der Zertifikate muss noch automatisiert werden. Ich werde dafür einen timer und einen service erzeugen.
/usr/bin/certbot renew --dry-run
Die Service Unit wird nur vom Timer verwendet und selbst nicht aktiviert.
cat <<EOISERV |sudo SYSTEMD_EDITOR=tee systemctl edit --full --force certbot.service
[Unit]
Description=Certbot
Documentation=file:///usr/share/doc/python-certbot-doc/html/index.html
Documentation=https://certbot.eff.org/docs
[Service]
Type=oneshot
ExecStart=/usr/bin/certbot -q renew
PrivateTmp=true
EOISERV
Der Timer wird aktiviert und startet zweimal am Tag zu zufälligen Zeitpunkten.
cat <<EOISERV |sudo SYSTEMD_EDITOR=tee systemctl edit --full --force certbot.timer
[Unit]
Description=Run certbot twice daily
[Timer]
OnCalendar=*-*-* 00,12:00:00
RandomizedDelaySec=43200
Persistent=true
[Install]
WantedBy=timers.target
EOISERV
systemctl enable --now certbot.timer
Fehlt etwas?
- Authentication
- Websocket
Es gibt eine ausführliche Doku.
Nextcloud hinter HAProxy mit SSL Passthrough (Layer4) link.
ToDo
Code
Keine Kommentare:
Kommentar veröffentlichen