Reverse Proxy in Docker mit Let’s Encrypt-Zertifikaten

Reverse Proxy Struktur

In diesem Blogbeitrag werden wir uns detailliert mit der Konfiguration eines Reverse Proxys in Docker mit Let’s Encrypt-Zertifikaten befassen und zeigen, wie diese in den Prozess integriert werden können. Wir werden gemeinsam Schritt für Schritt die erforderlichen Maßnahmen durchgehen, um einen sicheren und zuverlässigen Reverse Proxy aufzusetzen, der den Datenverkehr effizient verteilt und gleichzeitig eine verschlüsselte Kommunikation gewährleistet.

Egal, ob du bereits mit Docker vertraut bist oder gerade erst anfängst, dieser Blogbeitrag bietet dir eine umfassende Anleitung, um Reverse Proxy und Let’s Encrypt-Zertifikate erfolgreich zu implementieren. Tauche ein in die Welt des sicheren Datenverkehrs und erfahre, wie du deine Webanwendungen oder Websites vor potenziellen Bedrohungen schützen kannst.

Grundlagen

Ein Reverse Proxy agiert als Vermittler zwischen den Clients und den Servern. Er nimmt eingehende Anfragen entgegen und leitet sie an die entsprechenden Backend-Server bzw. App Container weiter. Dadurch können wir den Datenverkehr effizient verteilen, Lastausgleich betreiben und zusätzliche Sicherheitsschichten einführen.

Ein wichtiger Aspekt bei der Implementierung eines Reverse Proxys ist die Integration von Let’s Encrypt-Zertifikaten. Let’s Encrypt ist eine kostenlose und automatisierte Zertifizierungsstelle, die es Website-Betreibern ermöglicht, SSL/TLS-Zertifikate einfach zu erhalten und ihre Verbindung zu verschlüsseln. Durch die Verwendung von Let’s Encrypt-Zertifikaten stellen wir sicher, dass die Kommunikation zwischen den Clients und den Backend-Servern verschlüsselt ist und vor potenziellen Angreifern geschützt wird.

Voraussetzungen

Um den Reverse Proxy in Docker mit Let’s Encrypt-Zertifikaten erfolgreich zu implementieren, gibt es einige grundlegende Voraussetzungen, die erfüllt sein müssen. Stelle sicher, dass du die folgenden Anforderungen erfüllst, bevor du mit der Konfiguration beginnst:

  1. Docker und Docker Compose: Da wir Docker und Docker Compose verwenden, um den Reverse Proxy zu erstellen und zu verwalten, musst du diese Software auf deinem System installiert haben. Falls du dies noch nicht getan hast, habe ich dir eine Anleitung verlinkt.

  2. Grundkenntnisse von Docker: Um den Reverse Proxy in Docker effektiv zu konfigurieren, ist es wichtig, grundlegende Kenntnisse von Docker und seinen Kernkonzepten zu haben. Du solltest mit Begriffen wie Containern, Images, Dockerfiles und dem Docker-CLI (Command Line Interface) vertraut sein. Wenn du noch nicht mit Docker vertraut bist, habe ich dir eine Beitragsserie zu dem Thema Docker verlinkt. 

  3. Grundkenntnisse von Docker Compose: Docker Compose ist ein Tool, das es ermöglicht, mehrere Docker-Container als Dienste zu definieren und zu verwalten. Es erleichtert die Konfiguration und das Zusammenspiel von Containern. Um den Reverse Proxy mit Let’s Encrypt-Zertifikaten in Docker zu implementieren, ist es hilfreich, grundlegende Kenntnisse von Docker Compose zu haben. Du solltest wissen, wie man eine Docker-Compose-Datei erstellt, Dienste definiert und Umgebungsvariablen festlegt. Einen kurzen Crashkurs habe ich dir hier verlinkt

Docker-Compose-Datei

Docker Netzwerk

