Loading...
Alle Artikel
Security · 7 min read

Secrets Management mit HashiCorp Vault in Kubernetes

Ein praxisorientierter Leitfaden zum Deployment von HashiCorp Vault auf Kubernetes, zur Konfiguration dynamischer Secrets, zur Integration mit Anwendungen über den Vault Agent Injector und zur Umsetzung von Best Practices für produktives Secrets-Management.

Das Secrets-Problem in Kubernetes

Jede Anwendung braucht Secrets – Datenbank-Passwörter, API-Keys, TLS-Zertifikate, Encryption-Keys. In Kubernetes besteht der Default-Ansatz darin, diese in Kubernetes Secrets abzulegen, die base64-kodiert (nicht verschlüsselt) und für jeden mit RBAC-Zugang zum Namespace zugänglich sind.

Das ist keine Sicherheit. Das ist Sicherheitstheater.

Gängige Anti-Patterns, die wir in Produktionsumgebungen sehen:

  • Secrets fest einkodiert in Container-Images – in jedem Registry und Build-Log sichtbar
  • Secrets in Environment-Variablen – sichtbar in der Ausgabe von kubectl describe pod
  • Secrets in Git eingecheckt – selbst „private" Repos werden kompromittiert
  • Gemeinsam genutzte statische Credentials – keine Rotation, kein Audit Trail, kein Ablauf

HashiCorp Vault löst diese Probleme durch zentralisiertes Secrets-Management mit dynamischer Credential-Generierung, automatischer Rotation, detailliertem Audit-Logging und feingranularer Zugriffssteuerung.

Schritt 1: Vault auf Kubernetes mit Helm deployen

Der empfohlene Ansatz ist, Vault im Hochverfügbarkeitsmodus mit integriertem Raft-Storage zu betreiben:

# Add the HashiCorp Helm repository
helm repo add hashicorp https://helm.releases.hashicorp.com
helm repo update

# Create a dedicated namespace
kubectl create namespace vault

# Install Vault with HA configuration
helm install vault hashicorp/vault \
  --namespace vault \
  --set server.ha.enabled=true \
  --set server.ha.replicas=3 \
  --set server.ha.raft.enabled=true \
  --set server.dataStorage.size=10Gi \
  --set server.dataStorage.storageClass=gp3 \
  --set server.auditStorage.enabled=true \
  --set server.auditStorage.size=10Gi \
  --set injector.enabled=true \
  --set ui.enabled=true

Nach dem Deployment initialisieren und unsealen Sie den ersten Vault-Pod:

# Initialize Vault with 5 key shares, 3 required to unseal
kubectl exec -n vault vault-0 -- vault operator init \
  -key-shares=5 \
  -key-threshold=3 \
  -format=json > vault-init.json

# Unseal the first pod (repeat with 3 different keys)
kubectl exec -n vault vault-0 -- vault operator unseal <key-1>
kubectl exec -n vault vault-0 -- vault operator unseal <key-2>
kubectl exec -n vault vault-0 -- vault operator unseal <key-3>

# Join the other pods to the Raft cluster
kubectl exec -n vault vault-1 -- vault operator raft join \
  http://vault-0.vault-internal:8200

kubectl exec -n vault vault-2 -- vault operator raft join \
  http://vault-0.vault-internal:8200

# Unseal the remaining pods
# (In production, use auto-unseal with AWS KMS, GCP KMS, or Azure Key Vault)

Für produktive Deployments konfigurieren Sie immer Auto-Unseal, um manuelle Eingriffe nach Pod-Neustarts zu vermeiden:

# vault-config.hcl
seal "awskms" {
  region     = "us-east-1"
  kms_key_id = "alias/vault-unseal-key"
}

Schritt 2: Kubernetes-Authentifizierung konfigurieren

Vault muss verifizieren können, dass Pods, die Secrets anfordern, auch die sind, die sie vorgeben zu sein. Die Kubernetes-Auth-Methode nutzt Service-Account-Tokens zur Authentifizierung:

# Login to Vault
export VAULT_ADDR="http://127.0.0.1:8200"
vault login <root-token>

# Enable Kubernetes auth method
vault auth enable kubernetes

# Configure it to communicate with the Kubernetes API
vault write auth/kubernetes/config \
  kubernetes_host="https://kubernetes.default.svc:443"

Erstellen Sie eine Policy, die definiert, auf welche Secrets ein Service zugreifen darf:

# payment-service-policy.hcl
path "secret/data/payment-service/*" {
  capabilities = ["read"]
}

path "database/creds/payment-service-role" {
  capabilities = ["read"]
}

path "pki/issue/payment-service" {
  capabilities = ["create", "update"]
}

