DevOps GitOps Secret-Management: 6 Produktionsmuster von Sealed Secrets bis External Secrets Operator

DevOps

Secrets in Git committen? Sie sitzen auf einer Zeitbombe

GitOps dreht sich um "Git als Single Source of Truth", aber Kubernetes Secrets sind lediglich Base64-kodiert — nicht verschlüsselt. Base64-kodierte Secrets in ein Git-Repository zu pushen ist gleichbedeutend damit, Datenbankpasswörter, API-Schlüssel und TLS-Zertifikate im Klartext zu speichern. Sobald das Repository geleakt wird (interne Fehlkonfiguration, Drittanbieter-Lieferkettenverletzung, vergessene Bereinigung in einem Fork), sind alle Secrets sofort offengelegt.

2026 ist DevOps GitOps Secret-Management nicht mehr optional — es ist eine harte Anforderung für Produktionsbereitstellungen. Dieser Artikel behandelt 6 produktionsreife Secret-Management-Muster, von verschlüsselter Speicherung mit Sealed Secrets bis zur dynamischen Injektion mit External Secrets Operator, und hilft Ihnen, die Secret-Sicherheit in GitOps-Workflows vollständig zu lösen.

Wichtigste Erkenntnisse

  • 3 Architekturpattern für GitOps Secret-Management verstehen: verschlüsselte Speicherung, externe Injektion und Hybrid
  • Vollständige Bereitstellung und Konfiguration von Sealed Secrets, External Secrets Operator und SOPS beherrschen
  • Automatische Secret-Rotation und Multi-Cluster-Synchronisation implementieren
  • 5 häufigste Produktionsfehler und 10 wiederkehrende Fehler vermeiden
  • Eine Vergleichsentscheidungsmatrix zur Toolauswahl erhalten

Inhaltsverzeichnis

  • GitOps Secret-Management Kernkonzepte
  • Muster 1: Sealed Secrets Verschlüsselte Speicherung
  • Muster 2: External Secrets Operator + Vault
  • Muster 3: SOPS + Age/GPG Verschlüsselung
  • Muster 4: Automatische Secret-Rotation
  • Muster 5: Multi-Cluster Secret-Synchronisation
  • Muster 6: Audit und Compliance
  • 5 Häufige Fallstricke und Lösungen
  • 10 Häufige Fehlerbehebung
  • Erweiterte Optimierungstipps
  • Vergleichsanalyse
  • Empfohlene Online-Tools

GitOps Secret-Management Kernkonzepte

Drei Architekturpattern

┌─────────────────────────────────────────────────────────────┐
│       GitOps Secret-Management: 3 Pattern                    │
├─────────────────┬──────────────────┬────────────────────────┤
│  Verschlüsselte  │  Externe         │  Hybrid                │
│  Speicherung     │  Injektion       │  (SOPS + ESO)          │
│  (Sealed Secrets)│  (ESO + Vault)   │                        │
├─────────────────┼──────────────────┼────────────────────────┤
│  Git speichert   │  Git speichert   │  Git speichert         │
│  Chiffretext     │  nur Referenzen  │  verschlüsselte Werte  │
│  Cluster-interne │  Cluster holt    │  + Referenzen          │
│  Entschlüsselung │  Klartext        │  Cluster-Entschlüsselung│
│  Offline-Audit   │  Externe Abhän-  │  oder Abruf            │
│  Neu-verschlüsseln│ gigkeiten       │  Flexible Kombination  │
│  zum Rotieren    │  Native Rotation │  Teilweise Rotation    │
│                  │  unterstützt     │  unterstützt           │
└─────────────────┴──────────────────┴────────────────────────┘

Das grundlegende Problem mit K8s Secrets

# Ein Standard-Secret erstellen
kubectl create secret generic db-credentials \
  --from-literal=username=admin \
  --from-literal=password='SuperSecret123!'

# Die "Verschlüsselung" ist nur Base64-Kodierung — jeder kann es dekodieren
kubectl get secret db-credentials -o yaml

# Dekodieren erfordert nur einen Befehl
kubectl get secret db-credentials \
  -o jsonpath='{.data.password}' | base64 -d
# Ausgabe: SuperSecret123!

# Mit dem ToolsKu Base64-Kodierer/Dekodierer überprüfen
# https://toolsku.com/de/encode/base64

Kernprinzipien des DevOps GitOps Secret-Managements

Prinzip Beschreibung Folge bei Verletzung
Null Klartext Niemals Klartext-Secrets in Git speichern Repository-Leck = vollständige Kompromittierung
Geringste Berechtigung Jede App greift nur auf eigene Secrets zu Laterale Secret-Leckage
Auto-Rotation Secrets werden regelmäßig automatisch aktualisiert Langlebige Schlüssel werden brutal erzwungen
Auditierbar Jeder Secret-Zugriff wird protokolliert Leck-Quelle nicht rückverfolgbar
Wiederherstellbar Secrets können nach Verlust schnell wiederhergestellt werden Geschäftsunterbrechung

Muster 1: Sealed Secrets Verschlüsselte Speicherung

Sealed Secrets ist eine Open-Source-GitOps-Secret-Management-Lösung von Bitnami. Die Kernidee: Secrets lokal mit kubeseal verschlüsseln, SealedSecret-Ressourcen generieren, die in Git committet werden, und der cluster-interne Sealed Secrets Controller entschlüsselt sie automatisch zurück zu K8s Secrets.

Architektur

┌──────────────────────────────────────────────────────┐
│              Entwickler-Arbeitsstation                  │
│  ┌────────────┐    ┌────────────┐                     │
│  │ secret.yaml │───▶│  kubeseal   │                    │
│  │ (Klartext,  │    │ (Verschlüs- │                    │
│  │  nicht      │    │  selungs-   │                    │
│  │  committen) │    │  tool)      │                    │
│  └────────────┘    └─────┬──────┘                     │
│                          │ verschlüsseln               │
│                    ┌─────▼──────┐                     │
│                    │sealed-secret│                     │
│                    │.yaml(Chiffre)│                    │
│                    └─────┬──────┘                     │
└──────────────────────────┼───────────────────────────┘
                           │ git push
