Zum Inhalt springen
Zurück zu Ressourcen
Platform Engineering

Trusted CA Injection in Enterprise Kubernetes

Eigene CAs gehören zu jedem Enterprise-Cluster: TLS-Inspection, internes PKI, selbstsignierte Services. Container vertrauen ihnen ab Werk nicht. Dieses Whitepaper zeigt, warum die üblichen Workarounds in der Skalierung kippen, was cainjekt auf Runtime-Ebene anders macht und welche Code-Pfade und Failure-Modes wir auf dem Weg zur Production gesehen haben.

23 Seiten14 minJan Lauber
Den vollständigen Leitfaden erhalten

Open Source unter github.com/natrontech/cainjekt

Was Sie erwartet

  1. 1Das Trust-Store-Problem im Massstab
  2. 2Vier Workarounds und warum jeder einzelne kippt
  3. 3Runtime-Ebene: warum NRI
  4. 4cainjekt von innen: drei Phasen, ein Binary
  5. 5Was der OCI-Hook pro Distribution wohin schreibt
  6. 6Warum die JVM ein Sonderfall ist
  7. 7Production-Hardening, das im Upstream-MVP fehlte
  8. 8Betrieb: Metriken, Alerts, Debugging
  9. 9Vergleich mit cert-manager-trust-manager und Mutating-Webhooks
  10. 10Lessons aus dem Betrieb
Vorschau
~55% des Leitfadens

In jedem Enterprise-Kubernetes-Cluster steckt mindestens eine eigene CA. Mal ist es der TLS-Inspection-Proxy am Egress, mal das interne PKI für Service-zu-Service-mTLS, mal ein selbstsigniertes Zertifikat auf einem Legacy-Admin-Endpunkt. Häufig sind es alle drei.

Damit ein Container über HTTPS mit diesen Diensten spricht, muss er die zugehörigen CAs als vertrauenswürdig erkennen. Standardmässig tut er das nicht. Mit jeder neuen Anwendung kommt der gleiche Fehler an die Oberfläche, mit jeder CA-Rotation eine flottenweite Restart-Welle, und mit jedem neuen Team eine neue Variante des immer gleichen Workarounds.

Wir betreiben seit 2021 Managed Kubernetes für Schweizer Unternehmen und haben dabei jeden dieser Workarounds wachsen und kippen sehen. Dieses Whitepaper geht das Problem konkret durch — mit echten Pfaden, echten Variablen, echtem Code — und erklärt, was unser Tool cainjekt auf der Container-Runtime-Schicht löst, was nicht, und warum.

Das Trust-Store-Problem im Massstab

Ein Container bringt das CA-Bundle seiner Base-Distribution mit. Welcher Pfad das genau ist, hängt von der Distribution ab:

DistributionTrust-Store-DateiAnchor-Verzeichnis
Debian / Ubuntu/etc/ssl/certs/ca-certificates.crt/usr/local/share/ca-certificates/
RHEL / Fedora / CentOS/etc/pki/tls/certs/ca-bundle.crt/etc/pki/ca-trust/source/anchors/
openSUSE / SLES/etc/ssl/ca-bundle.pem/etc/pki/trust/anchors/
Alpine/etc/ssl/certs/ca-certificates.crt/usr/local/share/ca-certificates/
Arch/etc/ssl/certs/ca-certificates.crt/etc/ca-certificates/trust-source/anchors/

Was in diesen Bundles steht: alle öffentlich bekannten CAs, die ein Container im offenen Internet je antreffen wird. Was nicht drinsteht: ein einziges Byte aus dem internen Trust-Root des Unternehmens.

Daraus entsteht der eine Fehler, den jede Platform Engineerin schon hundertmal gesehen hat:

x509: certificate signed by unknown authority

In einem kleinen Setup wirst du das einfach abfangen. Eine CA in zwei, drei Images nachziehen, neu bauen, Restart, fertig. Sobald du eine Plattform mit hundert Services und mehreren Teams betreibst, wird das Thema zum Dauerbegleiter. Jeder neue Microservice ist ein potenzieller Trust-Failure. Jede CA-Rotation reisst Pods im halben Cluster mit. Und jedes neue Team fängt vorne an: erst der erste Fehler, dann die erste Recherche, dann der erste Workaround.