Wenden Sie die Policy an und binden Sie sie an einen Kubernetes-Service-Account:

# Write the policy
vault policy write payment-service payment-service-policy.hcl

# Create a role that maps Kubernetes SA to Vault policy
vault write auth/kubernetes/role/payment-service \
  bound_service_account_names=payment-service \
  bound_service_account_namespaces=production \
  policies=payment-service \
  ttl=1h

Schritt 3: Dynamische Datenbank-Credentials umsetzen

Statische Datenbank-Passwörter sind ein Risiko – sie laufen nie ab, werden über Umgebungen hinweg geteilt und sind ohne Downtime kaum rotierbar. Vaults Database Secrets Engine generiert einzigartige, kurzlebige Credentials on demand.

# Enable the database secrets engine
vault secrets enable database

# Configure the PostgreSQL connection
vault write database/config/payments-db \
  plugin_name=postgresql-database-plugin \
  allowed_roles="payment-service-role" \
  connection_url="postgresql://{{username}}:{{password}}@payments-db.production.svc:5432/payments?sslmode=require" \
  username="vault_admin" \
  password="initial-setup-password"

# Rotate the root password so only Vault knows it
vault write -force database/rotate-root/payments-db

# Create a role that generates credentials with a 1-hour TTL
vault write database/roles/payment-service-role \
  db_name=payments-db \
  creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
  revocation_statements="REVOKE ALL ON ALL TABLES IN SCHEMA public FROM \"{{name}}\"; DROP ROLE IF EXISTS \"{{name}}\";" \
  default_ttl="1h" \
  max_ttl="24h"

Jeder Pod-Start bekommt nun ein einzigartiges Datenbank-Credential, das automatisch abläuft. Wenn ein Credential kompromittiert wird, ist der Blast-Radius auf einen Pod und eine Stunde beschränkt.

Schritt 4: Secrets mit dem Vault Agent in Pods injizieren

Der Vault Agent Injector nutzt Kubernetes Mutating Webhooks, um Secrets automatisch über Annotationen in Pods zu injizieren. Keine Änderungen am Anwendungscode erforderlich.

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: payment-service
  namespace: production
spec:
  replicas: 3
  selector:
    matchLabels:
      app: payment-service
  template:
    metadata:
      labels:
        app: payment-service
      annotations:
        vault.hashicorp.com/agent-inject: "true"
        vault.hashicorp.com/role: "payment-service"

        # Static secrets
        vault.hashicorp.com/agent-inject-secret-config: "secret/data/payment-service/config"
        vault.hashicorp.com/agent-inject-template-config: |
          {{- with secret "secret/data/payment-service/config" -}}
          export STRIPE_API_KEY="{{ .Data.data.stripe_api_key }}"
          export WEBHOOK_SECRET="{{ .Data.data.webhook_secret }}"
          {{- end }}

        # Dynamic database credentials
        vault.hashicorp.com/agent-inject-secret-db: "database/creds/payment-service-role"
        vault.hashicorp.com/agent-inject-template-db: |
          {{- with secret "database/creds/payment-service-role" -}}
          export DB_USERNAME="{{ .Data.username }}"
          export DB_PASSWORD="{{ .Data.password }}"
          {{- end }}

        # Auto-renew credentials before they expire
        vault.hashicorp.com/agent-cache-enable: "true"
        vault.hashicorp.com/agent-cache-listener-port: "8200"
    spec:
      serviceAccountName: payment-service
      containers:
        - name: payment-service
          image: myorg/payment-service:v1.2.3
          command: ["/bin/sh", "-c"]
          args:
            - source /vault/secrets/config &&
              source /vault/secrets/db &&
              /app/payment-service
          ports:
            - containerPort: 8080
          resources:
            limits:
              cpu: 500m
              memory: 256Mi
            requests:
              cpu: 100m
              memory: 128Mi

Der Vault-Agent-Sidecar kümmert sich um Authentifizierung, Secret-Abruf und automatische Erneuerung – Ihre Anwendung liest einfach Dateien oder Environment-Variablen.

Schritt 5: Alternative mit dem External Secrets Operator

Für Teams, die einen Kubernetes-nativen Workflow bevorzugen, synchronisiert der External Secrets Operator (ESO) Vault-Secrets automatisch in Kubernetes Secrets:

# secret-store.yaml
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: vault-backend
spec:
  provider:
    vault:
      server: "http://vault.vault.svc:8200"
      path: "secret"
      version: "v2"
      auth:
        kubernetes:
          mountPath: "kubernetes"
          role: "external-secrets"
          serviceAccountRef:
            name: external-secrets
            namespace: external-secrets

---
# external-secret.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: payment-service-secrets
  namespace: production
