Einleitung

In diesem Artikel zeige ich, wie ich meinen Pangolin Reverse Proxy mit Mutual TLS (mTLS) abgesichert habe. Dadurch können nur Geräte mit einem gültigen Client-Zertifikat auf das Dashboard und die dahinter liegenden Dienste zugreifen.

Was ist Pangolin?

Pangolin ist ein selbst gehosteter Reverse Proxy, der auf Traefik basiert. Er ermöglicht es, interne Dienste sicher über das Internet erreichbar zu machen und bietet dabei eine benutzerfreundliche Oberfläche zur Verwaltung.

Was ist mTLS?

Bei einer normalen HTTPS-Verbindung authentifiziert sich nur der Server gegenüber dem Client (einseitiges TLS). Bei Mutual TLS (mTLS) müssen sich beide Seiten authentifizieren:

  • Der Server weist sich mit seinem Zertifikat aus (wie bei normalem HTTPS)

  • Der Client muss ebenfalls ein gültiges Zertifikat vorlegen

Das bedeutet: Selbst wenn jemand die URL kennt, kann er ohne das passende Client-Zertifikat nicht auf den Dienst zugreifen. Das ist eine zusätzliche Sicherheitsebene, die über Passwörter hinausgeht.

Vorteile von mTLS

  • Starke Authentifizierung: Zertifikate sind schwerer zu stehlen als Passwörter

  • Keine Passwort-Müdigkeit: Keine komplexen Passwörter merken

  • Geräte-Bindung: Das Zertifikat ist an ein spezifisches Gerät gebunden

  • Schutz vor Phishing: Ohne Zertifikat kein Zugriff, selbst bei geleakten Credentials


Schritt 1: Ordnerstruktur anlegen

Zuerst habe ich die notwendige Ordnerstruktur für die Zertifikate erstellt:


mkdir -p config/traefik/certs/ca

mkdir -p config/traefik/certs/clients

Die Struktur sieht dann so aus:


config/traefik/certs/

├── ca/ # Certificate Authority (CA) Zertifikate

│ ├── ca.crt # CA Zertifikat (öffentlich)

│ └── ca.key # CA Private Key (geheim!)

├── clients/ # Client-Zertifikate

│ ├── benutzer1.crt

│ ├── benutzer1.key

│ ├── benutzer1.p12 # Für Browser-Import

│ └── ...

└── create-client.sh # Skript zur Zertifikatserstellung

Schritt 2: Certificate Authority (CA) erstellen

Bevor ich Client-Zertifikate ausstellen kann, benötige ich eine eigene Certificate Authority (CA). Diese CA signiert später alle Client-Zertifikate.


cd config/traefik/certs/ca

  

# CA Private Key erstellen (4096 Bit für hohe Sicherheit)

openssl genrsa -out ca.key 4096

  

# CA Zertifikat erstellen (10 Jahre gültig)

openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -out ca.crt \

-subj "/C=DE/ST=Berlin/L=Berlin/O=Meine Organisation/OU=IT/CN=Meine mTLS CA"

Wichtig: Den ca.key unbedingt sicher aufbewahren! Wer diesen Key besitzt, kann gültige Client-Zertifikate ausstellen.


Schritt 3: Traefik Dynamic Configuration anpassen

Der Kern der mTLS-Konfiguration liegt in der dynamischen Traefik-Konfiguration. Wichtig ist, dass man die drei Routen next-router, api-router und ws-router mit mTLS ausstattet, da Traefik sonst Fehlermeldungen wirft, dass die Routen unterschiedliche TLS-Optionen nutzen. Zudem werden dadurch alle Domains, die man im Pangolin Dashboard sichert, zusätzlich mit mTLS geschützt, da man sich ja erst beim Pangolin Dashboard authentifizieren muss.

Ich habe folgende Datei erstellt bzw. angepasst:



# config/traefik/dynamic_config.yml

tls:
  options:
      # mTLS erforderlich - Client MUSS gültiges Zertifikat haben
    mtls-strict:
      clientAuth:
        caFiles:
          - /etc/traefik/certs/ca/ca.crt
        clientAuthType: RequireAndVerifyClientCert
      minVersion: VersionTLS12
      sniStrict: true


