Kubernetes und Vault Agent Injector: Dynamisches Secrets Management


Bicycle

In einer cloud-nativen Kubernetes-Umgebung ist das Management von Secrets ein kritischer Aspekt der Sicherheit. HashiCorp Vault ist ein beliebtes Tool für die Verwaltung von Secrets und den Schutz sensibler Daten. In Kombination mit dem Vault Agent Injector für Kubernetes können Sie eine leistungsstarke und sichere Plattform für den Betrieb Ihrer Workloads mit nahtloser Secret-Injektion erstellen.

Hinweis: Dieser Artikel baut auf den Konzepten auf, die in unserem vorherigen Beitrag über HashiCorp Nomad and Vault: Dynamic Secrets eingeführt wurden, wo wir ähnliche Patterns mit HashiCorp Nomad erforscht haben. Hier übertragen wir diese Konzepte auf eine Kubernetes-Umgebung unter Verwendung des Vault Agent Injectors.

Dynamische Secrets in HashiCorp Vault

HashiCorp Vault bietet eine dynamische Secrets Engine, die Secrets on-demand generiert. Diese Funktion ermöglicht es Ihnen, kurzlebige Credentials für Datenbanken, Cloud-Provider und andere Services zu erstellen. Durch die Verwendung von dynamischen Secrets können Sie das Expositionsrisiko reduzieren und die Lebensdauer sensibler Daten begrenzen.

Die Workload

In diesem Beispiel werden wir eine einfache Webanwendung mit Kubernetes deployen. Die Anwendung benötigt Zugriff auf eine MySQL-Datenbank, und wir werden HashiCorp Vault mit dem Agent Injector verwenden, um dynamische Credentials für die Datenbank zu generieren, die HashiCorp Vault Transit Engine nutzen, um die Datenbankwerte on-the-fly zu verschlüsseln, und die native Kubernetes Service Discovery nutzen.

Voraussetzungen

  1. Kubernetes Cluster (minikube, kind, oder Cloud Provider)
  2. Externes HashiCorp Vault Cluster läuft auf vault.example.com:8200
  3. Vault Agent Injector installiert in Ihrem Kubernetes Cluster
  4. Helm für die Installation des Vault Agent Injectors

Vault Agent Injector installieren

Da Vault extern läuft, müssen wir nur den Vault Agent Injector installieren:

1helm repo add hashicorp https://helm.releases.hashicorp.com
2helm repo update
3
4# Nur den Agent Injector installieren (kein Vault Server)
5helm install vault-injector hashicorp/vault \
6  --set "global.externalVaultAddr=https://vault.example.com:8200" \
7  --set "injector.enabled=true" \
8  --set "server.enabled=false" \
9  --set "csi.enabled=false"

Vault-Authentifizierung konfigurieren

Kubernetes-Authentifizierung in Ihrem externen Vault Cluster einrichten:

 1# Vault Adress-Umgebungsvariable setzen
 2export VAULT_ADDR="https://vault.example.com:8200"
 3
 4# Kubernetes Auth Method aktivieren
 5vault auth enable kubernetes
 6
 7# Kubernetes Cluster-Informationen abrufen
 8KUBERNETES_HOST=$(kubectl config view --raw --minify --flatten -o jsonpath='{.clusters[].cluster.server}')
 9KUBERNETES_CA_CERT=$(kubectl config view --raw --minify --flatten -o jsonpath='{.clusters[].cluster.certificate-authority-data}' | base64 -d)
10
11# Service Account für Vault-Authentifizierung erstellen
12kubectl create serviceaccount vault-auth
13kubectl apply -f - <<EOF
14apiVersion: rbac.authorization.k8s.io/v1
15kind: ClusterRoleBinding
16metadata:
17  name: role-tokenreview-binding
18roleRef:
19  apiGroup: rbac.authorization.k8s.io
20  kind: ClusterRole
21  name: system:auth-delegator
22subjects:
23- kind: ServiceAccount
24  name: vault-auth
25  namespace: default
26EOF
27
28# JWT Token abrufen
29TOKEN_REVIEWER_JWT=$(kubectl create token vault-auth)
30
31# Kubernetes Auth konfigurieren
32vault write auth/kubernetes/config \
33    token_reviewer_jwt="$TOKEN_REVIEWER_JWT" \
34    kubernetes_host="$KUBERNETES_HOST" \
35    kubernetes_ca_cert="$KUBERNETES_CA_CERT"