spec:
  refreshInterval: 5m
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore
  target:
    name: payment-service-secrets
    creationPolicy: Owner
  data:
    - secretKey: stripe-api-key
      remoteRef:
        key: secret/data/payment-service/config
        property: stripe_api_key
    - secretKey: webhook-secret
      remoteRef:
        key: secret/data/payment-service/config
        property: webhook_secret

Dieser Ansatz eignet sich gut, wenn Ihre Anwendungen bereits aus Kubernetes Secrets lesen und Sie Vault als Quelle der Wahrheit einführen wollen, ohne Anwendungscode zu ändern.

Schritt 6: Audit-Logging aktivieren

In regulierten Umgebungen müssen Sie nachweisen können, wer wann auf welche Secrets zugegriffen hat. Aktivieren Sie Vaults Audit-Log:

# Enable file-based audit logging
vault audit enable file file_path=/vault/audit/audit.log

# For production, ship logs to your SIEM
# Example: enable syslog backend
vault audit enable syslog tag="vault" facility="AUTH"

Jede Vault-Operation – Reads, Writes, Authentifizierungsversuche, Policy-Änderungen – wird zusammen mit dem vollständigen Request und Response (sensible Werte HMAC-gehasht) aufgezeichnet. Leiten Sie diese Logs an Ihr SIEM (Splunk, Elastic oder Datadog) weiter, um auf verdächtige Zugriffsmuster zu alarmieren.

Checkliste Production-Hardening

Bevor Sie in Produktion gehen, verifizieren Sie diese Punkte:

  • Auto-Unseal konfiguriert mit Cloud-KMS (niemals Unseal-Keys auf Disk speichern)
  • TLS aktiviert für jede Vault-Kommunikation (mTLS bevorzugt)
  • Audit-Logging aktiviert und an SIEM weitergeleitet
  • Root-Token widerrufen nach initialem Setup (Identity-basierte Auth nutzen)
  • Namespaces konfiguriert, um Teams zu isolieren (Vault Enterprise)
  • Disaster-Recovery-Replikation über Regionen hinweg konfiguriert
  • Backup und Restore getestet mit Raft-Snapshots
  • Sentinel-Policies, die organisatorische Regeln erzwingen (Vault Enterprise)
  • Resource Limits gesetzt auf Vault-Pods, um Noisy-Neighbor-Probleme zu vermeiden
  • Network Policies, die einschränken, welche Pods Vault erreichen dürfen
# Take a Raft snapshot for backup
vault operator raft snapshot save /tmp/vault-backup.snap

# Verify the snapshot
vault operator raft snapshot inspect /tmp/vault-backup.snap

# Automate daily backups with a CronJob
# vault-backup-cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: vault-backup
  namespace: vault
spec:
  schedule: "0 2 * * *"
  jobTemplate:
    spec:
      template:
        spec:
          serviceAccountName: vault-backup
          containers:
            - name: backup
              image: hashicorp/vault:1.15
              command:
                - /bin/sh
                - -c
                - |
                  vault operator raft snapshot save /tmp/vault-$(date +%Y%m%d).snap
                  aws s3 cp /tmp/vault-$(date +%Y%m%d).snap \
                    s3://my-vault-backups/$(date +%Y%m%d).snap
              env:
                - name: VAULT_ADDR
                  value: "http://vault-active.vault.svc:8200"
                - name: VAULT_TOKEN
                  valueFrom:
                    secretKeyRef:
                      name: vault-backup-token
                      key: token
          restartPolicy: OnFailure

Fazit

Secrets-Management ist nicht optional – es ist eine fundamentale Sicherheitsanforderung, die sich nur schwerer nachrüsten lässt, je größer Ihre Infrastruktur wird. HashiCorp Vault, sauber auf Kubernetes deployt, bietet dynamische Secrets, automatische Rotation, feingranulare Zugriffskontrolle und umfassendes Audit-Logging – Dinge, die statische Secrets schlicht nicht leisten können.

Bei DevOpsVibe haben wir Vault über Dutzende von Kubernetes-Clustern hinweg deployt und betrieben – für Organisationen vom Start-up bis zum Enterprise. Ob Sie Hilfe beim initialen Deployment, bei der Migration von statischen Secrets oder bei der Integration in Ihre bestehenden CI/CD-Pipelines benötigen – unser Team bringt Sie in Wochen, nicht Monaten, zu produktivem Secrets-Management. Sichern Sie Ihre Infrastruktur noch heute.

abgelegt unter
vaultvaultsecrets-managementkuberneteskubernetessecurityhashicorpdevops
mit uns arbeiten

Soll unser Team Ihrer Infrastruktur helfen?

talk to an engineerFree 30-min discovery callBook
close