Traefik SSL Reverse Proxy für Docker Container

Aus Laub-Home Wiki
Traefik Proxy

Traefik Proxy ist super geeignet um diverse Docker Container von außen erreichbar zu machen, ohne jeden einzelnen mittels Port Weiterleitung freizugeben. Man muss so nicht Unmengen von Ports auf dem localhost managen, sondern kann einfach mittel HTTP Reverse Proxy die Webapplikationen nach außen freigeben. Zusätzlich bietet Traefik Proxy auch eine SSL Lösung mit LetsEncrypt Zertifikaten. Man kann aber auch einfach für interne Zwecke ein Self Signed Zertifikat verwenden. Ich zeige hier, wie man mittels docker compose einen Traefik Proxy deployt und verschiedene Wege, wie man andere Docker Applikationen damit für die Außenwelt freigibt.

Traefik Docker-Compose Projekt

Zuerst legen wir unser Docker Compose Projekt an. Dafür erstellen wir die folgende Ordner Struktur und wechseln in das Projekt Verzeichnis.

mkdir -p /opt/traefik/data/conf/certs
mkdir -p /opt/traefik/data/conf/dynamic
cd /opt/traefik/

Da wir ein Docker Netzwerk brauchen, welches Traefik mit den einzelnen Containern verbinden, sozusagen das Transfer Netz, erstellen wir ein persistentes Netzwerk mittels docker Befehl. Das Netzwerk heißt dann traefik-nw:

docker network create --driver=bridge --attachable --internal=false -o "com.docker.network.bridge.name"="br-traefik" traefik-nw

Nun erstellen wir unser docker-compose.yml:

  • Traefik latest Image
  • Ports 80 / 443 und 8080 werden zum Host freigegeben
  • Der Container soll im Falle eines Ausfalles neugestartet werden
  • Mapping der Docker Socket Datei um Informationen zu den Container zu bekommen
  • Configurations Dateien und der Ordner für die SSL Zertifikate werden ebenfalls in den Container gemappt
  • Das oben angelegte traefik-nw Netzwerk wird verwendet (external ist hier sehr wichtig!)
  • Optional: das Label "com.centurylinklabs.watchtower.enable=true" sorgt dafür das watchtower den Container aktualisiert sollte es ein neues Image von traefik:latest geben.

/opt/traefik/docker-compose.yml

version: '3.7'

services:
  traefik:
    image: traefik:latest
    restart: always
    ports:
      - "80:80"
      - "443:443"
      - "8080:8080"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./data/conf/traefik.yml:/etc/traefik/traefik.yml:ro
      - ./data/conf/dynamic:/etc/traefik/dynamic:ro
      - ./data/certs:/etc/traefik/certs:ro
    environment:
      - TZ=Europe/Berlin
    labels:
      - "com.centurylinklabs.watchtower.enable=true"
    networks:
      - traefik-nw

networks:
  traefik-nw:
    external:
      name: traefik-nw

Self Signed SSL Zertifikat

Nun erstellen wir uns für unseren Host ein Self Signed SSL Zertifikat mittels openssl.

Sollte openssl noch nicht installiert sein:

apt install openssl -y

Tauscht hier bitte yourFQDN mit eurem FQDN des Servers/Rechners/Raspberry Pi aus (zum Beispiel: traefik.testdomain.local):

cd /opt/traefik
openssl req -new -newkey rsa:4096 -x509 -days 3650 -nodes -out data/certs/yourFQD.crt -keyout data/certs/yourFQDN.key

dann die Fragen einfach ausfüllen. Das einzig wichtige ist, tragt den richtigen FQDN ein:

Generating a RSA private key
........................................................................++++
................................................................................................................................++++
writing new private key to 'data/certs/yourFQDN.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:DE
State or Province Name (full name) [Some-State]:BaWu
Locality Name (eg, city) []:Karlsruhe
Organization Name (eg, company) [Internet Widgits Pty Ltd]:laub-home
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:traefik.testdomain.local
Email Address []:

Traefik Konfiguration

Nun kommen wir zu den Konfigurationsdateien traefik.yml und dynamic_conf.yml.

/opt/traefik/data/conf/traefik.yml

global:
  checkNewVersion: true
  sendAnonymousUsage: false
  
entryPoints:
  web:
    address: :80
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
          permanent: true

  websecure:
    address: :443
    http:
      tls: true

api:
  insecure: true
  
