Cloud Intelligence™Cloud Intelligence™

Cloud Intelligence™

Secrets aus AWS, GCP oder Vault in einen Kubernetes Pod einschleusen

By Ami MahloofMar 23, 20208 min read

Diese Seite ist auch in English, Español, Français, Italiano, 日本語 und Português verfügbar.

1 pylutmoqk4ivd vequwuda

In Kubernetes setzen wir auf Automatisierung und vermeiden doppelten Code. Genauso sollte das Einbinden von Secrets aus einem Secret Manager in Kubernetes ablaufen. So funktioniert es.

Dieser Beitrag knüpft an meinen vorherigen Blogbeitrag über das automatisierte Einschleusen von Secrets in Kubernetes Pods mit Vault an.

Secrets in Kubernetes sind nach wie vor nichts anderes als base64-kodierter Klartext.

Sie lassen sich zwar im Ruhezustand verschlüsseln, doch auf dem Pod selbst liegen sie unverschlüsselt vor. Zudem gilt der Verschlüsselungsschlüssel global für Etcd (wo die Secrets gespeichert werden) – ein Risiko in Multi-Tenant-Clustern, in denen alle Secrets mit demselben globalen Schlüssel verschlüsselt sind.

1 pylutmoqk4ivd vequwudaSecrets sicher aus AWS, GCP oder Vault in einen Kubernetes Pod einschleusen

Mit GCP KMS lässt sich ein Envelope-Schlüssel auf Anwendungsebene erzeugen, der dieses Problem entschärft. Am Ende der Kette liegt der Pod das Secret aber trotzdem im Klartext vor.

Neben Hashicorp Vault gibt es die Secret Manager von AWS und GCP.

AWS und GCP bieten zwar nicht den vollen Funktionsumfang von Vault – etwa Authentifizierungs-Backends, Audit-Logs für Secret-Zugriffe, Widerruf oder dynamische Secrets –, dafür aber eine vergleichbare Möglichkeit, Secrets versioniert zu speichern.

Die Herausforderung: Secrets in Kubernetes Pods nutzen

Secrets in einem Kubernetes Pod zu nutzen, ist nicht trivial: Sie brauchen ein eigenes Wrapper-Skript, das die Authentifizierung übernimmt und die Secrets als Umgebungsvariablen bereitstellt. Anschließend muss dieses Skript durch den Wrapper-Prozess ersetzt werden, damit dieser sämtliche Umgebungsvariablen erbt und zur PID 1 wird – entscheidend, damit Termination-Signale in Containern sauber verarbeitet werden.

In Kubernetes versuchen wir, derartige Querschnittsaufgaben zu automatisieren und doppelten Code zu vermeiden (ein weiteres Beispiel ist die Automatisierung von DNS und Zertifikaten). Für Secrets aus einem Secret Manager sollte nichts anderes gelten.

Manche Unternehmen möchten keine eigene Vault-Installation betreiben – schon allein deshalb greifen sie zu den Secret Managern von AWS oder GCP.

Diese Kubernetes-Webhooks sollten Sie kennen

Kubernetes bringt einen Admission Controller mit, der Anfragen an den Kubernetes-API-Server abfangen kann – nach Authentifizierung und Autorisierung, aber bevor das Objekt persistiert wird.

Im kube-apiserver-Binary sind zwei spezielle Controller eingebaut:

  • MutatingAdmissionWebhook
  • ValidatingAdmissionWebhook

Sie übernehmen die Mutation und Validierung von Anfragen nach Authentifizierung und Autorisierung.

Mutating Controller können die zugelassenen Objekte verändern; Validating Controller entscheiden, ob ein Objekt am API-Server zugelassen wird oder nicht.

Diese Webhook-Objekte lassen sich wie jedes andere Kubernetes-Objekt per kubectl installieren (sie enthalten die HTTPS-Adresse für die eigentliche Webhook-Logik).

Einmal installiert, durchläuft jede Anfrage diese Logik.

Secrets Consumer Mutation Webhook

Der folgende Code und das zugrundeliegende Konzept basieren maßgeblich auf der hervorragenden Arbeit von Banzai Cloud – die volle Anerkennung gebührt ihnen (lesen Sie unbedingt deren Blog, er ist ausgezeichnet!).

Die wesentlichen Unterschiede zwischen den Webhooks von Banzai Cloud und meinen sind:

  1. Unterstützung für die Secret Manager von AWS, GCP und Vault.
  2. Die consulTemplates- und Dynamic-Secrets-Funktion aus dem Banzai-Cloud-Webhook entfällt.
  3. Vault-Authentifizierung nicht nur über ein Kubernetes-Backend, sondern auch über ein GCP-Backend.
  4. Wahlweise gezielt einzelne Secrets aus dem Secret Manager auswählen oder alle Secrets abrufen.
  5. Den Secret-Pfad als Verzeichnis nutzen und alle darunter liegenden Secrets abrufen (begrenzt auf die erste Ebene).
  6. Secret-Namen als Schlüssel verwenden, wenn der Secret-Pfad mit / endet und jeder Wert ein Einzelwert ist:

Der Anwendungsfall: Secret-Inhalte besser handhaben – weniger Fehler beim Hinzufügen oder Bearbeiten eines Secrets und weniger erforderliche Schritte (Lesen, Anhängen, Aktualisieren). 7. Wildcard-Secret-Pfade: Endet der Secret-Pfad etwa mit / und gibt es mehrere Secrets, die mit db_ beginnen, lässt sich der Secret-Pfad als /secret/path/db_* angeben. 8. Automatische Erkennung, ob das Secret KV1 oder KV2 ist (kein /data-Präfix im Secret-Pfad mehr nötig).

So funktioniert der Mutation Webhook

Jede neue Anfrage an den API-Server durchläuft diesen Webhook.

Der Webhook prüft, ob es sich beim Objekt um einen Pod handelt, und mutiert es nur, wenn bestimmte Pod-Annotationen vorhanden sind, zum Beispiel:

https://gist.github.com/innovia/3e58eeb38668c9f4da4f1f76a736fbca

Findet er diese Annotationen, ändert er das Pod-Objekt wie folgt:

  • Ein gemeinsames In-Memory-Volume wird ergänzt
  • Ein Init-Container mit dem vault-env-Binary wird hinzugefügt, samt Befehl, vault-env in dieses gemeinsame Volume zu kopieren
  • Der Pod-Befehl wird auf vault-env umgestellt
  • Vault-Umgebungsvariablen (ROLE, CA_PATH, SECRET_PATH) werden gesetzt, um Vault-Operationen zu erleichtern.

Wichtig zu wissen:

Der Webhook hängt die Secrets nicht als Umgebungsvariablen an den Pod. Vertrauliche Daten landen niemals dauerhaft auf der Festplatte – nicht einmal vorübergehend oder in etcd – und sind auch nicht im modifizierten Pod-Objekt einsehbar.

Alle Secrets liegen ausschließlich im Speicher und sind nur für den anfordernden Prozess sichtbar.

Charts ohne explizite container.command und container.args verwenden

Der Webhook kann inzwischen ENTRYPOINT und CMD des Containers selbst ermitteln – anhand der Image-Metadaten, die er aus der Image-Registry abruft. Diese Daten werden gecacht, bis der Webhook-Pod neu gestartet wird. Ist die Registry öffentlich zugänglich (ohne Authentifizierung), ist nichts weiter zu tun. Erfordert sie eine Authentifizierung, müssen die Zugangsdaten im Abschnitt imagePullSecrets des Pods hinterlegt sein.

Nahezu jedes Chart erlaubt das Setzen eigener podAnnotations – die verfügbaren Annotationen finden Sie auf der Webhook-README-Seite.

Secrets außerhalb von Kubernetes nutzen

Das Tool secrets-consumer-env lässt sich über einige Umgebungsvariablen ausführen – alle verfügbaren Optionen finden Sie in der README.md.

Den Webhook installieren

Vor der Installation des Charts müssen Sie einen eigenen Namespace dafür anlegen. Grund ist die Reihenfolge, in der die Ressourcen aus den Charts angewendet werden: Helm sammelt alle Ressourcen eines Charts samt Abhängigkeiten, gruppiert sie nach Ressourcentyp und installiert sie in einer vordefinierten Reihenfolge (siehe hier – Helm 2.10).

Die MutatingWebhookConfiguration wird vor dem eigentlichen Backend-Pod erstellt, der den Webhook bereitstellt. Kubernetes würde diesen Pod ebenfalls mutieren wollen, ist dazu aber noch nicht in der Lage (Endlosschleife in der Logik).

export WEBHOOK_NS=`<namepsace>`
WEBHOOK_NS=${WEBHOOK_NS:-vault-secrets-webhook}
kubectl create namespace "${WEBHOOK_NS}"
kubectl label ns "${WEBHOOK_NS}" name="${WEBHOOK_NS}"

Chart abrufen:

git clone https://github.com/innovia/secrets-consumer-webhook.git

Chart installieren:

helm upgrade --namespace ${WEBHOOK_NS} --install secrets-consumer-webhook secrets-consumer-webhook --wait

HINWEIS: --wait ist wegen Timing-Problemen in Helm nötig, siehe dieses Issue.

Hinweise zu privaten GKE-Clustern

Konfiguriert Google die Control Plane für private Cluster, richtet es automatisch VPC-Peering zwischen dem Netzwerk Ihres Kubernetes-Clusters und einem separaten, von Google verwalteten Projekt ein.

Die automatisch erzeugten Regeln öffnen zwischen Mastern und Nodes nur die Ports 10250 und 443. Um die Webhook-Komponente in einem privaten GKE-Cluster zu nutzen, brauchen Sie deshalb eine zusätzliche Firewall-Regel, die Ihrer Master-CIDR den Zugriff auf den Webhook-Pod über Port 8443 erlaubt.

Mehr zum Hinzufügen von Firewall-Regeln für die Control-Plane-Nodes von GKE finden Sie in der GKE-Dokumentation.

Explizite vs. nicht-explizite Secrets (alle abrufen)

