Wenn ein Kubernetes-Cluster aus Infrastructure-as-Code heraus befüllt wird — mit Terraform, Helm oder reinen Manifesten — taucht immer wieder dieselbe Frage auf: Wie hält man Secrets aus Orten heraus, an denen sie nichts zu suchen haben? Datenbank-Passwörter, API-Tokens, Zertifikate müssen am Ende im Cluster landen. Alles dazwischen — Git, State-Files, CI-Pipelines — darf sie nie im Klartext sehen.
Dieser Artikel beschreibt ein Setup, das wir projektübergreifend einsetzen: verschlüsselte Secrets liegen neben dem Code in Git, entschlüsselt werden sie erst im Cluster. Genutzt werden SOPS, der sops-secrets-operator und age für das Key-Management.
Das Problem mit den naheliegenden Ansätzen
Secrets als Klartext in einem Git-Repository abzulegen — selbst in einem privaten — vergrößert ihren Wirkungskreis drastisch. Jeder mit Repo-Zugriff, jedes Backup und jeder CI-Runner, der das Repo klont, hat das Passwort.
Terraform verschärft das. Der State trackt deployte Ressourcen inklusive der Werte von kubernetes_secret. Remote-State (S3, GitLab) leakt Secrets in weitere Storage-Ebenen. Selbst in-memory Plans können sensible Daten in CI-Logs sichtbar machen.
Die Recherche nach der richtigen Lösung ist mühsam, weil es viele Optionen gibt:
- Cloud-Vaults (AWS KMS, GCP KMS, Azure Key Vault): stark, aber Provider-Lock-in, kostenpflichtig, und Secret-Rotation ist entkoppelt vom Code-Deployment.
- Team-Passwort-Vaults (1Password, Bitwarden): gut für Menschen, schlecht für Automatisierung.
- GitOps mit Verschlüsselung at rest: verschlüsselte Secrets neben dem Code, Entschlüsselung erst beim Deployment. Genau das macht SOPS-Operator.

Was SOPS und sops-secrets-operator machen
SOPS ist ein von Mozilla gepflegtes Tool, das YAML, JSON, ENV, INI und Binärdateien verschlüsseln und entschlüsseln kann. Als Backend kommen AWS KMS, GCP KMS, Azure Key Vault, age oder PGP in Frage.
Der sops-secrets-operator erweitert diesen Mechanismus in Richtung Kubernetes. Er führt eine Custom Resource Definition namens SopsSecret ein. Das verschlüsselte Manifest liegt in Git. Der Operator beobachtet den Cluster, entschlüsselt die sensiblen Felder und reconciled daraus normale Kubernetes-Secret-Ressourcen. Die konsumierenden Pods merken davon nichts — aus ihrer Perspektive bleibt alles beim Alten.

Warum age statt PGP
age ist ein schlankes Verschlüsselungs-Tool, 2019 von einem Google-Engineer geschrieben. Es löst bewusst nur ein Problem — Dateiverschlüsselung — und verzichtet auf den ausufernden Feature-Set (und die Angriffsfläche) von PGP/GnuPG.
Gegenüber GPG bietet age:
- Einen Bruchteil des Featureumfangs und Codeumfangs.
- Kein 20-jähriges Legacy mit alten Modes und Kompatibilitäts-Fallen.
- Moderne Primitive (X25519 asymmetrische Verschlüsselung).
- Einen einfacheren Key-Lifecycle (
age-keygenproduziert eine einzige Identity-Datei).
Mozilla empfiehlt age gegenüber PGP für SOPS. Für diesen Use Case stimmen wir zu.
Keys in age:
- Identity (privater Schlüssel, beginnt mit
AGE-SECRET-KEY-): gehört in einen Passwort-Vault. Wer die Identity hält, kann entschlüsseln. - Recipient (öffentlicher Schlüssel, beginnt mit
age): kann bedenkenlos committet werden. Darauf wird verschlüsselt.
Verschlüsselungs- und Entschlüsselungs-Fluss
Lokale Verschlüsselung (was ein Entwickler vor dem Commit macht):

Entschlüsselung im Cluster (was der Operator kontinuierlich macht):