providers:
  docker:
    exposedByDefault: false
    
  file:
      directory: /etc/traefik/dynamic
      watch: true

Hier passiert das folgende:

  • Entrypoints 80 (HTTP, Name: web) und 443 (HTTPS, Name: websecure) werden angelegt
  • Entrypoint 80/web wird immer auf 443/websecure umgeleitet, somit ist alles immer über HTTPs erreichbar
  • Die API wird insecure freigegeben
  • Wir binden Docker und die Datei dynamic_conf.yml als Provider ein
  • Docker Container werden nicht standardmäßig eingelesen, nur wenn diese das Label "traefik.enable=true" haben.

/opt/traefik/data/conf/dynamic/dynamic_conf.yml

# Dynamic configuration

# SSL Certs
tls:
  certificates:
    - certFile: /etc/traefik/certs/yourFQDN.crt
      keyFile: /etc/traefik/certs/yourFQDN.key

Hier werden nun die Zertifikate eingebunden.

Mehr Informationen zu den Konfigurationsmöglichkeiten:

Starten des Traefik Proxy

nun ist es soweit, den ersten Start von Traefik auszuführen und zu testen ob das Traefik Dashboard erreichbar ist:

docker-compose up -d

Wenn alles passt sollte nun das Dashboard unter:

  • http://yourFQDN:8080

erreichbar sein.

Whoami Test Container

Nun wollen wir natürlich testen ob wir einen Container erfolgreich über den Traefik Proxy laufen lassen können. Hierfür verwende ich whoami. Ein einfacher NGINX Webserver der den Containernamen Ausspuckt.

mkdir -p /opt/whoami
cd /opt/whoami

/opt/whoami/docker-compose.yml

version: '3.7'

services:
  whoami-traefik-test:
    # Raspberry Pi Image
    image: hypriot/rpi-whoami
    # i386 image
    #image: containous/whoami:latest
    restart: always
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.whoami-tls.rule=PathPrefix(`/whoami`)"
      - "traefik.http.routers.whoami-tls.entrypoints=websecure"
      - "traefik.http.routers.whoami-tls.tls=true"
    networks:
      - traefik-nw

networks:
  traefik-nw:
    external:
      name: traefik-nw

Was passiert hier:

  • Wir nehmen ein whoami Docker Image und starten es
  • Wir aktivieren den Traefik: "traefik.enable=true"
  • Wir wollen das die Applikation unter dem Port 443/TLS und dem Subpfad /whoami verfügbar ist
  • somit also unter https://yourFQDN/whoami erreichbar ist
  • Auch hier wird dem Kontainer unser Traefik Netzwerk (traefik-nw) mitgegeben

nun starten wir das Docker Compose Projekt:

docker-compose up -d

Nun sollten wir beim Aufruf von:

  • http://yourFQDN/whoami/

direkt eine Weiterleitung nach https geschehen, was sicherlich ersteinmal zu einem Zertifikatsfehler im Browser führt da es ein Self Signed Zertifikat ist. Deshalb das Zertifikat akzeptieren. Dann solltet ihr aber folgenden Output im Browser sehen:

I'm 24c89d83bb4d

Im Traefik dashboard sollte nun auch unter HTTP der Status angezeigt werden:

Weitere Beispiele für Subpfad Freigaben

hier ein paar weitere Label Beispiele für Applikationen die ich per Subpfad freigegeben habe.

Grafana

Die folgende Konfiguration bringt Grafana dazu vom Traefik als Subpath /grafana freigegeben zu werden. Hierzu müssen ebenfalls die Environment Variablen von Grafana erweitert werden, da man hier ebenfalls den subpath einrichten muss.

    environment:
      - GF_RENDERING_CALLBACK_URL=http://grafana:3000/grafana/
      - GF_SERVER_SERVE_FROM_SUB_PATH=true
      - GF_SERVER_ROOT_URL=https://yourFQDN/grafana
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.grafana.rule=PathPrefix(`/grafana`)"
      - "traefik.http.routers.grafana.entrypoints=websecure"
      - "traefik.http.routers.grafana.tls=true"
      - "traefik.http.routers.grafana.middlewares=addtrailingslash@file"
      - "traefik.docker.network=traefik-nw"

Auch der Renderer muss wissen das Grafana nun unter /grafana lauscht.

Erreichbar ist das nun unter:

  • http://yourFQDN/grafana/

Chronograf

