SSL-Zertifikate fürs Homelab – noch einfacher mit Caddy!

SSL-Zertifikate fürs Homelab – noch einfacher mit Caddy!

Übersicht

Zu Beginn der Homelab-Reihe habe ich euch bereits eine Möglichkeit gezeigt, wie ihr kinderleicht SSL-Zertifikate für eure Homelab-Anwendungen generieren könnt, um sicher per HTTPS darauf zuzugreifen. Dabei haben wir den Nginx Proxy Manager genutzt.

Heute möchte ich euch eine noch einfachere Lösung zeigen, die deutlich weniger Schritte erfordert. Außerdem kann diese Variante gut zum Automatisieren genutzt werden, da wir keine GUI mehr benötigen, um unsere Anwendungen hinzuzufügen.

Im heutigen Beitrag dreht es sich um das Tool Caddy. Caddy gibt uns die Möglichkeit, einen Reverse Proxy in Docker zu betreiben. Für die Konfiguration benötigen wir lediglich eine Konfigurationsdatei, in die wir unsere gewünschten Subdomains hinzufügen - und das war's!

Auch hier haben wir wieder die Möglichkeit, alle Container nur über den Reverse-Proxy erreichbar zu machen, ohne die "unsicheren" Ports zu exposen. Dies realisieren wir mit Hilfe eines Docker-Netzwerks.

Das Video zum Beitrag hier: https://youtu.be/I8c7u28K9z8


Information

Wir schauen uns zwei Varianten an, Caddy zu konfigurieren. Zunächst konfigurieren wir Caddy für die Nutzung einer HTTP-Challenge, um Zertifikate von Let´s Encrypt zu erhalten. Diese Variante eignet sich für Server, die per HTTP & HTTPS erreichbar sind und direkt mit Let´s Encrypt kommunizieren können.

Für unser möglicherweise nicht von außen erreichbares Homelab sehen wir uns dann die zweite Variante an: die Konfiguration mit dem DNS-Challenge Addon. Die DNS-Challenge ermöglicht es uns, SSL-Zertifikate von Let´s Encrypt zu erhalten, ohne den Server von außen erreichbar machen zu müssen. Dabei wird durch einen API-Key automatisch ein DNS-Eintrag für unsere Domain gesetzt, um den Besitz zu bestätigen.


Caddy mit HTTP-Challenge

Dies ist die Variante mit "weniger" Aufwand. Dafür benötigen wir lediglich das Caddy Basic Image.

DNS

Bevor wir Caddy einrichten, können wir bereits die entsprechenden DNS-Einträge für unsere Domain setzen. Je nachdem, was wir verwenden möchten, können wir entweder die gesamte Domain mit einem Wildcard-Eintrag auf unseren Caddy-Server zeigen lassen, was uns ermöglicht, später jede Subdomain zu nutzen. Falls wir genau wissen, welche Subdomains wir vergeben wollen, können wir diese auch spezifisch eintragen. In meinem Fall wähle ich die Wildcard-Variante.

DNS-Eintrag

Docker

Da Caddy später in Docker läuft, benötigen wir natürlich Docker. Dies kann ganz einfach mit dem offiziellen Convenience-Script installiert werden:

curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh

Docker-Netzwerk

Um später die anderen Container nur über Caddy erreichbar zu machen, müssen wir noch ein Docker-Netzwerk erstellen, das wir später mit allen Containern nutzen können:

docker network create caddy_net

Caddy Docker Compose

Wir richten Caddy als Docker-Compose ein. Dafür benötigen wir eine Compose-Datei. In dieser Datei nutzen wir die Standardeinstellungen und fügen das Caddy-Netzwerk hinzu, das später für andere Container genutzt werden kann:

services:
  caddy:
    image: caddy:latest
    container_name: caddy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
      - caddy_config:/config
    networks:
      - caddy_net

networks:
  caddy_net:
    external: true

volumes:
  caddy_data:
  caddy_config:

Caddyfile

Nachdem wir die Compose für Caddy erstellt haben, erstellen wir nun unser Caddyfile. Dies ist das einzige Konfigurations-File, das wir benötigen, um Caddy zu betreiben. Die Caddyfile-Datei erstellen wir im selben Ordner wie die Compose für Caddy:

touch Caddyfile

Nun können wir den Container starten:

sudo docker compose up -d

Caddy erfolgreich gestartet

Damit ist Caddy prinzipiell funktionsfähig. Nun kümmern wir uns darum, einen Service erreichbar zu machen.

Hinzufügen von Services

Um einen Anwendungsdienst mit gewünschter Subdomain hinzuzufügen, müssen wir das Caddyfile bearbeiten.

Der Aufbau für die hinzuzufügenden Einträge ist simpel. Je nachdem, was wir hinzufügen möchten - entweder ein Service auf einem entfernten Host oder ein Service, der als Docker-Container auf demselben Host läuft - sieht die Syntax wie folgt aus:

app1.domain.de {
    reverse_proxy IP:PORT
}

app2.domain.de {
    reverse_proxy CONTAINERNAME:PORT
}

Docker

Für die Docker-Container ist es wichtig, sicherzustellen, dass die Container auch das caddy_net-Netzwerk nutzen. Als Beispiel nutzen wir einen HTTP-Webserver. Es ist wichtig, das Netzwerk entsprechend in die Compose-Datei aufzunehmen. Die Port-Sektion kann anschließend entfernt werden, da wir über Caddy darauf zugreifen und Caddy durch das Netzwerk sowieso auf die Ports zugreifen kann.

name: http-server
services:
    simple-http-server:
        image: jdkelley/simple-http-server:latest
        volumes:
            - ./file:/serve
        networks:
            - caddy_net
