Kubernetes Secrets ohne Verschlüsselung in Git-Repositories zu speichern ist einer der häufigsten Sicherheitsfehler bei Cloud-nativen Deployments. Obwohl Kubernetes Secrets Base64-Kodierung verwenden, bietet dies keinerlei kryptographischen Schutz — jeder mit Repository-Zugriff kann sie sofort dekodieren. SOPS (Secrets OPerationS) in Kombination mit Age-Verschlüsselung löst dieses Problem elegant und ermöglicht es, verschlüsselte Secrets direkt in Git zu speichern, bei voller Kompatibilität mit GitOps-Workflows wie ArgoCD und FluxCD.
Voraussetzungen
- Ein laufender Kubernetes-Cluster (v1.24+) mit konfiguriertem
kubectl sopsv3.8+ installiertagev1.1+ installiert- Grundkenntnisse über Kubernetes Secrets und YAML-Manifeste
- Git-Repository für Ihre Kubernetes-Manifeste
- Optional: ArgoCD oder FluxCD für GitOps-Integration
Risiken von Kubernetes Secrets Verstehen
Kubernetes Secrets werden oft als sicherer Speichermechanismus missverstanden. In Wirklichkeit haben sie erhebliche Einschränkungen, die Sie verstehen müssen, bevor Sie Ihre Secrets-Strategie entwerfen.
Base64 Ist Keine Verschlüsselung
Ein Standard-Kubernetes-Secret speichert Werte als Base64-kodierte Strings:
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
data:
username: YWRtaW4=
password: cDRzc3cwcmQxMjM=
Das Dekodieren dieser Werte ist trivial:
echo "cDRzc3cwcmQxMjM=" | base64 -d
# Ausgabe: p4ssw0rd123
Jeder, der Ihr Repository klont oder Lesezugriff auf etcd erhält, kann jedes Secret in Ihrem Cluster extrahieren.
Bedenken zur etcd-Speicherung
Standardmäßig speichert Kubernetes Secrets unverschlüsselt in etcd. Obwohl Sie die Verschlüsselung im Ruhezustand mit EncryptionConfiguration aktivieren können, schützt dies nur die etcd-Datendateien — nicht die API-Server-Antworten oder die in Git gespeicherten Manifeste. Sie brauchen eine Lösung, die Secrets verschlüsselt, bevor sie in Ihr Versionskontrollsystem gelangen.
Das Git-Problem
GitOps erfordert, dass der gewünschte Cluster-Zustand in Git lebt. Aber Secrets im Klartext in Git zu committen bedeutet:
- Jeder Entwickler mit Repository-Zugriff sieht Produktions-Credentials
- Die Secret-Historie bleibt für immer in Git bestehen, auch nach dem Löschen
- Geleakte Repositories legen jedes jemals committete Secret offen
- Compliance-Frameworks (SOC 2, PCI-DSS) verbieten Klartext-Credentials in der Versionskontrolle
SOPS und Age Installieren
Linux
# Age installieren
sudo apt-get install age
# Oder von GitHub-Releases
curl -LO https://github.com/FiloSottile/age/releases/download/v1.2.0/age-v1.2.0-linux-amd64.tar.gz
tar xzf age-v1.2.0-linux-amd64.tar.gz
sudo mv age/age age/age-keygen /usr/local/bin/
# SOPS installieren
curl -LO https://github.com/getsops/sops/releases/download/v3.9.4/sops-v3.9.4.linux.amd64
sudo mv sops-v3.9.4.linux.amd64 /usr/local/bin/sops
sudo chmod +x /usr/local/bin/sops
macOS
brew install sops age
Überprüfen Sie beide Installationen:
sops --version
# sops 3.9.4
age --version
# v1.2.0
Secrets mit SOPS und Age Verschlüsseln
Age-Schlüsselpaar Generieren
age-keygen -o age-key.txt
# Public key: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
Speichern Sie den privaten Schlüssel sicher. Der öffentliche Schlüssel kann bedenkenlos geteilt und in Ihr Repository committet werden:
# Schlüssel dort speichern, wo SOPS ihn finden kann
mkdir -p ~/.config/sops/age
mv age-key.txt ~/.config/sops/age/keys.txt
# Oder die Umgebungsvariable setzen
export SOPS_AGE_KEY_FILE=~/.config/sops/age/keys.txt
.sops.yaml-Konfiguration Erstellen
Erstellen Sie eine .sops.yaml-Datei im Root Ihres Repositories, um Verschlüsselungsregeln zu definieren:
creation_rules:
# Alle Dateien im secrets/-Verzeichnis verschlüsseln
- path_regex: secrets/.*\.yaml$
age: >-
age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
# Staging-Secrets mit anderem Schlüssel verschlüsseln
- path_regex: envs/staging/secrets/.*\.yaml$
age: >-
age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p,
age1lzd99uca0lqtmgahfmxj4gvr2fcswcaxmxnz30fwcmm22hjvrzrqsnqxsl
# Anderer Schlüssel für Produktion
- path_regex: envs/production/secrets/.*\.yaml$
age: >-
age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p,
age1an5quvgyl8uny5mkmgkzpnpe9wuufrl2vvmqsht4xp3k96s608q0eamcl
Mehrere Empfänger (kommagetrennt) ermöglichen Team-Zugriff — jeder aufgelistete Schlüssel kann die Datei entschlüsseln.
Ein Kubernetes Secret Verschlüsseln
Beginnen Sie mit Ihrem Klartext-Secret-Manifest:
# secrets/db-credentials.yaml
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
namespace: production
type: Opaque
stringData:
username: admin
password: "s3cur3-pr0d-p@ssw0rd"
connection-string: "postgresql://admin:s3cur3-pr0d-p@ssw0rd@db.internal:5432/myapp"
Verschlüsseln Sie es mit SOPS:
sops --encrypt --in-place secrets/db-credentials.yaml
Die verschlüsselte Datei behält die YAML-Struktur bei, verschlüsselt aber die Werte:
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
namespace: production
type: Opaque
stringData:
username: ENC[AES256_GCM,data:k8mN3w==,iv:abc...,tag:def...,type:str]
password: ENC[AES256_GCM,data:dGhpcyBpcyBl...,iv:ghi...,tag:jkl...,type:str]
connection-string: ENC[AES256_GCM,data:bG9uZ2VyIHN0cmluZw==...,iv:mno...,tag:pqr...,type:str]
sops:
age:
- recipient: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
...
-----END AGE ENCRYPTED FILE-----
lastmodified: "2026-02-17T10:00:00Z"
version: 3.9.4
Schlüssel (apiVersion, kind, metadata) bleiben lesbar, während Werte verschlüsselt sind. Das bedeutet, dass git diff zeigt, welche Secrets sich geändert haben, ohne die tatsächlichen Werte zu verraten.
Entschlüsseln und Anwenden
# Entschlüsseln und in einem Befehl anwenden
sops --decrypt secrets/db-credentials.yaml | kubectl apply -f -
# Oder in eine temporäre Datei entschlüsseln
sops --decrypt secrets/db-credentials.yaml > /tmp/secret.yaml
kubectl apply -f /tmp/secret.yaml
rm -f /tmp/secret.yaml
Integration mit GitOps-Workflows
ArgoCD mit KSOPS
KSOPS ist ein Kustomize-Plugin, das SOPS-verschlüsselte Dateien während ArgoCD-Sync-Operationen entschlüsselt.
Installieren Sie KSOPS in Ihrem ArgoCD-Repo-Server:
# argocd-repo-server Patch
apiVersion: apps/v1
kind: Deployment
metadata:
name: argocd-repo-server
namespace: argocd
spec:
template:
spec:
containers:
- name: argocd-repo-server
env:
- name: SOPS_AGE_KEY
valueFrom:
secretKeyRef:
name: sops-age-key
key: age-key.txt
- name: XDG_CONFIG_HOME
value: /.config
volumeMounts:
- mountPath: /.config/kustomize/plugin/viaduct.ai/v1/ksops
name: custom-tools
initContainers:
- name: install-ksops
image: viaductoss/ksops:v4.3.2
command: ["/bin/sh", "-c"]
args:
- cp /usr/local/bin/ksops /.config/kustomize/plugin/viaduct.ai/v1/ksops/ksops
volumeMounts:
- mountPath: /.config/kustomize/plugin/viaduct.ai/v1/ksops
name: custom-tools
volumes:
- name: custom-tools
emptyDir: {}
Erstellen Sie einen KSOPS-Generator in Ihrem Kustomize-Overlay:
# secret-generator.yaml
apiVersion: viaduct.ai/v1
kind: ksops
metadata:
name: secret-generator
files:
- secrets/db-credentials.yaml
- secrets/api-keys.yaml
Native FluxCD-Integration
FluxCD hat eingebaute SOPS-Unterstützung. Erstellen Sie ein Entschlüsselungs-Secret und konfigurieren Sie Ihre Kustomization:
# Age-Schlüssel-Secret im flux-system Namespace erstellen
kubectl create secret generic sops-age \
--namespace=flux-system \
--from-file=age.agekey=~/.config/sops/age/keys.txt
# flux-kustomization.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: my-app
namespace: flux-system
spec:
interval: 10m
path: ./envs/production
prune: true
sourceRef:
kind: GitRepository
name: my-app
decryption:
provider: sops
secretRef:
name: sops-age
FluxCD entschlüsselt automatisch alle SOPS-verschlüsselten Dateien im angegebenen Pfad während der Reconciliation.
SOPS+Age vs Sealed Secrets vs Vault vs External Secrets
| Eigenschaft | SOPS + Age | Sealed Secrets | HashiCorp Vault | External Secrets |
|---|---|---|---|---|
| Verschlüsselungsort | Client-seitig | Controller-seitig | Server-seitig | Server-seitig |
| Git-kompatibel | Ja (verschlüsseltes YAML) | Ja (Custom Resource) | Nein (nur Referenzen) | Nein (nur Referenzen) |
| Infrastruktur nötig | Keine | Cluster-Controller | Vault-Server | Operator + Backend |
| Schlüsselverwaltung | Age-Schlüsseldateien | Cluster-Zertifikat | Vault-Policies | Backend-abhängig |
| Multi-Umgebung | .sops.yaml-Regeln | Zertifikate pro Cluster | Namespaces/Policies | Mehrere Stores |
| GitOps-Integration | ArgoCD/Flux-Plugins | Natives K8s | CSI-Driver/Injector | Operator-Sync |
| Rotation | Neu verschlüsseln mit SOPS | Neu versiegeln nötig | Dynamische Secrets | Backend-abhängig |
| Komplexität | Niedrig | Niedrig | Hoch | Mittel |
| Offline-fähig | Ja | Nein | Nein | Nein |
| Ideal für | Kleine-mittlere Teams | Einzelne Cluster | Enterprise/Compliance | Multi-Cloud |
SOPS+Age glänzt, wenn Sie Einfachheit, Offline-Fähigkeit und echtes GitOps ohne zusätzliche Infrastruktur wollen.
Praxisszenario
Sie verwalten ein Multi-Umgebungs-Kubernetes-Deployment über Entwicklungs-, Staging- und Produktions-Cluster. Ihr Team von acht Ingenieuren nutzt ArgoCD für GitOps, und Sie brauchen eine Secrets-Lösung, die:
- Entwicklern ermöglicht, Secrets ohne Cluster-Zugriff zu erstellen und zu aktualisieren
- Verschlüsselte Secrets in Git für die Nachvollziehbarkeit aufbewahrt
- Verschiedene Verschlüsselungsschlüssel pro Umgebung verwendet
- Schlüsselrotation ohne Neuverteilung jedes Secrets erlaubt
So würden Sie Ihr Repository strukturieren:
k8s-manifests/
├── .sops.yaml
├── base/
│ ├── deployment.yaml
│ └── service.yaml
├── envs/
│ ├── dev/
│ │ ├── kustomization.yaml
│ │ ├── secret-generator.yaml
│ │ └── secrets/
│ │ └── app-secrets.yaml # mit Dev-Schlüssel verschlüsselt
│ ├── staging/
│ │ ├── kustomization.yaml
│ │ ├── secret-generator.yaml
│ │ └── secrets/
│ │ └── app-secrets.yaml # mit Staging-Schlüssel verschlüsselt
│ └── production/
│ ├── kustomization.yaml
│ ├── secret-generator.yaml
│ └── secrets/
│ └── app-secrets.yaml # mit Prod-Schlüssel verschlüsselt
Jede Umgebung hat ihr eigenes Age-Schlüsselpaar. Entwickler halten den Dev-Schlüssel, Teamleiter halten Dev+Staging, und nur die CI/CD-Pipeline hält den Produktionsschlüssel. Die .sops.yaml-Datei leitet die Verschlüsselung basierend auf dem Dateipfad zum richtigen Schlüssel.
Fallstricke und Grenzfälle
Schlüsselrotation erfordert Neuverschlüsselung. Wenn Sie einen Age-Schlüssel rotieren, müssen Sie jede Datei mit dem alten Schlüssel entschlüsseln und mit dem neuen neu verschlüsseln. SOPS bietet sops updatekeys dafür, aber testen Sie es zuerst:
# Schlüssel für eine einzelne Datei aktualisieren (nutzt .sops.yaml-Regeln)
sops updatekeys secrets/db-credentials.yaml
# Alle Secrets im Batch neu verschlüsseln
find . -name "*.yaml" -path "*/secrets/*" -exec sops updatekeys {} \;
Teilweise Verschlüsselung mit encrypted_regex. Standardmäßig verschlüsselt SOPS alle Werte. Verwenden Sie encrypted_regex in .sops.yaml, um nur bestimmte Schlüssel zu verschlüsseln:
creation_rules:
- path_regex: secrets/.*\.yaml$
encrypted_regex: "^(data|stringData)$"
age: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
Binäre Secrets benötigen zuerst Base64-Kodierung. SOPS arbeitet mit Textdateien. Für binäre Secrets (TLS-Zertifikate, Keystores) kodieren Sie sie vor der Verschlüsselung in Base64:
# Zertifikat kodieren, dann SOPS den Base64-String verschlüsseln lassen
cat tls.crt | base64 -w0 > tls.crt.b64
Committen Sie niemals den privaten Age-Schlüssel. Fügen Sie ihn sofort zur .gitignore hinzu:
echo "age-key.txt" >> .gitignore
echo "*.agekey" >> .gitignore
Der SOPS-Metadatenbereich wächst mit den Empfängern. Jeder zusätzliche Age-Empfänger fügt ~300 Bytes Metadaten hinzu. Bei vielen Empfängern erwägen Sie, einen gemeinsamen Team-Schlüssel über einen sicheren Kanal zu verteilen, anstatt individuelle Schlüssel zu verwenden.
Fehlerbehebung
Fehler: could not find common decryption key
Der private Age-Schlüssel ist nicht verfügbar. Überprüfen Sie den Speicherort der Schlüsseldatei:
# Prüfen ob die Schlüsseldatei existiert
ls -la ~/.config/sops/age/keys.txt
# Oder die Umgebungsvariable prüfen
echo $SOPS_AGE_KEY_FILE
# Prüfen ob der Schlüssel zum Empfänger passt
grep "public key" ~/.config/sops/age/keys.txt
Fehler: failed to decrypt
Die Datei wurde mit einem anderen Age-Schlüssel verschlüsselt. Prüfen Sie, welcher Schlüssel verwendet wurde:
sops --decrypt --verbose secrets/db-credentials.yaml 2>&1 | grep "recipient"
ArgoCD-Sync schlägt mit KSOPS-Fehlern fehl Stellen Sie sicher, dass der KSOPS-Init-Container erfolgreich abgeschlossen wurde und das Age-Schlüssel-Secret existiert:
kubectl logs deployment/argocd-repo-server -n argocd -c install-ksops
kubectl get secret sops-age-key -n argocd
FluxCD Kustomization bleibt bei Not Ready hängen
Prüfen Sie die Logs des Kustomize-Controllers:
kubectl logs deployment/kustomize-controller -n flux-system | grep -i sops
Häufige Ursache: Das sops-age Secret ist im falschen Namespace oder hat den falschen Schlüsselnamen (muss age.agekey sein).
Verschlüsselte Datei zeigt mac mismatch-Fehler
Die Datei wurde nach der Verschlüsselung ohne sops --set oder sops edit modifiziert. Verschlüsseln Sie erneut aus der Klartextquelle:
sops --decrypt secrets/db-credentials.yaml > /tmp/plain.yaml
sops --encrypt /tmp/plain.yaml > secrets/db-credentials.yaml
rm -f /tmp/plain.yaml
Zusammenfassung
- Kubernetes Secrets verwenden Base64-Kodierung, keine Verschlüsselung — sie sind standardmäßig nicht sicher
- SOPS verschlüsselt YAML-Werte und hält Schlüssel lesbar, was aussagekräftige Git-Diffs ermöglicht
- Age bietet einfacheres Schlüsselmanagement als PGP ohne Konfigurationsaufwand
- Die
.sops.yaml-Datei definiert pfadbasierte Verschlüsselungsregeln für Multi-Umgebungs-Setups - ArgoCD integriert sich über das KSOPS-Plugin; FluxCD hat native SOPS-Entschlüsselungs-Unterstützung
- Schlüsselrotation erfordert die Neuverschlüsselung aller betroffenen Dateien mit
sops updatekeys - Speichern Sie private Age-Schlüssel immer außerhalb des Repositories und in der
.gitignore - Für Enterprise-Umgebungen mit dynamischen Secrets erwägen Sie HashiCorp Vault