Secrets Management

1. Einführung – Warum Secrets Management?

In nahezu jedem modernen Softwareprojekt gibt es sie: Passwörter, API-Keys, Zertifikate, Datenbankverbindungsstrings, OAuth-Tokens und andere sensible Werte. Diese Informationen – zusammenfassend als Secrets bezeichnet – müssen irgendwo gespeichert werden, damit Anwendungen und Dienste zur Laufzeit darauf zugreifen können. Das Problem ist so alt wie die Softwareentwicklung selbst, wird aber mit zunehmender Automatisierung, Containerisierung und CI/CD-Pipelines immer relevanter.

Der klassische Fehler: Secrets landen direkt im Quellcode oder in Konfigurationsdateien, die in Git eingecheckt werden. Ein öffentliches GitHub-Repository mit einem hartcodierten AWS-Access-Key ist keine Seltenheit – die Folgen reichen von unerwarteten Cloud-Rechnungen in fünfstelliger Höhe bis hin zu vollständigen Datenpannen. Selbst in privaten Repositories ist das Speichern von Secrets in Git ein ernstes Risiko, weil der gesamte Git-Verlauf dauerhaft erhalten bleibt. Ein Secret, das einmal committed wurde, existiert in der Historie – auch nach einem späteren Löschen des Eintrags.

Umgebungsvariablen sind ein häufiger Ausweichweg. Sie sind besser als Klartext im Code, lösen das Problem aber nicht vollständig. In Container-Umgebungen landen Umgebungsvariablen häufig in Deployment-Manifesten (z. B. Kubernetes YAML-Dateien), die wiederum in Git versioniert werden. Außerdem sind Umgebungsvariablen oft für alle Prozesse eines Systems sichtbar, bieten keine Zugriffskontrolle, kein Audit-Log und keine automatische Rotation.

Secrets Management löst diese Probleme durch einen zentralisierten, kontrollierten und auditierbaren Umgang mit sensiblen Daten. Statt Secrets in Dateien oder Umgebungsvariablen zu verteilen, werden sie in einem dedizierten System gespeichert, das Zugriffskontrollen, Verschlüsselung, Ablaufdaten und Protokollierung bietet. Die Anwendung ruft das Secret zur Laufzeit ab – und nur wenn sie die nötige Berechtigung hat.

2. HashiCorp Vault

HashiCorp Vault ist eines der bekanntesten und mächtigsten Open-Source-Werkzeuge im Bereich Secrets Management. Es bietet eine zentrale Plattform zur sicheren Speicherung, zum Zugriff und zur Verwaltung von Secrets aller Art. Vault ist in Go geschrieben, läuft als einzelne Binary und ist für Produktionsumgebungen wie für den Homelab-Betrieb geeignet.

2.1 Architektur

Vault besteht aus mehreren zentralen Komponenten:

Das Storage Backend ist für die dauerhafte Speicherung der verschlüsselten Daten zuständig. Vault selbst speichert nichts im Klartext – alle Daten werden vor dem Schreiben verschlüsselt. Unterstützte Backends sind unter anderem Consul, PostgreSQL, S3 und das integrierte Raft-Backend, das für die meisten Installationen empfohlen wird.

Auth Methods steuern, wie sich Clients gegenüber Vault authentifizieren. Es gibt Methoden für Token-basierte Authentifizierung, AppRole (für Maschinen und CI-Systeme), Kubernetes, LDAP, GitHub und viele weitere. Ein Kubernetes-Pod kann sich beispielsweise automatisch über seinen Service-Account-Token bei Vault anmelden.

Secret Engines sind Plugins, die verschiedene Arten von Secrets verwalten. Die KV-Engine speichert beliebige Key-Value-Paare, die PKI-Engine stellt X.509-Zertifikate aus, die Datenbank-Engine generiert dynamische Zugangsdaten, und die Transit-Engine stellt kryptografische Operationen als Dienst bereit (Encryption as a Service).

2.2 Dev-Modus vs. Produktions-Setup

Für lokale Entwicklung und Tests bietet Vault einen Dev-Modus, der sich mit einem einzigen Befehl starten lässt:

vault server -dev

Im Dev-Modus ist Vault automatisch initialisiert und entsiegelt, verwendet In-Memory-Storage (alle Daten gehen beim Neustart verloren) und gibt einen Root-Token direkt auf der Konsole aus. Dieser Modus eignet sich ausschließlich für Experimente – niemals für produktive Workloads.