MySQL Datenbank

Das eigentliche Deployment der MySQL-Datenbank erfolgt über die folgenden Kubernetes-Manifeste. Die MySQL-Datenbank wird als Deployment und Service gestartet, wobei das Root-Passwort als Umgebungsvariable gesetzt wird.

 1# mysql-deployment.yaml
 2apiVersion: apps/v1
 3kind: Deployment
 4metadata:
 5  name: mysql-server
 6  namespace: demo
 7  labels:
 8    app: mysql-server
 9spec:
10  replicas: 1
11  selector:
12    matchLabels:
13      app: mysql-server
14  template:
15    metadata:
16      labels:
17        app: mysql-server
18    spec:
19      containers:
20      - name: mysql
21        image: mysql:9
22        env:
23        - name: MYSQL_ROOT_PASSWORD
24          value: "super-duper-password"
25        ports:
26        - containerPort: 3306
27          name: mysql
28        resources:
29          requests:
30            cpu: 500m
31            memory: 1Gi
32          limits:
33            cpu: 500m
34            memory: 1Gi
35
36---
37# mysql-service.yaml
38apiVersion: v1
39kind: Service
40metadata:
41  name: mysql-server
42  namespace: demo
43spec:
44  selector:
45    app: mysql-server
46  ports:
47  - port: 3306
48    targetPort: 3306
49    name: mysql
50  type: ClusterIP

Hardcodiertes Deployment

Als ersten Schritt werden wir die Webanwendung mit hardcodierten Credentials deployen. Das könnte ein Workflow sein, den Sie kennen - mit weniger sicheren Einstellungen beginnen und sie schrittweise verbessern.

Die Webanwendung ist nur eine einfache Python Flask-Anwendung, die sich mit der MySQL-Datenbank verbindet und die Datenbankwerte auf einer Webseite anzeigt, und als alternative Ansicht kann sie die reinen Datenbankwerte anzeigen.

 1# dynamic-app-hardcoded.yaml
 2apiVersion: apps/v1
 3kind: Deployment
 4metadata:
 5  name: dynamic-app-hardcoded
 6  namespace: demo
 7  labels:
 8    app: dynamic-app
 9    version: hardcoded
10spec:
11  replicas: 1
12  selector:
13    matchLabels:
14      app: dynamic-app
15      version: hardcoded
16  template:
17    metadata:
18      labels:
19        app: dynamic-app
20        version: hardcoded
21    spec:
22      containers:
23      - name: dynamic-app
24        image: ghcr.io/infralovers/nomad-vault-mysql:1.0.0
25        ports:
26        - containerPort: 8080
27          name: web
28        env:
29        - name: CONFIG_FILE
30          value: "/app/config/config.ini"
31        volumeMounts:
32        - name: config
33          mountPath: /app/config
34        resources:
35          requests:
36            cpu: 256m
37            memory: 256Mi
38          limits:
39            cpu: 256m
40            memory: 256Mi
41        livenessProbe:
42          httpGet:
43            path: /health
44            port: 8080
45          initialDelaySeconds: 30
46          periodSeconds: 10
47        readinessProbe:
48          httpGet:
49            path: /health
50            port: 8080
51          initialDelaySeconds: 5
52          periodSeconds: 5
53      volumes:
54      - name: config
55        configMap:
56          name: dynamic-app-config-hardcoded
57
58---
59# ConfigMap mit hardcodierten Credentials
60apiVersion: v1
61kind: ConfigMap
62metadata:
63  name: dynamic-app-config-hardcoded
64  namespace: demo
65data:
66  config.ini: |
67    [DEFAULT]
68    Port = 8080
69
70    [DATABASE]
71    Address = mysql-server.demo.svc.cluster.local
72    Port = 3306
73    Database = my_app
74    User = root
75    Password = super-duper-password
76
77---
78# Service
79apiVersion: v1
80kind: Service
81metadata:
82  name: dynamic-app-hardcoded
83  namespace: demo
84spec:
85  selector:
86    app: dynamic-app
87    version: hardcoded
88  ports:
89  - port: 80
90    targetPort: 8080
91    name: web
92  type: ClusterIP

