Wie Pipelines auf Secrets zugreifen, ohne sie zu speichern

Sie haben eine Pipeline, die Ihre Anwendung baut, testet und bereitstellt. Irgendwann während dieses Prozesses benötigt sie ein Datenbankpasswort, einen API-Key oder ein Zertifikat. Der natürliche Instinkt ist, dieses Secret in eine Pipeline-Variable, eine Konfigurationsdatei oder sogar direkt in ein Skript zu schreiben. Aber das schafft ein Problem: Sobald ein Secret Ihre Pipeline betritt, kann es an Orten landen, die Sie nie vorgesehen haben.

Secrets gelangen in Build-Logs. Sie werden in Docker-Images eingebacken. Sie tauchen in Artefakt-Dateien auf. Sie bleiben in Workspace-Caches erhalten. In dem Moment, in dem ein Secret Ihren Pipeline-Code berührt, verlieren Sie die Kontrolle darüber, wo es hingeht.

Die Lösung ist nicht, Secrets vollständig aus der Pipeline herauszuhalten. Die Pipeline braucht sie, um ihre Aufgabe zu erfüllen. Die Lösung ist, Secrets so zu injizieren, dass sie nur im Arbeitsspeicher existieren, nur für die Dauer der Aufgabe und niemals dauerhaft gespeichert werden. Es gibt drei gängige Ansätze, und jeder hat seine eigenen Vor- und Nachteile.

Umgebungsvariablen: Einfach, aber undicht

Der häufigste Ansatz ist, ein Secret aus einem Vault oder Secret Store zu holen und als Umgebungsvariable im laufenden Prozess zu setzen. Wenn Ihre Pipeline npm test oder dotnet run ausführt, ist die Variable DB_PASSWORD bereits im Prozessspeicher verfügbar.

Dieser Ansatz ist einfach und schnell. Er funktioniert mit fast jedem Tool und Framework. Sie müssen nicht ändern, wie Ihre Anwendung die Konfiguration liest. Die meisten Sprachen und Laufzeitumgebungen unterstützen Umgebungsvariablen nativ.

Zum Beispiel können Sie mit der Vault-CLI ein Secret abrufen und exportieren, bevor Sie Ihren Build ausführen:

# Datenbankpasswort aus Vault holen und als Umgebungsvariable exportieren
export DB_PASSWORD=$(vault kv get -field=password secret/db-prod)

# Den Build-Befehl ausführen, der das Secret benötigt
npm run build

Das Problem ist, dass Umgebungsvariablen leicht auslaufen können. Viele Anwendungen geben beim Debuggen alle Umgebungsvariablen aus. Logging-Frameworks schreiben Umgebungsvariablen oft standardmäßig in Logdateien. Sobald ein Secret in einem Log auftaucht, kann es jeder lesen, der Zugriff auf das Logging-System hat. Das schließt Entwickler, Support-Mitarbeiter und potenziell Angreifer ein, falls das Logging-System kompromittiert wird.

Es gibt ein weiteres Risiko: Build-Artefakte. Wenn Ihre Pipeline eine JAR-Datei, ein Docker-Image oder ein kompiliertes Binary erstellt, können Umgebungsvariablen erfasst werden, wenn der Build-Prozess versehentlich alle Variablen ausliest. Ein Docker-Build, der ARG- oder ENV-Anweisungen verwendet, kann Secrets in die Image-Layer einbetten. Sobald das Secret im Image ist, bleibt es für immer dort, es sei denn, Sie bauen alles neu und stellen es erneut bereit.

Umgebungsvariablen eignen sich gut für kurzlebige Pipelines, bei denen Sie das Logging und den Build-Prozess streng kontrollieren. Sie sind gefährlich in Pipelines, die Artefakte produzieren oder ausführliches Logging haben.

Dateien einbinden: Mehr Kontrolle, mehr Aufräumarbeit

Der zweite Ansatz ist, das Secret aus dem Vault zu holen und in eine temporäre Datei im Container oder Workspace zu schreiben. Die Anwendung liest diese Datei beim Start. Nach Abschluss der Aufgabe wird die Datei gelöscht.

Dieser Ansatz gibt Ihnen mehr Kontrolle. Sie können Dateiberechtigungen so setzen, dass nur der Anwendungsprozess die Datei lesen kann. Sie können die Datei schreibgeschützt einbinden. Sie können sie sofort nach der Verwendung löschen. Viele moderne Frameworks unterstützen das Lesen von Konfigurationen aus Dateien. Spring Boot liest aus application.properties oder YAML-Dateien. .NET liest aus JSON-Konfigurationsdateien. Sie können diese Frameworks auf eine temporäre Datei verweisen, die nur die für diesen Lauf benötigten Secrets enthält.

Das Risiko ist, dass Dateien zurückbleiben können. Wenn Ihre Pipeline den Workspace nach Abschluss nicht bereinigt, bleibt die Secret-Datei auf der Festplatte. In Container-Umgebungen kann eine eingebundene Datei von anderen Prozessen im selben Container gelesen werden, wenn die Berechtigungen nicht korrekt gesetzt sind. Wenn Ihre Pipeline Caching verwendet, wie z. B. Docker-Layer-Caching, kann die Secret-Datei zwischengespeichert werden und in nachfolgenden Builds auftauchen.

Das Einbinden von Dateien ist sicherer als Umgebungsvariablen, weil Sie die Lebensdauer und die Berechtigungen der Datei kontrollieren. Aber es erfordert Disziplin: Sie müssen nach jedem Lauf aufräumen und sicherstellen, dass Caching-Mechanismen die Datei nicht konservieren.