Ein Produktions-Setup benötigt hingegen ein persistentes Storage-Backend, eine sorgfältig konfigurierte TLS-Terminierung, einen dedizierten Initialisierungsprozess und ein durchdachtes Unseal-Verfahren.

2.3 Seal und Unseal

Vault startet in einem versiegelten (sealed) Zustand. Es kann zwar laufen, aber nicht auf die verschlüsselten Daten zugreifen, weil der Verschlüsselungsschlüssel nicht im Speicher liegt. Dieser Zustand schützt vor einem unbefugten Zugriff auf das Storage-Backend.

Beim ersten Start wird Vault mit folgendem Befehl initialisiert:

vault operator init -key-shares=5 -key-threshold=3

Dieser Befehl erzeugt fünf Unseal-Keys und einen initialen Root-Token. Drei der fünf Keys (entsprechend dem Schwellenwert) werden benötigt, um Vault zu entsiegeln. Dieses auf Shamir's Secret Sharing basierende Verfahren stellt sicher, dass kein einzelner Administrator allein Zugriff erlangen kann. Die Unseal-Keys müssen sicher und getrennt voneinander aufbewahrt werden. Für automatisiertes Unsealing in Produktionsumgebungen wird oft Auto-Unseal über Cloud-KMS-Dienste (AWS KMS, GCP Cloud KMS) oder HashiCorp's eigene Transit-Engine verwendet.

3. Vault KV Engine

Die KV-Engine (Key-Value) ist die einfachste und am häufigsten genutzte Secret Engine in Vault. Sie erlaubt das Speichern beliebiger Schlüssel-Wert-Paare unter einem definierten Pfad.

3.1 KV v1 vs. KV v2

Es gibt zwei Versionen der KV-Engine. KV v1 ist die ältere, einfachere Variante: Secrets werden gespeichert und beim Überschreiben vollständig ersetzt. Es gibt keine Versionsverwaltung, kein automatisches Aufbewahren älterer Werte und keine Metadaten zum Zeitpunkt der letzten Änderung.

KV v2 führt Versionierung ein. Jeder Schreibvorgang erzeugt eine neue Version des Secrets. Standardmäßig werden die letzten zehn Versionen behalten. Das ist besonders nützlich für Audits, Rollbacks und die Secret-Rotation: Wenn ein rotiertes Secret Probleme verursacht, lässt sich die vorherige Version schnell wiederherstellen.

Eine KV v2 Engine wird folgendermaßen aktiviert und befüllt:

# Engine aktivieren
vault secrets enable -path=secret kv-v2

# Secret schreiben
vault kv put secret/myapp/database \
  username="appuser" \
  password="s3cr3tP@ssw0rd"

# Secret lesen
vault kv get secret/myapp/database

# Bestimmte Version lesen
vault kv get -version=2 secret/myapp/database

# Metadaten anzeigen
vault kv metadata get secret/myapp/database

3.2 Policies und ACLs

Vault verwendet ein capabilities-basiertes Rechtesystem. Zugriffsrechte werden über Policies in HCL (HashiCorp Configuration Language) definiert. Eine Policy legt fest, welche Pfade ein Token lesen, schreiben oder verwalten darf.

# policy-myapp.hcl
path "secret/data/myapp/*" {
  capabilities = ["read", "list"]
}

path "secret/metadata/myapp/*" {
  capabilities = ["read", "list"]
}

# Policy in Vault einspielen
vault policy write myapp-readonly policy-myapp.hcl

# Token mit dieser Policy erstellen
vault token create -policy="myapp-readonly"

Das Prinzip der minimalen Rechtevergabe (Principle of Least Privilege) ist hier direkt umsetzbar: Jede Anwendung bekommt nur Zugriff auf die Pfade, die sie tatsächlich benötigt – lesend oder schreibend, je nach Bedarf.

4. Vault Dynamic Secrets

Eine der mächtigsten Funktionen von Vault ist die Generierung dynamischer Secrets. Statt eines statischen Datenbankpassworts, das manuell rotiert werden muss, generiert Vault auf Anfrage temporäre Zugangsdaten mit begrenzter Lebensdauer – direkt in der Zieldatenbank. Kein Passwort wird vorab gespeichert oder an irgendeiner Stelle dauerhaft hinterlegt.

4.1 PostgreSQL-Beispiel