Sie haben die Wahl: gezielt einzelne Secrets für Ihren Prozess freigeben oder alle Secrets eines Pfads bzw. Namens abrufen.

Um Secrets aus dem Secret Manager gezielt auszuwählen, fügen Sie Ihrem Pod nach folgendem Muster eine Umgebungsvariable hinzu:

env:
- name: <variable name to export>
value: vault:<vault key name from secret>

Kubernetes-Backend-Authentifizierung mit Vault einrichten

Vault kann sich gegenüber Kubernetes per Kubernetes-Service-Account authentifizieren.

Das geschieht über einen weiteren Service-Account namens vault-reviewer mit der Berechtigung auth-delegator. Damit kann er ein anderes Service-Account-Token zur Authentifizierung an den Kubernetes-Master weiterreichen.

Ist die Authentifizierung gegenüber Kubernetes erfolgreich, gibt Vault ein Client-Token zurück, mit dem die Anmeldung an Vault möglich ist. Vault prüft anhand eines Mappings aus Vault-Rolle, Service-Account, Namespace und Policy, ob der Zugriff gewährt oder verweigert wird.

Das Service-Account-Token von vault-reviewer wird per Vault-CLI in Vault hinterlegt.

Legen wir den Service-Account für den vault-reviewer an.

https://gist.github.com/innovia/5435f2336e4dd0045dbb5842880b3334

Link zum Original-Gist

Bitte beachten: Wenn Sie Vault in einem anderen Namespace eingerichtet haben, passen Sie diese Datei entsprechend an.

kubectl apply -f vault-reviewer.yaml

Aktivieren Sie das Kubernetes-Auth-Backend:

$ vault login
Token (will be hidden):
$ vault auth enable kubernetes
Success! Enabled kubernetes auth method at: kubernetes/

Vault mit dem Vault-Reviewer-Token und der CA konfigurieren:

Hinweis: Wenn Sie Vault in einem anderen Namespace betreiben, hängen Sie an jeden kubectl-Befehl die Option -n an.

$ VAULT_SA_TOKEN_NAME=$(kubectl get sa vault-reviewer -o jsonpath="{.secrets[*]['name']}")
$ SA_JWT_TOKEN=$(kubectl get secret "$VAULT_SA_TOKEN_NAME" -o jsonpath="{.data.token}" | base64 --decode; echo)
$ SA_CA_CRT=$(kubectl get secret "$VAULT_SA_TOKEN_NAME" -o jsonpath="{.data['ca\.crt']}" | base64 --decode; echo)
$ vault write auth/kubernetes/config token_reviewer_jwt="$SA_JWT_TOKEN" kubernetes_host=https://kubernetes.default kubernetes_ca_cert="$SA_CA_CRT"
Success! Data written to: auth/kubernetes/config

Beispiel für ein Rollen-Mapping auf einen Service-Account und Namespace in Vault

vault write auth/kubernetes/role/tester
bound_service_account_names=tester
bound_service_account_namespaces=default
policies=test_policy
ttl=1h

GCP-Backend-Authentifizierung mit Vault einrichten

Aktivieren Sie die Google-Cloud-Auth-Methode:

$ vault auth enable gcp

Konfigurieren Sie die Zugangsdaten der Auth-Methode:

$ vault write auth/gcp/config \ credentials=@/path/to/credentials.json

Wenn Sie Instance-Credentials verwenden oder die Zugangsdaten über eine Umgebungsvariable festlegen möchten, können Sie diesen Schritt überspringen.

Erstellen Sie eine benannte Rolle:

Für eine Rolle vom Typ iam:

$ vault write auth/gcp/role/my-iam-role \
type="iam" \
policies="dev,prod" \
bound_service_accounts="[email protected]"

Für eine Rolle vom Typ gce:

$ vault write auth/gcp/role/my-gce-role \
type="gce" \
policies="dev,prod" \
bound_projects="my-project1,my-project2" \
bound_zones="us-east1-b" \
bound_labels="foo:bar,zip:zap" \
bound_service_accounts="[email protected]"

Erforderliche GCP-Berechtigungen

Berechtigungen für den Vault-Server

Für Vault-Rollen vom Typ iam kann Vault diese Rollen erhalten:

roles/iam.serviceAccountKeyAdmin

Für Vault-Rollen vom Typ gce kann Vault diese Rollen erhalten:

roles/compute.viewer

Wenn Sie stattdessen eine eigene Rolle mit ausschließlich den exakt benötigten GCP-Berechtigungen anlegen möchten, verwenden Sie diese Liste:

iam.serviceAccounts.get
iam.serviceAccountKeys.get
compute.instances.get
compute.instanceGroups.list

Berechtigungen für die Authentifizierung gegenüber Vault

Beachten Sie: Die zuvor genannten Berechtigungen werden den Vault-Servern zugewiesen. Der IAM-Service-Account oder die GCE-Instanz, die sich gegenüber Vault authentifiziert, benötigt folgende Rolle:

roles/iam.serviceAccountTokenCreator

Sie sind eingeladen, einen Blick in den Quellcode zu werfen:

https://github.com/doitintl/secrets-consumer-webhook.git