http:
  middlewares:
    redirect-to-https:
      redirectScheme:
        scheme: https

  routers:
    main-app-router-redirect:
      rule: "Host(`pangolin.domain.de`)"
      service: next-service
      entryPoints:
        - web
      middlewares:
        - redirect-to-https

    next-router:
      rule: "Host(`pangolin.domain.de`) && !PathPrefix(`/api/v1`)"
      service: next-service
      entryPoints:
        - websecure
      tls:
        certResolver: letsencrypt
        options: mtls-strict


    api-router:
      rule: "Host(`pangolin.domain.de`) && PathPrefix(`/api/v1`)"
      service: api-service
      entryPoints:
        - websecure
      tls:
        certResolver: letsencrypt
        options: mtls-strict
 

    ws-router:
      rule: "Host(`pangolin.domain.de`)"
      service: api-service
      entryPoints:
        - websecure
      tls:
        certResolver: letsencrypt
        options: mtls-strict



  services:
    next-service:
      loadBalancer:
        servers:
          - url: "http://pangolin:3002"

    api-service:
      loadBalancer:
        servers:
          - url: "http://pangolin:3000"

Erklärung der wichtigsten Einstellungen

| Einstellung | Beschreibung |

|————-|————–|

| clientAuthType: RequireAndVerifyClientCert | Client-Zertifikat ist zwingend erforderlich |

| caFiles | Pfad zur CA, die Client-Zertifikate signiert hat |

| minVersion: VersionTLS12 | Mindestens TLS 1.2 (für Sicherheit) |

| sniStrict: true | Strikte SNI-Prüfung aktiviert |

| options: mtls-strict | Wendet die mTLS-Option auf den Router an |


Schritt 4: Docker Compose anpassen

In meiner docker-compose.yml habe ich das Zertifikats-Volume für Traefik eingebunden:


  traefik:
    image: traefik:v3.3.3
    container_name: traefik
    restart: unless-stopped
    network_mode: service:gerbil # Ports appear on the gerbil service
    depends_on:
      pangolin:
        condition: service_healthy
    command:
      - --configFile=/etc/traefik/traefik_config.yml
    volumes:
      - ./config/traefik:/etc/traefik:ro # Volume to store the Traefik configuration
      - ./config/letsencrypt:/letsencrypt # Volume to store the Let's Encrypt certificates
      - ./config/traefik/logs:/var/log/traefik:rw
      - ./config/traefik/certs:/etc/traefik/certs:ro #Volume to store mtls certs

Schritt 5: Client-Zertifikate erstellen

Um die Erstellung von Client-Zertifikaten zu vereinfachen, habe ich folgendes Skript erstellt:


#!/bin/bash
set -e
if [ -z "$1" ]; then
    echo "Usage: $0 <client-name> [email]"
    exit 1
fi
CLIENT_NAME="$1"
EMAIL="${2:-$CLIENT_NAME@email.com}"
DAYS="${3:-730}"
cd "$(dirname "$0")"
echo "=== Erstelle Client-Zertifikat für: $CLIENT_NAME ==="
# Config erstellen
cat > clients/${CLIENT_NAME}.cnf << CNFEOF
[req]
distinguished_name = req_distinguished_name
prompt = no
[req_distinguished_name]
C = DE
ST = City
L = State
O = User
OU = Benutzer
CN = ${CLIENT_NAME}
emailAddress = ${EMAIL}
CNFEOF
# Key erstellen
openssl genrsa -out clients/${CLIENT_NAME}.key 4096
# CSR erstellen
openssl req -new -key clients/${CLIENT_NAME}.key \
    -out clients/${CLIENT_NAME}.csr \
    -config clients/${CLIENT_NAME}.cnf