Für PostgreSQL sieht das Setup folgendermaßen aus:

# Datenbank-Engine aktivieren
vault secrets enable database

# PostgreSQL-Verbindung konfigurieren
vault write database/config/mypostgres \
  plugin_name=postgresql-database-plugin \
  allowed_roles="readonly" \
  connection_url="postgresql://{{username}}:{{password}}@db.example.com:5432/mydb" \
  username="vault_admin" \
  password="admin_password"

# Rolle definieren: welche SQL-Statements beim Anlegen eines Users ausgeführt werden
vault write database/roles/readonly \
  db_name=mypostgres \
  creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' \
    VALID UNTIL '{{expiration}}'; \
    GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
  default_ttl="1h" \
  max_ttl="24h"

# Dynamische Zugangsdaten abrufen
vault read database/creds/readonly

Vault legt in PostgreSQL automatisch einen neuen Benutzer an, gibt Benutzername und Passwort zurück und löscht den Account nach Ablauf der TTL (Time-to-Live) wieder. Der anfragende Client bekommt einmalig gültige Zugangsdaten – und nur für die Dauer, die er tatsächlich benötigt.

4.2 TTL und Lease

Jedes dynamische Secret in Vault ist mit einem Lease verknüpft – einer Mietdauer, nach deren Ablauf das Secret ungültig wird. Leases können vor Ablauf erneuert (renew) oder vorzeitig widerrufen (revoke) werden. Das ist besonders wertvoll in Notfallsituationen: Wenn Zugangsdaten kompromittiert wurden, können alle aktiven Leases für eine Rolle mit einem einzigen Befehl widerrufen werden.

# Alle Leases einer Datenbankrolle widerrufen
vault lease revoke -prefix database/creds/readonly

5. SOPS (Secrets OPerationS)

SOPS ist ein von Mozilla entwickeltes Kommandozeilenwerkzeug zum Verschlüsseln von Dateien, die Secrets enthalten. Im Gegensatz zu Vault, das einen laufenden Dienst voraussetzt, arbeitet SOPS dateibasiert. Das macht es ideal für Konfigurationsmanagement, GitOps-Workflows und Umgebungen, in denen ein vollständiges Vault-Setup zu aufwändig wäre.

Die zentrale Idee: YAML-, JSON-, ENV- oder INI-Dateien werden teilweise verschlüsselt. SOPS verschlüsselt nur die Werte, nicht die Schlüssel. Das bedeutet, dass die Struktur einer Konfigurationsdatei im Repository lesbar bleibt – nur die sensiblen Werte sind chiffriert. Die verschlüsselten Dateien können sicher in Git eingecheckt werden.

5.1 SOPS mit age

age ist ein modernes, einfaches Verschlüsselungswerkzeug und die bevorzugte Wahl für neue SOPS-Setups. PGP wird weiterhin unterstützt, ist aber in der Schlüsselverwaltung deutlich komplexer. Mit age kommt man mit einem einzigen Schlüsselpaar aus.

# age-Schlüsselpaar generieren
age-keygen -o ~/.config/sops/age/keys.txt

# Öffentlichen Schlüssel anzeigen (wird in .sops.yaml eingetragen)
age-keygen -y ~/.config/sops/age/keys.txt

5.2 .sops.yaml Konfiguration

Die Datei .sops.yaml im Projektstamm steuert, welche Schlüssel für welche Dateien verwendet werden sollen. Über reguläre Ausdrücke lassen sich verschiedene Regeln für verschiedene Dateitypen oder Verzeichnisse definieren:

# .sops.yaml
creation_rules:
  - path_regex: secrets/.*\.yaml$
    age: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p

  - path_regex: .env\..*
    age: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p

5.3 Dateien verschlüsseln und entschlüsseln

Eine bestehende YAML-Datei mit Secrets wird folgendermaßen verschlüsselt:

# Ausgangsdatei secrets/database.yaml (Klartext)
# database:
#   host: db.example.com
#   username: appuser
#   password: s3cr3tP@ssw0rd

# Datei in-place verschlüsseln
sops --encrypt --in-place secrets/database.yaml

# Nach der Verschlüsselung: Schlüssel lesbar, Werte chiffriert
# database:
#     host: ENC[AES256_GCM,data:...,tag:...,type:str]
#     username: ENC[AES256_GCM,data:...,tag:...,type:str]
#     password: ENC[AES256_GCM,data:...,tag:...,type:str]