networks:
  caddy_net:
     external: true

Den vergebenen Containernamen können wir nun in Caddy verwenden. Falls ihr nicht wisst, welchen Port der Service verwendet, lässt sich dies mit docker ps herausfinden. Dort könnt ihr auch den Namen eures Containers sehen.

Docker-Container gestartet

test.juseclab.de {
    reverse_proxy http-server-simple-http-server-1:8000
}

Speichern der Änderungen

Nachdem wir unser Caddyfile nach unseren Wünschen angepasst haben, müssen wir den Container einmal neu starten, um die Änderungen zu übernehmen:

sudo docker restart caddy

Ergebnis

Anschließend sollte euer gewünschter Service unter der gewählten Subdomain erreichbar sein und über ein gültiges SSL-Zertifikat verfügen.

Ergebnis


Caddy mit DNS-Challenge

Für unser Homelab möchten wir gerne die DNS-Challenge nutzen, da unsere Server nicht von außen erreichbar sind und somit auch nicht von Let´s Encrypt per HTTP-Challenge überprüft werden können. Um die DNS-Challenge nutzen zu können, müssen wir ein eigenes Caddy-Image für Docker erstellen, das das benötigte Addon enthält. Das klingt jedoch komplizierter, als es ist!

Addon suchen

Für jeden Anbieter gibt es ein eigenes Addon. Ihr müsst also herausfinden, wie das Addon für euren jeweiligen Anbieter heißt. Sucht einfach auf der Downloadseite von Caddy nach eurem Anbieter und kopiert den entsprechenden Repository-Namen.

Addon-Suche

Image erstellen

Nachdem wir nun das benötigte Addon gefunden haben, können wir unser eigenes Image für Caddy bauen. Dafür benötigen wir lediglich ein Dockerfile in dem Verzeichnis, in dem später Caddy laufen soll.

Im Dockerfile geben wir an, dass das Standard-Caddy-Image zusammen mit dem Addon zu einem neuen Image gebaut werden soll:

FROM caddy:2-builder AS builder

RUN xcaddy build \
    --with github.com/caddy-dns/cloudflare # Gebt hier eure Repository für den Anbieter ein

FROM caddy:2

COPY --from=builder /usr/bin/caddy /usr/bin/caddy

Nachdem wir die Datei gespeichert haben, können wir das Image einfach mit folgendem Befehl erstellen lassen:

docker build -t caddy-dns .

Docker-Netzwerk

Auch hier erstellen wir ein neues Docker-Netzwerk, das später auch für unsere anderen Container genutzt werden kann, damit diese nur über Caddy erreichbar sind:

docker network create caddy_net

Caddy Docker Compose

Als Nächstes bereiten wir die Compose-Datei für Caddy vor. Hier gibt es nun einen kleinen Unterschied: Wir verwenden den build-Befehl, um unser Image zu nutzen.

services:
  caddy:
    build: .
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
      - caddy_config:/config   
    networks:
      - caddy_net

networks:
  caddy_net:
    external: true

volumes:
  caddy_data:
  caddy_config:

DNS-API

Je nach Anbieter kann der Vorgang hier variieren. Ich kann euch jedoch sehr empfehlen, die Nameserver von Cloudflare für eure Domains zu nutzen. Bei Cloudflare kann man sehr genau angeben, welche Berechtigungen der API Key haben soll und den Zugriff auf die DNS-Zone der gewünschten Domain genau einschränken. Bei vielen anderen Anbietern erhält man einfach einen API-Key für alles.

Cloudflare API Key erstellen

In Cloudflare navigieren wir auf dem Dashboard oben rechts zu unserem Benutzernamen.

Cloudflare Dashboard

Nun haben wir links den Reiter "API-Token".

API-Token Reiter

Dort klicken wir nun auf Token erstellen und wählen die Vorlage Zonen-DNS bearbeiten aus.

In der nächsten Übersicht müssen wir nur noch angeben, welche Domain verwaltet werden darf. Der Rest kann so bleiben wie er ist.

Token-Einstellungen

Nachdem wir die Einstellungen gespeichert haben, wird uns der Token angezeigt. Diesen müssen wir sofort kopieren, da er nur einmal angezeigt wird.

API-Token

Caddyfile

Auch hier müssen wir zuletzt unser Caddyfile erstellen. Als Beispiel führe ich denselben Webserver auf dem Host als Docker-Container aus. Wir müssen in unserer Konfiguration nun eine zusätzliche Zeile hinzufügen, um anzugeben, dass Caddy nun die DNS-Challenge für unsere Einträge nutzen soll.

{
    acme_dns <provider> <key>
}

homelab.juseclab.de {
    reverse_proxy http-server-simple-http-server-1:8000
}

Speichern der Änderungen

Auch hier speichern wir unsere Änderungen mit einem Neustart von Caddy, falls Caddy bereits läuft:

sudo docker restart caddy

DNS-Umschreibung

Um jetzt unsere Services im Homelab erreichen zu können, müssen wir auf unserem lokalen DNS-Server eine DNS-Umschreibung konfigurieren, damit uns der DNS-Server zu unserem Caddy-Server leitet, wenn wir die gewünschte Domain aufrufen.

Dies kann beispielsweise sehr einfach erfolgen, wenn ihr einen Adguard DNS-Server betreibt. Hier können wir dann beispielsweise die gesamte Domain mit einem Wildcard umleiten auf unseren Caddy-Server.

DNS-Umschreibung

Ergebnis

Wenn wir jetzt die Domain für unseren hinterlegten Service aufrufen, erhalten wir ebenfalls eine Seite mit einem gültigen SSL-Zertifikat und HTTPS.

Ergebnis