┌──────────────────────────▼───────────────────────────┐
│                    Git-Repository                       │
│       (speichert nur verschlüsselte SealedSecrets)      │
└──────────────────────────┬───────────────────────────┘
                           │ git pull (ArgoCD/Flux)
┌──────────────────────────▼───────────────────────────┐
│                 K8s-Cluster                            │
│  ┌──────────────────────────────┐                     │
│  │  Sealed Secrets Controller    │                     │
│  │  (entschlüsselt mit privatem  │                     │
│  │   Schlüssel)                  │                     │
│  └──────────────┬───────────────┘                     │
│                 │ entschlüsseln                        │
│           ┌─────▼──────┐                              │
│           │  K8s Secret │                              │
│           │ (Klartext,  │                               │
│           │  cluster-   │                               │
│           │  intern)    │                               │
│           └────────────┘                               │
└───────────────────────────────────────────────────────┘

Sealed Secrets installieren

# Controller im Cluster installieren
kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.27.0/controller.yaml

# Auf Bereitschaft des Controllers warten
kubectl wait --for=condition=available --timeout=120s \
  deployment/sealed-secrets-controller -n kube-system

# kubeseal CLI installieren
KUBESEAL_VERSION=0.27.0
curl -sLO "https://github.com/bitnami-labs/sealed-secrets/releases/download/v${KUBESEAL_VERSION}/kubeseal-linux-amd64"
chmod +x kubeseal-linux-amd64
sudo mv kubeseal-linux-amd64 /usr/local/bin/kubeseal

# Installation überprüfen
kubeseal --version

Secrets verschlüsseln

# Aus Klartext-Secret-Datei verschlüsseln
kubectl create secret generic db-credentials \
  --from-literal=username=admin \
  --from-literal=password='SuperSecret123!' \
  --dry-run=client -o yaml | \
  kubeseal --format yaml > sealed-secret.yaml

# Aus Dateien erstellen
kubectl create secret generic tls-cert \
  --from-file=tls.crt=server.crt \
  --from-file=tls.key=server.key \
  --dry-run=client -o yaml | \
  kubeseal --format yaml > sealed-tls.yaml

# Namespace und Namen angeben (Strict-Modus)
kubectl create secret generic api-key \
  --from-literal=key=abc123xyz \
  --namespace production \
  --dry-run=client -o yaml | \
  kubeseal --format yaml \
  --scope namespace-wide > sealed-api-key.yaml

SealedSecret-Ressourcenbeispiel

apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  name: db-credentials
  namespace: production
spec:
  encryptedData:
    username: AgBfj8k2mN3pQ7sT9vWxYz...
    password: AgCdH5lM6nO8qR0tU2wXyZa...
  template:
    metadata:
      name: db-credentials
      namespace: production
    type: Opaque

Verschlüsselungsbereich-Steuerung

# strict: nur mit gleichem Namespace und Namen entschlüsselbar (Standard)
kubeseal --scope strict

# namespace-wide: innerhalb desselben Namespace umbenennbar
kubeseal --scope namespace-wide

# cluster-wide: in jedem Namespace im Cluster verwendbar
kubeseal --scope cluster-wide
Bereich Sicherheitsstufe Flexibilität Anwendungsfall
strict Höchste Niedrigste Produktions-Secrets
namespace-wide Mittel Mittel Multi-App gleicher Namespace
cluster-wide Niedrigste Höchste Geteilte Ressourcen wie Zertifikate

Backup und Wiederherstellung des Hauptschlüssels

# Hauptschlüssel-Backup (alle SealedSecrets werden unentschlüsselbar, wenn verloren!)
kubectl get secret -n kube-system \
  sealed-secrets-key -o yaml > sealed-secrets-key-backup.yaml

# Hauptschlüssel in neuem Cluster wiederherstellen
kubectl apply -f sealed-secrets-key-backup.yaml

# Hauptschlüssel rotieren
kubectl delete secret -n kube-system sealed-secrets-key
# Controller generiert automatisch einen neuen Schlüssel, alte SealedSecrets funktionieren weiterhin

Muster 2: External Secrets Operator + Vault

External Secrets Operator (ESO) integriert externe Secret-Management-Systeme (Vault, AWS Secrets Manager, GCP Secret Manager usw.) mit Kubernetes. Git-Repositories speichern nur SecretStore-Referenzen, und der cluster-interne ESO Controller ruft Secrets aus externen Systemen ab und erstellt K8s Secrets.

Architektur

┌──────────────────────────────────────────────────────┐
│                    Git-Repository                       │
│  ┌──────────────┐  ┌──────────────┐                  │
│  │ SecretStore   │  │ ExternalSecret│                  │
│  │ (Vault-Ver-  │  │ (Secret-Ref- │                  │
│  │  bindungs-   │  │  Mapping)    │                  │
│  │  konfig)     │  │              │                  │
│  └──────────────┘  └──────────────┘                  │
└──────────────────────┬───────────────────────────────┘
                       │ git pull
┌──────────────────────▼───────────────────────────────┐
│                 K8s-Cluster                            │
│  ┌──────────────────────────────┐                     │
│  │   External Secrets Operator   │                     │
│  │   (überwacht ExternalSecrets) │                     │
│  └──────────────┬───────────────┘                     │
│                 │ Secrets abrufen                      │
│    ┌────────────▼────────────┐                        │
│    │                         │                        │
│    ▼                         ▼                        │
│ ┌──────────┐         ┌──────────┐                    │
│ │  K8s     │         │ HashiCorp│                    │
│ │  Secret  │         │  Vault   │                    │
│ └──────────┘         └──────────┘                    │
└───────────────────────────────────────────────────────┘