# Datei im Editor öffnen und bearbeiten (auto re-encrypt beim Speichern)
sops secrets/database.yaml

# Einzelnen Wert lesen
sops -d --extract '["database"]["password"]' secrets/database.yaml

# Gesamte Datei entschlüsselt ausgeben (stdout, kein Schreiben auf Disk)
sops -d secrets/database.yaml

# Als Umgebungsvariablen exportieren (.env-Datei)
export $(sops -d .env.encrypted | xargs)

SOPS öffnet beim direkten Aufruf ohne Flags die Datei im konfigurierten Editor (EDITOR-Variable). Beim Speichern wird automatisch re-verschlüsselt. Für CI/CD-Pipelines steht der -d-Flag (decrypt) zur Verfügung, der die Datei in den stdout dekodiert, ohne sie auf die Festplatte zu schreiben.

5.4 Entschlüsselung zur Laufzeit in CI/CD

In einer CI/CD-Pipeline wird der private age-Schlüssel als verschlüsselte Pipeline-Variable hinterlegt. Zur Laufzeit wird er in die Datei ~/.config/sops/age/keys.txt geschrieben, und SOPS kann die Secrets automatisch entschlüsseln. Da der Schlüssel selbst nie in Git landet, bleibt das gesamte Setup sicher. Ein Beispiel für GitHub Actions:

# .github/workflows/deploy.yml (Ausschnitt)
- name: Secrets entschlüsseln
  env:
    SOPS_AGE_KEY: ${{ secrets.AGE_PRIVATE_KEY }}
  run: |
    mkdir -p ~/.config/sops/age
    echo "$SOPS_AGE_KEY" > ~/.config/sops/age/keys.txt
    sops -d secrets/database.yaml > /tmp/database-decrypted.yaml

6. Vergleich Vault vs. SOPS

Vault und SOPS lösen dasselbe Grundproblem auf unterschiedliche Weise und sind für verschiedene Einsatzszenarien geeignet. Beide Werkzeuge schließen sich nicht gegenseitig aus – in größeren Umgebungen werden sie oft kombiniert eingesetzt.

6.1 HashiCorp Vault – Stärken und Einsatzszenarien

Vault ist die richtige Wahl, wenn es viele Anwendungen und Teams gibt, die zentral verwaltete Secrets benötigen. Wenn dynamische Secrets mit automatischer Rotation und Ablaufdaten gefordert sind, ist Vault unschlagbar. Weitere typische Vault-Szenarien sind: umfangreiches Audit-Log und Zugriffskontrolle auf Secret-Ebene, PKI und Zertifikatsmanagement, Encryption as a Service und native Kubernetes-Integration via Vault Agent Injector oder dem Vault Secrets Operator.

Der Nachteil ist der Betriebsaufwand: Vault muss hochverfügbar betrieben, regelmäßig aktualisiert und abgesichert werden. Ein Vault-Ausfall bedeutet, dass keine Anwendung mehr auf ihre Secrets zugreifen kann – sofern kein lokales Caching implementiert ist. Für ein einzelnes Projekt oder ein Homelab ist das oft zu viel Overhead.

6.2 SOPS – Stärken und Einsatzszenarien

SOPS ist die richtige Wahl, wenn Secrets in Git versioniert werden sollen – typischerweise in GitOps-Workflows, Helm-Charts oder Ansible-Playbooks. Es wird kein dauerhaft laufender Dienst benötigt, und die Einrichtung dauert wenige Minuten. Für Homelab-Projekte, kleine Teams oder einzelne Entwickler ist SOPS oft die pragmatischere Entscheidung.

SOPS hat keine dynamischen Secrets, kein natives Audit-Log und keine feingranulare Zugriffskontrolle. Wer den privaten age-Schlüssel besitzt, kann alle damit verschlüsselten Secrets entschlüsseln. Schlüsselrotation ist möglich, erfordert aber manuelles Re-Verschlüsseln aller betroffenen Dateien.

6.3 Entscheidungshilfe auf einen Blick

Für ein Homelab oder ein kleines Einzelprojekt ist SOPS mit age der pragmatische Einstieg: minimaler Aufwand, kein zusätzlicher Dienst, direkte Git-Integration. Wächst das Projekt und werden mehr Kontrolle, Auditing und dynamische Zugangsdaten notwendig, ist HashiCorp Vault der natürliche nächste Schritt. Wer früh auf Vault setzt, profitiert von einem zentralen Secret-Store für alle Dienste – zahlt dafür aber mit erhöhtem Betriebsaufwand.

