Veröffentlicht am

External Secrets: Kubernetes-Secrets sicher verwalten

Teilen:
Authors

TL;DR

Der External Secrets Operator (ESO) holt Secrets aus externen Quellen wie AWS Secrets Manager, HashiCorp Vault oder Azure Key Vault und erstellt daraus native Kubernetes-Secrets. Die Synchronisierung läuft automatisch, Rotation wird ohne Pod-Restart möglich. Zwei CRDs reichen: SecretStore für die Verbindung, ExternalSecret für das Mapping.


Secrets gehören nicht ins Cluster

Kubernetes-Secrets sind Base64-kodiert, nicht verschlüsselt. Wer Secrets direkt in Manifesten oder Git speichert, hat ein Sicherheitsproblem. Der External Secrets Operator löst das: Secrets bleiben in einem dedizierten Secret-Manager, Kubernetes bekommt nur eine Referenz.

External Secrets Operator installieren

Die Installation läuft über Helm:

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

Prüfe, ob die Pods laufen:

kubectl get pods -n external-secrets
kubectl get crd | grep external-secrets

Du solltest drei CRDs sehen: secretstores, clustersecretstores und externalsecrets.

Architektur im Überblick

Der Operator arbeitet mit zwei Haupt-Ressourcen:

RessourceScopeAufgabe
SecretStoreNamespaceVerbindung zum externen Provider (Credentials, Endpoint)
ClusterSecretStoreCluster-weitWie SecretStore, aber namespace-übergreifend nutzbar
ExternalSecretNamespaceDefiniert welche Secrets geholt und wie gemappt werden

Der Ablauf: ExternalSecret referenziert einen SecretStore, der Operator holt den Wert vom Provider und erstellt ein Kubernetes-Secret. Bei Änderungen im Provider wird das Secret automatisch aktualisiert.

Provider 1: AWS Secrets Manager

Für AWS brauchst du IAM-Credentials mit Zugriff auf Secrets Manager.

SecretStore konfigurieren

# aws-secret-store.yaml
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: aws-secrets-manager
  namespace: production
spec:
  provider:
    aws:
      service: SecretsManager
      region: eu-central-1
      auth:
        secretRef:
          accessKeyIDSecretRef:
            name: aws-credentials
            key: access-key-id
          secretAccessKeySecretRef:
            name: aws-credentials
            key: secret-access-key

Das Bootstrap-Secret mit den AWS-Credentials muss einmalig manuell erstellt werden:

kubectl create secret generic aws-credentials \
  --namespace production \
  --from-literal=access-key-id=AKIAIOSFODNN7EXAMPLE \
  --from-literal=secret-access-key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

kubectl apply -f aws-secret-store.yaml

ExternalSecret anlegen

# aws-external-secret.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: database-credentials
  namespace: production
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: aws-secrets-manager
    kind: SecretStore
  target:
    name: db-credentials
    creationPolicy: Owner
  data:
    - secretKey: username
      remoteRef:
        key: prod/database
        property: username
    - secretKey: password
      remoteRef:
        key: prod/database
        property: password

Nach dem Apply erstellt der Operator ein Kubernetes-Secret namens db-credentials:

kubectl apply -f aws-external-secret.yaml

# Status prüfen
kubectl get externalsecret -n production
# STATUS sollte "SecretSynced" sein

# Erstelltes Secret ansehen
kubectl get secret db-credentials -n production -o jsonpath='{.data.username}' | base64 -d

Provider 2: HashiCorp Vault

Vault ist der Klassiker für Secrets Management. Der ESO unterstützt sowohl KV v1 als auch v2.

SecretStore für Vault

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

Die Kubernetes-Auth-Methode ist die sauberste Variante — kein statisches Token nötig. Vault authentifiziert den ServiceAccount direkt:

# Vault-seitig: Kubernetes Auth konfigurieren
vault auth enable kubernetes

vault write auth/kubernetes/config \
  kubernetes_host="https://kubernetes.default.svc:443"

vault write auth/kubernetes/role/external-secrets \
  bound_service_account_names=external-secrets-sa \
  bound_service_account_namespaces=production \
  policies=readonly-secrets \
  ttl=1h

