Stocker des secrets dans des Kubernetes Secrets en base64 est une fausse sécurité — ils sont lisibles par quiconque a accès à etcd. Vault résout ce problème en devenant la source de vérité pour les secrets, avec rotation automatique et audit complet.
Les trois approches d’intégration
1. Vault Agent Injector (sidecar)
Le Vault Agent Injector injecte automatiquement un conteneur sidecar dans les pods annotés. Ce sidecar s’authentifie auprès de Vault, récupère les secrets, et les écrit dans des fichiers montés dans le conteneur principal.
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
template:
metadata:
annotations:
# Active l'injection
vault.hashicorp.com/agent-inject: "true"
# Rôle Vault pour l'authentification Kubernetes
vault.hashicorp.com/role: "myapp-role"
# Secret à récupérer → écrit dans /vault/secrets/db-creds
vault.hashicorp.com/agent-inject-secret-db-creds: "secret/data/myapp/db"
# Template pour formater le fichier
vault.hashicorp.com/agent-inject-template-db-creds: |
{{- with secret "secret/data/myapp/db" -}}
export DB_HOST="{{ .Data.data.host }}"
export DB_PASSWORD="{{ .Data.data.password }}"
{{- end }}
spec:
serviceAccountName: myapp # SA utilisé pour l'auth Kubernetes
containers:
- name: app
image: myapp:1.0
command: ["/bin/bash", "-c"]
args:
- source /vault/secrets/db-creds && exec python app.py
2. External Secrets Operator
ESO synchronise des secrets Vault vers des Kubernetes Secrets natifs. Plus simple à intégrer dans des apps existantes qui lisent déjà des env vars.
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: myapp-db-secret
spec:
refreshInterval: 1h # re-sync toutes les heures
secretStoreRef:
name: vault-backend
kind: SecretStore
target:
name: myapp-db-secret # nom du K8s Secret créé
creationPolicy: Owner
data:
- secretKey: password # clé dans le K8s Secret
remoteRef:
key: secret/myapp/db # chemin dans Vault
property: password # champ dans le secret Vault
- secretKey: username
remoteRef:
key: secret/myapp/db
property: username
# SecretStore : la connexion à Vault
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault-backend
spec:
provider:
vault:
server: "https://vault.example.com"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes"
role: "myapp-role"
serviceAccountRef:
name: myapp
3. Vault Secrets Operator (HashiCorp officiel)
Plus récent que ESO, natif HashiCorp, avec support de la rotation automatique des secrets dans les Deployments.
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: myapp-config
spec:
vaultAuthRef: my-vault-auth
mount: secret
type: kv-v2
path: myapp/db
destination:
name: myapp-db-secret
create: true
rolloutRestartTargets:
- kind: Deployment
name: myapp # redémarre automatiquement le deployment quand le secret change
Configurer l’auth Kubernetes dans Vault
# Activer le backend d'auth Kubernetes
vault auth enable kubernetes
# Configurer avec le service account du cluster
vault write auth/kubernetes/config \
kubernetes_host="https://kubernetes.default.svc" \
kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
token_reviewer_jwt=@/var/run/secrets/kubernetes.io/serviceaccount/token
# Créer un rôle qui associe un SA Kubernetes à des policies Vault
vault write auth/kubernetes/role/myapp-role \
bound_service_account_names=myapp \
bound_service_account_namespaces=production \
policies=myapp-policy \
ttl=1h
Policy Vault : principe du moindre privilège
# myapp-policy.hcl
# L'application peut uniquement lire ses propres secrets
path "secret/data/myapp/*" {
capabilities = ["read"]
}
# Accès explicitement refusé aux secrets d'autres apps
path "secret/data/otherapp/*" {
capabilities = ["deny"]
}
vault policy write myapp-policy myapp-policy.hcl
Rotation de secrets avec Vault Dynamic Secrets
Pour les bases de données, Vault peut générer des credentials temporaires à la demande — les credentials expirent automatiquement, éliminant le problème des credentials permanents volés.
# Configurer le backend database
vault secrets enable database
vault write database/config/mydb \
plugin_name=postgresql-database-plugin \
connection_url="postgresql://{{username}}:{{password}}@db:5432/myapp" \
allowed_roles="myapp-role" \
username="vault-admin" \
password="vault-admin-password"
# Créer un rôle qui génère des credentials temporaires
vault write database/roles/myapp-role \
db_name=mydb \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
default_ttl="1h" \
max_ttl="24h"
Chaque pod reçoit maintenant des credentials PostgreSQL qui expirent au bout d’une heure. En cas de fuite, la fenêtre d’exploitation est limitée à cette durée.
Notre formation Vault couvre l’intégration Kubernetes avec des ateliers sur des clusters réels.