Key Value Secret Engine

Der nächste Schritt zur Verbesserung unserer Reise zu sichereren Deployments ist die Verwendung der Key Value Secret Engine von HashiCorp Vault. Diese Engine ermöglicht es Ihnen, beliebige Secrets zu speichern und abzurufen. In diesem Beispiel werden wir die Datenbank-Credentials in Vault speichern und sie zur Laufzeit mit dem Vault Agent Injector abrufen.

Zuerst Vault konfigurieren:

1# KV Secrets Engine aktivieren
2vault secrets enable -path=dynamic-app/kv kv-v2
3
4# Datenbank-Credentials speichern
5vault kv put dynamic-app/kv/database username=root password=super-duper-password

Eine Vault Policy erstellen, die Lesezugriff auf den dynamic-app/kv Pfad gewährt:

 1vault policy write dynamic-app-kv - <<EOF
 2path "dynamic-app/kv/data/database" {
 3  capabilities = ["read"]
 4}
 5EOF
 6
 7# Eine Kubernetes Rolle erstellen
 8vault write auth/kubernetes/role/dynamic-app-kv \
 9    bound_service_account_names=dynamic-app-kv \
10    bound_service_account_namespaces=demo \
11    policies=dynamic-app-kv \
12    ttl=24h

Jetzt die Anwendung mit dem Vault Agent Injector deployen:

 1# dynamic-app-kv.yaml
 2apiVersion: v1
 3kind: ServiceAccount
 4metadata:
 5  name: dynamic-app-kv
 6  namespace: demo
 7
 8---
 9apiVersion: apps/v1
10kind: Deployment
11metadata:
12  name: dynamic-app-kv
13  namespace: demo
14  labels:
15    app: dynamic-app
16    version: kv
17spec:
18  replicas: 1
19  selector:
20    matchLabels:
21      app: dynamic-app
22      version: kv
23  template:
24    metadata:
25      labels:
26        app: dynamic-app
27        version: kv
28      annotations:
29        vault.hashicorp.com/agent-inject: "true"
30        vault.hashicorp.com/agent-inject-status: "update"
31        vault.hashicorp.com/agent-inject-vault-addr: "https://vault.example.com:8200"
32        vault.hashicorp.com/role: "dynamic-app-kv"
33        vault.hashicorp.com/agent-inject-secret-config.ini: "dynamic-app/kv/data/database"
34        vault.hashicorp.com/agent-inject-template-config.ini: |
35          [DEFAULT]
36          Port = 8080
37
38          [DATABASE]
39          Address = mysql-server.demo.svc.cluster.local
40          Port = 3306
41          Database = my_app
42          User = {{ .Data.data.username }}
43          Password = {{ .Data.data.password }}
44    spec:
45      serviceAccountName: dynamic-app-kv
46      containers:
47      - name: dynamic-app
48        image: ghcr.io/infralovers/nomad-vault-mysql:1.0.0
49        ports:
50        - containerPort: 8080
51          name: web
52        env:
53        - name: CONFIG_FILE
54          value: "/vault/secrets/config.ini"
55        resources:
56          requests:
57            cpu: 256m
58            memory: 256Mi
59          limits:
60            cpu: 256m
61            memory: 256Mi
62        livenessProbe:
63          httpGet:
64            path: /health
65            port: 8080
66          initialDelaySeconds: 30
67          periodSeconds: 10
68        readinessProbe:
69          httpGet:
70            path: /health
71            port: 8080
72          initialDelaySeconds: 5
73          periodSeconds: 5
74
75---
76# Service
77apiVersion: v1
78kind: Service
79metadata:
80  name: dynamic-app-kv
81  namespace: demo
82spec:
83  selector:
84    app: dynamic-app
85    version: kv
86  ports:
87  - port: 80
88    targetPort: 8080
89    name: web
90  type: ClusterIP

