Server mit Traefik als Reverse Proxy für Docker einrichten
Server mit Docker Containern einrichten, mithilfe von Docker Compose Traefik installieren und als Reverse Proxy zu nutzen.
Inhaltsverzeichnis
Server installieren
Der erste Schritt besteht darin, sich einen Server zu besorgen. In dieser Anleitung benutze ich einen Server, der sich nicht bei mir zu Hause befindet, dies bietet meistens einige Vorteile, wie beispielsweise:
-
Einfachere Erreichbarkeit durch eine feste IPv4 Adresse
-
Schnellere Internetgeschwindigkeit
-
Bessere Kostenprognose möglich
-
Keine Abnutzung eigener Hardware
-
Keine Stromkosten
-
Geringere Anschaffungskosten
-
Keine Geräuschemission zu Hause
Selbstverständlich sollte man sich auch den Nachteilen bewusst sein. Jedenfalls lässt sich diese Anleitung mit jedem Server befolgen, auf dem Docker installiert werden kann. Probleme hatte ich in der Vergangenheit mit virtuellen Servern, die mit OpenVZ statt KVM virtualisiert waren.
Ich werde hier einen VPS S SSD von Contabo nutzen (Kosten liegen bei ca. 6 € / Monat).
Empfehlenswert sind meiner Meinung auch die VPS und insbesondere auch Root-Server (dezidierte CPU) von Netcup. Falls du dort noch nicht Kunde sein solltest, bekommst du mit folgenden Gutscheinen einen Rabatt in Höhe von 5 €, oder einen VPS oder Root-Server günstiger. Ich bekomme eine Provision für geworbene Neukunden. Sollten die Gutscheine abgelaufen sein, kannst du mich gerne nach neuen anschreiben.
Nachdem der Zugriff auf den Server erteilt wird, lässt sich dieser aus dem Kundenbereich heraus neu installieren. Ich wähle als Betriebssystem Ubuntu 20.04 aus. Ein anderes wäre auch möglich, aber ich habe einige Erfahrung mit Ubuntu und sehe keinen Grund zu wechseln. Des Weiteren ist das Ende des Standard-Supports erst im April 2025, somit wird es vermutlich erst mal nicht nötig sein, das Betriebssystem auf die nächste Version zu upgraden.
Nach ein paar Minuten sollte der Server mit der Installation fertig sein. Nun wird es Zeit sich mit dem Server zu verbinden. Dies lässt sich unter Windows mit dem Windows Terminal, PuTTY oder einem ähnlichen Programm bewerkstelligen. Ich nutze das Windows Terminal.
Anschließend gegebenenfalls die Frage mit „yes“ beantworten und das Passwort eingeben. Während der Eingabe wird das Passwort nicht angezeigt. Folgende Meldung sollte nun erscheinen (oder ähnlich):
Als Erstes schauen wir mal, ob es Updates gibt:
apt update
Falls Updates existieren, installieren wir diese anschließend:
apt upgrade
Die Frage, ob man updaten möchte, mit Y bestätigen.
Der Texteditor Nano sollte schon standardmäßig installiert sein, aber zur Sicherheit kann man ihn so installieren:
apt install nano
Als Nächstes benennen wir den Server um:
nano /etc/hostname
In Nano wird die Datei mit Strg + X verlassen. Nano wird euch Fragen, ob ihr die Änderungen speichern wollt (mit Y bestätigen) und wie ihr die Datei nennen wollt. Da wir sie überschreiben möchten, ändern wir den Namen nicht und bestätigen einfach mit Enter.
Sobald du den Server neu startest reboot
und dich anschließend wieder mit ihm verbindest ssh admin@{ip-Adresse}
ist der neue Host-Name sichtbar.
Jetzt ist es sinnvoll einen Nutzer anzulegen, der nicht root ist. Ohne Rechteeinschränkungen ist es mit root nämlich ziemlich einfach sein System zu zerstören.
useradd -m -G sudo namedesneuennutzers
passwd namedesneuennutzers
useradd
ist der Befehl, um einen neuen Nutzer anzulegen. Das -m
sorgt dafür, dass dieser Nutzer einen Home-Ordner eingerichtet bekommt. Das -G
packt den Nutzer in eine Gruppe, anschließend wird die Gruppe genannt. Die Sudo Gruppe darf Befehle mit sudo
ausführen. Zuletzt folgt der Name des neuen Nutzers. Mit passwd
wird ein Password für den Nutzer erstellt.
Sollte die sudo Gruppe nicht existieren, musst du eine Gruppe erstellen.
groupadd namederneuengruppe
Diese Gruppe sollte das Recht haben Sudo-Befehle auszuführen:
EDITOR=nano visudo
In der Datei muss dann die neue Gruppe erwähnt werden.
Nun stellen wir bei Bedarf die richtige Zeit ein.
date
Falls nicht die richtige Zeit angezeigt wird:
tzselect
Über die Zahlen könnt ihr eure Zeitzone auswählen.
timedatectl set-timezone 'Europe/Berlin'
Es ist an der Zeit den Server neu zu starten und sich als neu angelegter Nutzer anzumelden.
reboot
Als Nächstes installiere ich Htop (ein Monitoring-Programm).
apt install htop
Jetzt müsste der Server die Ausführung verweigern und fragen, ob man root sei.
Also noch mal mit Superuser do.
sudo apt install htop
Über die Eingabe von htop
kann man sich nun die Auslastung des Servers anschauen.
Damit lässt sich gut verfolgen, wie stark die CPU und der RAM des Servers beansprucht werden. Mit F10 oder STRG + C verlässt du die Ansicht wieder.
Ich möchte gerne noch eine andere Shell haben.
sudo apt install git curl
sudo apt install zsh
Jetzt installiere ich zim und ändere die Standard-Shell auf zsh.
curl -fsSL https://raw.githubusercontent.com/zimfw/install/master/install.zsh | zsh
chsh -s /bin/zsh
exec bash
Jetzt müsste die Shell (subjektiv) besser aussehen.
Der wichtigste Part der Installation sind Docker und Docker Compose. Außerdem optional noch Lazydocker, für eine sehr einfache Möglichkeit sich seine Docker-Container und deren aktuelle Logs über SSH anzusehen.
sudo apt install ca-certificates curl gnupg lsb-release
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose
curl https://raw.githubusercontent.com/jesseduffield/lazydocker/master/scripts/install_update_linux.sh | bash
Jetzt muss noch Docker aktiviert werden.
sudo systemctl enable --now docker
Sollte die nächste Zeile nicht erscheinen mit STRG + C nachhelfen. Nun noch den angelegten Nutzer der Dockergruppe zuordnen.
sudo gpasswd -a namedesneuennutzers docker
Sicherheitstechnisch sollte man das nicht so machen. Dann muss man aber immer wieder sudo
vor die Docker Befehle zu schreiben. Überprüfe noch, ob der Benutzer tatsächlich in der Gruppe gelandet ist.
id namedesneuennutzers
Als Letztes richten wir einen Ordner im Heimverzeichnis des neuen Nutzers an.
cd ~
mkdir docker
Mit cd
(“change directory”) bewegen wir uns durch die Ordner, wobei die Tilde (~) für home steht. mkdir
erstellt einen neuen Ordner. Man kann Ordner bei Bedarf auch wieder löschen.
mkdir docker2
# erstellt Ordner
mkdir -rf docker2
# löscht Ordner
Folgende Eingabe sollte nun den neu erstellten Ordner zeigen.
ls
Wir können uns auch die Berechtigungen des Ordners anschauen und den Eigentümer.
ls -l
Oder auch die ausgeblendeten Ordner.
ls -A
# bzw.
ls -Al
Wir können uns in den neuen Ordner bewegen.
cd docker
# und wieder zurück
cd ..
Im Übrigen kann man viel Zeit mit der Tabulator-Taste sparen. Diese Taste vervollständigt Befehle. Gibt man im Home-Ordner cd d
ein und drückt Tab, wird das Wort automatisch vervollständigt. Über die Pfeiltasten oben und unten lassen sich die letzten Befehle anzeigen. Pfeiltaste nach rechts übernimmt den vollständigen Befehl, wenn einer vorgeschlagen wird.
Sollte sich Lazydocker öffnen lassen, ist die Einrichtung abgeschlossen.
sudo lazydocker
Mit Q, ESC oder STRG + C lässt sich Lazydocker wieder beenden. Mit exit
verlassen wir den Server.
exit
Einrichtung einer Domain
Der Server besitzt nur eine IP-Adresse, wir möchten aber eventuell mehrere Dienste auf dem Server einrichten. Möglichkeit 1 wäre es, diese über verschiedene Ports laufen zu lassen, dann würde man die Dienste über die Port-Nummer ansteuern müssen (123.45.67.89:443, 123.45.67.89:9000, usw.). Das ist erstens schwer zu merken und zweitens gibt es ein Problem mit den SSL Zertifikaten, es entsteht ein Sicherheitsrisiko. Deswegen ist es besser einen Proxy Manager auf Port 80 und 443 (die Standardports für http und https) zu schalten, dieser leitet dann die Anfragen weiter.
Dafür benötigen wir (Sub-)Domains. So ist es beispielsweise mit der Subdomain “mail.domain1.de” das Mailprogramm zu erreichen und mit” blog.domain1.de” den Blog. Möglich wäre das zwar auch mit Unterverzeichnissen (wie z. B. “domain1.de/mail” und “domain1.de/blog”), aber ich finde Subdomains schöner.
Es gibt zwar kostenlose Domains bei bspw. DuckDNS, aber die sind meist mit Einschränkungen verbunden. Studenten können sich bei Github auch eine vollwertige kostenlose Domain für ein Jahr besorgen.
The best developer tools, free for students. Get your GitHub Student Developer Pack now.
Jedoch ist es auch nicht so teuer, sich eine Domain zu kaufen. Bei Netcup kostet eine .de Domain dauerhaft 5 € im Jahr. Jeder andere Registrar ist auch in Ordnung, solange das DNS und am besten auch die Nameserver eingestellt werden können. Achte auch darauf, dass es bei einigen Anbietern ab dem zweiten Jahr teurer wird, insbesondere dann, wenn es im ersten Jahr nur 1 € kostet.
Im CCP von Netcup rufe ich den Menüpunkt Domains auf:
Uns interessiert der Tab mit den DNS Einstellungen:
Ich verringere zunächst die TTL ein wenig (24h ist mir zu lang).
Anschließend müssen einige Einträge gemacht werden. Das Zeichen @ bedeutet, dass es um den Root der Zone geht, also meinedomain.de wird auf die IP-Adresse 160.90.80.80 (IP-Adresse meines Servers) verwiesen. Außerdem werden alle (deshalb das ”_”) Subdomains von meinedomain.de wiederum auf meinedomain.de geroutet. Statt _ könnte man auch einzelne Einträge machen. Das kann z. B. dann sinnvoll sein, wenn man mehrere Server unter unterschiedlichen IP-Adressen betreibt.
Die Einstellungen werden über die Schaltfläche gespeichert. Nun muss man warten. Es kann einen Tag dauern bis die Einstellungen übernommen werden, in seltenen Fällen sogar auch zwei. In der Regel geht es aber deutlich schneller.
Wir testen, ob es geklappt hat, indem wir das Terminal aufrufen und versuchen die Domain zu pingen.
ping dieeingerichtetedomain.de
Erscheint jetzt die Meldung, dass die Ping-Anforderung den Host nicht finden konnte, wissen wir, dass die Einträge noch nicht übernommen wurden.
Manchmal hilft es, die gecachten DNS im System zu leeren.
ipconfig /flushdns
Anschließend noch mal versuchen. Wenn die IP-Adresse des Servers angezeigt wird, ist die Domain fürs Erste eingerichtet.
Einrichtung von Traefik
Wir benötigen einen Proxy Manager, der jetzt die Anfragen in Abhängigkeit der (Sub-)Domain weiterleiten kann. Auch wenn ich mich in der Vergangenheit eigentlich mit dem Nginx Proxy Manager angefreundet habe, möchte ich hier Traefik v2 nutzen.
Traefik ist ebenfalls ein open source Reverse Proxy, der aber auf Docker aufsetzt. Zwar ist die Einrichtung meiner Meinung nach komplizierter, dafür ist es anschließend leichter, dynamisch weitere Container mit Traefik zu starten. An dieser Stelle verweise ich außerdem auf zwei andere Anleitungen, die ich zur Konfiguration genutzt habe. Das ist einmal ein Blogeintrag von DigitalOcean und einer von Smarthomebeginner.
Zunächst geben wir der Gruppe Docker die nötigen Rechte am Ordner docker. Dazu benötigen wir ACL.
sudo apt install acl
cd ~
sudo setfacl -Rdm g:docker:rwx docker
sudo setfacl -Rm g:docker:rwx docker
sudo chmod -R 775 docker
getfacl docker
Es müsste nun angezeigt werden, dass die Gruppe Docker am Ordner docker die Rechte zum Lesen ( read), Schreiben (write) und Ausführen (execute) hat.
Wir erstellen eine Datei mit Umgebungsvariablen. Es gibt zwar auch Stimmen, die meinen, man sollte Umgebungsvariablen vermeiden bzw. für sensible Daten nicht nutzen, aber mir erscheint es immer noch besser, als sie direkt einzusetzen (meiner Meinung nach noch unsicherer) oder so was wie Keywhiz zu nutzen (ist mir zu viel Aufwand). Deshalb erstellen wir die Datei mit den nötigen Variablen. Zuvor müssen wir unsere User ID und die Gruppen ID von Docker auslesen.
id
Diese beiden Zahlen müssen wir in die Umgebungsdatei einfügen.
cd docker
nano .env
PUID=1000
PGID=114
Jetzt sollten noch die Zeitzone, die Verzeichnispfade und die eigene Domain eingefügt werden.
pwd
nano .env
PUID=1000
PGID=114
TZ=Europe/Berlin
USERDIR=/home/jewgeni
DOCKERDIR=/home/jewgeni/docker
DOMAINNAME=deployn.de
In der Shell ist es überdies möglich über > bzw. >> Dinge direkt in eine Datei zu schreiben. Der Befehl echo wiederholt die Eingabe. Das testen wir mal.
cd ~
echo "hallo"
Es sollte jetzt das Wort „hallo“ erscheinen.
echo "hallo" > test.txt
ls
# Wir sehen, dass im Home-Verzeichnis unseres Benutzers nun eine Textdatei liegt.
# Die Raute am Anfang der Zeile steht übrigens für einen Kommentar.
# Dieser Code wird nicht ausgeführt.
nano test.txt
In der Datei steht „hallo“ :) Diese Datei lässt sich durch weiteren Text ergänzen.
echo "tschüss" > test.txt
# Das gibt einen Fehler, weil die Datei schon exisitiert.
# Mit >> fügen wir einen Text in eine bestehende Datei als neue Zeile ein.
echo "tschüss" >> test.txt
nano test.txt
Wenn bei der Eingabe im Terminal grauer Text erscheint, lässt sich dieser mit der Pfeiltaste nach rechts übernehmen. In der Textdatei sind nun zwei Zeilen. Wir löschen sie wieder.
rm test.txt
ls
Es sollte nur noch der Ordner “docker” sichtbar sein. Nun zum weiteren Vorgang. Wir benötigen als nächstes eine Möglichkeit gehashte Passwörter zu erstellen, dafür installieren wir die Apache2 Hilfsprogramme und testen ob über htpasswd ein gehastes Passwort erstellt wird.
sudo apt install apache2-utils
htpasswd -nb benutzername passwort
Ergebnis (sollte ähnlich aussehen):
Jetzt nutzt du die Daten, die wirklich zur Authentifizierung dienen sollen (am besten ein neues Passwort über einen Passwortgenerator erstellen).
mkdir ~/docker/shared/
htpasswd -nb einbenutzername neuespasswort > ~/docker/shared/.htpasswd
Als Nächstes benötigen wir die Daten für unsere Zertifikate. Wenn sich deine Domain nicht bei Netcup befindet, müssen die Angaben angepasst werden. Bei Netcup benötigen wir drei Angaben: Kundennummer, API-Schlüssel und API-Passwort.
In den Stammdaten des CCP gibt es den API-Menüpunkt. Hier kannst du dir einen API-Schlüssel generieren lassen. Lies jedoch vorher die Nutzungsbedingungen durch. Mit dem API-Schlüssel ist es möglich Domainkäufe zu tätigen. Allein aus diesem Grund sollte das Passwort geheim gehalten werden (im Übrigen auch dein Passwort zum CCP).
Diese Werte tragen wir in die .env Datei ein, außerdem noch eine eigene E-Mail Adresse für die Let’s encrypt Zertifikate
nano ~/docker/.env
PUID=1000
PGID=114
TZ=Europe/Berlin
USERDIR=/home/jewgeni
DOCKERDIR=/home/jewgeni/docker
DOMAINNAME=einedomain.de
NETCUP_CUSTOMER_NUMBER=50000
NETCUP_API_KEY=LDBfH4MNyDlgBgww0lGry0OkouhkMqUI5E
NETCUP_API_PASSWORD=uEzCAzISBUBwGqW9oSEzRJuL3D26Gm1yQFMlnqSB1Lhjjb5Z98
EMAIL_ADDRESS=mail@hallo.de
Als Nächstes erstellen wir einen Ordner für Traefik und unsere Zertifikate. Außerdem noch die Zertifikat-Datei und eine Log-Datei.
mkdir ~/docker/traefik
mkdir ~/docker/traefik/acme
touch ~/docker/traefik/traefik.log
touch ~/docker/traefik/acme/acme.json
chmod 600 ~/docker/traefik/acme/acme.json
Jetzt erstellen wir uns ein neues Netzwerk in Docker.
docker network create web
docker network ls
Es sind nun vermutlich vier Einträge zu sehen (bridge, host, none und web). Zeit unsere hauptsächliche docker-compose-Datei zu erstellen.
nano ~/docker/docker-compose.yml
Jetzt den Text eingeben.
version: "3.7"
### NETWORKS ###
networks:
web:
external:
name: web
default:
driver: bridge
### SERVICES ###
services:
traefik:
container_name: traefik
image: traefik:latest
restart: unless-stopped
command:
- --global.checkNewVersion=true
- --global.sendAnonymousUsage=false
- --entryPoints.http.address=:80
- --entryPoints.https.address=:443
- --entryPoints.traefik.address=:8080
- --api=true
- --api.dashboard=true
- --log=true
- --log.level=DEBUG
- --accessLog=true
- --accessLog.filepath=/traefik.log
- --accessLog.bufferingSize=100
- --accessLog.filters.statusCodes=400-499
- --providers.docker=true
- --providers.docker.endpoint=unix:///var/run/docker.sock
- --providers.docker.exposedByDefault=false
- --providers.docker.network=web
- --providers.docker.swarmMode=false
- --providers.file.directory=/rules
- --providers.file.watch=true
- --certificatesResolvers.dns-netcup.acme.dnsChallenge.provider=netcup
- --certificatesResolvers.dns-netcup.acme.caServer=https://acme-staging-v02.api.letsencrypt.org/directory
- --certificatesResolvers.dns-netcup.acme.email=$EMAIL_ADDRESS
- --certificatesResolvers.dns-netcup.acme.storage=/acme.json
- --certificatesResolvers.dns-netcup.acme.dnsChallenge.resolvers=1.1.1.1:53,8.8.8.8:53
- --certificatesResolvers.dns-netcup.acme.dnschallenge.delayBeforeCheck=60
networks:
- web
security_opt:
- no-new-privileges:true
ports:
- target: 80
published: 80
protocol: tcp
mode: host
- target: 443
published: 443
protocol: tcp
mode: host
- target: 8080
published: 8080
protocol: tcp
mode: host
volumes:
- $DOCKERDIR/traefik/rules:/rules
- /var/run/docker.sock:/var/run/docker.sock:ro
- $DOCKERDIR/traefik/acme/acme.json:/acme.json
- $DOCKERDIR/traefik/traefik.log:/traefik.log
- $DOCKERDIR/shared:/shared
environment:
- NETCUP_CUSTOMER_NUMBER=$NETCUP_CUSTOMER_NUMBER
- NETCUP_API_KEY=$NETCUP_API_KEY
- NETCUP_API_PASSWORD=$NETCUP_API_PASSWORD
labels:
- "traefik.enable=true"
- "traefik.http.routers.http-catchall.entrypoints=http"
- "traefik.http.routers.http-catchall.rule=HostRegexp(`{host:.+}`)"
- "traefik.http.routers.http-catchall.middlewares=redirect-to-https"
- "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
- "traefik.http.routers.traefik-rtr.entrypoints=https"
- "traefik.http.routers.traefik-rtr.rule=Host(`traefik.$DOMAINNAME`)"
- "traefik.http.routers.traefik-rtr.tls=true"
- "traefik.http.routers.traefik-rtr.tls.certresolver=dns-netcup"
- "traefik.http.routers.traefik-rtr.tls.domains[0].main=$DOMAINNAME"
- "traefik.http.routers.traefik-rtr.tls.domains[0].sans=*.$DOMAINNAME"
- "traefik.http.routers.traefik-rtr.service=api@internal"
- "traefik.http.routers.traefik-rtr.middlewares=middlewares-basic-auth@file"
Damit die letzte Zeile funktioniert, müssen wir die Middleware konfigurieren. Das passiert mit einer Datei im Ordner mit allen Regeln.
mkdir ~/docker/traefik/rules
nano ~/docker/traefik/rules/middlewares.toml
[http.middlewares]
[http.middlewares.middlewares-basic-auth]
[http.middlewares.middlewares-basic-auth.basicAuth]
realm = "Traefik Basic Auth"
usersFile = "/shared/.htpasswd"
Jetzt ist der Zeitpunkt gekommen alles zu testen.
cd ~/docker
docker-compose up -d
Sobald alles fertig ist, schauen wir uns das automatische Protokoll im rechten Fenster an.
lazydocker
Wenn die Meldung kommt, dass auf eine DNS record propagation gewartet wird, ist alles soweit richtig.
In diesem Fall schrauben wir den Log-Level zurück auf WARN und holen echte Zertifikate, indem wir die Zeile mit --certificatesResolvers.dns-netcup.acme.caServer=https://acme-staging-v02.api.letsencrypt.org/directory
auskommentieren.
nano ~/docker/docker-compose.yml
Anschließend löschen wir den Inhalt der acme-Datei.
nano ~docker/traefik/acme/acme.json
In dieser Datei den kompletten Inhalt löschen (auch die Leerzeilen). Jetzt Traefik neu starten.
cd ~/docker
docker-compose down
docker-compose up -d
Warten (5 Minuten), danach versuchen das Dashboard über traefik.dieeigenedomain.de zu öffnen. Falls es nicht klappt, noch mal den Log-Level auf DEBUG setzen, Container neu starten und schauen, was das Log anzeigt.
Um ins Dashboard zu gelangen, musst du den Benutzernamen und das nicht gehashte Passwort eingeben, das du in die htpasswd Datei geschrieben hast. Wenn alles geklappt hat, können wir Traefik dazu bewegen ein Wildcard-Zertifikat zu nutzen.
nano ~/docker/docker-compose.yml
Diesmal muss das Label “traefik.http.routers.traefik-rtr.tls.certresolver=dns-netcup” auskommentiert werden.
cd ~/docker
docker-compose down
docker-compose up -d
Wir ergänzen unsere Middleware um ein Rate-Limit zur Anmeldung und Security Headers. Hier oder hier sind zwei Beispiele für weitere Informationen über Security Headers.
nano ~/docker/traefik/rules/middlewares.toml
[http.middlewares]
[http.middlewares.middlewares-basic-auth]
[http.middlewares.middlewares-basic-auth.basicAuth]
realm = "Traefik Basic Auth"
usersFile = "/shared/.htpasswd"
[http.middlewares.middlewares-rate-limit]
[http.middlewares.middlewares-rate-limit.rateLimit]
average = 100
burst = 50
[http.middlewares.middlewares-secure-headers]
[http.middlewares.middlewares-secure-headers.headers]
accessControlAllowMethods= ["GET", "OPTIONS", "PUT"]
accessControlMaxAge = 100
hostsProxyHeaders = ["X-Forwarded-Host"]
sslRedirect = true
stsSeconds = 63072000
stsIncludeSubdomains = true
stsPreload = true
forceSTSHeader = true
customFrameOptionsValue = "allow-from https:dieeigenedomain.de"
contentTypeNosniff = true
browserXssFilter = true
referrerPolicy = "same-origin"
featurePolicy = "camera 'none'; geolocation 'none'; microphone 'none'; payment 'none'; usb 'none'; vr 'none';"
[http.middlewares.middlewares-secure-headers.headers.customResponseHeaders]
X-Robots-Tag = "none,noarchive,nosnippet,notranslate,noimageindex,"
server = ""
Wir haben drei Middlewares eingerichtet:
- Authentifizierung
- Rate-Limit
- Security-Headers
Um sie zu nutzen, können wir sie einzeln in die docker-compose Datei eintragen oder Ketten mit Kombinationen aus ihnen einrichten.
Um eine Kette zu erstellen, kreieren wir eine neue Datei im Rules-Ordner:
nano ~/docker/traefik/rules/middlewares-chains.toml
[http.middlewares]
[http.middlewares.chain-no-auth]
[http.middlewares.chain-no-auth.chain]
middlewares = [ "middlewares-rate-limit", "middlewares-secure-headers"]
[http.middlewares.chain-basic-auth]
[http.middlewares.chain-basic-auth.chain]
middlewares = [ "middlewares-rate-limit", "middlewares-secure-headers", "middlewares-basic-auth"]
Natürlich könnt ihr auch andere Kombinationen ergänzen, aber dann ist es eventuell einfacher, die oben genannte Code-Zeile zu nutzen und die Dinge zu kombinieren, die man haben möchte. Durch die Ketten ist es möglich, die drei oben gezeigten Zeilen in eine zu ändern:
Nachdem dieses „Etikett“ in der docker-compose Datei eingetragen ist, muss Traefik neu gestartet werden, damit die Änderungen in Kraft treten.
cd ~/docker
nano docker-compose
# Datei anpassen (- "traefik.http.routers.traefik-rtr.middlewares=middlewares-chain-basic-auth@file" in die labels einfügen)
# Auf die Einrückung beim Einfügen achten
docker-compose down
docker-compose up -d
Jetzt im Dashboard nachsehen, ob die Middleware richtig geladen werden.
Und prüfen, ob die Kette als Middleware im HTTP-Router von Traefik geladen wird:
Google Authentifizierung
Die Authentifizierung, die wir bereits eingerichtet haben, bringt zwar zusätzliche Sicherheit, aber es gibt Authentifizierungsmethoden mit mehr Funktionen. Deshalb möchten wir uns mal Google OAuth anschauen.
Meet your business challenges head on with cloud computing services from Google, including data management, hybrid & multi-cloud, and AI & ML.
— Cloud Computing Services | Google Cloud
Es ist nötig sich in der Cloud-Konsole zu registrieren (kostenlos). Nach einigen Klicks gelangt man ins Dashboard.
Hier erstellen wir ein neues Projekt und benennen es so, dass wir es wiederfinden.
Zunächst muss ein Zustimmungsbildschirm erstellt werden.
Dahin gelangen wir über den Menüpunkt APIs & Dienste.
Ein Name muss angegeben werden, eine E-Mail-Adresse auch. Optional kann auch ein Logo hochgeladen werden.
Eine Domain muss autorisiert werden. Das Limit liegt bei 10.
Bei den Bereichen wählen wir die drei nicht kritischen Bereiche aus. E-Mail, Profile und OpenID.
Bei den Testnutzern haben wir eine Obergrenze von 100. Diese 100 Nutzer dürfen die Anwendung dahinter nutzen. Also trag dich da am besten selbst ein, andere Nutzer lassen sich später nachtragen.
Über APIs & Dienste wählen wir Anmeldedaten.
Nun fügen wir eine OAuth-Client-ID hinzu.
Wir tragen eine autorisierte Weiterleitungsdomain ein.
Die Schlüssel, die uns soeben angezeigt werden, speichern wir in die .env Datei ab.
nano ~/docker/.env
Die neuen Einträge nennen wir GOOGLE_CLIENT_ID
und GOOGLE_CLIENT_SECRET
. Danach erstellen wir ein GOOGLE_OAUTH_SECRET
.
echo "GOOGLE_OAUTH_SECRET=$(openssl rand -hex 16)" >> ~/docker/.env
nano ~/docker/.env
Die Datei dürfte jetzt ungefähr so aussehen:
PUID=1000
PGID=114
TZ=Europe/Berlin
USERDIR=/home/jewgeni
DOCKERDIR=/home/jewgeni/docker
DOMAINNAME=irgendeinedomain.de
NETCUP_CUSTOMER_NUMBER=50000
NETCUP_API_KEY=lksjfrjgoigKJLKjlhir
NETCUP_API_PASSWORD=SlkjiojKLhiuuzvTFDENgdeu
EMAIL_ADDRESS=mail@gmail.com
GOOGLE_CLIENT_ID=uhrlkiaungopterktdsnf.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=lkiz537Jkhkr
GOOGLE_OAUTH_SECRET=leaj7GjejmG
Jetzt muss diese Authentifizierungsmethode auch Traefik hinzugefügt werden. Zunächst als Middleware.
nano ~/docker/traefik/rules/middlewares.toml
[http.middlewares]
[http.middlewares.middlewares-basic-auth]
[http.middlewares.middlewares-basic-auth.basicAuth]
realm = "Traefik Basic Auth"
usersFile = "/shared/.htpasswd"
[http.middlewares.middlewares-oauth]
[http.middlewares.middlewares-oauth.forwardAuth]
address = "http://oauth:4181"
trustForwardHeader = true
authResponseHeaders = ["X-Forwarded-User"]
[http.middlewares.middlewares-rate-limit]
[http.middlewares.middlewares-rate-limit.rateLimit]
average = 100
burst = 50
[http.middlewares.middlewares-secure-headers]
[http.middlewares.middlewares-secure-headers.headers]
accessControlAllowMethods= ["GET", "OPTIONS", "PUT"]
accessControlMaxAge = 100
hostsProxyHeaders = ["X-Forwarded-Host"]
sslRedirect = true
stsSeconds = 63072000
stsIncludeSubdomains = true
stsPreload = true
forceSTSHeader = true
customFrameOptionsValue = "allow-from https:example.com"
contentTypeNosniff = true
browserXssFilter = true
referrerPolicy = "same-origin"
featurePolicy = "camera 'none'; geolocation 'none'; microphone 'none'; payment 'none'; usb 'none'; vr 'none';"
[http.middlewares.middlewares-secure-headers.headers.customResponseHeaders]
X-Robots-Tag = "none,noarchive,nosnippet,notranslate,noimageindex,"
server = ""
Und dann als Kette.
nano ~/docker/traefik/rules/middlewares-chains.toml
[http.middlewares]
[http.middlewares.chain-no-auth]
[http.middlewares.chain-no-auth.chain]
middlewares = [ "middlewares-rate-limit", "middlewares-secure-headers"]
[http.middlewares.chain-basic-auth]
[http.middlewares.chain-basic-auth.chain]
middlewares = [ "middlewares-rate-limit", "middlewares-secure-headers", "middlewares-basic-auth"]
[http.middlewares.chain-oauth]
[http.middlewares.chain-oauth.chain]
middlewares = [ "middlewares-rate-limit", "middlewares-secure-headers", "middlewares-oauth"]
Bevor wir das testen können, müssen wir aber den OAuth-Dienst in Docker starten. Also ergänzen wir die docker-compose Datei.
nano ~/docker/docker-compose.yaml
version: "3.7"
### NETWORKS ###
networks:
web:
external:
name: web
default:
driver: bridge
### SERVICES ###
services:
### TRAFIK - REVERSE PROXY ###
traefik:
container_name: traefik
image: traefik:latest
restart: unless-stopped
command:
- --global.checkNewVersion=true
- --global.sendAnonymousUsage=false
- --entryPoints.http.address=:80
- --entryPoints.https.address=:443
- --entryPoints.traefik.address=:8080
- --api=true
- --api.dashboard=true
- --log=true
- --log.level=WARN
- --accessLog=true
- --accessLog.filepath=/traefik.log
- --accessLog.bufferingSize=100
- --accessLog.filters.statusCodes=400-499
- --providers.docker=true
- --providers.docker.endpoint=unix:///var/run/docker.sock
- --providers.docker.exposedByDefault=false
- --providers.docker.network=web
- --providers.docker.swarmMode=false
- --providers.file.directory=/rules
- --providers.file.watch=true
- --certificatesResolvers.dns-netcup.acme.dnsChallenge.provider=netcup
# - --certificatesResolvers.dns-netcup.acme.caServer=https://acme-staging-v02.api.letsencrypt.org/directory
- --certificatesResolvers.dns-netcup.acme.email=$EMAIL_ADDRESS
- --certificatesResolvers.dns-netcup.acme.storage=/acme.json
- --certificatesResolvers.dns-netcup.acme.dnsChallenge.resolvers=1.1.1.1:53,8.8.8.8:53
- --certificatesResolvers.dns-netcup.acme.dnschallenge.delayBeforeCheck=10
networks:
- web
security_opt:
- no-new-privileges:true
ports:
- target: 80
published: 80
protocol: tcp
mode: host
- target: 443
published: 443
protocol: tcp
mode: host
- target: 8080
published: 8080
protocol: tcp
mode: host
volumes:
- $DOCKERDIR/traefik/rules:/rules
- /var/run/docker.sock:/var/run/docker.sock:ro
- $DOCKERDIR/traefik/acme/acme.json:/acme.json
- $DOCKERDIR/traefik/traefik.log:/traefik.log
- $DOCKERDIR/shared:/shared
environment:
- NETCUP_CUSTOMER_NUMBER=$NETCUP_CUSTOMER_NUMBER
- NETCUP_API_KEY=$NETCUP_API_KEY
- NETCUP_API_PASSWORD=$NETCUP_API_PASSWORD
labels:
- "traefik.enable=true"
- "traefik.http.routers.http-catchall.entrypoints=http"
- "traefik.http.routers.http-catchall.rule=HostRegexp(`{host:.+}`)"
- "traefik.http.routers.http-catchall.middlewares=redirect-to-https"
- "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
- "traefik.http.routers.traefik-rtr.entrypoints=https"
- "traefik.http.routers.traefik-rtr.rule=Host(`traefik.$DOMAINNAME`)"
- "traefik.http.routers.traefik-rtr.tls=true"
# - "traefik.http.routers.traefik-rtr.tls.certresolver=dns-netcup"
- "traefik.http.routers.traefik-rtr.tls.domains[0].main=$DOMAINNAME"
- "traefik.http.routers.traefik-rtr.tls.domains[0].sans=*.$DOMAINNAME"
- "traefik.http.routers.traefik-rtr.service=api@internal"
- "traefik.http.routers.traefik-rtr.middlewares=chain-oauth@file"
### GOOGLE OAUTH ###
oauth:
container_name: oauth
image: thomseddon/traefik-forward-auth:latest
restart: unless-stopped
networks:
- web
security_opt:
- no-new-privileges:true
environment:
- CLIENT_ID=$GOOGLE_CLIENT_ID
- CLIENT_SECRET=$GOOGLE_CLIENT_SECRET
- SECRET=$GOOGLE_OAUTH_SECRET
- COOKIE_DOMAIN=$DOMAINNAME
- INSECURE_COOKIE=false
- AUTH_HOST=oauth.$DOMAINNAME
- URL_PATH=/_oauth
- WHITELIST=$EMAIL_ADDRESS
- LOG_LEVEL=info
- LOG_FORMAT=text
- LIFETIME=2592000
labels:
- "traefik.enable=true"
- "traefik.http.routers.oauth-rtr.entrypoints=https"
- "traefik.http.routers.oauth-rtr.rule=Host(`oauth.$DOMAINNAME`)"
- "traefik.http.routers.oauth-rtr.tls=true"
- "traefik.http.routers.oauth-rtr.service=oauth-svc"
- "traefik.http.services.oauth-svc.loadbalancer.server.port=4181"
- "traefik.http.routers.oauth-rtr.middlewares=chain-oauth@file"
Sollen mehrere E-Mail-Adressen sich anmelden dürfen, muss die Umgebung bei WHITELIST
erweitert werden. WHITELIST=$EMAIL_ADDRESS, $EMAIL_ADDRESS2, …
Diese Konstanten müssen dann auch in die .env Datei eingetragen werden und ggf. in die Google Cloud Konsole.
Webseite hosten
Als einfachen Beispieldienst möchte ich jetzt eine Webseite (kann auch jeder andere Dienst sein) hosten. Dazu nutze ich das offizielle Docker Image von Nginx.
cd ~/docker
mkdir appdata
mkdir appdata/testseite
cd ~/docker/appdata/testseite
nano index.html
Hier kann nun ein einfacher HTML-Code zum Test eingetragen werden.
<!doctype html>
<html>
<head>
<title>Testseite</title>
</head>
<body>
<p>Hallo</p>
</body>
</html>
Danach benötigen wir eine neue docker-compose Datei für den Dienst.
cd ~/docker
nano docker-compose-testseite.yml
version: "3.7"
### NETWORKS ###
networks:
web:
external:
name: web
default:
driver: bridge
### SERVICE ###
services:
testseite:
container_name: testseite
image: nginx:latest
restart: unless-stopped
networks:
- web
security_opt:
- no-new-privileges:true
volumes:
- $DOCKERDIR/appdata/testseite:/usr/share/nginx/html:ro
labels:
- "traefik.enable=true"
- "traefik.http.routers.testseite-rtr.entrypoints=https"
- "traefik.http.routers.testseite-rtr.rule=Host(`testseite.$DOMAINNAME`)"
- "traefik.http.routers.testseite-rtr.tls=true"
- "traefik.http.routers.testseite-rtr.service=testseite-svc"
- "traefik.http.services.testseite-svc.loadbalancer.server.port=80"
- "traefik.http.routers.testseite-rtr.middlewares=chain-no-auth@file"
docker-compose -f docker-compose-testseite.yml up -d
Die Seite müsste daraufhin wenige Augenblicke später unter testseite.domain.de erreichbar sein. Doch was ist, wenn man eine zweite Seite online haben möchte? Gleiche Prozedur:
cd ~/docker
mkdir appdata
mkdir appdata/testseite2
cd ~/docker/appdata/testseite2
nano index.html
<!doctype html>
<html>
<head>
<title>Testseite</title>
</head>
<body>
<p>Hallo</p>
<p>Das ist die zweite Seite.</p>
</body>
</html>
cd ~/docker
nano docker-compose-testseite2.yml
version: "3.7"
### NETWORKS ###
networks:
web:
external:
name: web
default:
driver: bridge
### SERVICE ###
services:
testseite2:
container_name: testseite2
image: nginx:latest
restart: unless-stopped
networks:
- web
security_opt:
- no-new-privileges:true
volumes:
- $DOCKERDIR/appdata/testseite2:/usr/share/nginx/html:ro
labels:
- "traefik.enable=true"
- "traefik.http.routers.testseite2-rtr.entrypoints=https"
- "traefik.http.routers.testseite2-rtr.rule=Host(`testseite2.$DOMAINNAME`)"
- "traefik.http.routers.testseite2-rtr.tls=true"
- "traefik.http.routers.testseite2-rtr.service=testseite2-svc"
- "traefik.http.services.testseite2-svc.loadbalancer.server.port=80"
- "traefik.http.routers.testseite2-rtr.middlewares=chain-basic-auth@file"
docker-compose -f docker-compose-testseite2.yml up -d
lazydocker
In Lazydocker sollten jetzt beide Dienste zu sehen sein.
Über testseite.domain.de und testseite2.domain.de sind die beiden Seiten erreichbar, Portfreigaben mussten nicht erstellt werden. Wenn die Einstellungen meiner Docker-Compose Dateien übernommen werden, erscheint bei der testseite2 das Anmeldefenster mit der einfachen Authentifizierung. Die Container können indessen wieder gestoppt werden.
cd ~/docker
docker-compose -f docker-compose-testseite.yml down
docker-compose -f docker-compose-testseite2.yml down
Die Einrichtung ist damit fertig 😃