Veröffentlicht am

Sealed Secrets Anleitung: Kubernetes Secrets in Git

Teilen:
Authors

TL;DR

Sealed Secrets lösen das Problem, dass Kubernetes Secrets nur base64-kodiert und nicht verschlüsselt sind. Mit dem Controller im Cluster und dem kubeseal-CLI verschlüsselst du Secrets lokal — nur der Controller kann sie entschlüsseln. So landen Secrets sicher in Git.


Sealed Secrets: Secrets sicher in Git speichern

Wer GitOps macht, steht vor einem Problem: Kubernetes Secrets sind nur base64-kodiert. Wer base64 -d kennt, kann sie lesen. Secrets einfach ins Git-Repo committen? Keine Option.

Sealed Secrets von Bitnami lösen genau das. Der Ablauf:

┌─────────────┐    kubeseal    ┌──────────────┐    Git Push    ┌─────────┐
Secret YAML │───────────────▶│ SealedSecret │───────────────▶│   Git (Klartext)verschlüsselt│ (verschlüss.)│               │  Repo└─────────────┘                └──────────────┘                └────┬────┘
                   ┌──────────────┐    entschlüsselt    ┌──────────▼──┐
Secret     │◀────────────────────│  Controller                    (im Cluster)  (Cluster)                   └──────────────┘                     └─────────────┘

Du verschlüsselst lokal mit kubeseal, pushst die SealedSecret-YAML ins Repo, und der Controller im Cluster entschlüsselt sie automatisch zu einem normalen Secret.

Installation

Zwei Teile: Der Controller im Cluster und das CLI-Tool auf deinem Rechner.

Controller installieren

# Per Helm (empfohlen)
helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
helm repo update

kubectl create namespace sealed-secrets

helm install sealed-secrets sealed-secrets/sealed-secrets \
  --namespace sealed-secrets \
  --set fullnameOverride=sealed-secrets-controller
# Prüfen, ob der Controller läuft
kubectl get pods -n sealed-secrets
kubectl get svc -n sealed-secrets

kubeseal CLI installieren

# macOS
brew install kubeseal

# Linux
KUBESEAL_VERSION=0.27.3
curl -LO "https://github.com/bitnami-labs/sealed-secrets/releases/download/v${KUBESEAL_VERSION}/kubeseal-${KUBESEAL_VERSION}-linux-amd64.tar.gz"
tar -xzf kubeseal-${KUBESEAL_VERSION}-linux-amd64.tar.gz
sudo mv kubeseal /usr/local/bin/

# Version prüfen
kubeseal --version

Erstes Secret verschlüsseln

Angenommen, du brauchst ein Datenbank-Passwort als Secret:

Schritt 1: Secret YAML erstellen (nicht committen!)

kubectl create secret generic db-credentials \
  --from-literal=username=dbadmin \
  --from-literal=password=S3cur3-P4ssw0rd \
  --namespace production \
  --dry-run=client -o yaml > /tmp/db-secret.yaml

Die Datei unter /tmp/ erstellen — sie enthält das Klartext-Secret und darf nie in Git landen.

Schritt 2: Mit kubeseal verschlüsseln

kubeseal --format yaml \
  --controller-name sealed-secrets-controller \
  --controller-namespace sealed-secrets \
  < /tmp/db-secret.yaml > sealed-db-credentials.yaml

Schritt 3: SealedSecret prüfen und committen

# Inhalt ansehen — komplett verschlüsselt
cat sealed-db-credentials.yaml

Die Ausgabe sieht so aus:

apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  name: db-credentials
  namespace: production
spec:
  encryptedData:
    password: AgBy8h3kF...langer-verschlüsselter-String...
    username: AgCtr9s2P...langer-verschlüsselter-String...
  template:
    metadata:
      name: db-credentials
      namespace: production

Diese Datei kannst du bedenkenlos committen:

git add sealed-db-credentials.yaml
git commit -m "Add sealed database credentials"
git push

Schritt 4: Im Cluster anwenden

kubectl apply -f sealed-db-credentials.yaml

# Der Controller erstellt automatisch das echte Secret
kubectl get secret db-credentials -n production
kubectl get secret db-credentials -n production -o jsonpath='{.data.password}' | base64 -d

Verschlüsselungs-Scopes

Sealed Secrets unterstützen drei Scopes, die bestimmen, wo ein Secret entschlüsselt werden darf:

ScopeFlagBeschreibung
strict (Standard)--scope strictSecret ist an Name UND Namespace gebunden
namespace-wide--scope namespace-wideSecret kann im Namespace umbenannt werden
cluster-wide--scope cluster-wideSecret funktioniert in jedem Namespace

Für die meisten Fälle ist strict richtig. Nur wenn du Secrets dynamisch benennen musst:

# Namespace-wide Scope
kubeseal --format yaml --scope namespace-wide \
  --controller-name sealed-secrets-controller \
  --controller-namespace sealed-secrets \
  < /tmp/db-secret.yaml > sealed-db-credentials.yaml