Dynamische Secrets Engine

Der vorletzte Schritt in unserer Reise ist die Verwendung der dynamischen Secrets Engine von HashiCorp Vault. Diese Engine generiert kurzlebige Credentials für Datenbanken, Cloud-Provider und andere Services. In diesem Beispiel werden wir die MySQL-Datenbank Secrets Engine verwenden, um dynamische Credentials für die Datenbank zu generieren.

Die Datenbank Secrets Engine in Vault konfigurieren:

 1# Datenbank Secrets Engine aktivieren
 2vault secrets enable -path=dynamic-app/db database
 3
 4# MySQL-Verbindung konfigurieren
 5vault write dynamic-app/db/config/mysql \
 6    plugin_name=mysql-database-plugin \
 7    connection_url="{{username}}:{{password}}@tcp(mysql-server.demo.svc.cluster.local:3306)/" \
 8    allowed_roles="*" \
 9    username="root" \
10    password="super-duper-password"
11
12# Root-Credentials rotieren
13vault write -force dynamic-app/db/database/rotate-root/mysql
14
15# Eine Rolle für dynamische Credentials erstellen
16vault write dynamic-app/db/roles/app \
17    db_name=mysql \
18    creation_statements="CREATE USER '{{name}}'@'%' IDENTIFIED BY '{{password}}';GRANT ALL ON my_app.* TO '{{name}}'@'%';" \
19    default_ttl="1h" \
20    max_ttl="24h"

Die Vault Policy aktualisieren, um Zugriff auf dynamische Secrets zu erlauben:

 1vault policy write dynamic-app-db - <<EOF
 2path "dynamic-app/db/creds/app" {
 3  capabilities = ["read"]
 4}
 5EOF
 6
 7# Eine Kubernetes Rolle für dynamische Secrets erstellen
 8vault write auth/kubernetes/role/dynamic-app-db \
 9    bound_service_account_names=dynamic-app-db \
10    bound_service_account_namespaces=demo \
11    policies=dynamic-app-db \
12    ttl=24h

Die Anwendung mit dynamischen Secrets deployen:

 1# dynamic-app-db.yaml
 2apiVersion: v1
 3kind: ServiceAccount
 4metadata:
 5  name: dynamic-app-db
 6  namespace: demo
 7
 8---
 9apiVersion: apps/v1
10kind: Deployment
11metadata:
12  name: dynamic-app-db
13  namespace: demo
14  labels:
15    app: dynamic-app
16    version: dynamic-db
17spec:
18  replicas: 1
19  selector:
20    matchLabels:
21      app: dynamic-app
22      version: dynamic-db
23  template:
24    metadata:
25      labels:
26        app: dynamic-app
27        version: dynamic-db
28      annotations:
29        vault.hashicorp.com/agent-inject: "true"
30        vault.hashicorp.com/agent-inject-status: "update"
31        vault.hashicorp.com/agent-inject-vault-addr: "https://vault.example.com:8200"
32        vault.hashicorp.com/role: "dynamic-app-db"
33        vault.hashicorp.com/agent-inject-secret-config.ini: "dynamic-app/db/creds/app"
34        vault.hashicorp.com/agent-inject-template-config.ini: |
35          [DEFAULT]
36          Port = 8080
37
38          [DATABASE]
39          Address = mysql-server.demo.svc.cluster.local
40          Port = 3306
41          Database = my_app
42          User = {{ .Data.username }}
43          Password = {{ .Data.password }}
44    spec:
45      serviceAccountName: dynamic-app-db
46      containers:
47      - name: dynamic-app
48        image: ghcr.io/infralovers/nomad-vault-mysql:1.0.0
49        ports:
50        - containerPort: 8080
51          name: web
52        env:
53        - name: CONFIG_FILE
54          value: "/vault/secrets/config.ini"
55        resources:
56          requests:
57            cpu: 256m
58            memory: 256Mi
59          limits:
60            cpu: 256m
61            memory: 256Mi
62        livenessProbe:
63          httpGet:
64            path: /health
65            port: 8080
66          initialDelaySeconds: 30
67          periodSeconds: 10
68        readinessProbe:
69          httpGet:
70            path: /health
71            port: 8080
72          initialDelaySeconds: 5
73          periodSeconds: 5
74
75---
76# Service
77apiVersion: v1
78kind: Service
79metadata:
80  name: dynamic-app-db
81  namespace: demo
82spec:
83  selector:
84    app: dynamic-app
85    version: dynamic-db
86  ports:
87  - port: 80
88    targetPort: 8080
89    name: web
90  type: ClusterIP

