External Secrets vs. Sealed Secrets
Zwei GitOps-taugliche Wege — mit unterschiedlichen Trade-offs.
von PlatPrep
Wer Secrets in Git speichern will — weil alles andere im GitOps-Flow im Git ist — steht vor einer grundsätzlichen Entscheidung. Die hängt weniger von Technik ab als von der eigenen Threat-Model-Analyse.
Sealed Secrets: Verschlüsselung im Repo
Sealed Secrets verschlüsselt Kubernetes-Secrets clientseitig mit dem Public Key des Controllers. Das kubeseal-CLI nimmt ein normales Secret entgegen und erzeugt ein SealedSecret-Objekt, das gefahrlos committet werden kann:
kubectl create secret generic db-credentials \
--from-literal=password='s3cret' --dry-run=client -o yaml \
| kubeseal --format yaml > db-credentials-sealed.yaml
Im Cluster entschlüsselt der Controller mit seinem Private Key und erstellt das eigentliche Secret. Keine externe Abhängigkeit, alles im Cluster, das Modell ist in einer Stunde verstanden.
Zwei Details entscheiden in der Praxis über Wohl und Wehe. Erstens die Scopes: Standardmäßig ist ein SealedSecret an Name und Namespace gebunden — es lässt sich nicht in einen anderen Namespace kopieren und dort entschlüsseln. Das ist ein Feature (verhindert Replay durch andere Tenants), überrascht aber jeden, der Umgebungen per Namespace-Umbenennung klont. Mit namespace-wide oder cluster-wide Scope lässt sich das lockern — bewusst und sparsam.
Zweitens das Key-Management: Der Private Key liegt im Cluster, und der Controller generiert standardmäßig alle 30 Tage einen neuen (alte bleiben zum Entschlüsseln erhalten). Daraus folgen zwei Pflichten. Die Schlüssel müssen gesichert werden — verlierst du den Cluster ohne Key-Backup, sind alle committeten SealedSecrets wertlos und jedes einzelne muss neu beschafft und versiegelt werden. Und umgekehrt: Wer den Cluster kompromittiert und die Keys exfiltriert, kann die gesamte Git-Historie an SealedSecrets entschlüsseln. Rotation der Controller-Keys macht alte Commits nicht unlesbar; dafür müsste man die eigentlichen Secret-Werte ändern und neu versiegeln.
External Secrets Operator: Git kennt nur Referenzen
Der External Secrets Operator (ESO) dreht das Modell um: Die Secret-Werte leben in einem externen Store — AWS Secrets Manager, SSM Parameter Store, Vault, GCP Secret Manager, Azure Key Vault. In Git steht nur die Referenz:
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
name: db-credentials
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets
kind: ClusterSecretStore
target:
name: db-credentials
data:
- secretKey: password
remoteRef:
key: prod/checkout/db
property: password
Der Controller holt die Werte, erstellt das Kubernetes-Secret und prüft im refreshInterval, ob sich etwas geändert hat — Rotation im Store propagiert damit automatisch in den Cluster. Das ist der große strukturelle Vorteil: zentrales Secret-Management mit bestehenden IAM-Policies, Audit-Logs und Rotationsmechanismen, während Git sauber bleibt.
Die Kosten: eine externe Abhängigkeit und ein Henne-Ei-Problem. ESO braucht selbst Credentials für den Store-Zugriff — sauber gelöst über IRSA bzw. Workload Identity, womit kein statisches Secret nötig ist, aber eben Cloud-Anbindung vorausgesetzt. Und zur Verfügbarkeit gehört Ehrlichkeit in beide Richtungen: Bereits erstellte Kubernetes-Secrets bleiben bestehen, wenn der Store ausfällt — laufende Workloads sind nicht betroffen. Aber neue Secrets, Updates und frisch erstellte Umgebungen hängen am Store. Wer Disaster Recovery für den Cluster plant, muss den Secret-Store mitdenken: Ein leeres Cluster-Rebuild braucht zuerst funktionierenden Store-Zugriff.
Entscheiden — und ehrlich bleiben
Die Faustregel: Gibt es bereits etabliertes Secret-Management in AWS, GCP oder Vault, ist ESO fast immer der sauberere Weg — die Organisation hat den Store ohnehin, ESO ist nur die Brücke in den Cluster. Sealed Secrets passt, wenn minimale Abhängigkeiten zählen: kleine Teams, On-Prem-Cluster ohne Cloud-Secret-Store, Setups, in denen ein zusätzliches Vault-Deployment mehr Betriebslast wäre als es Sicherheit bringt.
Auch der Alltags-Workflow unterscheidet sich spürbar. Bei Sealed Secrets braucht jeder, der ein Secret ändern will, das kubeseal-CLI und den richtigen Public Key des Ziel-Clusters — bei mehreren Clustern heißt das mehrere Keys, und das Versiegeln gegen den falschen ist ein Klassiker. Bei ESO ändert man den Wert im Store, und Git bleibt unberührt; dafür wandert die Frage “wer darf welches Secret ändern?” komplett in die IAM- bzw. Vault-Policies des Stores. Beides ist handhabbar, aber das Team sollte vorher wissen, welchen der beiden Workflows es jeden Tag leben will.
Was bei beiden gern übersehen wird: Am Ende entsteht in jedem Fall ein normales Kubernetes-Secret — base64, nicht verschlüsselt, lesbar für jeden mit entsprechendem RBAC. Beide Tools lösen das Problem “Secrets sicher nach Git bzw. in den Cluster bringen”, nicht das Problem “Secrets im Cluster schützen”. Dafür braucht es Encryption-at-Rest für etcd und diszipliniertes RBAC, sonst ist die schönste Sealing-Strategie nur Fassade.
Beide Tools können in derselben Organisation koexistieren — nicht für dasselbe Secret, aber für verschiedene Use Cases. Das ist kein Widerspruch, das ist Pragmatismus. Vertiefung gibt es in den Lernpfaden zu External Secrets und Sealed Secrets — und wer dynamische, kurzlebige Credentials statt statischer Werte will, sollte sich Vault mit Kubernetes-Auth ansehen.