Security ~4 Min.

cert-manager: Internal CA und mTLS

TLS automatisieren — intern und nach außen.

von PlatPrep

“Wir brauchen Zertifikate für interne Service-zu-Service-Kommunikation.” Diese Aussage hat früher bedeutet: ein Ticket erstellen, auf das Security-Team warten, Zertifikat in einer Datei bekommen, manuell updaten wenn es abläuft.

cert-manager macht das zu einer Kubernetes-nativen Selbstbedienung.

Der Ausgangspunkt ist ein ClusterIssuer mit einer internen CA. Im einfachsten Fall liegt das CA-Keypair als TLS-Secret im Cluster und der Issuer ist vom Typ ca. Sauberer ist die Anbindung an Vault: Die PKI Secret Engine übernimmt die CA-Rolle, der private Schlüssel verlässt Vault nie, und cert-manager reicht Zertifikatsanfragen über den vault-Issuer durch. Wer ohnehin Vault betreibt, bekommt damit Audit-Log und Rollen-basierte Ausstellungsregeln geschenkt — mehr dazu im Artikel über Vault und Kubernetes-Auth.

Pro Service gibt es dann ein Certificate-Objekt:

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: checkout-mtls
  namespace: payments
spec:
  secretName: checkout-mtls-tls
  duration: 720h      # 30 Tage
  renewBefore: 240h   # 10 Tage vor Ablauf rotieren
  privateKey:
    rotationPolicy: Always
  dnsNames:
    - checkout.payments.svc.cluster.local
  issuerRef:
    name: internal-ca
    kind: ClusterIssuer

Zwei Felder verdienen Aufmerksamkeit. renewBefore steuert, wann cert-manager das Zertifikat erneuert — bei kurzen Laufzeiten will man hier großzügig sein, damit ein hängender Issuer nicht sofort zu abgelaufenen Zertifikaten führt. Und rotationPolicy: Always sorgt dafür, dass bei jeder Erneuerung auch ein neuer privater Schlüssel generiert wird. Der Default behält den alten Schlüssel — was den Sinn der Rotation halb aushebelt.

Das Reload-Problem

Die unbequeme Wahrheit: cert-manager rotiert das Secret zuverlässig, aber niemand garantiert, dass deine Applikation das mitbekommt. Viele Anwendungen laden TLS-Material genau einmal beim Start. Nach der Rotation läuft der Pod mit dem alten Zertifikat weiter — bis es abläuft und Verbindungen mit Handshake-Fehlern sterben. Das passiert gern nachts, Wochen nach der Einführung, wenn niemand mehr an die Zertifikate denkt.

Drei Lösungswege, in absteigender Eleganz: Erstens applikationsseitiges Hot-Reload — der Server beobachtet die Zertifikatsdateien und lädt sie bei Änderung neu; Go macht das mit GetCertificate-Callbacks fast trivial. Zweitens ein Tool wie Reloader, das Deployments bei Secret-Änderungen rollend neu startet — robust, kostet aber einen Restart pro Rotation. Drittens der csi-driver aus dem cert-manager-Projekt, der Zertifikate als ephemeres Volume direkt in den Pod mountet, mit Laufzeiten, die an die Pod-Lebensdauer gekoppelt sind. Für mTLS-Szenarien mit vielen kurzlebigen Zertifikaten ist das oft die sauberste Variante, weil gar kein Kubernetes-Secret mehr entsteht.

Vertrauen verteilen — und die CA-Rotation überleben

Ein Zertifikat zu besitzen ist die halbe Miete; die andere Hälfte ist, dass Gegenstellen der ausstellenden CA vertrauen. Hier kommt trust-manager ins Spiel, ein Schwesterprojekt von cert-manager: Eine Bundle-Ressource verteilt das CA-Bundle als ConfigMap in alle (oder ausgewählte) Namespaces. Damit gibt es genau eine Quelle der Wahrheit für “welchen CAs vertrauen wir intern” — statt zwanzig kopierter ConfigMaps mit unklarem Stand.

Richtig wichtig wird das bei der CA-Rotation, dem Teil, den fast jeder beim Design vergisst. Eine interne CA läuft irgendwann ab oder muss ersetzt werden. Der einzige unterbrechungsfreie Weg ist eine Übergangsphase, in der das Trust-Bundle alte und neue CA enthält, während die Leaf-Zertifikate nach und nach von der neuen CA ausgestellt werden. Erst wenn kein Zertifikat der alten CA mehr im Umlauf ist, fliegt sie aus dem Bundle. Wer das Bundle hart auf eine einzelne CA verdrahtet hat, steht bei der Rotation vor einem Big-Bang-Cutover mit garantierten Ausfällen.

Noch eine Abgrenzung, die in der Praxis Architektur-Diskussionen spart: Wenn ein Service Mesh wie Istio oder Linkerd im Einsatz ist, übernimmt das Mesh die mTLS-Identitäten zwischen Sidecars komplett selbst — inklusive Rotation im Stundenbereich. cert-manager hat dann weiterhin seinen Platz für Ingress-TLS, Webhooks und Workloads außerhalb des Mesh, aber zwei Systeme für denselben Scope zu betreiben endet in Verwirrung darüber, welches Zertifikat gerade wirklich verwendet wird. Wie Istio das löst, steht im Artikel zu Istio mTLS und Traffic Management.

Der typische Gesamtschnitt sieht so aus: Let’s Encrypt über den ACME-Issuer für alles, was von außen erreichbar ist; die interne CA für Service-zu-Service-Verkehr und interne Endpoints. Beides läuft im selben cert-manager, sauber getrennt über zwei Issuer.

Zum Schluss das Monitoring, ohne das die ganze Automatisierung auf Vertrauen basiert: cert-manager exportiert Prometheus-Metriken, allen voran certmanager_certificate_expiration_timestamp_seconds. Ein Alert auf “Zertifikat läuft in weniger als x Tagen ab, obwohl renewBefore längst greifen müsste” fängt hängende Issuer, kaputte Vault-Policies und festgefahrene CertificateRequest-Ketten ab, bevor der erste Handshake fehlschlägt. Abgelaufene Zertifikate sind die vermeidbarste Incident-Kategorie überhaupt — vorausgesetzt, jemand schaut hin. Wer tiefer einsteigen will — Issuer-Typen, ACME-Challenges, Troubleshooting — findet das im cert-manager-Lernpfad.