ESO installieren

# Mit Helm installieren
helm repo add external-secrets https://charts.external-secrets.io
helm repo update

helm install external-secrets \
  external-secrets/external-secrets \
  --namespace external-secrets \
  --create-namespace \
  --set installCRDs=true \
  --set replicaCount=2 \
  --set leaderElect=true

# Überprüfen
kubectl get pods -n external-secrets

HashiCorp Vault SecretStore konfigurieren

apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: vault-backend
  namespace: production
spec:
  provider:
    vault:
      server: "https://vault.internal.example.com"
      path: "secret"
      version: "v2"
      auth:
        kubernetes:
          mountPath: "kubernetes"
          role: "eso-role"
          serviceAccountRef:
            name: "external-secrets-sa"

Vault-Richtlinienkonfiguration

# Vault-Richtlinie: ESO auf bestimmte Pfade beschränken
path "secret/data/production/*" {
  capabilities = ["read"]
}

path "secret/data/production/database/*" {
  capabilities = ["read", "list"]
}

# Zugriff auf andere Umgebungen verweigern
path "secret/data/staging/*" {
  capabilities = ["deny"]
}

path "secret/data/development/*" {
  capabilities = ["deny"]
}

ExternalSecret-Ressource

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: db-credentials
  namespace: production
spec:
  refreshInterval: 15m
  secretStoreRef:
    name: vault-backend
    kind: SecretStore
  target:
    name: db-credentials
    creationPolicy: Owner
    template:
      type: Opaque
      data:
        DATABASE_URL: "postgresql://{{ .username }}:{{ .password }}@db.internal:5432/mydb"
  data:
    - secretKey: username
      remoteRef:
        key: secret/data/production/database
        property: username
    - secretKey: password
      remoteRef:
        key: secret/data/production/database
        property: password

Multi-Quelle: ClusterSecretStore

apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: aws-secrets-manager
spec:
  provider:
    aws:
      service: SecretsManager
      region: us-east-1
      auth:
        jwt:
          serviceAccountRef:
            name: eso-aws-sa
            namespace: external-secrets
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: aws-api-keys
  namespace: production
spec:
  refreshInterval: 5m
  secretStoreRef:
    name: aws-secrets-manager
    kind: ClusterSecretStore
  target:
    name: aws-api-keys
  dataFrom:
    - extract:
        key: production/api-keys

Muster 3: SOPS + Age/GPG Verschlüsselung

SOPS (Secrets OPerationS) ist ein von Mozilla entwickeltes Secret-Verschlüsselungstool, das mehrere Verschlüsselungs-Backends einschließlich AES, PGP und Age unterstützt. Im Gegensatz zu Sealed Secrets verschlüsselt SOPS die Datei selbst — es kann YAML/JSON/ENV-Formate verschlüsseln und verschlüsselt nur Werte, nicht Schlüssel. Das bedeutet, Sie können Git Diff verwenden, um strukturelle Änderungen in Secrets zu sehen.

Architektur

┌──────────────────────────────────────────────────────┐
│              Entwickler-Arbeitsstation                  │
│  ┌────────────┐    ┌────────────┐                     │
│  │ secret.yaml │───▶│    SOPS     │                    │
│  │ (Klartext,  │    │ + Age/GPG   │                    │
│  │  nicht      │    │             │                    │
│  │  committen) │    │             │                    │
│  └────────────┘    └─────┬──────┘                     │
│                          │ verschlüsseln               │
│                    ┌─────▼──────┐                     │
│                    │secret.enc.  │                     │
│                    │yaml(Chiffre)│                     │
│                    └─────┬──────┘                     │
└──────────────────────────┼───────────────────────────┘
                           │ git push
┌──────────────────────────▼───────────────────────────┐
│                    Git-Repository                       │
│      (speichert verschlüsselte Dateien, Schlüssel       │
│       sichtbar, Werte verschlüsselt)                   │
└──────────────────────────┬───────────────────────────┘
                           │ git pull
┌──────────────────────────▼───────────────────────────┐
│                 CI/CD-Pipeline                          │
│  ┌──────────────────────────────┐                     │
│  │ sops --decrypt + kubectl apply│                    │
│  │ oder Flux Kustomization SOPS  │                    │
│  │ Integration                   │                    │
│  └──────────────────────────────┘                     │
└───────────────────────────────────────────────────────┘

SOPS und Age installieren

# SOPS installieren
curl -sLO https://github.com/getsops/sops/releases/download/v3.9.0/sops-v3.9.0.linux.amd64
chmod +x sops-v3.9.0.linux.amd64
sudo mv sops-v3.9.0.linux.amd64 /usr/local/bin/sops

# Age installieren (empfohlenes Verschlüsselungs-Backend, einfacher als GPG)
curl -sLO 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 /usr/local/bin/
sudo mv age/age-keygen /usr/local/bin/

# Age-Schlüsselpaar generieren
age-keygen -o age.key
# Öffentlicher Schlüssel: age1abc123xyz...

# Überprüfen
sops --version
age --version

Secret-Dateien verschlüsseln

# secret.yaml (vor der Verschlüsselung)
apiVersion: v1
kind: Secret
metadata:
  name: app-config
  namespace: production
type: Opaque
stringData:
  DB_HOST: "db.internal.example.com"
  DB_PASSWORD: "SuperSecret123!"
  API_KEY: "sk-proj-abc123xyz456"
  REDIS_URL: "redis://redis.internal:6379"
# Mit öffentlichem Age-Schlüssel verschlüsseln
sops --encrypt \
  --age age1abc123xyz456... \
  --encrypted-regex '^(DB_PASSWORD|API_KEY)$' \
  --in-place secret.yaml