Direkte API-Aufrufe: Kein Secret in der Pipeline

Der dritte Ansatz ist, der Pipeline das Secret gar nicht erst zu geben. Stattdessen ruft die Anwendung oder das Skript bei jedem Bedarf die Vault-API direkt auf. Die Pipeline handhabt das Secret nicht. Sie setzt keine Umgebungsvariablen. Sie schreibt keine Dateien. Die Anwendung selbst kontaktiert den Vault und holt sich, was sie braucht.

Zum Beispiel ruft die Anwendung GET /v1/secret/db-password auf, wenn sie eine Verbindung zur Datenbank herstellen muss, anstatt ein Datenbankpasswort als Umgebungsvariable zu übergeben. Der Vault authentifiziert die Anfrage, gibt das Secret zurück, und die Anwendung verwendet es sofort.

Das folgende Sequenzdiagramm veranschaulicht diesen Ablauf:

sequenceDiagram participant Pipeline participant App as Anwendung participant Vault Pipeline->>App: Aufgabe starten App->>Vault: Secret anfordern (Auth-Token) Vault-->>App: Secret zurückgeben App->>App: Secret im Speicher verwenden App->>Vault: Weiteres Secret anfordern Vault-->>App: Secret zurückgeben App->>App: Secret im Speicher verwenden App-->>Pipeline: Aufgabe abgeschlossen

Dies ist der sicherste Ansatz. Das Secret existiert nie im Pipeline-Speicher. Es wird nie in eine Datei geschrieben. Es taucht nie in Logs auf. Selbst wenn jemand Zugriff auf den Pipeline-Workspace erhält, kann er das Secret nicht finden, weil es nie da war.

Der Nachteil ist die Verfügbarkeit. Ihre Anwendung ist jetzt darauf angewiesen, dass der Vault erreichbar ist. Wenn der Vault ausfällt oder nicht erreichbar ist, kann Ihre Anwendung nicht starten. Jeder API-Aufruf fügt Latenz hinzu und hinterlässt eine Spur im Audit-Log des Vaults. Dieser Ansatz eignet sich am besten für Secrets, die selten verwendet werden, oder für dynamische Secrets mit kurzer Lebensdauer, wie z. B. temporäre Datenbankanmeldeinformationen, die nach einigen Minuten ablaufen.

Direkte API-Aufrufe sind ideal für Produktionsumgebungen mit hohen Sicherheitsanforderungen, in denen Infrastrukturteams die Verfügbarkeit des Vaults garantieren können. Sie sind übertrieben für Entwicklungs- oder Test-Pipelines, bei denen das Risiko von Secret-Leaks geringer ist.

Den richtigen Ansatz wählen

Es gibt keine einzelne beste Methode. Die Wahl hängt davon ab, wie oft das Secret verwendet wird, wie streng Ihre Zugriffskontrolle sein muss und wie zuverlässig Ihre Vault-Infrastruktur ist.

Für schnelle Entwicklungspipelines, bei denen sich Secrets selten ändern, sind Umgebungsvariablen in Ordnung, solange Sie Logging und Artefakterstellung kontrollieren. Für Produktionsbereitstellungen, bei denen Secrets nicht auslaufen dürfen, sind direkte API-Aufrufe oder das Einbinden von Dateien mit strenger Bereinigung besser. Für containerisierte Umgebungen eignen sich eingebundene Dateien mit schreibgeschützten Berechtigungen und automatischer Bereinigung nach Beenden des Containers gut.

Das Wichtigste ist, die Schwachstellen jedes Ansatzes zu verstehen und Ihre Pipeline so zu gestalten, dass diese Lücken geschlossen werden. Wählen Sie nicht einfach die einfachste Methode. Wählen Sie die Methode, die Ihrer Risikotoleranz und Betriebsfähigkeit entspricht.

Praktische Checkliste

Bevor Sie entscheiden, wie Ihre Pipeline auf Secrets zugreift, überprüfen Sie diese Punkte:

  • Produziert Ihre Pipeline Artefakte (Docker-Images, JAR-Dateien, kompilierte Binaries), die Umgebungsvariablen erfassen könnten?
  • Geben Ihre Logging-Frameworks standardmäßig Umgebungsvariablen aus?
  • Können Sie Dateiberechtigungen für eingebundene Secrets in Ihrer Container-Umgebung setzen?
  • Bereinigt Ihre Pipeline Workspace-Dateien nach jedem Lauf?
  • Ist Ihre Vault-Infrastruktur zuverlässig genug für direkte API-Aufrufe von Anwendungen?
  • Benötigen Sie Audit-Trails für jeden Secret-Zugriff?

Die eigentliche Herausforderung

Secrets in die Pipeline zu bekommen, ohne sie zu speichern, ist nur die halbe Miete. Der schwierigere Teil ist sicherzustellen, dass sie nicht an unerwarteten Orten auslaufen: Logs, Artefakte, Versionskontrolle oder zwischengespeicherte Build-Layer. Jeder Ansatz bietet eine Möglichkeit, Secrets zu injizieren, aber keiner verhindert automatisch das Auslaufen. Das erfordert kontinuierliche Disziplin, automatisierte Prüfungen und ein klares Verständnis davon, wo Ihre Secrets landen können.

Das Ziel ist nicht, eine perfekte Methode zu finden. Das Ziel ist, eine Methode zu wählen, die zu Ihrer Umgebung passt, und dann Sicherheitsvorkehrungen um ihre Schwachstellen herum zu bauen.