Wenn du deine Website nicht nur mit HTTP, sondern auch mit dem sicheren HTTPS-Protokoll anbieten möchtest, benötigst du ein signiertes SSL-Zertifikat. Hier sind einige Gründe, warum du HTTPS brauchst….
-
Sicherheit: HTTPS hilft, die Verbindung zwischen einem Client und einem Server zu sichern, indem es die zwischen ihnen übertragenen Daten verschlüsselt.
-
Vertrauen: HTTPS verleiht deiner Website ein gewisses Maß an Vertrauen und Glaubwürdigkeit. Heutige Browser zeigen meist eine Warnung an wenn eine Webseite nur HTTP anbietet, oder das Zertifikat nicht durch eine Zertifizierungsstelle wie beispielsweise Let’s Encrypt signiert ist.
-
SEO: Suchmaschinen verwenden SSL/TLS-Zertifikate als Rankingfaktor. Das bedeutet, dass ein SSL/TLS-Zertifikat dazu beitragen kann, das Suchmaschinenranking deiner Website zu verbessern.
HTTPS ist grob gesagt der Stand der Technik, den jede Website unterstützen sollte.
Das signierte Zertifikat bekommst du von einer Zertifizierungsstelle (engl. CA). In diesem Artikel benutzen wir Let’s Encrypt.
Theorie
Eigentlich kann dir die Theorie egal sein und du könntest direkt zur Praxis springen, weil der Cert-Manager und der Ingress Controller alles für dich machen. Aber für eventuelles Troubleshooting hier die Theorie.
Was ist Let’s Encrypt
Let’s Encrypt ist eine kostenlose und offene Zertifizierungsstelle (CA). Es bietet Domain Validation (DV) SSL/TLS-Zertifikate, die zum Sichern und Verschlüsseln von Daten verwendet werden, die zwischen einem Client (z. B. einem Webbrowser) und einem Server (z. B. einer Website) gesendet werden.
Der große Vorteil ist, dass die Zertifizierung bei Let’s Encrypt vollkommen automatisch abläuft. Das bedeutet, dass du das System nur einmal aufsetzen musst und dich danach nie wieder um Zertifikate kümmern musst.
Du brauchst keinen Account bei Let’s Encrypt, da die komplette Kommunikation automatisch zwischen deinem Kubernetes Cluster und der Webseite von Let’s Encrypt abgehandelt wird.
Wie funktioniert die Zertifizierung bei Let’s Encrypt
Um ein SSL/TLS-Zertifikat von Let’s Encrypt zu erhalten, muss dein System zwei Schritte durchlaufen: 1. Domänenvalidierung (Domain Validation) und 2. Zertifikatsausstellung (Certificate Issurance).
Diese Zertifikate sind in der Regel 90 Tage gültig und werden dann automatisch erneuert.
Domänenvalidierung
Beim ersten Schritt muss dein Kubernetes Cluster beweisen, dass er die Domäne - für die er das Zertifikat haben will - besitzt. Das funktioniert wie folgt:
- Kubernetes fragt bei Lets Encrypt nach einer Domänenvalidierung.
- Let’s Encrypt stellt eine Herausforderung (Challenge*) und schickt eine Nonce zum signieren.
- Kubernetes löst die Herausforderung und signiert die Nonce mit seinem privaten Schlüssel.
- Let’s Encrypt prüft ob die Herausforderungen gelöst wurden und verifiziert die Signatur auf der Nonce.
- Hat alles geklappt ist das von Kubernetes verwendete Schlüsselpaar ist jetzt ein autorisiertes Schlüsselpaar.
* Eine Herausforderung (engl. Challenge) kann die Bereitstellung eines DNS Eintrags sein, oder einfach das Hosten einer von Let’s Encrypt gewählten Datei und Pfad.
Zertifikatsausstellung
Mit dem autorisierten Schlüsselpaar kann dein Kubernetes Cluster Zertifikate für deine Domäne anfordern, erneuern und widerrufen. Das funktioniert so:
- Kubernetes stellt eine “PKCS#10” Zertifikatssignierungsanforderung (engl. Certificate Signing Request / CSR). Diese CSR wird durch das autorisierte Schlüsselpaar signiert.
- Let’s Encrypt verifiziert beide Signaturen. Wenn alles gut aussieht, stellen sie ein Zertifikat für die gewünschte Domäne mit dem öffentlichen Schlüssel des CSR aus und sendet es zurück.
- Kubernetes kann fortan dieses Zertifikat für jegliche SSL Kommunikation nutzen.
Praxis
Ich verwende Azure Kubernetes Service (AKS) in der Version 1.23. Auf einem selbst gehosteten Kubernetes Cluster sollte das allerdings genauso funktionieren, gebt mir gerne Feedback!
Ziel der Übung ist, dass du mithilfe von Ingress Regeln ein Zertifikat beantragen kannst.
Prinzipiell braucht dein Kubernetes Cluster dazu 3 Dinge:
- den Cert-Manager
- eine ClusterIssuer Ressource
- einen Ingress-Controller
Cert-Manager installation
Der Cert-Manager fügt Zertifikate (engl Certificate) und Zertifikataussteller (engl. certificate issuers) als Ressourcentypen in Kubernetes hinzu und stellt sicher, dass Zertifikate gültig und aktuell sind, und versucht Zertifikate vor Ablauf zu erneuern.
Prinzipiell kann es mit unterschiedlichen Systemen wie z.B. Hashicorp Vault oder Venafi kommunizieren. Wir brauchen es aber nur für Let’s Encrypt.
Ich verwende den Cert-Manager aktuell in der neusten Version 1.10.1. Diese Version unterstützt die Kubernetes Versionen 1.20 bis 1.26. Eventuelle Versionsunverträglichkeiten kannst du hier prüfen.
Du kannst ihn ganz einfach mit dem Helm-Chart aus dem Jetstack Repository auf deinem Kubernetes Server ausrollen.
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace --version v1.10.1 --set installCRDs=true
Diese Befehle erzeugen direkt einen neuen Namespace mit dem Namen “cert-manager”. Aufgrund des --set installCRDs=true
Flags werden außerdem weitere Custom Resource Definitions angelegt.
Du wirst feststellen, dass dort nun 3 neue Pods laufen.
ClusterIssuer installieren
Jetzt wo Cert-Manager installiert ist, müssen wir ihm noch sagen dass wir Let’s Encrypt als Zertifizierungsstelle (engl. CA) verwenden wollen. Das geht mithilfe einer ClusterIssuer Ressource. Diese legst du einfach mithilfe einer .yaml Datei an.
clusterissuer.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: DEINE@EMAIL.COM
privateKeySecretRef:
name: letsencrypt
solvers:
- http01:
ingress:
class: nginx
Ersetze DEINE@EMAIL.COM mit deiner Emailadresse. Diese verwendet Letsencrypt um dich vor auslaufenden Zertifikaten zu warnen falls die automatische Erneuerung nicht funktioniert hat. Du brauchst keinen Account bei Let’s Encrypt oder ähnliches.
Diese YAML deployst du nun wie gewohnt auf deinem K8s Cluster.
kubectl apply -f clusterissuer.yaml
Ingress Controller installieren
Solltest du noch keinen Ingress Controller verwenden ist jetzt der ideale Zeitpunkt einen zu installieren. In einem vorherigen Beitrag habe ich den NGINX Ingress Controller vorgestellt.
Kurz gesagt ist der Ingress Controller das System das eingehende Anfragen entgegennimmt und anhand von konfigurierten Ingress Regeln an deine internen Services weiterverteilt.
So installierst du ihn mit Helm in den Namespace “ingress-basic”.
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm install ingress-nginx ingress-nginx/ingress-nginx --create-namespace --namespace ingress-basic --set controller.service.annotations."service\.beta\.kubernetes\.io/azure-load-balancer-health-probe-request-path"=/healthz --set controller.replicaCount=3 --set controller.service.externalTrafficPolicy=Local
Beachte, dass ich hier “replicaCount=3” gesetzt habe. Je größer die Replica Anzahl, desto mehr Anfragen kann dein Cluster gleichzeitig entgegen nehmen. Allerdings braucht jede Replica auch etwas CPU und RAM. Ein guter Wert ist 1 Replica pro Knoten.
Mit controller.service.externalTrafficPolicy=Local werden die IP Adressen zu deinen Pods weitergeleitet. Eventuell brauchst du das bei deinem Setup nicht. In der Regel sollte das aber keine Probleme verursachen.
Auch hier werden ein paar Pods gestartet.
Ingress Regeln: Praktisches Beispiel
Seit etwa einer Woche hoste ich diesen Blog nicht mehr auf WordPress, sondern auf Azure Blob Storage als Static Website.
Das hat zwei Gründe. Zum einen war WordPress mit der wachsenden Anzahl an Plugins ziemlich langsam und zum anderen muss ich bei Azure für eine managed Disk immer die Maximalgröße bezahlen. Bei Blob Storages zahle ich nur das was ich auch tatsächlich auch nutze.
Der Blob Storage liegt mithilfe eines C-Eintrags auf www.quisl.de. Aber ich möchte, dass der Blog auch direkt auf quisl.de (ohne www) aufrufbar ist. Hier kommt der Kubernetesserver als Weiterleitung ins Spiel.
Zuerst erstelle ich einen Service der sich die Daten als ExternalName von www.quisl.de holt.
service.yaml
apiVersion: v1
kind: Service
metadata:
name: blog
spec:
type: ExternalName
externalName: www.quisl.de
ports:
- port: 80
targetPort: 80
name: http
protocol: TCP
- port: 443
targetPort: 443
name: https
protocol: TCP
Und jetzt kann ich eine Ingress Regel schreiben, die jeden Traffic von quisl.de auf diesen Service weiterleitet.
ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: blog-ingress
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/rewrite-target: /$1
nginx.ingress.kubernetes.io/use-regex: "true"
cert-manager.io/cluster-issuer: letsencrypt
nginx.ingress.kubernetes.io/backend-protocol: https
nginx.ingress.kubernetes.io/upstream-vhost: www.quisl.de
spec:
tls:
- hosts:
- quisl.de
secretName: tls-secret
rules:
- host: quisl.de
http:
paths:
- backend:
service:
name: blog
port:
number: 443
path: /(.*)
pathType: ImplementationSpecific
Das ganze habe ich mit apply auf dem Cluster ausgespielt.
kubectl apply -f .\service.yaml
kubectl apply -f .\ingress.yaml
Cert-Manager kümmert sich im Hintergrund um das Zertifikat und quisl.de war innerhalb von einer Minute erreichbar.
Abschluss
Jetzt sollte alles funktionieren. Falls nicht kann ich dir die Troubleshooting Webseite von Cert-Manager empfehlen.
Mithilfe von Ingress Regeln kannst du nun für jede URL ein Zertifikat beantragen. Natürlich müssen diese URLs jeweils einen A-Eintrag haben der auf die IP deines Kubernetes Clusters zeigt. Nur so kann der Cert-Manager die Herausforderung erfüllen und ein Zertifikat von Let’s Encrypt erhalten.
Konnte ich helfen? Ich freue mich über einen Drink!
💙