Hi zusammen,
Ich will hier mal berichten, was ich (in mühevoller Kleinarbeit) mit Docker anstelle.
Was man dafür können muss/bzw. lernen kann:
- docker (siehe geniale Tutorials von Frank)
- docker-compose um docker-container zusammen zu konfigurieren
- SSL-Verbindungen testen, z.B. mit curl
- Sinn und Zweck eines Reverse Proxy verstehen
- log-Dateien lesen und interpretieren
- Namensauflösung global und lokal
- Letsencrypt Zertifikat Handling
Das folgende docker-compose-file hat folgende Voraussetzungen
- normales docker auf einem ubuntu 18.04
- ich verwende intern auch die externe domäne (hier: meine-schule.de)
- ich leite jeglichen port 443 und 80 Trafik von extern auf den Dockerhost um (zwei Regeln im IPFire), auf dem IPFire läuft also kein Reverse Proxy mehr (weder pound noch haproxy)
- manche Hosts haben Aliase, die im bind des lmn-servers definiert werden müssen. Ich habe bei Belwue gebeten alle diese subdomänen als Aliase (CNAME, denke ich) für meine IP einzutragen.
docker-compose.yml im Detail erklärt
proxy
version: '2'
services:
proxy:
restart: always
image: jwilder/nginx-proxy
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- /srv/docker/nginx/certs:/etc/nginx/certs:ro
- /srv/docker/nginx/vhost.d:/etc/nginx/vhost.d
- /srv/docker/nginx/html:/usr/share/nginx/html
# - /srv/docker/nginx/custom.conf:/etc/nginx/conf.d/my_proxy.conf:ro # if you need a custom global nginx configuration
- /srv/docker/nginx/nginx.tmpl:/app/nginx.tmpl # if you need a custom (global) nginx template
labels:
- "com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy"
Nach Anleitung vom Ersteller ist das die Beschreibung für einen Dockercontainer, der nginx als Proxy startet und auf den Ports für http (80) und https (443) lauscht. Den Port 80 brauche ich nur, damit ich den einfachsten Letsencrypt-modus über http verwenden kann. Wer es anders macht (dns-01 challenge) braucht den nicht.
- Ein Reverse Proxy ist ein Verteiler von Anfragen. Klassisch z.B. frage ich bei cloud.meine-schule.de nach Dateien und bei moodle.meine-schule.de nach der Lernumgebung. Beide stehen in der Schule und die Schule hat nur eine weltweite IP-Adresse. Mit dem Proxy geht das trotzdem.
- Die Zeile
/srv/docker/nginx/certs
sagt, dass im Proxy-container dieser Ordner für SSL-Zertifikate verwendet wird (gemanaged werden di Zertifikate im nächsten Container, daher sind sie hier ro=read-only) - Die nächste Zeile erlaubt pro gestartetem Dockercontainer eine eigene nginx-config. Eigentlich für Pro-user, aber ich brauche das schon, damit ich bei nextcloud mehr als 2MB hochladen kann:
/srv/docker/nginx/vhost.d/cloud.meine-schule.de
:client_max_body_size 500m;
- Ähnliches gilt für die weiteren Zeilen, z.B. Ich habe eine eigene
nginx.tmpl
eingefügt, weil ich http/2 ausschalten musste/wollte und weil ich noch dafür sorgen musste, dass Anfragen an Adressen, die nicht im Dockercontainer laufen auch funktionieren (meine Cloud z.B.)
/srv/docker/nginx/nginx.tmpl
snip... server { server_name {{ $host }}; listen 443 ssl {{ $default_server }}; snip...
- Die Labelzeile ist für die Aktualisierung der Letsencrypt-Zertifikate nötig.
Proxy Compagnon für LE-Zertifikate
proxy-le-companion:
restart: always
image: jrcs/letsencrypt-nginx-proxy-companion
volumes:
- /srv/docker/nginx/certs:/etc/nginx/certs:rw
- /var/run/docker.sock:/var/run/docker.sock:ro
volumes_from:
- proxy
Das beschreibt den Container, der die Zertifikate von Letsencrypt holt und abspeichert (Doku beginnt auch bei jwilder, aber dann liest man hier weiter.)
Bsp: Ein eigener Server im Dockercontainer
api:
image: hgkvplan/linuxmuster-vplan
restart: always
volumes:
- /root/vplan/linuxmuster-vplan-fetch-hgk.sh:/app/linuxmuster-vplan-fetch.sh
- /root/vplan/configuration-hgk.py:/app/configuration.py
- /root/vplan/dsbservice.rsa:/app/dsbservice.rsa
- /etc/hosts:/etc/hosts:ro
- ./parser.log:/app/parser.log
environment:
- "VIRTUAL_HOST=api.meine-schule.de, docker.meine-schule.de"
- "LETSENCRYPT_HOST=api.meine-schule.de, docker.meine-schule.de"
- "LETSENCRYPT_EMAIL=t.kuechel@humboldt-ka.de"
# - "LETSENCRYPT_TEST=true"
Ein beliebiger Server, der als Dockercontainer läuft lässt sich seeehr einfach mit SSL-Zertifikat ausstatten und von außen erreichen. In diesem Fall ist das ein eigener API-Server, der meinen Vertretungsplan zurückliefert.
- Er läuft als Dockercontainer und braucht ein paar Dateien, wie einen RSA-Schlüssel und log-dateien und eine /etc/hosts-Datei.
- Am VIRTUAL_HOST erkennt man, wie der API-Server von außen angesprochen wird. Aus historischen Gründen hieß der host “docker.meine-schule.de”, jetzt soll er von außen aber mit “api.meine-schule.de” angesprochen werden. Beides geht gleichzeitig.
- am LETSENCRYPT_HOST erkennt man für welche Namen bei LE Zertifikate geholt werden sollen.
Nextcloud: Ein Server, der nicht im Docker liegt
cloud:
image: python:3-slim
restart: always
command: tail -f /dev/null
environment:
- "VIRTUAL_HOST=cloud.meine-schule.de"
- "VIRTUAL_PORT=443"
- "VIRTUAL_PROTO=https"
- "UPSTREAM_NAME=172.16.17.2"
- "LETSENCRYPT_HOST=cloud.meine-schule.de, cleese.meine-schule.de"
### need to copy the certificate
- "LETSENCRYPT_EMAIL=t.kuechel@humboldt-ka.de"
# - "LETSENCRYPT_TEST=true"
Jetzt mal ein Server, der nicht als Dockercontainer läuft: Meine Cloud (auf einem virtuell und physikalisch anderen Rechner)
- Dieser Containerdienst ist ein Dummy: das Image “python:3-slim” ist arbitrary, denn das Kommando
tail -f /dev/null
macht gar nix. Interessant ist was dann kommt: - VIRTUAL_HOST siehe oben,
- VIRTUAL_PORT und _PROTO sind klar: das sind die Daten um mit SSL auf meine Cloud zuzugreifen, die aber nicht im Dockercontainer läuft,
- stattdessen ist UPSTREAM_NAME die IP-Adresse der Cloud-instanz.
- Alles weitere kennt man, wobei Kommentar daraufhin deutet, dass hier im Dummy-Container das LE-Zertifikat hergestellt wird, ich es aber noch irgendwie auf den Cloud-Server bekommen muss.
Dieser Teil war der schwierigste von allen. Ohne die Änderungen am nginx.tmpl
von CWempe würde diese Weiterleitung an einen externen Server nicht funktionieren, zumindest nicht inklusive Bereitstellung der LE-Zertifikate.
lmn-server: Nicht im Docker
server:
image: python:3-slim
restart: always
command: tail -f /dev/null
environment:
- "VIRTUAL_HOST=server.meine-schule.de"
- "VIRTUAL_PORT=443"
- "VIRTUAL_PROTO=https"
- "UPSTREAM_NAME=10.16.1.1"
- "LETSENCRYPT_HOST=server.meine-schule.de, meine-schule.de"
### need to copy the certificate
- "LETSENCRYPT_EMAIL=t.kuechel@humboldt-ka.de"
# - "LETSENCRYPT_TEST=true"
Weiteres Beispiel für einen externen Service. Den gleichen Kram mache ich nochmal, weil ich auf meinem v6.2-Server ein MRBS laufen habe, dass erreichbar sein soll. Theoretisch kann man hier auch die Schulkonsole. Praktisch wird bei mir auch mit dem MRBS auch der horde-dienst exponiert (!)
Office für Nextcloud: als Dockercontainer
office:
image: collabora/code
restart: always
ports:
- 9980:9980
cap_add:
- MKNOD
environment:
- "domain=cloud.meine-schule.de"
- "VIRTUAL_HOST=office.meine-schule.de"
- "VIRTUAL_PORT=9980"
- "VIRTUAL_PROTO=https"
- "LETSENCRYPT_HOST=office.meine-schule.de"
- "LETSENCRYPT_EMAIL=t.kuechel@humboldt-ka.de"
# - "LETSENCRYPT_TEST=true"
Das hier beschreibt wieder einen ordentlichen Container: in dem läuft collabora online (aka libreoffice für Nextcloud) und
- man erkennt die Änderungen, die man laut der Anleitung (unten bei Getting started in 3 steps) machen muss,
- nur dass der ganze proxy-quark wegfällt, weil das der nginx-container von oben schon alles automatisch (magisch, ich weiß nicht genau, warum es tut) macht.
- Am LETSENCRYPT-Teil sieht man, dass hier ordentliche Zertifikate für office.meine-schule.de erzeugt werden.
Obwohl also meine Cloud nicht im Docker liegt, liegt im Docker wiederum der Zugriff auf die Officebearbeitung.
gitlab: Ein externer HTTP-only Server
git:
image: python:3-slim
restart: always
command: tail -f /dev/null
environment:
- "VIRTUAL_HOST=git.meine-schule.de, idle.meine-schule.de"
- "VIRTUAL_PORT=80"
- "VIRTUAL_PROTO=http"
- "UPSTREAM_NAME=172.16.17.1"
- "LETSENCRYPT_HOST=git.meine-schule.de, idle.meine-schule.de"
- "LETSENCRYPT_EMAIL=t.kuechel@humboldt-ka.de"
# - "LETSENCRYPT_TEST=true"
Jetzt noch ein Beispiel für einen externen Webserver (hier ein Server, der ein gitlab enthält), der selbst kein SSL beherrscht (weil ich ihn faulerweise so eingerichtet habe). Das Prozedere wie oben:
- ein dummy-Container,
- die Zertifikate werde für ihn dennoch korrekt erstellt und
- hinter dem nginx wird per http/port 80 unverschlüsselt kommuniziert.
kleiner cron-job für die Zertifikate
Bei mir ist immernoch der linuxmuster-Server die Zentrale. Von dort komme ich passwortlos auch zum docker oder bei mir zur cloud, daher kann man folgenden cron-job leicht verstehen und anpassen:
#!/bin/sh
#exit 0
function copy_cert_if_different {
chain=${le_hostpath}/${maindomain}/fullchain.pem
key=$(dirname $chain)/key.pem
copied=0
tmp=$(mktemp) #empty tmp-files
tmp2=$(mktemp)
scp ${le_host}:$key $tmp
scp ${le_host}:$chain $tmp2
if [ -s $tmp -a -s $tmp2 ]; then # exists and not empty
echo >> $tmp # since they do not end with a newline
cat $tmp2 >> $tmp
if ! diff $TARGETDIR/$cert $tmp >/dev/null; then
echo " + Sichere altes Zertifikat"
cp -a $TARGETDIR/$cert $TARGETDIR/${cert}_$TIMESTAMP
echo " + Kopiere neues Zertifikat nach $TARGETDIR"
cp $tmp $TARGETDIR/$cert
chmod 0640 $TARGETDIR/$cert
chown root:ssl-cert $TARGETDIR/$cert
copied=1
else
echo " -- keine Differenz zwischen Zertifikaten"
fi
else
echo "either $chain and $key were not copied from $le_host or it is empty!!"
fi
rm -f $tmp $tmp2
return $copied
}
# TARGET in the standard linuxmuster.net-installation
TARGETDIR=/etc/ssl/private
TIMESTAMP=$(date +%s)
le_host=docker
le_hostpath=/srv/docker/nginx/certs
maindomain=server.meine-schule.de
cert=server.pem
copy_cert_if_different
if [ $? -eq 1 ] ; then
echo " + Restart services"
service apache2 reload
service slapd restart
service cyrus-imapd reload
fi
maindomain=cloud.meine-schule.de
cert=cloud.pem
copy_cert_if_different
if [ $? -eq 1 ] ; then
echo " + Copy to cleese, restart apache2 there"
rsync -avP $TARGETDIR/$cert cleese:/etc/ssl/private/server.pem
ssh cleese service apache2 restart
fi
Für jedes Zertifikat, dass ich kopieren muss, definiere ich die erste Domäne und den Namen und definiere was ich mit dem Zertifikat anfangen will, z.B. auf den entsprechenden Server kopieren und dort die Dienste neu starten. Diese Datei liegt bei mir in /etc/cron.daily/
. Die LE-Zertifikate auf dem Dockerhost werden selbstverständlich automatisch neu erzeugt.
Kritikpunkte
- office.meine-schule.de ist von außen erreichbar. Das ist vollkommen unnötig und sogar DOS-gefährlich, denke ich.
- in der Doku zu nginx-proxy sollte man noch den docker-gen container extra laufen lassen.
- MRBS könnt man auch im container laufen lassen
- komplexes setup?
kommentare, meinungen, kritik willkommen.
VG, Tobias