Bonus: Transit Engine zur Verschlüsselung von Datenbankwerten

Der letzte Schritt in unserer Reise ist die Verwendung der Transit Engine von HashiCorp Vault zur Verschlüsselung der Datenbankwerte on-the-fly. Diese Engine bietet eine Möglichkeit, Daten zu verschlüsseln und zu entschlüsseln, ohne die Verschlüsselungsschlüssel zu speichern. In diesem Beispiel werden wir die Transit Engine verwenden, um die Datenbank Information zu verschlüsseln, bevor sie in der Datenbank gespeichert werden.

Die Transit Engine konfigurieren:

1# Transit Secrets Engine aktivieren
2vault secrets enable -path=dynamic-app/transit transit
3
4# Einen Verschlüsselungsschlüssel erstellen
5vault write -f dynamic-app/transit/keys/app

Die Vault Policy aktualisieren, um Transit Engine-Zugriff einzuschließen:

 1vault policy write dynamic-app-full - <<EOF
 2path "dynamic-app/db/creds/app" {
 3  capabilities = ["read"]
 4}
 5path "dynamic-app/transit/encrypt/app" {
 6  capabilities = ["create", "update"]
 7}
 8path "dynamic-app/transit/decrypt/app" {
 9  capabilities = ["create", "update"]
10}
11EOF
12
13# Eine Kubernetes Rolle für vollständigen Zugriff erstellen
14vault write auth/kubernetes/role/dynamic-app-full \
15    bound_service_account_names=dynamic-app-full \
16    bound_service_account_namespaces=demo \
17    policies=dynamic-app-full \
18    ttl=24h

Die Anwendung mit Transit-Verschlüsselung deployen:

  1# dynamic-app-full.yaml
  2apiVersion: v1
  3kind: ServiceAccount
  4metadata:
  5  name: dynamic-app-full
  6  namespace: demo
  7
  8---
  9apiVersion: apps/v1
 10kind: Deployment
 11metadata:
 12  name: dynamic-app-full
 13  namespace: demo
 14  labels:
 15    app: dynamic-app
 16    version: full
 17spec:
 18  replicas: 1
 19  selector:
 20    matchLabels:
 21      app: dynamic-app
 22      version: full
 23  template:
 24    metadata:
 25      labels:
 26        app: dynamic-app
 27        version: full
 28      annotations:
 29        vault.hashicorp.com/agent-inject: "true"
 30        vault.hashicorp.com/agent-inject-status: "update"
 31        vault.hashicorp.com/agent-inject-vault-addr: "https://vault.example.com:8200"
 32        vault.hashicorp.com/role: "dynamic-app-full"
 33        vault.hashicorp.com/agent-inject-secret-config.ini: "dynamic-app/db/creds/app"
 34        vault.hashicorp.com/agent-inject-template-config.ini: |
 35          [DEFAULT]
 36          Port = 8080
 37
 38          [DATABASE]
 39          Address = mysql-server.demo.svc.cluster.local
 40          Port = 3306
 41          Database = my_app
 42          User = {{ .Data.username }}
 43          Password = {{ .Data.password }}
 44
 45          [VAULT]
 46          Enabled = True
 47          InjectToken = True
 48          Namespace =
 49          Address = https://vault.example.com:8200
 50          KeyPath = dynamic-app/transit
 51          KeyName = app
 52    spec:
 53      serviceAccountName: dynamic-app-full
 54      containers:
 55      - name: dynamic-app
 56        image: ghcr.io/infralovers/nomad-vault-mysql:1.0.0
 57        ports:
 58        - containerPort: 8080
 59          name: web
 60        env:
 61        - name: CONFIG_FILE
 62          value: "/vault/secrets/config.ini"
 63        - name: VAULT_ADDR
 64          value: "https://vault.example.com:8200"
 65        resources:
 66          requests:
 67            cpu: 256m
 68            memory: 256Mi
 69          limits:
 70            cpu: 256m
 71            memory: 256Mi
 72        livenessProbe:
 73          httpGet:
 74            path: /health
 75            port: 8080
 76          initialDelaySeconds: 30
 77          periodSeconds: 10
 78        readinessProbe:
 79          httpGet:
 80            path: /health
 81            port: 8080
 82          initialDelaySeconds: 5
 83          periodSeconds: 5
 84
 85---
 86# Service
 87apiVersion: v1
 88kind: Service
 89metadata:
 90  name: dynamic-app-full
 91  namespace: demo
 92spec:
 93  selector:
 94    app: dynamic-app
 95    version: full
 96  ports:
 97  - port: 80
 98    targetPort: 8080
 99    name: web
