Kai Ole Hartwig
7 Min. Lesezeit
Niedrig
Von

Traefik weist sich selbst aus

Für alle, die einen Ingress-Proxy vor mehrere Mandanten stellen und auch die Strecke dahinter mit mTLS absichern. In der letzten Feldnotiz lief das Netz innerhalb der Mandanten schon auf Pod-Zertifikaten. Offen blieb die Strecke von Traefik zu den Mandanten. Traefik meldete sich bei jedem Mandanten noch mit einem Zertifikat aus einem Secret an. Hier steht, wie das ohne Secret geht und warum die eigentliche Arbeit im Neuladen steckte.

01 — Die Ausgangslage

Vor jedem Mandanten sitzt ein eigener Reverse Proxy, und Traefik spricht ihn über mTLS an. Der Proxy verlangt ein Client-Zertifikat, das von der CA seines Mandanten stammt. Dieses Zertifikat stellte bisher cert-manager aus, und Traefik las es aus einem Secret im Namensraum des Mandanten.

Genau das war der Haken. Damit Traefik dieses Secret lesen konnte, brauchte es Leserechte in jedem Mandanten-Namensraum. Der Prozess, der jede Anfrage aus dem Internet parst, hielt also Schlüssel in der Hand, die er gar nicht hätte sehen müssen.

Zwei Eigenschaften von Traefik machen den direkten Umstieg schwer. Ein ServersTransport aus der Kubernetes-API nimmt Client-Zertifikate nur aus Secrets, nicht aus Dateien. Und eine Zertifikatsdatei, die sich ändert, liest Traefik nicht neu ein. Ein Pod-Zertifikat liegt aber genau so vor: als Datei, die der kubelet regelmäßig austauscht.

02 — Der Entwurf

Jeder Mandant hat seinen eigenen Signer. Jeder dieser Signer bekommt genau eine zusätzliche Freigabe: für den ServiceAccount von Traefik, nur als Client und ohne DNS-Namen. Damit kann Traefik von jedem Mandanten eine Client-Identität erhalten, aber kein Serverzertifikat für irgendeinen Namen.

Der Traefik-Pod bindet ein projiziertes Volume mit einem Pod-Zertifikat je Signer ein, dazu das Trust-Bundle jedes Mandanten:

 

volumes:
  - name: edge-client
    projected:
      sources:
        - podCertificate:
            signerName: example.com/tenant-a
            keyType: ECDSAP256
            keyPath: tenant-a.key
            certificateChainPath: tenant-a.crt
        - clusterTrustBundle:
            signerName: example.com/tenant-a
            labelSelector: {}
            path: tenant-a-ca.crt
        # ein solches Paar je Mandant

 

Weil Traefik diese Dateien nicht über die Kubernetes-API nutzen kann, übernimmt der File-Provider. Ein kleiner Sidecar liest das Volume und schreibt daraus eine dynamische Konfiguration mit einem ServersTransport je Mandant. Die Schlüssel kopiert er mit Modus 0600 in ein Volume im Arbeitsspeicher, das nur Traefik und der Sidecar sehen.

03 — Neu laden ohne Neustart

Der File-Provider beobachtet sein Verzeichnis und lädt die Konfiguration neu, sobald sich eine Datei darin ändert. Eine Zertifikatsdatei, auf die die Konfiguration nur verweist, zählt dabei nicht mit, sodass Traefik ein erneuertes Pod-Zertifikat erst beim nächsten Neustart gesehen hätte.

Deshalb trägt jede Zertifikatsdatei die Prüfsumme ihres Inhalts im Namen. Wenn der kubelet ein Zertifikat erneuert, legt der Sidecar es unter einem neuen Namen ab und schreibt die Konfiguration mit dem neuen Pfad neu. Weil sich damit die Konfigurationsdatei selbst ändert, lädt Traefik sie, ohne dass jemand eingreifen muss:

 

http:
  serversTransports:
    tenant-a-mtls:
      serverName: proxy
      rootCAs: [/edge/certs/tenant-a-ca-3f9c2e.crt]
      certificates:
        - certFile: /edge/certs/tenant-a-3f9c2e.crt
          keyFile: /edge/certs/tenant-a-3f9c2e.key

 

Bevor das in den Cluster ging, habe ich es lokal gemessen, mit Traefik vor einem Server, der ein Client-Zertifikat verlangt. Nach der ersten Erneuerung meldete der Server die neue Identität, obwohl Traefik die ganze Zeit weiterlief. Fehlt einem Mandanten einmal das Material, schreibt der Sidecar keine halbe Konfiguration, sondern bricht ab und lässt die letzte gültige stehen.

04 — Prüfen, dann umschalten

Damit das funktioniert, müssen drei Listen zueinander passen: die Transports in der Konfiguration, die Zertifikatsquellen im Volume und die Freigaben der Signer. Fehlt eine Freigabe, wartet ein neuer Traefik-Pod endlos auf sein Zertifikat, und fehlt ein Transport, zeigt ein Mandant ins Leere. Ein CI-Gate vergleicht deshalb alle drei mit dem, was die Charts tatsächlich rendern, und für jede Richtung gibt es eine Mutation, die das Gate nachweislich rot macht.

Ausgerollt habe ich in zwei Schritten. Zuerst bekam Traefik die Zertifikate, während jeder Mandant noch den alten Transport nutzte. Erst danach stellte ein Schalter je Mandant die Annotation am Ingress auf den neuen Transport um, etwa auf tenant-a-mtls@file, und der alte blieb als Rückweg stehen.