# secret.yaml (nach der Verschlüsselung)
apiVersion: v1
kind: Secret
metadata:
  name: app-config
  namespace: production
type: Opaque
stringData:
  DB_HOST: "db.internal.example.com"
  DB_PASSWORD: ENC[AES256_GCM,data:Wk5kPQ==,tag:abc123==,type:str]
  API_KEY: ENC[AES256_GCM,data:Zm9vYmFy,tag:def456==,type:str]
  REDIS_URL: "redis://redis.internal:6379"
sops:
  kms: []
  gcp_kms: []
  azure_kv: []
  hc_vault: []
  age:
    - recipient: age1abc123xyz456...
      enc: |
        -----BEGIN AGE ENCRYPTED FILE-----
        YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA...
        -----END AGE ENCRYPTED FILE-----
  lastmodified: "2026-06-16T10:00:00Z"
  mac: ENC[AES256_GCM,data:abc==,tag:xyz==,type:str]
  pgp: []
  encrypted_regex: ^(DB_PASSWORD|API_KEY)$
  version: 3.9.0

Flux CD SOPS-Integration

apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: app-secrets
  namespace: flux-system
spec:
  interval: 10m
  path: ./clusters/production
  prune: true
  sourceRef:
    kind: GitRepository
    name: flux-system
  decryption:
    provider: sops
    secretRef:
      name: sops-age-key
# Age-Privatschlüssel-Secret für Flux-Entschlüsselung erstellen
kubectl create secret generic sops-age-key \
  --namespace flux-system \
  --from-file=age.agekey=age.key \
  --dry-run=client -o yaml | \
  kubeseal --format yaml > sealed-sops-key.yaml

Multi-Schlüssel-Verschlüsselung (Teamzusammenarbeit)

# .sops.yaml-Konfigurationsdatei — projektweite Verschlüsselungskonfiguration
cat > .sops.yaml << 'EOF'
creation_rules:
  - path_regex: ^clusters/production/.*\.yaml$
    key_groups:
      - age:
          - age1abc123xyz456  # Produktionsschlüssel
          - age1def789uvw012  # SRE-Team-Schlüssel
  - path_regex: ^clusters/staging/.*\.yaml$
    key_groups:
      - age:
          - age1ghi345rst678  # Staging-Schlüssel
  - path_regex: ^clusters/.*\.yaml$
    key_groups:
      - age:
          - age1abc123xyz456  # Standardschlüssel
EOF

Muster 4: Automatische Secret-Rotation

Die Secret-Rotation ist der am meisten vernachlässigte Aspekt des DevOps GitOps Secret-Managements. Statische Secrets, die sich nie ändern, schaffen ein unendliches Angriffsfenster, sobald sie geleakt sind. Die Best Practice 2026: Alle Produktions-Secrets alle 90 Tage rotieren und hochsensible Secrets alle 7 Tage.

Rotationsarchitektur

┌──────────────────────────────────────────────────────┐
│        Secret-Auto-Rotationsarchitektur                 │
│                                                       │
│  ┌──────────┐  Auslöser    ┌──────────────┐         │
│  │ CronJob   │─────────────▶│ Vault Rotate  │         │
│  │ (Zeitplan)│              │ (neu erzeugen)│         │
│  └──────────┘              └──────┬───────┘         │
│                                   │ neues Secret      │
│                            ┌──────▼───────┐         │
│                            │ ExternalSecret│         │
│                            │ (auto refresh)│         │
│                            └──────┬───────┘         │
│                                   │ aktualisieren     │
│                            ┌──────▼───────┐         │
│                            │  K8s Secret   │         │
│                            │ (auto Update) │         │
│                            └──────┬───────┘         │
│                                   │ Rolling Restart   │
│                            ┌──────▼───────┐         │
│                            │   Pods        │         │
│                            │ (neuen Schlüssel│       │
│                            │  lesen)       │         │
│                            └──────────────┘         │
└──────────────────────────────────────────────────────┘

ESO Auto-Rotationskonfiguration

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: rotating-api-key
  namespace: production
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: vault-backend
    kind: SecretStore
  target:
    name: api-key
    creationPolicy: Owner
    template:
      type: Opaque
      metadata:
        annotations:
          reflector.v1.k8s.emberstack.com/reflection-allowed: "true"
  data:
    - secretKey: api-key
      remoteRef:
        key: secret/data/production/api
        property: key

Vault Dynamische Secrets

# Vault dynamische Datenbank-Anmeldeinformationskonfiguration
resource "vault_database_secret_backend_connection" "postgresql" {
  backend       = "database"
  name          = "postgresql-production"
  allowed_roles = ["app-readonly", "app-readwrite"]

  postgresql {
    connection_url = "postgresql://{{username}}:{{password}}@db.internal:5432/mydb?sslmode=require"
    username       = "vault_admin"
    password       = "VaultAdminPassword123!"
  }
}

resource "vault_database_secret_backend_role" "app_readwrite" {
  backend             = "database"
  name                = "app-readwrite"
  db_name             = vault_database_secret_backend_connection.postgresql.name
  default_ttl         = 3600
  max_ttl             = 86400
  creation_statements = [
    "CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';",
    "GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO \"{{name}}\";"
  ]
}

Automatische Erkennung von Secret-Änderungen in Anwendungen

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-server
  namespace: production
spec:
  replicas: 3
  selector:
    matchLabels:
      app: api-server
  template:
    metadata:
      labels:
        app: api-server
      annotations:
        secret.reloader.stakater.com/reload: "api-key,db-credentials"
    spec:
      containers:
        - name: api-server
          image: registry.example.com/api-server:v2.1.0
          envFrom:
            - secretRef:
                name: api-key
            - secretRef:
                name: db-credentials
# Reloader installieren — automatischer Rolling Restart von Pods bei Secret-Änderungen
helm repo add stakater https://stakater.github.io/stakater-charts
helm repo update