Docker-Container können standardmäßig nicht von externen Quellen angesprochen werden. Daher müssen die Ports des Containers auf die des Host-Rechners gemappt werden. Auf diese Weise kann der Container über die IP-Adresse und den entsprechenden Port erreicht werden. Wenn jedoch bestimmte Dienste für das Internet freigegeben werden sollen, kann dies sehr aufwendig sein. Es erfordert nicht nur das Mappen der Container-Ports, sondern auch das Einrichten von Portfreigaben am Router. Dadurch entsteht eine größere Angriffsfläche für potenzielle Hacker. Zusätzlich können Container standardmäßig nicht direkt miteinander kommunizieren. Um sicherzustellen, dass der Reverse Proxy auf die verschiedenen Anwendungen zugreifen kann, muss zunächst ein separates Netzwerk erstellt werden, das unabhängig von den Containern ist.

docker network create proxy_network

Du kannst den Namen proxy_network nach Belieben ändern. Durch die Verbindung der Container mit diesem Netzwerk wird sichergestellt, dass der Proxy auf die Container zugreifen kann. Es ist ratsam, nur die Container mit diesem Netzwerk zu verbinden, auf die von außerhalb des Heimnetzes zugegriffen werden soll.

Nachfolgend werden wir die Datei Schritt für Schritt gemeinsam aufbauen, damit die Installation des Revers Proxy in Docker verständlich ist.

nginx-proxy

In der Docker-Compose-Datei beginnen die ersten beiden Zeilen wie in jeder anderen Datei. Die version gibt an, auf welcher Version die Datei aufbaut. Unter dem Abschnitt services definieren wir unsere Container. Zuerst wird der Reverse Proxy Container definiert.

version: '3'
services:

Wir haben uns für das Image von jwilder in der alpine Version entschieden, da es einen geringeren Angriffsvektor für potenzielle Angreifer bietet. Zusätzlich enthält es die empfohlenen Einstellungen der Mozilla-Organisation auf dem Intermediate-Level, um einen Kompromiss zwischen Sicherheit und Kompatibilität zu gewährleisten.

nginx-proxy:
    image: jwilder/nginx-proxy:alpine

Diese Zeile befasst sich mit den Labels, die dem Container zugewiesen werden sollen. Diese sind wichtig, da sie es dem nachfolgenden LetsEncrypt-Container ermöglichen, den Proxy-Container zu identifizieren.

    labels:
      com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy: true

Anschließend legen wir den Namen des Containers fest.

    container_name: proxy

Im Abschnitt networks tragen wir den Namen des Netzwerkes ein, welches wir im Vorhinein erstellt haben. Durch diese Angabe wird der Container mit dem benannten Netzwerk verbunden.

    networks:
      - proxy_network

Um den Container von außen erreichbar zu machen, ist es wichtig, den Port 80 freizugeben, da er für eine HTTP-Verbindung erforderlich ist. Auf diese Weise kann LetsEncrypt das Ziel erreichen und ein gültiges Zertifikat erstellen. Über den Port 443 wird die verschlüsselte HTTPS Verbindung aufgebaut.

    ports:
      - 80:80
      - 443:443

Damit der Proxy unter anderem seine Konfiguration speichern und auf die generierten Zertifikate Zugriff hat, werden dem Container entsprechende Ordner bereitgestellt. Damit ist sichergestellt, dass diese auch nach dem beenden der Container weiter verfügbar sind.

    volumes:
      - /var/proxy/vhost.d:/etc/nginx/vhost.d:ro
      - /var/proxy/html:/usr/share/nginx/html:rw
      - /var/proxy/certs:/etc/nginx/certs:ro
      - /var/proxy/conf/:/etc/nginx/conf.d/
      - /etc/localtime:/etc/localtime:ro
      - /var/run/docker.sock:/tmp/docker.sock:ro

Abschließend wird die Neustart Richtlinie des Container definiert. Diese definiert, dass der Container immer wieder neu gestartet wird, außer er wird manuell oder durch einen Fehler gestoppt. 

    restart: unless-stopped

letsencrypt