Key-Management und Rotation

Der Controller generiert beim Start ein Schlüsselpaar. Diesen Schlüssel musst du sichern — ohne ihn sind alle SealedSecrets verloren.

Schlüssel sichern

# Alle Sealing-Keys exportieren
kubectl get secret -n sealed-secrets \
  -l sealedsecrets.bitnami.com/sealed-secrets-key \
  -o yaml > /safe/location/sealed-secrets-master-key.yaml

Diese Datei gehört an einen sicheren Ort (Vault, verschlüsselter USB-Stick, KMS) — niemals in Git.

Schlüssel wiederherstellen (Disaster Recovery)

# Vor der Neuinstallation: Key wiederherstellen
kubectl apply -f /safe/location/sealed-secrets-master-key.yaml

# Dann Controller neu installieren
helm install sealed-secrets sealed-secrets/sealed-secrets \
  --namespace sealed-secrets \
  --set fullnameOverride=sealed-secrets-controller

Key Rotation

Der Controller erstellt standardmäßig alle 30 Tage einen neuen Key. Alte Keys bleiben erhalten, damit bestehende SealedSecrets weiter entschlüsselt werden können.

Um SealedSecrets mit dem neuesten Key neu zu verschlüsseln:

# Alle SealedSecrets im Repo neu verschlüsseln
kubeseal --re-encrypt \
  --controller-name sealed-secrets-controller \
  --controller-namespace sealed-secrets \
  < sealed-db-credentials.yaml > sealed-db-credentials-rotated.yaml

mv sealed-db-credentials-rotated.yaml sealed-db-credentials.yaml

GitOps-Integration mit ArgoCD

Wenn du ArgoCD nutzt, funktionieren SealedSecrets nahtlos. ArgoCD sieht die SealedSecret-Ressource im Git-Repo, wendet sie an, und der Controller erstellt das Secret:

# argocd-application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-app
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/dein-org/k8s-manifests.git
    path: apps/production
    targetRevision: main
  destination:
    server: https://kubernetes.default.svc
    namespace: production
  syncPolicy:
    automated:
      selfHeal: true

Dein Repository-Layout könnte so aussehen:

k8s-manifests/
├── apps/
│   └── production/
│       ├── deployment.yaml
│       ├── service.yaml
│       └── sealed-db-credentials.yaml    # verschlüsselt, sicher in Git

Troubleshooting

SealedSecret wird nicht entschlüsselt:

# Events des SealedSecret prüfen
kubectl describe sealedsecret db-credentials -n production

# Controller-Logs
kubectl logs -n sealed-secrets deployment/sealed-secrets-controller

Häufigste Ursache: Der Namespace in der SealedSecret stimmt nicht mit dem Namespace überein, in dem sie applied wird (bei strict Scope).

"certificate signed by unknown authority":

# Zertifikat explizit vom Controller holen
kubeseal --fetch-cert \
  --controller-name sealed-secrets-controller \
  --controller-namespace sealed-secrets > /tmp/sealed-secrets-cert.pem

# Dann mit Zertifikat verschlüsseln
kubeseal --format yaml --cert /tmp/sealed-secrets-cert.pem \
  < /tmp/db-secret.yaml > sealed-db-credentials.yaml

FAQ

Was passiert, wenn der Controller gelöscht wird?

Bestehende Secrets im Cluster bleiben erhalten — sie sind normale Kubernetes Secrets. Aber neue SealedSecrets werden nicht mehr entschlüsselt. Deshalb: Sealing-Keys immer sichern.

Kann ich SealedSecrets offline erstellen?

Ja. Exportiere das Public-Key-Zertifikat mit kubeseal --fetch-cert und nutze es mit --cert Flag. So brauchst du keinen direkten Cluster-Zugriff zum Verschlüsseln.

Wie unterscheiden sich Sealed Secrets von SOPS oder External Secrets?

Sealed Secrets verschlüsseln direkt in Kubernetes-Ressourcen und brauchen keinen externen Key-Store. SOPS verschlüsselt beliebige Dateien und braucht KMS/AGE-Keys. External Secrets Operator synchronisiert aus Vault/AWS SecretsManager. Für reine GitOps-Workflows ohne externe Infrastruktur sind Sealed Secrets am einfachsten.

Muss ich alle Secrets neu verschlüsseln, wenn der Key rotiert?

Nein, alte Keys werden behalten. Aber es ist Best Practice, SealedSecrets periodisch mit kubeseal --re-encrypt zu aktualisieren und die ältesten Keys dann zu entfernen.


Nächster Schritt: Kombiniere Sealed Secrets mit ArgoCD für einen vollständigen GitOps-Workflow, bei dem auch Secrets automatisch deployed werden.

Kubernetes-Beratung gesucht?

Wir helfen deutschen Unternehmen bei der Kubernetes-Implementierung, Migration und Optimierung. DSGVO-konform und praxiserprobt.

📖 Verwandte Artikel

Weitere interessante Beiträge zu ähnlichen Themen