Auch bei Chronograf muss ähnlich wie bei Grafana, der Subpath auch als Pfad Variable chronograf mitgegeben werden. Die geschieht mittel Environment Variable:

    environment:
      - BASE_PATH=/chronograf
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.chronograf.rule=PathPrefix(`/chronograf`)"
      - "traefik.http.routers.chronograf.entrypoints=websecure"
      - "traefik.http.routers.chronograf.tls=true"
      - "traefik.http.routers.chronograf.middlewares=addtrailingslash@file"
      - "traefik.docker.network=traefik-nw"

Erreichbar ist das nun unter:

  • http://yourFQDN/chronograf/

Portainer

Dies sind die Labels mit denen man Portainer via Traefik und Subpath freigeben kann.

    lables:
      - "traefik.enable=true"
      - "traefik.http.routers.portainer.rule=PathPrefix(`/portainer`)"
      - "traefik.http.routers.portainer.entrypoints=websecure"
      - "traefik.http.routers.portainer.tls=true"
      - "traefik.http.routers.portainer.middlewares=portainer-stripprefix"
      - "traefik.http.middlewares.portainer-stripprefix.stripprefix.prefixes=/portainer"
      - "traefik.docker.network=traefik-nw"

Aufrufbar ist das Ganze nun unter:

  • http://yourFQDN/portainer/

Smokeping

Dies sind die Labels mit denen man Smokeping via Traefik und Subpath freigeben kann.

    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.smokeping.rule=PathPrefix(`/smokeping`)"
      - "traefik.http.routers.smokeping.entrypoints=websecure"
      - "traefik.http.routers.smokeping.tls=true"
      - "traefik.docker.network=traefik-nw"

Aufrufbar ist das Ganze nun unter:

  • http://yourFQDN/smokeping/

Pi-hole

Dies sind die Labels mit denen man Pi-hole via Traefik und Subpath freigeben kann.

    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.pihole.rule=PathPrefix(`/pihole`)"
      - "traefik.http.routers.pihole.entrypoints=websecure"
      - "traefik.http.routers.pihole.tls=true"
      - "traefik.http.routers.pihole.middlewares=pihole-stripprefix, pihole-addprefix"
      - "traefik.http.middlewares.pihole-stripprefix.stripprefix.prefixes=/pihole"
      - "traefik.http.middlewares.pihole-stripprefix.stripprefix.forceSlash=false"
      - "traefik.http.middlewares.pihole-addprefix.addprefix.prefix=/admin"
      - "traefik.http.services.pihole.loadbalancer.server.port=80"
      - "traefik.docker.network=traefik-nw"

Aufrufbar ist das Ganze nun unter:

  • http://yourFQDN/pihole/

Sickbeard

Docker Labels:

    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.sickbeard.rule=PathPrefix(`/sickbeard`)"
      - "traefik.http.routers.sickbeard.entrypoints=websecure"
      - "traefik.http.routers.sickbeard.tls=true"

Neben den Docker Labels muss die Sickbeard config.ini noch angepasst werden:

[General]
..
    web_root = "/sickbeard"
..

Aufrufbar ist das Ganze nun unter:

  • http://yourFQDN/sickbeard

Sonarr

Docker Labels:

    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.sonarr.rule=PathPrefix(`/sonarr`)"
      - "traefik.http.routers.sonarr.entrypoints=websecure"
      - "traefik.http.routers.sickbeard.tls=true"

Neben den Docker Labels muss die Sickbeard config.xml noch angepasst werden:

<UrlBase>/sonarr</UrlBase>

Aufrufbar ist das Ganze nun unter:

  • http://yourFQDN/sonarr

AdGuard Home

Dies sind die Labels mit denen man AdGuard Home via Traefik und Subpath freigeben kann.

    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.adguardhome.rule=PathPrefix(`/adguardhome`)"
      - "traefik.http.routers.adguardhome.entrypoints=websecure"
      - "traefik.http.routers.adguardhome.tls=true"
      - "traefik.http.routers.adguardhome.middlewares=addtrailingslash@file, adguardhome-stripprefix"
      - "traefik.http.middlewares.adguardhome-stripprefix.stripprefix.prefixes=/adguardhome"
      - "traefik.http.middlewares.adguardhome-stripprefix.stripprefix.forceSlash=false"
      - "traefik.http.services.adguardhome.loadbalancer.server.port=80"
      - "traefik.docker.network=traefik-nw"