100  type: ClusterIP

Fazit

In diesem Artikel haben wir demonstriert, wie man HashiCorp Vault Agent Injector mit Kubernetes verwendet, um eine sichere Webanwendung mit dynamischen Secrets zu deployen. Durch die Verwendung des Vault Agent Injectors können Sie:

  • Code-Änderungen eliminieren: Anwendungen erhalten Secrets über das Dateisystem ohne die Notwendigkeit einer Vault SDK-Integration
  • Automatische Secret-Erneuerung: Der Vault Agent übernimmt die Credential-Rotation transparent
  • Erhöhte Sicherheit: Kurzlebige, dynamisch generierte Credentials reduzieren Expositionsfenster
  • Vereinfachte Operationen: Deklarative Konfiguration über Kubernetes Annotations

Auch wenn Sie mit hardcodierten Credentials beginnen, bietet die Key Value Secret Engine eine gute Grundlage zur schrittweisen Verbesserung Ihrer Sicherheitseinstellungen. Der nächste Schritt ist die Verwendung der dynamischen Secrets Engine zur Generierung kurzlebiger Credentials für Ihre Services. Wie im Beispiel demonstriert, sind nur minimale Änderungen in Ihren Deployment-Annotations erforderlich, um die dynamische Secrets Engine zu verwenden.

Wichtige Vorteile dieses Kubernetes- und Vault Agent Injector-Ansatzes:

  1. Nahtlose Integration: Keine Anwendungscode-Änderungen für Secret-Verbrauch erforderlich
  2. Dynamisches Credential-Management: Automatische Generierung und Rotation kurzlebiger Credentials
  3. Feingliedrige Zugriffskontrolle: Kubernetes Service Account-basierte Authentifizierung
  4. Zero-Trust-Sicherheit: Anwendungen handhaben niemals langlebige Secrets direkt

Sie finden alle Code-Beispiele in einem Kubernetes-kompatiblen Format, um dieses Beispiel in Ihrem eigenen Cluster zu reproduzieren. Als Ausgangspunkt können Sie bestehende Kubernetes Cluster mit einer externen Vault-Installation verwenden.

Wenn Sie mehr über HashiCorp Vault und Kubernetes lernen möchten, schauen Sie sich unsere Kurse Cloud Native Essentials und HashiCorp Vault Enterprise an. Diese Kurse helfen Ihnen dabei, die Tools und Techniken zu meistern, die zum Aufbau sicherer und skalierbarer cloud-nativer Anwendungen erforderlich sind.

Zurück Unsere Trainings entdecken

Wir sind für Sie da

Sie interessieren sich für unsere Trainings oder haben einfach eine Frage, die beantwortet werden muss? Sie können uns jederzeit kontaktieren! Wir werden unser Bestes tun, um alle Ihre Fragen zu beantworten.

Hier kontaktieren