Als Pilot diente ein Mandant hinter Basic Auth, weil ein Fehler dort keine öffentlichen Besucher getroffen hätte. Sein Access-Log zeigte, dass die Antworten wirklich vom Proxy des Mandanten kamen und nicht von einer Middleware davor. Nachdem das gesichert war, folgten die übrigen Mandanten, der wichtigste zuletzt.

05 — Was schiefging

Der erste neue Traefik-Pod startete nicht. Er hatte von jedem Signer ein Zertifikat angefordert, und einer davon lehnte ab, weil er für den ServiceAccount von Traefik keine Freigabe kannte. Die Freigabe gehörte zwar zur selben Änderung, doch dieser Signer lud seine Policy erst 72 Sekunden nach der Anfrage neu. Eine abgelehnte Anfrage bleibt abgelehnt, denn der kubelet fragt für diesen Pod nicht noch einmal. Weil die alten Traefik-Pods weiterliefen, hat kein Besucher etwas bemerkt, und es reichte, den hängenden Pod zu löschen: Sein Nachfolger fragte neu und bekam alle Zertifikate.

Später am selben Tag traf dieselbe Ursache einen Dienst ohne Reserve-Pod, und dort dauerte es zehn Minuten, bis die Seite wieder antwortete. Seitdem gilt eine feste Reihenfolge, nach der die Freigabe ausgerollt und geladen sein muss, bevor irgendetwas auf sie umstellt. Ein Gate prüft das in jedem Merge Request gegen den Stand des Zielzweigs.

Einen zweiten Fehler fand nur das Gate. Zwei Merge Requests hatten sich überholt, als der eine den Namen änderte, unter dem Traefik einen Dienst anspricht, und der andere den neuen Transport noch mit dem alten Namen mitbrachte. Nach der Umstellung hätte die Konfiguration das Zertifikat dieses Dienstes nicht mehr akzeptiert, doch das Gate meldete den Widerspruch, bevor der Mandant umgestellt war.

06 — Was es gebracht hat

Nach einem Tag liefen alle Mandanten über die neuen Transports. Während der Umstellungen habe ich die Seiten durchgehend abgefragt, und dabei ist keine einzige Anfrage fehlgeschlagen. Weil Traefik für diese Strecke keine Secrets mehr braucht, entfällt auch der Grund, aus dem es Schlüssel in fremden Namensräumen lesen durfte.

Was noch bleibt, ist Aufräumen nach einer Beobachtungszeit, wenn die alten Zertifikate und Transports verschwinden und der Schalter zur Voreinstellung wird. Danach ist der Ingress nicht mehr der Hop, den man bei einem Audit als Erstes erklären muss, sondern der letzte, der von cert-manager zu Pod-Zertifikaten gewechselt ist.

Häufige Fragen

Warum nicht einfach Traefik neu starten?+

Weil ein Neustart von Traefik jede offene Verbindung trifft, und bei Zertifikaten mit einem Tag Laufzeit wäre das jeden Tag so. Der Reload über den File-Provider tauscht dagegen nur die Konfiguration aus, während die Verbindungen weiterlaufen.

Bekommt Traefik damit nicht Zugriff auf alle Mandanten?+

Traefik bekommt von jedem Mandanten eine Client-Identität, die es vorher auch schon hatte, nur jetzt ohne Secret. Weil die Freigabe keine DNS-Namen erlaubt, kann daraus kein Serverzertifikat werden, sodass sich Traefik zwar als Edge-Proxy ausweisen, aber keinen Dienst eines Mandanten vortäuschen kann.

Was passiert, wenn ein Signer nicht antwortet?+

Laufende Traefik-Pods behalten ihre Zertifikate, bis sie ablaufen, und ein neuer Pod startet erst, wenn alle seine Zertifikate da sind. Deshalb rollt Traefik nie alle Pods auf einmal aus, und die alten bedienen weiter, bis der neue bereit ist.

Fazit

Dass Traefik Client-Zertifikate nur aus Secrets liest, ist noch kein Grund, ihm Secrets zu geben, denn der File-Provider öffnet einen zweiten Weg, und ein Dateiname mit Prüfsumme macht aus jeder Erneuerung einen Reload. Das war der leichte Teil.

Die eigentliche Lehre liegt woanders. Eine Zertifikatsanfrage, die zu früh kommt, bleibt für immer abgelehnt, und ein Pod ohne Reserve wartet dann auf etwas, das nie kommt. Wer die Freigabe vor der Umstellung ausrollt, muss darauf nie warten.

Liest dein Ingress Secrets, die er nicht braucht?

Ich sehe mir deinen Ingress an: welche Schlüssel der Ingress lesen darf, welche Strecken sich für Pod-Zertifikate eignen und wie sich die Umstellung ohne Ausfall planen lässt.

Termin buchen →

Über den Autor

Foto von Kai Ole Hartwig.

Kai Ole Hartwig

Freiberuflicher DevSecOps-Berater · OnlyOle Consulting

Programmiert seit 2002 – autodidaktisch gelernt, 2012 mit KO-Web selbständig gemacht. Über 100 Projekte, Fokus auf Security, Performance, Automatisierung und Qualität. Heute freiberuflich: DevSecOps-Beratung, Schulungen und Softwareentwicklung.