Analog dem ersten Container werden wir nachfolgend die entsprechenden Abschnitte erklären. Um Zertifikate von Let´s Encrypt zu erstellen, verwenden wir das Image von nginxproxy.

  letsencrypt:
    image: nginxproxy/acme-companion

Diese Option definiert eine Abhängigkeit zum vorherigen Container, womit definiert wird, dass der Let´s Encrypt Container nach dem Proxy Container gestartet werden soll. 

    depends_on:
      - nginx-proxy

Anschließend legen wir den Namen des Containers fest.

    container_name: proxy-acme

Im Abschnitt networks tragen wir analog aus dem Proxy Abschnitt das zuvor erstelle Netzwerk ein.

    networks:
      - proxy_network

Damit der Let´s Encrypt Container die Konfiguration des Proxys lesen und die generierten Zertifikate speichern kann, wird dem Container die entsprechenden Ordner bereitgestellt. 

    volumes:
      - /var/proxy/vhost.d:/etc/nginx/vhost.d:rw
      - /var/proxy/html:/usr/share/nginx/html:rw
      - /var/proxy/certs:/etc/nginx/certs:rw
      - /var/proxy/acme:/etc/acme.sh
      - /var/proxy/conf/:/etc/nginx/conf.d/
      #- /var/proxy/acme_config/standalone.sh:/app/letsencrypt_user_data:ro # ggf. für die nächsten Abschnitte notwendig
      - /etc/localtime:/etc/localtime:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro

Standardmäßig erneuert der Container die Zertifikate 30 Tage vor Ablauf. Um eine Benachrichtigung per E-Mail zu erhalten, falls ein Zertifikat doch nicht korrekt erneuert wird, wird die nachfolgende Variable benutzt. Hier könnt ihr eure E-Mail Adresse eintragen.

    environment:
      - DEFAULT_EMAIL=EMAIL-ADRESSE

Gleichermaßen wie bei dem Proxy ist auch hier die Neustart Richtlinie definiert.

    restart: unless-stopped

In den beiden Containern haben wir das zuvor erstellte Proxy-Netzwerk verwendet. Um sicherzustellen, dass Docker Compose erkennt, dass es sich um ein extern erstelltes Netzwerk handelt, muss dieses zusätzlich global in die Compose-Datei eingebunden werden.

networks:
  proxy_network:
    external:
      name: proxy_network

Die vollständige Datei kannst du hier herunterladen.

Docker Compose Stack starten

Die obige Datei kannst du herunterladen und speichere diese z.B. unter dem Namen proxy-compose.yml ab. Anschließend kannst du den Stack mittels folgendem Befehl starten:

docker-compose --compatibility -f /PFAD/ZUR/COMPOSE/DATEI up -d

Die Container laufen im Hintergrund, damit kannst du den Reverse Proxy verwenden.

Konfiguration des Reverse Proxy in Docker mit Let´s Encrypt Zertifikaten

Mehrere Container unter einer Domain (Multipath)

Möchtest du mehrere Container unter einer einzigen Domain erreichen, beispielsweise tekkie.ninja/app1, tekkie.ninja/app2 oder einfach nur tekkie.ninja?
Dieses Ziel lässt sich wie folgt realisieren: Du erstellst eine Docker-Compose-Datei, in der die entsprechenden Container Zugriff auf das Proxy-Netzwerk haben. Der Standardcontainer, der aufgerufen wird, wenn tekkie.ninja aufgerufen wird, erhält die Umgebungsvariable VIRTUAL_HOST=…. Anschließend erzeugst du im Verzeichnis vhost.d eine Datei mit dem gleichen Namen wie die im Compose-File angegebene Domain. In dieser Datei gibst du die verschiedenen Pfade an und verweist auf die entsprechenden Container.

Ein Beispiel für eine solche Datei sieht wie folgt aus:

location /app1 {
    proxy_pass http://app1:8000;
}


location /app2 {
    proxy_pass http://app2:8000;
}

Dabei muss der Name des Containers die in der URL definierte app1 bzw. app2 ersetzen. Der Pfad wird entsprechend auf die Containernamen angepasst.