Schlimmer als das: die Bundle-Datei ist nicht das ganze Bild. Sprach-Runtimes haben jeweils eigene Vorstellungen davon, wo Trust herkommt:

RuntimeBerücksichtigt OS-Trust-Store?Override-Variable
OpenSSL / curl / wgetJaSSL_CERT_FILE
Go (crypto/tls)Liest OS-Store beim Start, respektiert SSL_CERT_FILESSL_CERT_FILE
Python (requests, urllib3)Häufig certifi, ignoriert OS-StoreREQUESTS_CA_BUNDLE, SSL_CERT_FILE
Node.jsNein, hat ein eigenes BundleNODE_EXTRA_CA_CERTS (additiv)
Java (JVM)Nein, hat einen eigenen Keystore (cacerts)JAVA_TOOL_OPTIONS mit -Djavax.net.ssl.trustStore=…
Ruby (OpenSSL)JaSSL_CERT_FILE
OS-Trust-Store

Datei im Container-Filesystem. Pfad und Format hängen von der Distribution ab.

Debian / Ubuntu
/etc/ssl/certs/ca-certificates.crt
RHEL / Fedora
/etc/pki/tls/certs/ca-bundle.crt
Alpine
/etc/ssl/certs/ca-certificates.crt
openSUSE
/etc/ssl/ca-bundle.pem
Sprach-Runtime

Eigenes Trust-Verhalten. Konfiguration über Umgebungsvariablen, oft unabhängig vom OS-Trust-Store.

Go / Python / Ruby
SSL_CERT_FILE
Node.js
NODE_EXTRA_CA_CERTS
Java (JDK 18+)
JAVA_TOOL_OPTIONS

Eine vollständige Lösung muss beide Schichten bedienen.

Eine Lösung, die nur die OS-Datei ergänzt, lässt die halbe Anwendungslandschaft im Regen stehen. Eine Lösung, die nur Variablen setzt, hilft Tools wie curl nicht. Eine vollständige Lösung muss beides bedienen.

Vier Workarounds und warum jeder einzelne kippt

Image-Baking

CA in jedes Container-Image einbauen

Bricht, sobald Vendor-Images mitspielen oder die CA rotiert.
Volume-Mount

ConfigMap als Datei in den Container

Patcht den OS-Trust-Store nicht. Sprach-Runtimes bleiben blind.
Init-Container

Vorgeschalteter Container patcht den Trust-Store

Scheitert auf einem Read-Only-Rootfs.
Mutating Webhook

Pod-Spec wird beim Admission ergänzt

Neuer kritischer Punkt im Pod-Erstellungspfad.

Workaround 1: CA in jedes Image einbauen

Der naheliegendste Schritt. Das Team, das die Base-Images pflegt, ergänzt die Firmen-CA per update-ca-certificates. Alle Downstream-Images erben sie. Fertig.

Das funktioniert, solange du jedes Image selbst baust. Sobald ein Vendor- oder Helm-Chart-Image mit ins Boot kommt, hört es auf. Dann rotiert die CA, und ein paar hundert Images müssen neu gebaut werden. Manche Teams hängen einen Tag dran, andere zehn. Während die Builds laufen, fallen Services mit veralteten Bundles um.

Selbst wenn das Image-Baking durchgeht, bleibt eine Falle übrig: der OS-Store ist aktualisiert, ein Python-Service mit certifi aber nicht. Ein Java-Service mit eigenem cacerts auch nicht. curl funktioniert, der eigentliche Code nicht. Stunden mit "warum geht curl, aber die App nicht?" lassen sich so verbringen.

Der eigentliche Kern: Trust-Infrastruktur und Build-Infrastruktur haben getrennte Lebenszyklen. Wer sie über das Image koppelt, sammelt zwischen den Updates Brüche ein.

Workaround 2: CA als Volume mounten

ConfigMap oder Secret enthält das Bundle, jeder Pod-Spec mountet es, die Anwendung wird auf den gemounteten Pfad konfiguriert.

Drei Probleme stapeln sich. Erstens patcht ein Volume-Mount den OS-Trust-Store nicht. Tools, die /etc/ssl/certs/ hart einlesen, sehen die zusätzliche CA gar nicht. Zweitens braucht jeder Pod-Spec den Mount; die nächste logische Eskalation heisst Mutating-Webhook (Workaround 4). Drittens fehlen die Sprach-Variablen weiterhin: ein blosser File-Mount setzt weder NODE_EXTRA_CA_CERTS noch JAVA_TOOL_OPTIONS.