helm install reloader stakater/reloader \
  --namespace reloader \
  --create-namespace \
  --set reloader.watchGlobally=false

Muster 5: Multi-Cluster Secret-Synchronisation

In Multi-Cluster-Szenarien müssen Secrets sicher über Cluster hinweg synchronisiert werden. DevOps GitOps Secret-Management erfordert: Jeder Cluster hat seinen eigenen Secret-Lebenszyklus, während die Konsistenz gewahrt bleibt.

Multi-Cluster-Architektur

┌──────────────────────────────────────────────────────┐
│        Multi-Cluster Secret-Sync-Architektur            │
│                                                       │
│  ┌─────────────────────────────────────┐             │
│  │      HashiCorp Vault (Zentral)       │             │
│  │  ┌──────────┐  ┌──────────┐        │             │
│  │  │prod-east/│  │prod-west/│        │             │
│  │  │ secrets  │  │ secrets  │        │             │
│  │  └──────────┘  └──────────┘        │             │
│  └──────────┬───────────────┬──────────┘             │
│             │               │                         │
│    ┌────────▼──────┐ ┌──────▼────────┐              │
│    │ Cluster East   │ │ Cluster West  │              │
│    │  ┌──────────┐ │ │ ┌──────────┐ │              │
│    │  │   ESO    │ │ │ │   ESO    │ │              │
│    │  │Controller│ │ │ │Controller│ │              │
│    │  └────┬─────┘ │ │ └────┬─────┘ │              │
│    │       ▼       │ │      ▼       │              │
│    │  ┌──────────┐ │ │ ┌──────────┐ │              │
│    │  │ Secrets  │ │ │ │ Secrets  │ │              │
│    │  └──────────┘ │ │ └──────────┘ │              │
│    └───────────────┘ └──────────────┘              │
└──────────────────────────────────────────────────────┘

Cluster-spezifischer unabhängiger SecretStore

# Cluster East
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: vault-east
  namespace: production
spec:
  provider:
    vault:
      server: "https://vault.internal.example.com"
      path: "secret"
      version: "v2"
      auth:
        kubernetes:
          mountPath: "kubernetes/east"
          role: "eso-east-role"
          serviceAccountRef:
            name: "external-secrets-sa"
# Cluster West
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: vault-west
  namespace: production
spec:
  provider:
    vault:
      server: "https://vault.internal.example.com"
      path: "secret"
      version: "v2"
      auth:
        kubernetes:
          mountPath: "kubernetes/west"
          role: "eso-west-role"
          serviceAccountRef:
            name: "external-secrets-sa"

TLS-Zertifikat Multi-Cluster-Verteilung

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: wildcard-tls
  namespace: ingress-nginx
spec:
  refreshInterval: 24h
  secretStoreRef:
    name: vault-east
    kind: SecretStore
  target:
    name: wildcard-tls
    creationPolicy: Owner
    template:
      type: kubernetes.io/tls
  data:
    - secretKey: tls.crt
      remoteRef:
        key: secret/data/shared/tls/wildcard
        property: cert
    - secretKey: tls.key
      remoteRef:
        key: secret/data/shared/tls/wildcard
        property: key

Sealed Secrets Multi-Cluster Hauptschlüssel-Sync

# Hauptschlüssel exportieren
kubectl get secret -n kube-system sealed-secrets-key \
  -o yaml > sealed-secrets-master-key.yaml

# In Ziel-Cluster importieren
kubectl apply -f sealed-secrets-master-key.yaml -n kube-system

# Controller neustarten, um Schlüssel zu laden
kubectl rollout restart deployment/sealed-secrets-controller -n kube-system

Muster 6: Audit und Compliance

DevOps GitOps Secret-Management muss Audit-Anforderungen erfüllen: wer hat wann auf welches Secret zugegriffen. Compliance-Standards 2026 (SOC2, ISO 27001, MLPS 2.0) erfordern allesamt nachverfolgbaren Secret-Zugriff.

Vault-Audit-Logs

# Vault-Audit-Logging aktivieren
audit {
  type = "file"
  options = {
    file_path = "/vault/audit/audit.log"
    mode      = "0600"
  }
}

# Syslog-Audit aktivieren
audit {
  type = "syslog"
  options = {
    facility = "AUTH"
    tag      = "vault"
    address  = "syslog.internal:514"
  }
}

ESO-Zugriffsprotokollierung

apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: audited-vault
  annotations:
    audit.external-secrets.io/enabled: "true"
    audit.external-secrets.io/log-access: "true"
spec:
  provider:
    vault:
      server: "https://vault.internal.example.com"
      path: "secret"
      version: "v2"
      auth:
        kubernetes:
          mountPath: "kubernetes"
          role: "eso-audited-role"
          serviceAccountRef:
            name: "external-secrets-sa"

Kubernetes-Audit-Richtlinie

apiVersion: audit.k8s.io/v1
kind: Policy
rules:
  - level: RequestResponse
    resources:
      - group: ""
        resources: ["secrets"]
    namespaces: ["production", "staging"]
    omitStages:
      - RequestReceived

  - level: Metadata
    resources:
      - group: ""
        resources: ["secrets"]
    namespaces: ["default"]
    omitStages:
      - RequestReceived

  - level: RequestResponse
    resources:
      - group: "external-secrets.io"
        resources: ["externalsecrets", "secretstores"]
    omitStages:
      - RequestReceived

Secret-Zugriffsüberwachung

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: restrict-secret-access
spec:
  validationFailureAction: Audit
  background: true
  rules:
    - name: block-default-sa-secret-access
      match:
        resources:
          kinds: [Pod]
      validate:
        message: "Zugriff auf Secrets mit dem Standard-ServiceAccount ist verboten"
        pattern:
          spec:
            serviceAccountName: "!default"

    - name: require-secret-annotations
      match:
        resources:
          kinds: [Secret]
          names: ["db-*", "api-*", "tls-*"]
      validate:
        message: "Produktions-Secrets müssen Besitzer- und Ablaufanmerkungen haben"
        pattern:
          metadata:
            annotations:
              owner: "?*"
              expiry: "?*"

