Security ~4 Min.

Vault: Kubernetes Auth und Dynamic Secrets

ServiceAccount-Identität und kurzlebige DB-Passwörter.

von PlatPrep

Es passierte bei einem Security Audit. Jemand hatte im Deployment-YAML unter env folgendes eingetragen: POSTGRES_PASSWORD: hunter2. Nicht als Secret-Referenz. Direkt als Klartext in der Kubernetes-Ressource. Committed ins Git-Repo. In dem jeder im Team Leserechte hatte.

Das ist kein Einzelfall. Es ist das natürliche Ergebnis davon, dass Credentials irgendwo herkommen müssen und der einfachste Weg meistens der direkteste ist.

Vaults Kubernetes-Auth-Methode macht es einfacher, es richtig zu machen, als es falsch zu machen — weil der Pod gar kein Secret mehr mitbringen muss, um an Secrets zu kommen.

Wie die Authentifizierung tatsächlich abläuft

Das Prinzip: ein Pod startet mit einem ServiceAccount, Kubernetes mountet dafür ein signiertes ServiceAccount-Token in den Pod. Der Pod (bzw. der Vault Agent) schickt dieses Token an Vaults auth/kubernetes/login-Endpoint. Vault validiert es über die TokenReview-API des Clusters — fragt also Kubernetes selbst: “Ist dieses Token echt und gehört es zu dem ServiceAccount, der behauptet wird?”

Auf Vault-Seite verbindet eine Role die Kubernetes-Identität mit Berechtigungen:

vault write auth/kubernetes/role/checkout \
  bound_service_account_names=checkout \
  bound_service_account_namespaces=shop \
  policies=checkout-db \
  ttl=1h

Nur der ServiceAccount checkout im Namespace shop bekommt ein Vault-Token mit der Policy checkout-db, gültig für eine Stunde. Die Identität ist damit an etwas gebunden, das Kubernetes garantiert — kein API-Key, der geleakt werden kann, kein Bootstrap-Secret, das wieder irgendwo gespeichert werden müsste. Genau dieses Bootstrap-Problem (“woher kommt das erste Secret?”) ist es, das statische Ansätze nie sauber lösen.

Erwähnenswert: moderne ServiceAccount-Tokens sind projected Tokens mit Ablaufzeit und Audience-Bindung — kurzlebig statt der früheren unbegrenzt gültigen Secrets. Das passt konzeptionell exakt zu Vaults Modell.

Dynamic Secrets: Credentials, die es vorher nicht gab

Dynamic Secrets gehen einen Schritt weiter als “Secrets sicher abrufen”. Vaults Database Secrets Engine hält eine eigene Verbindung zur Datenbank und legt bei jeder Anfrage einen frischen DB-User an — mit Zufallspasswort und einer Lease von z.B. einer Stunde:

vault write database/roles/checkout-readonly \
  db_name=shop-postgres \
  creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' \
    VALID UNTIL '{{expiration}}'; GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
  default_ttl=1h max_ttl=24h

Läuft die Lease ab und wird nicht erneuert, räumt Vault den DB-User wieder weg. Das verändert die Sicherheitslage qualitativ: ein kompromittierter Pod liefert Credentials, die Stunden später wertlos sind. Ein geleaktes Backup der App-Konfiguration enthält nichts Dauerhaftes. Und jeder Pod hat seinen eigenen DB-User — in den Postgres-Logs siehst du, welche Instanz welche Query gefahren hat, statt einen geteilten app_user für alles.

Der unbequeme Teil: die Applikation muss mit rotierenden Credentials umgehen können. Eine Connection, die mit abgelaufenen Credentials aufgebaut wurde, fliegt beim Reconnect raus. In der Praxis heißt das: Connection-Pool-Lebensdauer kürzer als die Lease konfigurieren oder auf Auth-Fehler mit Re-Read der Credential-Datei reagieren. Das ist lösbar, aber es ist Arbeit, die im Migrations-Aufwand eingeplant sein muss.

Integration im Pod: Sidecar, CSI oder Operator

Damit der Applikationscode Vault-agnostisch bleibt, gibt es drei gängige Wege. Der Vault Agent Injector hängt per Mutating Webhook einen Agent-Sidecar in annotierte Pods; der Agent authentifiziert sich, rendert Secrets als Dateien ins Pod-Filesystem und erneuert Leases automatisch — die App liest eine Datei, fertig. Der Vault CSI Provider mountet Secrets über das Secrets-Store-CSI-Volume, ohne Sidecar. Und der Vault Secrets Operator synct Vault-Secrets in native Kubernetes-Secrets — am kompatibelsten mit bestehenden Deployments, aber die Werte liegen dann wieder in etcd. Für den Sidecar-Ansatz spricht das automatische Lease-Renewal bei Dynamic Secrets; für den Operator die geringste App-Änderung. Wer nur statische Secrets aus einem externen Store syncen will, fährt oft mit dem schlankeren External Secrets Operator besser.

Ein Nebeneffekt, der oft erst im Audit auffällt: Vault schreibt ein vollständiges Audit-Log — jeder Secret-Zugriff mit Identität, Pfad und Zeitstempel. Die Frage „wer hatte wann Zugriff auf das Produktions-DB-Passwort?” wird damit beantwortbar, statt ein Schulterzucken zu bleiben.

Der ehrliche Preis

Vault selbst ist kritische Infrastruktur: HA-Setup mit Raft-Storage, regelmäßige Snapshots, eine Unsealing-Strategie (Auto-Unseal über einen Cloud-KMS erspart das manuelle Key-Sharing-Zeremoniell nach jedem Neustart) — und die unbequeme Wahrheit, dass bei nicht erreichbarem Vault keine neuen Pods mehr an Secrets kommen. Vault braucht ein eigenes SLO.

Deshalb: nicht alles auf einmal. Mit den kritischsten Credentials starten — Produktions-Datenbanken, externe API-Keys — und den Scope ausweiten, wenn Betrieb und Patterns sitzen. Das ist besser, als weiter auf statische Kubernetes-Secrets zu setzen und zu hoffen, dass der nächste Audit keine Klartext-Einträge findet. Die Grundlagen von Policies, Secret Engines und Auth-Methoden vertieft der Vault-Lernpfad.