Workaround 3: Init-Container, der den Trust-Store patcht

Ein vorgeschalteter Container ruft update-ca-certificates im Rootfs des Hauptcontainers auf, bevor die Anwendung startet.

Der Ansatz funktioniert, sobald der Hauptcontainer ein beschreibbares Rootfs hat. Er scheitert hart an readOnlyRootFilesystem: true. Wer die Policy nicht durchsetzt, hat ein anderes Plattformproblem. Dazu kommen die Folgekosten: längere Startup-Zeit, mehr Einzelteile, neue Failure-Punkte. Sprach-Runtimes mit eigenem Trust ignorieren den OS-Store ohnehin.

Workaround 4: Mutating Admission Webhook

Wenn die Volume-Mount-Logik in jeden Pod-Spec eingebaut werden soll, landet man irgendwann bei einem Webhook, der das beim Admission automatisch erledigt.

Damit ist die Arbeit zwar automatisiert, aber der Webhook ist nun selbst eine kritische Plattform-Komponente. Er steht synchron im Pfad jeder Pod-Erstellung. Fällt er aus, bekommen Pods keine CAs. Hat er einen Bug, sind alle Pods betroffen. Bei jedem Upgrade berührst du eine Komponente, die jeden Pod im Cluster mutiert. Und unter all dem stecken weiterhin die Limits aus Workaround 2: kein OS-Patch, jede Sprache muss separat geregelt werden.

Das Muster ist überall dasselbe. Jeder Workaround beantwortet das unmittelbare Problem vernünftig. Keiner setzt an der richtigen Stelle an.

Runtime-Ebene: warum NRI

Die richtige Stelle ist die Container-Runtime. Sobald der API-Server den Pod abgehakt und das Kubelet ihn an containerd übergeben hat, geht es nur noch ums Container-Filesystem und die Umgebung. Beides liegt bei der Runtime.

Containerd hat dafür das Node Resource Interface (NRI) eingeführt. NRI ist eine stabile, supportete Plugin-Schnittstelle. Damit kann eine Komponente Container-Lifecycle-Events beobachten und anpassen, ohne die Runtime zu forken, ohne im Pod-Erstellungspfad zu sitzen und ohne cluster-weite Sonderrechte.

Ein NRI-Plugin läuft als DaemonSet auf jedem Node. Erstellt containerd einen Container, bekommt das Plugin ein CreateContainer-Event mit der OCI-Spec, der Pod-Metadata und der Möglichkeit, Hooks, Mounts und Variablen vor dem Start einzufügen. Der Pod-Spec bleibt unangetastet, kein Webhook im Hot-Pfad, das Opt-In ist eine Annotation pro Pod oder pro Namespace.

Wichtig: NRI arbeitet auf Container-Ebene, nicht nur auf Pod-Ebene. Ein Pod hat oft mehrere Container — Init-Container, Hauptcontainer, Sidecars. Wir brauchen die Möglichkeit, gezielt zu sagen, welcher Container Injection bekommt und welcher nicht. Ein Service-Mesh-Sidecar wie istio-proxy hat seinen eigenen Trust und soll unangetastet bleiben.

Den vollständigen Leitfaden erhalten

Geben Sie Ihre E-Mail ein und erhalten Sie sofort den vollständigen Leitfaden als PDF.

  • Warum jede Plattform mit eigenen CAs früher oder später bei diesem Problem landet
  • Wie die vier üblichen Workarounds (Image-Baking, Volume-Mounts, Init-Container, Mutating-Webhooks) in der Skalierung versagen — mit konkreten Beispielen
  • Wie containerd NRI eine saubere Lösung auf Runtime-Ebene möglich macht
  • Welche Datei der OCI-Hook auf welcher Distribution wohin schreibt
  • Warum die JVM ein Sonderfall ist und wir den cacerts-Keystore nie anrühren
  • Was wir beim Betrieb von cainjekt in unserer Managed-Flotte gelernt haben

Kostenloser Download. Kein Spam. Wir geben Ihre Daten nie an Dritte weiter.

Open Source unter github.com/natrontech/cainjekt

Das könnte Sie auch interessieren