5 Häufige Fallstricke und Lösungen

Fallstrick 1: Verlust des Sealed Secrets Hauptschlüssels

Symptom: Nach Cluster-Neuaufbau können alle SealedSecrets nicht entschlüsselt werden. Der Controller meldet failed to unseal.

Ursache: Sealed Secrets verwendet asymmetrische Verschlüsselung. Der private Schlüssel existiert nur im Cluster. Zerstörung des Clusters verliert den privaten Schlüssel.

Lösung:

# 1. Regelmäßiges Hauptschlüssel-Backup (automatisiert)
kubectl get secret -n kube-system \
  sealed-secrets-key -o yaml | \
  sops --encrypt --age age1abc123xyz456... \
  /dev/stdin > sealed-secrets-key.enc.yaml

# 2. Verschlüsselten Schlüssel in einem anderen Git-Repo speichern
git add sealed-secrets-key.enc.yaml
git commit -m "backup: sealed secrets master key $(date +%Y%m%d)"

# 3. Wiederherstellungsprozess
sops --decrypt sealed-secrets-key.enc.yaml | \
  kubectl apply -f -

Fallstrick 2: ExternalSecret-Aktualisierungsverzögerung verursacht Pod-Startfehler

Symptom: Neu bereitgestellte Pods starten nicht, weil das Secret noch nicht existiert — ESO hat es noch nicht aus Vault abgerufen.

Lösung:

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: app-secrets
spec:
  refreshInterval: 5m
  target:
    creationPolicy: Owner
    template:
      type: Opaque
  dataFrom:
    - extract:
        key: production/app-secrets
# Sicherstellen, dass ESO synchronisiert hat, bevor die Anwendung bereitgestellt wird
# Methode 1: ArgoCD Sync Hooks verwenden
# Methode 2: Im CI auf Secret-Existenz warten
kubectl wait --for=condition=Ready \
  externalsecret/app-secrets -n production --timeout=120s

Fallstrick 3: SOPS-Schlüsselrotation bricht Entschlüsselung alter Chiffretexte

Symptom: Nachdem das Team Age-Schlüssel rotiert hat, können alte verschlüsselte Dateien nicht mehr entschlüsselt werden.

Lösung:

# Multi-Schlüssel-Verschlüsselung verwenden — Koexistenz alter und neuer Schlüssel
sops --encrypt \
  --age age1NEWKEY...,age1OLDKEY... \
  --in-place secret.yaml

# Oder Schlüsselgruppen über .sops.yaml verwalten
# Nach Schlüsselaktualisierung mit altem Schlüssel entschlüsseln und dann mit neuem neu verschlüsseln
sops --decrypt --age age1OLDKEY... secret.yaml | \
  sops --encrypt --age age1NEWKEY... \
  --filename-override secret.yaml /dev/stdin > secret_new.yaml

Fallstrick 4: Leaked Secrets können nicht schnell widerrufen werden

Symptom: Ein API-Key wurde geleakt, aber SealedSecret erfordert Neuverschlüsselung und Commit — das Widerruffenster ist zu lang.

Lösung:

# ESO-Ansatz: alten Schlüssel direkt in Vault deaktivieren
vault kv metadata put -delete-version-after=0s \
  secret/data/production/api-key

# Sealed Secrets-Ansatz: Notfall-Löschung über Controller
kubectl delete secret api-key -n production
# Sofort neu verschlüsseln und neues SealedSecret committen

# Generischer Ansatz: Netzwerkebene-Blockierung
kubectl apply -f - <<EOF
apiVersion: networkpolicies.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: block-compromised
  namespace: production
spec:
  podSelector: {}
  policyTypes: [Egress]
  egress:
    - to:
        - ipBlock:
            cidr: 0.0.0.0/0
            except:
              - compromised-api.example.com/32
EOF

Fallstrick 5: Teamübergreifend geteilte Secrets führen zu Zugriffskontroll-Chaos

Symptom: Mehrere Teams verwenden denselben Vault-Token für den Zugriff auf Secrets, was es unmöglich macht, zu unterscheiden, wer was getan hat.

Lösung:

# Vault-Richtlinien pro Team erstellen
path "secret/data/team-a/*" {
  capabilities = ["read", "list"]
}

path "secret/data/team-b/*" {
  capabilities = ["read", "list"]
}

# Explizite Verweigerung für Querzugriff
path "secret/data/team-a/*" {
  capabilities = ["deny"]
}
# An team-b-Rolle binden

# Vault-Namespaces zur Isolierung verwenden
namespace "team-a" {
  path "secret/*" {
    capabilities = ["read", "list", "create", "update"]
  }
}

10 Häufige Fehlerbehebung

1. failed to unseal: no private key found

# Prüfen, ob der Controller den Hauptschlüssel hält
kubectl get secrets -n kube-system -l sealedsecrets.bitnami.com/sealed-secrets-key

# Hauptschlüssel wiederherstellen
kubectl apply -f sealed-secrets-key-backup.yaml -n kube-system
kubectl rollout restart deployment/sealed-secrets-controller -n kube-system

2. ExternalSecret "not ready": could not get secret data

# SecretStore-Verbindung prüfen
kubectl describe secretstore vault-backend -n production

# Vault-Authentifizierung prüfen
kubectl logs -n external-secrets -l app.kubernetes.io/name=external-secrets

# ServiceAccount-Berechtigungen überprüfen
kubectl auth can-i get secret -n production \
  --as=system:serviceaccount:production:external-secrets-sa

3. sops error decrypting: could not find a matching key

