In der modernen Anwendungsentwicklung sind Secrets wie Zugangsdaten, API-Schlüssel und Zertifikate unverzichtbar. Da wir bei b-nova sehr viele Dinge ausprobieren, haben sich in den letzten Jahren recht viele Secrets an verschiedenen Orten verteilt. Wir haben beispielsweise Secrets im AWS Secrets Manager, welche wir per API in unterschiedlichen internen Applikationen auslesen. Wir haben Secrets in Github gespeichert, welche wir zur Ausführung unserer Pipelines brauchen, oder für das Deployment auf die verschiedenen Umgebungen. Weiterhin haben wir mehrere Kubernetes Cluster, in denen ebenfalls verschiedenste Secrets hinterlegt sind. Nun sind wir aber an dem Punkt angekommen, an dem wir langsam aber sicher wieder etwas Ordnung in die Verwaltung unserer Secrets bringen müssen. Die Gründe dafür liegen auf der Hand.
- Secrets liegen aktuell an mehreren Orten und es gibt keine SSOT (Single Source of Truth)
- Secrets sind redundant gespeichert. Das bedeutet, wenn man ein Secret anpasst, man es an mehreren Orten anpassen muss
- Die Übersicht, wo welches Secret liegt, ist nicht mehr gewährleistet
- Die Verwaltung, wer auf welches Secret Zugriff haben darf, ist nur noch schwer handhabbar
- Lifecycle-Management: Automatisierung und Verwaltung der Secrets-Rotation kann komplex werden
- Teilweise liegen die Secrets unverschlüsselt (K8s Secrets)
So kam also die Frage auf, wie wir das Secret Handling wieder in den Griff bekommen können. Die Wahl fiel dabei recht schnell auf Hashicorp Vault, da dieses Tool alle Anforderungen, die wir an einen Secret Manager haben, mit Bravour erfüllt.
- Verschlüsselung: Vault speichert Secrets verschlüsselt und bietet zusätzliche Sicherheit
- Zugriffskontrolle: Vault ermöglicht eine feingranulare Zugriffskontrolle durch ACLs und Rollen
- Lifecycle-Management: Vault unterstützt die Automatisierung der Secret-Rotation und -Verwaltung
Der Vault Server war sehr schnell aufgesetzt und die Secrets schnell übertragen. Aber wie funktioniert nun der Secret-Zugriff aus den einzelnen Applikationen, aus GitHub und aus dem K8s Cluster? Es gibt verschiedene Möglichkeiten, die wir in Betracht gezogen haben. Dabei ist wichtig zu betonen, dass der Grossteil unserer Applikationen derzeit auf K8s läuft. Die Secrets werden per Secret-Referenz zu den K8s Secrets als Umgebungsvariablen beim Deployment in die Applikationen injiziert.
Eine mögliche Lösung wäre nun beispielsweise, die Secrets per API aus Vault auszulesen. Der Vorteil dabei ist, dass das Secret nur noch dort gelesen wird, wo es auch wirklich gebraucht wird. Niemand könnte mehr über die Secrets irgendwelche Passwörter auslesen. Der Nachteil ist jedoch, dass wir in jeder Applikation den API Zugriff erst implementieren müssen. Eine andere Möglichkeit, Secrets sicher und effizient in Kubernetes zu verwalten, ist die Nutzung von Kubernetes External-Secrets in Kombination mit HashiCorp Vault als externen Secret-Speicher. Heute wollen wir daher untersuchen, wie diese beiden Technologien zusammenarbeiten und wie sie den Umgang mit Secrets in Kubernetes-Clustern optimieren können.
Schauen wir uns doch gleich mal an, was der Kubernetes External-Secrets Operator überhaupt ist.
Kubernetes External-Secrets Operator
Der Kubernetes External-Secrets Operator ist ein Kubernetes-Controller, der das Synchronisieren von Secrets aus externen Quellen, wie z. B. HashiCorp Vault, AWS Secrets Manager oder Google Cloud Secret Manager, in Kubernetes-Cluster ermöglicht. Er überwacht den Cluster auf das Vorhandensein von Custom-Resource-Definitions (CRDs) namens ExternalSecrets. Diese CRDs enthalten Metadaten, die beschreiben, wie die Secrets aus der externen Quelle abgerufen und in Kubernetes-Secrets gespeichert werden sollen. Der Operator liest die Konfiguration, holt die entsprechenden Secrets aus der externen Quelle ab und erstellt Kubernetes-Secrets, die diese Geheimnisse enthalten. Durch die Verwendung des External-Secrets Operators können Entwickler und Administratoren Secrets zentral in externen Secrets-Stores verwalten, während Anwendungen in Kubernetes weiterhin die nativen Kubernetes-Secrets verwenden. Dies erhöht die Sicherheit und ermöglicht die zentrale Verwaltung von Secrets, vereinfachte Zugriffskontrollen und Lifecycle-Management.
Der External-Secrets Operator kann über Helm oder kubectl installiert werden. Mit Helm sieht das folgendermassen aus:
|
|
Secret Store erstellen
Wenn der Operator installiert ist, müssen wir uns als erstes einen SecretStore erstellen. Dazu erstellen wir uns eine Datei mit dem Namen secret-store.yml
mit dem folgenden Inhalt:
|
|
Wir könnten hier auch einen globalen ClusterSecretStore
erstellen. Dieser müsste nicht pro Namespace definiert werden, sondern wäre über alle Namespaces verfügbar.
Wir definieren nun noch die Adresse unseres Vault-Servers, sowie die Version der KV (Key Value) Secret Engine. In der KV Secret Engine v2 wird nun auch die Versionierung von Secrets unterstützt. Nun erstellen wir einen Namespace und erstellen darin den SecretStore:
|
|
Wie wir sehen können, konnte der Store erfolgreich erstellt werden. Schauen wir, ob er auch ordnungsgemäss funktioniert:
|
|
Wir kriegen den Status InvalidProviderConfig
, es scheint also noch irgendwas falsch zu laufen. Schauen wir uns kurz an, was genau schief läuft:
|
|
Wir sehen, dass der Token, den wir in unserer Konfiguration angegeben haben, nicht existiert. Diesen Token müssen wir natürlich zuerst in unserem Vault generieren.
Einen Token generieren
Ich gehe in diesem Techup davon aus, dass der Vault Server bereits vollständig aufgesetzt ist. Wenn dies noch nicht der Fall ist, so müsste man diesen erst aufsetzen. Eine detaillierte Anleitung dazu findest du direkt auf der Hashicorp Vault Seite. Für unsere Applikation brauchen wir nun einen entsprechenden Vault Benutzer. Hier könnte man zum Beispiel unter Access -> AuthMethods
eine neue userpass
Methode hinzufügen. Wie der Name schon sagt, kann man damit einen Benutzer anlegen, welcher sich dann mit einem Passwort authentifizieren kann.
In der userpass
Auth Methode können wir uns diesen neuen Benutzer erstellen:
Wenn der Benutzer erstellt wurde, müssen wir uns bei Vault mit diesem einloggen, um den erforderlichen Token zu bekommen. Wir wählen in der Login Maske als Methode Username und geben die zuvor vergebenen Zugangsdaten ein. Anschliessend können wir über das Benutzermenu den Token des Benutzers kopieren.
Nun müssen wir das Secret in unserem Namespace entsprechend hinterlegen. Dies können wir am schnellsten mit kubectl
erledigen:
|
|
Nun wollen wir schauen, ob der Secret Store nach Anlegen des Secrets korrekt funktioniert:
|
|
Wir sehen nun, dass der Status “Valid” ist und der Secret Store funktioniert! 🎉
Sobald der Secret Store erfolgreich eingerichtet wurde, lassen sich die externen Secrets verwalten. Auch hierzu bietet uns der External-Secrets Operator eine CRD. Bevor wir das External-Secret jedoch einrichten, müssen wir dieses erstmal im Vault anlegen. Unter kv2 erstellen wir ein Secret mit dem Pfad application/secrets
. Hier können wir nun ein Key/Value-Paar anlegen mit dem Inhalt MY_TOKEN=1234567890
:
Wenn wir das Secret erstellt haben, können wir weitere Datei namens external-secret.yml
mit dem nachfolgenden Inhalt erstellen. Danach legen wir das Secret mit kubectl apply -f external-secret.yml
an:
|
|
Schauen wir uns wieder an, ob alles ordnungsgemäss funktioniert:
|
|
Um weitere Informationen zum Beispiel im Falle eine Fehlers zu erhalten, können wir diesen Befehl verwenden:
|
|
Das Secret sollte nun synchronisiert worden sein und wir können uns dieses nun anschauen:
|
|
Wie wir sehen können, wurde das Secret mit dem Namen my-secret
erfolgreich erstellt. Wenn wir uns den Inhalt anschauen sehen wir auch, dass der Inhalt dem entspricht, welcher auch im Vault hinterlegt wurde.
|
|
Nun steht der erwartete Wert im Secret. Testen wir nun einmal, ob das Update eines Secrets auch funktioniert. Dazu erstellen wir im Vault eine neue Version des Secrets und ändern den Wert ab:
Wir müssen jetzt noch maximal 15 Sekunden warten und sollten dann das neue Secret auch in Kubernetes sehen.
|
|
Perfekt, wir haben das neue Secret nun auch in unserem neuen Namespace verfügbar und können dieses über eine Secret-Referenz direkt in unserer Applikation als Umgebungsvariable oder als Volume nutzen. Eine detaillierte Anleitung dazu findest du direkt auf der Kubernetes Seite. 🤓
Zusammenfassung
Heute haben wir uns angeschaut, wie man seine Secrets zentral verwalten kann und diese ganz einfach mit dem Kubernetes Secrets Operator in einem Namespace synchronisieren kann. Es sind dafür keine Anpassungen am Code in der Applikation notwendig. Dies ist ein sehr eleganter Weg, da die Secrets erst dann ausgeliefert werden, wenn diese von der Applikation wirklich gebraucht werden. Bleib dran! 🚀