7. Best Practices

Unabhängig davon, welche Lösung eingesetzt wird, gelten einige Grundregeln, die im Secrets Management immer beachtet werden sollten.

7.1 Rotation

Secrets sollten regelmäßig rotiert werden – idealerweise automatisiert. Bei Vault erledigt das die Dynamic Secrets Engine automatisch. Bei statischen Secrets (SOPS, KV) muss ein Rotationsprozess explizit geplant und dokumentiert werden. Die TTL sollte so kurz wie möglich und so lang wie nötig gewählt werden. Ein kompromittiertes Secret mit einer Lebensdauer von einer Stunde verursacht deutlich weniger Schaden als ein dauerhaft gültiges Passwort, das seit Jahren nicht rotiert wurde.

7.2 Audit-Logs aktivieren und auswerten

Vault bietet integriertes Audit-Logging. Jeder Zugriff auf ein Secret – lesend oder schreibend – wird protokolliert, inklusive des verwendeten Tokens, des Zeitstempels und des angefragten Pfads. Das Audit-Log sollte an ein zentrales Log-System weitergeleitet und auf ungewöhnliche Zugriffsmuster überwacht werden. Bei SOPS gibt es kein natives Audit-Log; hier hilft die Git-Historie zumindest für Änderungen an verschlüsselten Dateien.

# Vault Audit-Backend aktivieren (Datei-Log)
vault audit enable file file_path=/var/log/vault/audit.log

# Aktive Audit-Backends anzeigen
vault audit list

7.3 Principle of Least Privilege

Jede Anwendung, jeder Dienst und jeder Benutzer soll nur auf die Secrets zugreifen können, die für ihre jeweilige Aufgabe tatsächlich notwendig sind. In Vault wird das über Policies umgesetzt. Eine Anwendung, die nur eine Datenbankverbindung benötigt, bekommt ausschließlich Lesezugriff auf den entsprechenden Vault-Pfad – nicht auf den gesamten secret/-Namensraum. Diese Einschränkung begrenzt den Schaden bei einer Kompromittierung erheblich.

7.4 Secrets niemals in Logs schreiben

Ein häufig unterschätztes Risiko ist das versehentliche Ausgeben von Secrets in Logs. Das betrifft Datenbankverbindungsstrings in Exception-Stacktraces, Authorization-Header in HTTP-Request-Logs und Umgebungsvariablen in Deployment-Protokollen. Logging-Frameworks sollten so konfiguriert sein, dass bekannte Secret-Muster (Regex für API-Keys, Passwörter in URLs) automatisch maskiert oder durch Platzhalter ersetzt werden. Ein Secret, das einmal in einem zentralen Log-System landet, ist faktisch kompromittiert – selbst wenn der Zugriff auf Logs eingeschränkt ist.

7.5 Keine Secrets in Container-Images

Docker-Images werden gebaut, gepusht und oft in öffentlichen oder halb-öffentlichen Registries gespeichert. Secrets, die beim Build-Prozess in ein Image gelangt sind – auch in einem früheren Layer – können mit docker history oder durch direktes Inspizieren der Layer-Archive extrahiert werden. Secrets gehören niemals in ein Dockerfile oder den Build-Kontext eines Images. Stattdessen sollten sie zur Laufzeit injiziert werden – über Vault Agent, Kubernetes Secrets (idealerweise mit einem externen KMS verschlüsselt) oder zur Laufzeit entschlüsselte SOPS-Dateien.

7.6 Notfallplan dokumentieren und üben

Jedes Secrets-Management-System sollte einen dokumentierten Notfallplan haben: Was passiert, wenn Vault nicht verfügbar ist? Wer hat Zugriff auf die Unseal-Keys, und wo werden sie aufbewahrt? Wie werden kompromittierte Secrets schnell widerrufen? Wie lange dauert eine vollständige Secret-Rotation im Ernstfall? Diese Prozesse müssen bekannt und regelmäßig geübt werden – am besten im Rahmen von Disaster-Recovery-Übungen, bevor ein echter Incident eintritt. Ein Secrets-Management-System, dessen Wiederherstellungsprozess unbekannt ist, bietet im Ernstfall nur falsches Sicherheitsgefühl.