Der Operator braucht exakt ein Secret: die age-Identity, gemountet in den sops-secrets-operator-Pod. Alles andere in Git ist verschlüsselt.
Schritt-für-Schritt-Anleitung
1. age-Keypair erzeugen
age-keygen -o age-key.txt
Die Datei enthält sowohl den Recipient (öffentlich, als Kommentarzeile) als auch die Identity (privat). Direkt im Team-Passwort-Vault sichern.
2. age-Identity in den Cluster bringen
Identity in Base64 kodieren:
sed -n -e '/^AGE-SECRET-KEY/p' age-key.txt | base64
Secret per kubectl anlegen (nicht per Terraform — sonst landet die Identity im State-File):
apiVersion: v1
kind: Secret
metadata:
name: sops-age-key-file
namespace: YOUR_NAMESPACE
type: Opaque
data:
key: YOUR_BASE64_ENCODED_IDENTITY
3. sops-secrets-operator installieren
Via Helm:
helm repo add sops https://isindir.github.io/sops-secrets-operator/
helm upgrade --install --create-namespace sops sops/sops-secrets-operator \
--namespace YOUR_NAMESPACE
Oder via Terraform:
resource "helm_release" "sops_secrets_operator" {
name = "sops-secrets-operator"
chart = "sops-secrets-operator"
repository = "https://isindir.github.io/sops-secrets-operator/"
namespace = "YOUR_NAMESPACE"
version = "0.14.0"
create_namespace = true
values = [
<<EOF
extraEnv:
- name: SOPS_AGE_KEY_FILE
value: /etc/sops-age-key-file/key
secretsAsFiles:
- mountPath: /etc/sops-age-key-file
name: sops-age-key-file
secretName: sops-age-key-file
EOF
]
}
4. SopsSecret schreiben und verschlüsseln
apiVersion: isindir.github.com/v1alpha3
kind: SopsSecret
metadata:
name: app-secrets
namespace: YOUR_NAMESPACE
spec:
secretTemplates:
- name: app-secrets
stringData:
DATABASE_PASSWORD: supersecret
API_TOKEN: also-supersecret
Mit SOPS und dem age-Recipient verschlüsseln:
sops --encrypt \
--age 'YOUR_AGE_RECIPIENT_PUBLIC_KEY' \
--encrypted-suffix Templates \
app-secrets.yml > app-secrets.enc.yml
--encrypted-suffix Templates sorgt dafür, dass SOPS nur Felder unterhalb von secretTemplates verschlüsselt. Metadaten bleiben in Git lesbar.
5. Deployment in den Cluster
Direkt:
kubectl apply -f app-secrets.enc.yml
Oder als Terraform-Resource:
cat app-secrets.enc.yml | tfk8s --strip -o app-secrets.tf
Dann wie gewohnt terraform apply. Die Tools sops, age und tfk8s via Paketmanager installieren (z. B. brew install sops age tfk8s).
6. Verifizieren
kubectl get secrets app-secrets -n YOUR_NAMESPACE -o yaml
Wenn das Secret fehlt, in die Operator-Logs schauen:
kubectl logs -l app.kubernetes.io/name=sops-secrets-operator -n YOUR_NAMESPACE
Typischer Workflow im Alltag

Im Alltag sehen Änderungen an Secrets aus wie jeder andere PR. Ein Entwickler bearbeitet eine verschlüsselte Datei lokal mit sops, committet und pusht. Die Pipeline applied Manifeste gegen den Cluster, der Operator übernimmt die Entschlüsselung.
Eine bestehende verschlüsselte Datei bearbeiten:
export SOPS_AGE_KEY_FILE=./age-key.txt
sops app-secrets.enc.yml
SOPS öffnet den $EDITOR, du bearbeitest Klartext, SOPS verschlüsselt beim Speichern neu.
Was dieser Ansatz nicht löst
- Zugriffskontrolle im Cluster. Jeder mit
kubectl get secrets -o yaml-Berechtigung sieht das Klartext-Secret im Cluster. Ergänzend RBAC und Audit-Logs aufsetzen. - Terraform, das entschlüsselte Secrets referenziert. Wenn ein
data.kubernetes_secret-Lookup den Wert für eine andere Terraform-Resource aus dem Cluster zieht, fließt er zurück in den State. Eine saubere Lösung dafür gibt es aktuell nicht — das Muster möglichst vermeiden. - Key-Rotation. Wenn die age-Identity kompromittiert ist, müssen alle
SopsSecret-Manifeste in Git auf einen neuen Recipient umverschlüsselt und die Cluster-Identity-Secret rotiert werden. Den Prozess einmal durchdenken, bevor man ihn braucht.
Fazit
Secrets sauber zu verwalten gehört zu den härtesten Problemen im produktiven Betrieb. SOPS-Operator mit age hält Klartext aus Repositories, State-Files, CI-Logs und Cloud-Provider-Storage heraus — bei gleichzeitig unveränderter Kubernetes-Secret-Schnittstelle für Workloads. Das Setup ist eine Nachmittags-Aufgabe und zahlt sich bei jedem Deployment aus.