Weiterleitung einer Domaine

Wenn du beabsichtigst, eine Domain über einen Reverse Proxy mit einem SSL-Zertifikat zu betreiben, ohne sie direkt auf einen Container abzubilden, sind einige Schritte erforderlich. Abhängig vom Pfad deiner Compose-Datei ist es notwendig, eine separate *.conf-Datei in dem Konfigurationsverzeichnis des Proxys, laut obigen Beispiel /var/proxy/conf, zu erstellen.

Innerhalb dieser Konfigurationsdatei legst du verschiedene Parameter fest, darunter den Domänennamen, die IP-Adresse zusammen mit dem Port sowie den Pfad zu den Zertifikaten und Schlüsseln. Die markierten Abschnitte in der Datei müssen individuell an den jeweiligen Anwendungsfall angepasst werden. Für nähere Informationen zur Beschaffung eines Zertifikates empfiehlt es sich, den Abschnitt „SSL-Zertifikat ohne zugehörigen Docker-Container“ zu konsultieren.

# Specify domain to be forwarded
upstream domainName {
    server IP_ADRESS:PORT;
}
server {
    server_name domainName;
    listen 80 ;
    access_log /var/log/nginx/access.log;
    # Do not HTTPS redirect Let'sEncrypt ACME challenge
    location ^~ /.well-known/acme-challenge/ {
    auth_basic off;
    auth_request off;
    allow all;
    root /usr/share/nginx/html;
    try_files $uri =404;
    break;
}
    location / {
        return 301 https://$host$request_uri;
      }
}
server {
    server_name domainName;
    listen 443 ssl http2 ;
    access_log /var/log/nginx/access.log;
    ssl_session_timeout 5m;
    ssl_session_cache shared:SSL:50m;
    ssl_session_tickets off;
    ssl_certificate /PATH/TO/domainName.crt;
    ssl_certificate_key /PATH/TO/domainName.key;
    ssl_dhparam /PATH/TO/domainName.dhparam.pem;
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /PATH/TO/domainName.chain.pem;
    add_header Strict-Transport-Security "max-age=31536000" always;
    include /etc/nginx/vhost.d/default;
    location / {
        proxy_pass http://domainName;
      }
}

Die Datei kann hier heruntergeladen werden.

SSL-Zertifikat ohne zugehörigen Docker-Container

Um SSL-Zertifikate unabhängig von Containern zu generieren, benötigt der ACME-Container Zugriff auf das Konfigurationsverzeichnis des Reverse-Proxy sowie auf die Docker-Sockets, wie in der Docker Compose-Datei angegeben.

Des Weiteren wird eine Datei in das Verzeichnis /app/letsencrypt_user_data eingebunden. Diese Datei ist eine ausführbare Bash-Datei und enthält z.B. folgenden Inhalt:

LETSENCRYPT_STANDALONE_CERTS=('web' 'app' 'tekkie')
LETSENCRYPT_web_HOST=('yourdomain.de' 'www.yourdomain.de')
LETSENCRYPT_app_HOST=('yourdomain.de' 'myapp.yourotherdomain.de' 'service.yourotherdomain.tld')
LETSENCRYPT_tekkie_HOST=('tekkieninja.de')

Dabei werden in der ersten Zeile verschiedene Zertifikate als Gruppe definiert. Dieser Gruppe können dann mehrere Domainnamen zugewiesen werden, wofür das Zertifikat gelten soll.

Befehlsergänzungen

Proxy Container

Um die Konfiguration neu zu laden, wird folgender Befehl verwendet:

docker exec -ti proxy nginx -s reload

ACME Container

Um die Zertifikatsprüfung erneut auszulösen, wird folgender Befehl verwendet:

docker exec proxy-acme /app/signal_le_service

Um die Erneuerung der Zertifikate zu erzwingen, nutzen wir den folgenden Befehl:

docker exec proxy-acme /app/force_renew

Offizielle Dokumentation

Die offizielle Dokumentation von jwilder kannst du hier finden.

Kommentar verfassen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Nach oben scrollen