# Prüfen, ob der Age-Schlüssel korrekt ist
age-keygen -y age.key

# Schlüsselkonfiguration in .sops.yaml überprüfen
cat .sops.yaml

# Schlüssel für Entschlüsselung explizit angeben
sops --decrypt --age age1YOURKEY... secret.yaml

4. SealedSecret "sealed-secrets" is invalid: metadata.name

# Namespace und Namen während der Verschlüsselung überprüfen
# Strict-Modus erfordert exakte Übereinstimmung
kubeseal --scope strict --namespace production \
  --name db-credentials < secret.yaml > sealed.yaml

5. Vault seal status: sealed

# Vault-Status prüfen
vault status

# Vault entsiegeln (erfordert 2 von 3 Unseal-Schlüsseln)
vault operator unseal <key1>
vault operator unseal <key2>

# Auto-Unseal-Konfiguration (für Produktion empfohlen)
# AWS KMS / GCP KMS / Azure Key Vault für Auto-Unseal verwenden

6. refreshInterval: cannot unmarshal

# Sicherstellen, dass das refreshInterval-Format korrekt ist
# Korrekt: "15m", "1h", "24h"
# Falsch: 15, "15", "15minutes"
spec:
  refreshInterval: 15m

7. kubeseal: error: failed to get certificate

# Prüfen, ob der Controller läuft
kubectl get deployment sealed-secrets-controller -n kube-system

# Zertifikats-Secret prüfen
kubectl get secret -n kube-system sealed-secrets-key

# Öffentlichen Schlüssel vom Controller abrufen
kubeseal --fetch-cert > sealed-secrets-cert.pem
kubeseal --cert sealed-secrets-cert.pem < secret.yaml

8. ExternalSecret status: SecretSyncedError

# Detaillierten Fehler anzeigen
kubectl describe externalsecret db-credentials -n production

# Häufige Ursache: Vault-Pfad existiert nicht
# Pfade in Vault prüfen
vault kv list secret/production/

# Häufige Ursache: unzureichende Berechtigungen
vault policy read eso-role

9. SOPS: mac mismatch: file has been modified

# Datei wurde nach der Verschlüsselung manuell geändert, MAC-Verifizierung fehlgeschlagen
# Sicherer Ansatz: neu verschlüsseln
sops --decrypt secret.yaml > secret_plain.yaml
# Ändern und dann neu verschlüsseln
sops --encrypt --age age1YOURKEY... secret_plain.yaml > secret.yaml

10. Kubernetes: secret "xxx" not found

# ESO hat die Synchronisation noch nicht abgeschlossen
kubectl get externalsecret -n production

# ESO-Controller-Logs prüfen
kubectl logs -n external-secrets -l app.kubernetes.io/name=external-secrets \
  --tail=100

# Synchronisation manuell auslösen
kubectl annotate externalsecret db-credentials \
  force-sync=$(date +%s) -n production \
  --overwrite

Erweiterte Optimierungstipps

1. Git Pre-commit-Hook zur Verhinderung von Klartext-Secret-Commits

#!/bin/bash
# .git/hooks/pre-commit
# Gestagte Dateien nach verdächtigen Secrets durchsuchen

STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.yaml$|\.yml$|\.env$')

for FILE in $STAGED_FILES; do
  # Base64-kodiertes K8s-Secret erkennen
  if grep -qE 'kind:\s*Secret' "$FILE" 2>/dev/null; then
    if ! grep -qE 'kind:\s*SealedSecret|encryptedData|sops:' "$FILE" 2>/dev/null; then
      echo "FEHLER: Unverschlüsselte Secret-Datei gefunden: $FILE"
      echo "Bitte vor dem Committen mit kubeseal oder sops verschlüsseln"
      exit 1
    fi
  fi

  # Häufige Secret-Muster erkennen
  if grep -qiE '(password|secret|api.key|token)\s*[:=]\s*["\x27]?[A-Za-z0-9+/=]{16,}' "$FILE" 2>/dev/null; then
    if ! grep -qE 'ENC\[|sops:|encryptedData' "$FILE" 2>/dev/null; then
      echo "WARNUNG: Verdächtiges Klartext-Secret in: $FILE"
      echo "Bitte Verschlüsselung bestätigen oder externes Secret-Management verwenden"
    fi
  fi
done

exit 0

2. ArgoCD Secret-Management-Integration

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: app-with-secrets
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/myorg/gitops-manifests.git
    targetRevision: main
    path: overlays/production
  destination:
    server: https://kubernetes.default.svc
    namespace: production
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true

3. Secret-Template-Engine

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: app-config
  namespace: production
spec:
  refreshInterval: 15m
  secretStoreRef:
    name: vault-backend
    kind: SecretStore
  target:
    name: app-config
    template:
      type: Opaque
      engineVersion: v2
      data:
        DATABASE_URL: "postgresql://{{ .db_user }}:{{ .db_pass }}@{{ .db_host }}:5432/{{ .db_name }}?sslmode=require"
        REDIS_URL: "redis://:{{ .redis_pass }}@{{ .redis_host }}:6379/0"
        JWT_SECRET: "{{ .jwt_secret }}"
        CONFIG_JSON: |
          {
            "database": {
              "host": "{{ .db_host }}",
              "port": 5432,
              "name": "{{ .db_name }}"
            },
            "redis": {
              "host": "{{ .redis_host }}"
            }
          }
  data:
    - secretKey: db_user
      remoteRef:
        key: secret/data/production/database
        property: username
    - secretKey: db_pass
      remoteRef:
        key: secret/data/production/database
        property: password
    - secretKey: db_host
      remoteRef:
        key: secret/data/production/database
        property: host
    - secretKey: db_name
      remoteRef:
        key: secret/data/production/database
        property: dbname
    - secretKey: redis_pass
      remoteRef:
        key: secret/data/production/redis
        property: password
    - secretKey: redis_host
      remoteRef:
        key: secret/data/production/redis
        property: host
    - secretKey: jwt_secret
      remoteRef:
        key: secret/data/production/auth
        property: jwt_secret