Leider ist das Ganze nicht ganz so gut benutzbar. Man muss beim ersten Login /login.html mit aufrufen. Ansonsten landet man wieder auf /

Aufrufbar ist das Ganze nun unter:

  • http://yourFQDN/adguardhome/login.html

Automatischen Slash (/) am Ende einfügen

Möchte man automatisch einen Slash an den Subpfad einfügen, also den sogenannten Trailing Slash, kann man dies über eine Traefik Middleware konfigurieren, hierfür gibt es zwei Möglichkeiten, via Docker Label oder Global in der Konfigurationsdatei.

Die Docker Labels, die man hinzufügen muss sehen so aus:

docker-compose.yml

      - "traefik.http.routers.whoami-tls.middlewares=whoami-addtrailingslash"
      - "traefik.http.middlewares.whoami-addtrailingslash.redirectregex.regex=^(https?://[^/]+/[a-z0-9_]+)$$"
      - "traefik.http.middlewares.whoami-addtrailingslash.redirectregex.replacement=$${1}/"

tauscht hier einfach whoami durch euren Applikationsnamen aus.

Via Konfigurationsdatei sieht das ganze so aus:

/opt/traefik/data/conf/dynamic/dynamic_conf.yml

# Middleware
http:
  middlewares:
    addtrailingslash:
      redirectregex:
        regex: "^(https?://[^/]+/[a-z0-9_]+)$"
        replacement: "${1}/"
        permanent: true

dann einfach der gewünschten Applikation das folgende Label hinzufügen:

docker-compose.yml

      - "traefik.http.routers.whoami-tls.middlewares=addtrailingslash@file"

nun kann man einfach beim Aufrufen der URL den letzten / weglassen:

  • http://yourFQDN/whoami

und wird dann automatisch auf

  • http://yourFQDN/whoami/

umgeleitet.

man muss beim Aufruf der Middleware auf die Reihenfolge achten. Am besten addtrailingslash an den Anfang stellen, hier das Beispiel von Pi-hole:

      - "traefik.http.routers.pihole.middlewares=addtrailingslash@file, pihole-stripprefix, pihole-addprefix"

Basic Auth

Über folgende Middleware Konfiguration können Host/ Pfade auch mit einer Basic-Auth versehen werden:

- "traefik.http.middlewares.<rulename>.basicauth.users=<user>:<password>"
- "traefik.http.routers.<routername>.middlewares=<rulename>"

Passwörter müssen hier als Hash (MD5, SHA1 oder BCrypt) angegeben werden. Bei der Verwendung von docker-compose ist zu beachten, dass das Zeichen $ in docker-compose zu Interpolation von Variabeln genutzt wird und daher überall mit einem zusätzlich Vorangestellten $ maskiert werden muss - das Ganze lässt sich als Einzeiler direkt bei der Passworterstellung umsetzen:

openssl passwd -apr1 | sed -E "s:[\$]:\$\$:g"

Komplettes Beispiel:

- "traefik.enable=true"
- "traefik.http.routers.dashboard.entrypoints=dashboard"
- "traefik.http.routers.dashboard.rule=Host(`dashboard.myhost.local`)"
- "traefik.http.routers.dashboard.service=api@internal"
- "traefik.http.routers.dashboard.middlewares=dashboard-auth"
- "traefik.http.middlewares.dashboard-auth.basicauth.users=admin:$$apr1$$REa/w1.F$$WxhZAj.S74o8IPLDn4r0d/"

Plugins

rewritebody

Plugin um mittels RegEx Response Content umzuschreiben.

  1. Traefik Pilot und Plugins aktivieren, z.B. in der statischen Konfiguration traefik.yml:
    pilot:
      token: "PILOT_INSTANCE_KEY" #your own pilot key
    
    experimental:
      plugins:
        rewritebody:
          modulename: "github.com/traefik/plugin-rewritebody"
          version: "v0.3.1"
    
  2. Konfiguration im Docker Container als Label:
     - "traefik.http.routers.mycontainer-tls.middlewares=my-rewrite"
     - "traefik.http.middlewares.my-rewrite.plugin.rewritebody.lastModified=true"
     - "traefik.http.middlewares.my-rewrite.plugin.rewritebody.rewrites[0].regex=css/"
     - "traefik.http.middlewares.my-rewrite.plugin.rewritebody.rewrites[0].replacement=path/css/"

Quellen