# ServiceAccount im Cluster erstellen
kubectl create serviceaccount external-secrets-sa -n production
kubectl apply -f vault-secret-store.yaml

ExternalSecret für Vault

# vault-external-secret.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: api-keys
  namespace: production
spec:
  refreshInterval: 30m
  secretStoreRef:
    name: vault-store
    kind: SecretStore
  target:
    name: api-keys
  data:
    - secretKey: stripe-key
      remoteRef:
        key: secret/data/production/api-keys
        property: stripe
    - secretKey: sendgrid-key
      remoteRef:
        key: secret/data/production/api-keys
        property: sendgrid

Provider 3: Azure Key Vault

Für Azure-Umgebungen bindet der ESO direkt an Key Vault an:

# azure-secret-store.yaml
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: azure-keyvault
  namespace: production
spec:
  provider:
    azurekv:
      tenantId: "00000000-0000-0000-0000-000000000000"
      vaultUrl: "https://mein-keyvault.vault.azure.net"
      authSecretRef:
        clientId:
          name: azure-credentials
          key: client-id
        clientSecret:
          name: azure-credentials
          key: client-secret

Das ExternalSecret folgt dem gleichen Muster — nur der remoteRef.key entspricht dem Secret-Namen in Azure:

# azure-external-secret.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: azure-db-secret
  namespace: production
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: azure-keyvault
    kind: SecretStore
  target:
    name: db-connection-string
  data:
    - secretKey: connection-string
      remoteRef:
        key: production-db-connection

Secret-Rotation in der Praxis

Der eigentliche Vorteil von External Secrets: automatische Rotation. Das refreshInterval steuert, wie oft der Operator den externen Provider abfragt. Ändert sich der Wert dort, wird das Kubernetes-Secret aktualisiert.

Damit Pods das neue Secret ohne Restart erhalten, gibt es zwei Ansätze:

# Option 1: Reloader-Annotation (erfordert stakater/Reloader)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  annotations:
    reloader.stakater.com/auto: "true"
spec:
  template:
    spec:
      containers:
        - name: app
          envFrom:
            - secretRef:
                name: db-credentials
# Option 2: Rollout manuell triggern nach Rotation
kubectl rollout restart deployment/myapp -n production

Troubleshooting

Wenn ein ExternalSecret nicht synchronisiert:

# Status und Events prüfen
kubectl describe externalsecret database-credentials -n production

# Operator-Logs anschauen
kubectl logs -n external-secrets -l app.kubernetes.io/name=external-secrets

# SecretStore-Verbindung testen
kubectl get secretstore -n production
# STATUS: Valid = Verbindung funktioniert

FAQ

Was passiert, wenn der externe Provider nicht erreichbar ist?

Das bestehende Kubernetes-Secret bleibt erhalten. Der Operator versucht bei jedem refreshInterval erneut zu synchronisieren. Events am ExternalSecret zeigen den Fehler an.

Kann ich ein Secret in mehrere Namespaces synchronisieren?

Ja, verwende einen ClusterSecretStore statt eines SecretStore. Dann können ExternalSecrets aus beliebigen Namespaces darauf zugreifen. Alternativ: ein ExternalSecret pro Namespace, das denselben Store referenziert.

Ist External Secrets Operator Production-ready?

Ja, ESO ist ein CNCF-Projekt im Incubating-Status und wird von zahlreichen Unternehmen in Production eingesetzt. Die API ist seit v1beta1 stabil.

Wie unterscheidet sich ESO von Sealed Secrets?

Sealed Secrets verschlüsselt Secrets für Git-Storage — der Wert lebt im Repository. ESO holt Secrets zur Laufzeit aus einem externen Provider. ESO ist die bessere Wahl, wenn du bereits einen Secret-Manager betreibst und Rotation brauchst.


Nächster Schritt: Kombiniere External Secrets mit dem Trivy Operator, um neben Secrets auch Container-Images automatisiert auf Schwachstellen zu prüfen.

Kubernetes-Security & Compliance?

Security-Audits, Penetration Tests und Compliance-Beratung für Ihre Container-Infrastruktur. BSI-Grundschutz, DSGVO, ISO 27001.

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