Zum Hauptinhalt springen
  1. Blog/

Kubernetes auf Hetzner Cloud: Automatisches SSL/TLS mit Let's Encrypt

Marcel Ryser
Autor
Marcel Ryser
Softwarearchitekt und Tech-Enthusiast aus Bern

Heute sind die meisten Websites mit TLS (https) gesichert. Mit Let’s Encrypt gibt es inzwischen einen sehr günstigen (kostenlosen) Weg zu SSL-Zertifikaten – gerade für uns Privatnutzer.

Weil Let’s Encrypt eine API zum Abrufen und Erneuern von Zertifikaten bereitstellt, können wir unserem Cluster einen automatischen Zertifikatsdienst hinzufügen: CertManager. Mit dieser Komponente erstellen und erneuern wir ein Zertifikat automatisch – allein durch das Deployen einer Ingress-Ressource mit einer bestimmten Annotation cert-manager.io/cluster-issuer.

cert-manager.io installieren
#

Zuerst legen wir die Custom Resource Definitions für den cert-manager an:

kubectl apply --validate=false -f https://raw.githubusercontent.com/jetstack/cert-manager/v0.13.1/deploy/manifests/00-crds.yaml

Jetzt erstellen wir einen Namespace, in dem der cert-manager leben kann:

kubectl create namespace cert-manager

Nun deployen wir den cert-manager per Helm in diesen Namespace:

helm repo add jetstack https://charts.jetstack.io

helm repo update

helm install \
  cert-manager \
  --namespace cert-manager \
  --version v0.13.1 \
  --set webhook.enabled=false \
  jetstack/cert-manager

Dir ist oben vielleicht webhook.enabled=false aufgefallen. Let’s Encrypt braucht eine Art Nachweis, dass dir eine Domain gehört. Zur Verifikation gibt es verschiedene Challenges, die du durchlaufen musst, um das Zertifikat zu erhalten. Das kann die http-01-Challenge sein, die per http auf die Let’s-Encrypt-Challenge antwortet. Die interessantere ist aber die dns-01-Challenge, die die Anfrage mit DNS-Einträgen beantwortet. Mit der dns-01-Challenge kannst du Wildcard-Zertifikate für deine Domains erstellen, was mit der http-01-Challenge nicht möglich wäre. Mit Cert-Manager und der Tatsache, dass wir CloudFlare für DNS nutzen, ist das kein Problem. Cert-Manager unterstützt CloudFlare out of the box. Damit Cert-Manager CloudFlare nutzen kann, erstellen wir das Secret mit dem “Global API Key” von CloudFlare (falls du dich fragst: Nein, das API-Token von CloudFlare funktioniert nicht, auch wenn das die naheliegendere Wahl wäre).

cat <<EOF | kubectl apply -f-
apiVersion: v1
kind: Secret
metadata:
  name: cloudflare-api-key
  namespace: cert-manager
data:
   api-key: $(echo -n '<your-global-api-key-goes-here>' | base64)
EOF

Bei Let’s Encrypt gibt es RateLimits, die die Zahl der Anfragen und pro Woche erstellbaren Zertifikate begrenzen. Bei einer Fehlkonfiguration in einer Ingress-Ressource oder einem anderen Zertifikatsanforderungs-Objekt (siehe https://cert-manager.io/docs/usage/certificate/) erreichst du dieses Limit ziemlich schnell. Ich empfehle immer, zuerst die Staging-Infrastruktur von Let’s Encrypt zu nutzen, um die Konfiguration zu testen, bevor du die Produktions-Infrastruktur verwendest. Darauf gehen wir gleich ein.

CertManager braucht eine Identität, um Zertifikate bei Let’s Encrypt anzufordern und zu erhalten. Wir wollen sowohl die Staging- als auch die Produktions-Infrastruktur von Let’s Encrypt nutzen, deshalb erstellen wir je einen ClusterIssuer für Let’s Encrypt Produktion und Staging.

cat <<EOF | kubectl apply -f-
apiVersion: cert-manager.io/v1alpha2
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    # You must replace this email address with your own.
    # Let's Encrypt will use this to contact you about expiring
    # certificates, and issues related to your account.
    email: <your-email>
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      # Secret resource used to store the account's private key.
      name: letsencrypt-staging-issuer-private-key
    # Add a single challenge solver, HTTP01 using nginx
    solvers:
    - dns01:
        cloudflare:
          email: <cloudflare-account-email>
          apiKeySecretRef:
            name: cloudflare-api-key
            key: api-key
---
apiVersion: cert-manager.io/v1alpha2
kind: ClusterIssuer
metadata:
  name: letsencrypt
spec:
  acme:
    # You must replace this email address with your own.
    # Let's Encrypt will use this to contact you about expiring
    # certificates, and issues related to your account.
    email: <your-email>
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      # Secret resource used to store the account's private key.
      name: letsencrypt-issuer-private-key
    # Add a single challenge solver, HTTP01 using nginx
    solvers:
    - dns01:
        cloudflare:
          email: <cloudflare-account-email>
          apiKeySecretRef:
            name: cloudflare-api-key
            key: api-key
EOF

Das war’s! Dein Cluster kann jetzt Zertifikate für Ingress-Ressourcen oder jede andere Zertifikatsanforderung ausstellen. Cert-Manager erneuert Zertifikate, wenn sie kurz vor dem Ablauf stehen.

Ein Beispiel, wie eine Ingress-Ressource automatisch ein Zertifikat konfiguriert, findest du unter https://cert-manager.io/docs/usage/ingress/. Du kannst cert-manager.io/cluster-issuer entweder auf letsencrypt-staging oder letsencrypt setzen, um zwischen beiden Infrastrukturen zu wechseln.

Wie geht’s weiter?
#

Mich stört, dass die Kommunikation zwischen Containern/Nodes nicht verschlüsselt ist. Das private Netzwerk zwischen den Servern ist schon besser als Kommunikation über das Internet. Aber ich migriere vielleicht weg vom privaten Netzwerk hin zu einem verschlüsselten WireGuard-Netzwerk zwischen den Servern.

Ein weiteres Thema wäre das Backup-Problem. Ich sichere den Cluster bzw. das PersistedVolume bisher nur manuell. So will ich das nicht. Ich schreibe einen kleinen Controller für Kubernetes, der den Cluster mit borg auf einen entfernten SSH-Server sichert – so kann ich verschlüsselte Backups auf einem entfernten Server erstellen. Da ich das alles in meiner Freizeit schreibe, brauche ich dafür ein paar Wochen (vielleicht nur wenige (unter 3) Stunden Code pro Woche).