# Mit CA signieren
openssl x509 -req -days $DAYS \
    -in clients/${CLIENT_NAME}.csr \
    -CA ca/ca.crt \
    -CAkey ca/ca.key \
    -CAcreateserial \
    -out clients/${CLIENT_NAME}.crt \
    -extfile <(echo "basicConstraints=CA:FALSE
keyUsage=digitalSignature,keyEncipherment
extendedKeyUsage=clientAuth")
# P12 für Browser erstellen
openssl pkcs12 -export \
    -out clients/${CLIENT_NAME}.p12 \
    -inkey clients/${CLIENT_NAME}.key \
    -in clients/${CLIENT_NAME}.crt \
    -certfile ca/ca.crt \
    -name "${CLIENT_NAME} mTLS Zertifikat"
echo ""
echo "=== Fertig! ==="
echo "Dateien erstellt:"
echo "  - clients/${CLIENT_NAME}.crt  (Zertifikat)"
echo "  - clients/${CLIENT_NAME}.key  (Private Key)"
echo "  - clients/${CLIENT_NAME}.p12  (Browser-Import)"

Verwendung des Skripts


# Skript ausführbar machen

chmod +x config/traefik/certs/create-client.sh


# Zertifikat für einen Benutzer erstellen

./create-client.sh max max@example.com


# Das Skript fragt nach einem Export-Passwort für die .p12 Datei

Schritt 6: Client-Zertifikat im Browser importieren

Firefox

  1. Einstellungen öffnen

  2. “Datenschutz & Sicherheit” → “Zertifikate”

  3. “Zertifikate anzeigen” klicken

  4. Tab “Ihre Zertifikate” → “Importieren”

  5. Die .p12 Datei auswählen und Passwort eingeben

Chrome / Edge

  1. Einstellungen → “Datenschutz und Sicherheit”

  2. “Sicherheit” → “Zertifikate verwalten”

  3. Tab “Eigene Zertifikate” → “Importieren”

  4. Die .p12 Datei auswählen

iOS (iPhone/iPad)

  1. Die .p12 Datei per AirDrop oder E-Mail aufs Gerät übertragen

  2. Auf die Datei tippen → “Profil geladen”

  3. Einstellungen → “Profil geladen” → “Installieren”

  4. Passwort eingeben

Android

  1. Einstellungen → “Sicherheit” → “Verschlüsselung & Anmeldedaten”

  2. “Zertifikat installieren” → “VPN- und App-Nutzerzertifikat”

  3. Die .p12 Datei auswählen


Ergebnis

Nach der Konfiguration passiert Folgendes, wenn jemand auf pangolin.domain.de zugreift:

  1. Mit gültigem Zertifikat: Der Browser zeigt eine Zertifikatsauswahl an, man wählt sein Zertifikat und hat Zugriff

  2. Ohne Zertifikat: Die Verbindung wird sofort abgelehnt - keine Anmeldeseite, keine Fehlermeldung, einfach “Verbindung abgelehnt”

Das bietet einen sehr starken Schutz, da Angreifer nicht einmal bis zur Anmeldeseite kommen.


Tipps und Best Practices

Zertifikate widerrufen

Sollte ein Zertifikat kompromittiert werden, gibt es zwei Möglichkeiten:

  1. Einfach: Neue CA erstellen und alle Client-Zertifikate neu ausstellen

  2. Professionell: Eine Certificate Revocation List (CRL) pflegen

Backup nicht vergessen

Folgende Dateien unbedingt sichern:

  • ca/ca.key - Ohne diesen Key können keine neuen Zertifikate erstellt werden

  • ca/ca.crt - Wird von Traefik benötigt

Zertifikate sicher übertragen

Die .p12 Dateien niemals unverschlüsselt per E-Mail versenden. Besser:

  • Persönlich übergeben

  • Über einen sicheren Kanal (Signal, etc.)

  • Temporär auf einem passwortgeschützten Share


Fazit

Mit mTLS habe ich eine zusätzliche, sehr starke Sicherheitsebene für meinen Pangolin Reverse Proxy eingerichtet. Nur Geräte mit einem gültigen Client-Zertifikat können überhaupt eine Verbindung aufbauen. Das schützt effektiv vor unbefugtem Zugriff, selbst wenn Zugangsdaten geleakt werden sollten.

Der initiale Aufwand lohnt sich - einmal eingerichtet, ist die Verwaltung mit dem Skript sehr einfach und die Sicherheit deutlich erhöht.