4. Secret-Gesundheitsprüfung

apiVersion: batch/v1
kind: CronJob
metadata:
  name: secret-health-check
  namespace: security
spec:
  schedule: "0 8 * * 1"
  jobTemplate:
    spec:
      template:
        spec:
          serviceAccountName: secret-checker
          containers:
            - name: checker
              image: bitnami/kubectl:1.30
              command:
                - /bin/bash
                - -c
                - |
                  echo "=== Secret-Gesundheitsprüfung $(date) ==="
                  echo ""
                  echo "--- Ablaufende TLS-Zertifikate prüfen ---"
                  kubectl get secrets --all-namespaces \
                    -o json | jq -r '.items[] |
                    select(.type=="kubernetes.io/tls") |
                    "\(.metadata.namespace)/\(.metadata.name)"' | \
                  while read secret; do
                    ns=$(echo $secret | cut -d/ -f1)
                    name=$(echo $secret | cut -d/ -f2)
                    cert=$(kubectl get secret $name -n $ns \
                      -o jsonpath='{.data.tls\.crt}' | base64 -d)
                    expiry=$(echo "$cert" | openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
                    if [ -n "$expiry" ]; then
                      days=$(( ($(date -d "$expiry" +%s) - $(date +%s)) / 86400 ))
                      if [ $days -lt 30 ]; then
                        echo "WARNUNG: Zertifikat $secret läuft in ${days} Tagen ab"
                      fi
                    fi
                  done
                  echo ""
                  echo "--- ExternalSecret-Synchronisationsstatus prüfen ---"
                  kubectl get externalsecrets --all-namespaces \
                    -o json | jq -r '.items[] |
                    select(.status.conditions[]?.type=="Ready" and
                           .status.conditions[]?.status!="True") |
                    "\(.metadata.namespace)/\(.metadata.name): NICHT BEREIT"'
          restartPolicy: OnFailure

Vergleich: Sealed Secrets vs External Secrets vs SOPS

Dimension Sealed Secrets External Secrets Operator SOPS
Verschlüsselung Asymmetrisch (Cluster-Pubkey) Keine Git-Inhaltsverschlüsselung Symmetrisch/Asymmetrisch
Git-Inhalt Chiffretext (SealedSecret) Referenzen (ExternalSecret) Chiffretext (Werte verschlüsselt)
Externe Abhängigkeiten Keine (autark) Vault/AWS/GCP usw. Keine (autark)
Secret-Rotation Neuverschlüsselung erforderlich Native Unterstützung Neuverschlüsselung erforderlich
Multi-Cluster Hauptschlüssel synchronisieren SecretStore pro Cluster Verschlüsselungsschlüssel teilen
Git-Diff-freundlich Nein (Chiffretext unlesbar) Ja (Referenzen lesbar) Ja (Schlüssel lesbar, Werte verschlüsselt)
Offline-Entschlüsselung Nein (benötigt Cluster-Privkey) Nein (benötigt externen Dienst) Ja (lokaler Schlüssel ausreichend)
Lernkurve Niedrig Mittel Mittel
Betriebskomplexität Niedrig Hoch (Vault erforderlich) Niedrig
Audit-Fähigkeit Git-Verlauf Vault-Audit-Logs Git-Verlauf
Dynamische Secrets Nicht unterstützt Unterstützt (Vault dynamische Credentials) Nicht unterstützt
Skalierungsfit Klein-mittlere Teams Mittel-große Unternehmen Jede Skala
Am besten für Einfaches GitOps Unternehmens-Secret-Management Multi-Format-Verschlüsselung

Auswahl-Entscheidungsbaum

Haben Sie bereits Vault/AWS Secrets Manager?
├── Ja → External Secrets Operator
│        └── Dynamische Secrets benötigt? → ESO + Vault dynamische Credentials
└── Nein → Verschlüsselten Chiffretext in Git speichern?
         ├── Ja → Git-Diff-freundlich?
         │        ├── Ja → SOPS + Age
         │        └── Nein → Sealed Secrets
         └── Nein → Dynamische Secrets benötigt?
                  ├── Ja → Vault + ESO bereitstellen
                  └── Nein → Sealed Secrets

Empfohlene Online-Tools

  • Base64-Kodierer/Dekodierer: /de/encode/base64 — K8s-Secret- und SealedSecret-Daten kodieren/dekodieren
  • RSA-Schlüsselgenerator: /de/encode/rsa — RSA-Schlüsselpaare für SOPS-GPG-Verschlüsselung generieren
  • Hash-Rechner: /de/encode/hash — Secret-Fingerabdrücke und Prüfsummen berechnen

Zusammenfassung: Es gibt keine Silberkugel für DevOps GitOps Secret-Management, aber es gibt einen Best-Practice-Pfad. Kleine Teams beginnen mit Sealed Secrets — null externe Abhängigkeiten für GitOps-Secret-Sicherheit. Mittel-große Unternehmen wählen External Secrets Operator + Vault mit nativer Secret-Rotation und dynamischen Credentials. Wenn Sie Multi-Format-Verschlüsselung und Git-Diff-Freundlichkeit benötigen, ist SOPS + Age die beste Wahl. Die Kernprinzipien 2026: null Klartextspeicherung, automatische Rotation, auditierbar und nachverfolgbar. Denken Sie daran — Secret-Lecks sind keine Frage des "Ob", sondern des "Wann".


Verwandte Beiträge:

Externe Referenzen:

Probiere diese browser-lokalen Tools aus — keine Registrierung erforderlich →

#DevOps#GitOps#密钥管理#Sealed Secrets#External Secrets#SOPS#2026#DevOps