Aller au contenu
Sécurité medium

Vault comme courtier d'identité (identity broker)

20 min de lecture

Vault n'est pas qu'un coffre-fort à secrets. Son vrai pouvoir est de servir de courtier d'identité : un workload prouve qui il est, Vault vérifie cette identité, puis délivre un accès temporaire vers une ressource cible.

Ce pattern est au cœur de l'approche zero trust : pas de credentials permanents, des accès basés sur l'identité vérifiée.

Le problème : credentials permanents dans les workloads

Section intitulée « Le problème : credentials permanents dans les workloads »

La façon la plus répandue de donner un accès à un pod consiste à lui monter un Secret Kubernetes contenant un mot de passe de base de données. Ce mot de passe ne change pas, il est identique pour toutes les répliques, et il reste valable tant que personne ne le fait tourner à la main. Comprendre pourquoi ce modèle finit toujours par poser problème est le préalable au pattern décrit dans cette page.

Le schéma ci-dessous montre la chaîne habituelle : un secret créé une fois, monté dans le pod, puis présenté tel quel à PostgreSQL. Le credential est permanent et le pod le détient en clair, ce qui a trois conséquences prévisibles.

Modèle classique : secret permanent dans Kubernetes

Ce qui arrive tôt ou tard :

  • Le secret fuite (commit Git, log, debug)
  • Une compromission d'un pod = accès permanent à la DB
  • L'audit montre "app_user a fait X" mais pas quel pod

La vision première de Vault est souvent "un endroit sécurisé pour stocker des secrets". C'est vrai, mais c'est la partie la moins intéressante.

La vraie valeur de Vault :

FonctionDescription
AuthentificationVérifie l'identité du demandeur
AutorisationVérifie ce qu'il a le droit de demander
ÉmissionGénère un credential temporaire, unique
AuditTrace qui a demandé quoi, quand
RévocationInvalide le credential à tout moment

Vault transforme une identité prouvée en accès temporaire.

Le courtier d'identité (identity broker) insère Vault entre le workload et la ressource qu'il veut atteindre. Le workload ne s'adresse plus directement à la base de données avec un mot de passe : il s'adresse d'abord à Vault avec sa preuve d'identité de plateforme, et Vault lui remet en échange un credential fabriqué pour lui, valable quelques minutes ou quelques heures.

Identity Broker Pattern : workload vers Vault vers ressource cible

Le workload ne possède jamais de credential permanent vers la cible. Il possède une identité source (service account K8s, rôle IAM, token OIDC) qui lui permet de prouver qui il est à Vault.

Le pattern identity broker diffère fondamentalement de la distribution classique de secrets :

  • Le workload ne reçoit pas un secret "par défaut" : il obtient un accès parce qu'il a prouvé qui il est
  • Vault devient le point de traduction entre identité source (K8s service account, cloud IAM, token OIDC) et accès cible (credentials base de données, clés cloud, certificats)
  • Pas de secret pré-distribué : l'identité source suffit à initier le flux, Vault fait le reste

Flux complet : authentification → identité → accès dynamique

Section intitulée « Flux complet : authentification → identité → accès dynamique »

Les huit étapes ci-dessous décrivent un cycle complet, de l'arrivée du pod jusqu'à la révocation automatique de son credential. Deux moments méritent votre attention : l'étape 2, où Vault interroge l'API Kubernetes et devient donc dépendant de sa disponibilité, et l'étape 8, où la révocation est portée par Vault et non par l'application. Les commandes montrées supposent l'auth Kubernetes et un secrets engine database déjà configurés.

  1. Le workload s'authentifie

    Le pod Kubernetes présente son service account token à Vault :

    Fenêtre de terminal
    vault write auth/kubernetes/login \
    role="my-app" \
    jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)"
  2. Vault vérifie l'identité

    Vault contacte l'API Kubernetes pour valider :

    • Le token est-il valide ?
    • Le service account correspond-il au rôle Vault configuré ?
    • Le namespace est-il autorisé ?
  3. Vault associe l'authentification à sa couche Identity

    Si valide, Vault associe l'authentification à sa couche Identity via des aliases, et peut exploiter des métadonnées pour appliquer des policies ou tracer l'accès :

    • Service account name
    • Namespace
    • Annotations Kubernetes (si configurées)
  4. Vault délivre un token Vault

    Le workload reçoit un token Vault avec les policies attachées au rôle.

  5. Le workload demande un credential dynamique

    Fenêtre de terminal
    vault read database/creds/app-readonly
  6. Vault génère et renvoie

    • Crée un user PostgreSQL unique
    • Retourne username/password avec lease
  7. Le workload utilise le credential

    Se connecte à PostgreSQL avec ces credentials temporaires.

  8. À expiration

    Vault révoque le credential (supprime l'user PostgreSQL).

Méthodes d'authentification adaptées au brokering

Section intitulée « Méthodes d'authentification adaptées au brokering »

Vault propose une vingtaine de méthodes d'authentification, mais toutes ne conviennent pas au brokering. Celles qui fonctionnent partagent un point commun : elles reposent sur une identité que la plateforme délivre elle-même au workload, sans qu'on ait à déposer un secret au préalable. Les trois familles ci-dessous couvrent la quasi-totalité des cas ; à l'inverse, une méthode comme AppRole suppose de distribuer un secret_id, ce qui ramène le problème de départ.

Idéal pour : pods dans un cluster K8s.

Le pod prouve son identité via son service account token. Vault contacte l'API Kubernetes pour valider.

Fenêtre de terminal
# Configurer l'auth Kubernetes dans Vault
vault auth enable kubernetes
vault write auth/kubernetes/config \
kubernetes_host="https://kubernetes.default.svc" \
kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
vault write auth/kubernetes/role/my-app \
bound_service_account_names="my-app-sa" \
bound_service_account_namespaces="production" \
policies="app-policy" \
ttl="1h"

Idéal pour : VMs, fonctions serverless, workloads cloud.

Le workload prouve son identité via son IAM role (AWS), service account (GCP), ou managed identity (Azure).

Fenêtre de terminal
# AWS IAM auth
vault auth enable aws
vault write auth/aws/role/ec2-role \
auth_type="iam" \
bound_iam_principal_arn="arn:aws:iam::123456789012:role/my-ec2-role" \
policies="app-policy" \
ttl="1h"

Avantage : pas de secret à distribuer au workload, il utilise son identité cloud native.

Idéal pour : CI/CD (GitHub Actions, GitLab CI), authentification humaine.

Le workload présente un token JWT signé par un provider OIDC. Vault vérifie la signature et les claims.

Fenêtre de terminal
vault auth enable jwt
vault write auth/jwt/config \
oidc_discovery_url="https://token.actions.githubusercontent.com" \
bound_issuer="https://token.actions.githubusercontent.com"
vault write auth/jwt/role/github-deploy \
role_type="jwt" \
bound_audiences="https://github.com/my-org" \
bound_claims='{"repository": "my-org/my-repo"}' \
user_claim="actor" \
policies="deploy-policy" \
ttl="15m"

Exemple : pod Kubernetes → credentials PostgreSQL

Section intitulée « Exemple : pod Kubernetes → credentials PostgreSQL »

Cet exemple assemble les pièces vues plus haut sur un cas complet : côté Vault la configuration du rôle et de la policy, côté cluster l'annotation du pod. Le point à retenir est la jointure entre les deux : c'est le nom du service account qui relie le pod au rôle Vault, et rien d'autre.

Un pod order-service dans le namespace production a besoin d'accéder à PostgreSQL en lecture.

Ces quatre commandes s'exécutent une seule fois, avec un token administrateur. Le default_ttl du rôle base de données fixe la durée de vie des credentials générés, et max_ttl la limite au-delà de laquelle plus aucun renouvellement n'est possible : passé ce délai, le workload doit redemander un credential neuf.

Fenêtre de terminal
# 1. Auth Kubernetes configurée (voir ci-dessus)
# 2. Database secrets configurés
vault write database/roles/order-readonly \
db_name="production-postgres" \
creation_statements="..." \
default_ttl="1h" \
max_ttl="4h"
# 3. Policy
vault policy write order-service - <<EOF
path "database/creds/order-readonly" {
capabilities = ["read"]
}
EOF
# 4. Rôle Kubernetes lié à la policy
vault write auth/kubernetes/role/order-service \
bound_service_account_names="order-service" \
bound_service_account_namespaces="production" \
policies="order-service" \
ttl="1h"

Côté cluster, l'application n'a aucun code Vault à embarquer. Le Vault Agent Injector ajoute un conteneur d'initialisation et un sidecar qui s'authentifient, récupèrent le credential et l'écrivent dans un fichier partagé. Tout se pilote par des annotations, ce qui rend le manifeste lisible mais aussi silencieux en cas d'erreur de frappe : une annotation mal orthographiée est simplement ignorée, sans message.

apiVersion: v1
kind: Pod
metadata:
name: order-service
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "order-service"
vault.hashicorp.com/agent-inject-secret-db: "database/creds/order-readonly"
vault.hashicorp.com/agent-inject-template-db: |
{{- with secret "database/creds/order-readonly" -}}
PGUSER={{ .Data.username }}
PGPASSWORD={{ .Data.password }}
{{- end }}
spec:
serviceAccountName: order-service
containers:
- name: app
image: order-service:2.1.0
command: ["source", "/vault/secrets/db", "&&", "./start.sh"]

Résultat :

  1. Le pod démarre avec son service account
  2. Vault Agent s'authentifie via Kubernetes auth
  3. Vault génère des credentials PostgreSQL uniques
  4. Le fichier /vault/secrets/db contient user/pass
  5. L'app utilise ces credentials
  6. Vault Agent renouvelle automatiquement avant expiration

Le même pattern s'applique hors de Kubernetes, avec une identité fournie cette fois par la forge. GitHub Actions peut émettre un token OIDC décrivant le dépôt, la branche et le workflow en cours ; Vault vérifie ces informations et délivre des credentials AWS temporaires. Résultat : plus aucune clé d'accès AWS stockée dans les secrets du dépôt.

Un workflow GitHub Actions doit déployer sur S3.

Le credential_type: assumed_role demande à Vault d'appeler AWS STS pour endosser un rôle existant, plutôt que de créer un utilisateur IAM jetable. Les bound_claims du rôle JWT sont la véritable barrière de sécurité : sans la contrainte sur ref, n'importe quelle branche du dépôt, y compris celle d'une pull request externe, obtiendrait les mêmes droits de déploiement.

Fenêtre de terminal
# 1. JWT auth pour GitHub Actions (voir ci-dessus)
# 2. AWS secrets configurés
vault write aws/roles/github-deploy \
credential_type="assumed_role" \
role_arns="arn:aws:iam::123456789012:role/S3DeployRole" \
default_sts_ttl="15m" \
max_sts_ttl="1h"
# 3. Policy
vault policy write github-deploy - <<EOF
path "aws/creds/github-deploy" {
capabilities = ["read"]
}
EOF
# 4. Rôle JWT lié
vault write auth/jwt/role/github-deploy \
bound_claims='{"repository": "my-org/my-repo", "ref": "refs/heads/main"}' \
policies="github-deploy" \
ttl="15m"

La permission id-token: write est indispensable : sans elle, GitHub n'émet aucun token OIDC et l'authentification Vault échoue avant même le premier appel réseau. Elle est déclarée au niveau du job, le workflow partant de permissions: {} pour n'accorder aucun droit par défaut aux autres jobs.

permissions: {}
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write # Nécessaire pour OIDC
contents: read
steps:
- uses: hashicorp/vault-action@892a26828f195e65540a40b4768ae4571f51ebfc # v4.0.0
with:
url: https://vault.example.com
method: jwt
role: github-deploy
jwtGithubAudience: https://github.com/my-org
secrets: |
aws/creds/github-deploy access_key | AWS_ACCESS_KEY_ID ;
aws/creds/github-deploy secret_key | AWS_SECRET_ACCESS_KEY ;
aws/creds/github-deploy security_token | AWS_SESSION_TOKEN
- run: aws s3 sync ./dist s3://my-bucket

Résultat :

  • Le workflow prouve son identité via le token OIDC de GitHub
  • Vault vérifie que c'est bien my-org/my-repo sur main
  • Vault assume un rôle IAM et retourne des credentials STS
  • Le workflow déploie sur S3 avec des credentials valides 15 min

Le tableau oppose les deux approches ligne par ligne. Lisez-le en séparant deux choses que l'on confond souvent : la complexité de gestion au quotidien, que le brokering réduit fortement, et la complexité de plateforme, qu'il augmente en ajoutant Vault à votre chemin critique. Les quatre dernières lignes portent précisément sur ce basculement.

AspectDistribution classiqueIdentity brokering
Secret permanent vers la cibleOui (dans K8s Secret, env vars)Non
Rotation des credentialsManuelle, coordinationAutomatique par TTL
Credential unique par workloadRarementToujours
Audit"app_user a fait X""pod order-abc123 a fait X"
CompromissionAccès permanentAccès limité au TTL
Complexité initialeFaibleMoyenne à élevée
Complexité de rotation/gestionHaute (coordination manuelle)Faible (automatisé)
Complexité de plateformeFaibleHaute (Vault à opérer)

Le pattern déplace le risque plus qu'il ne le supprime : vous n'avez plus de mot de passe permanent qui traîne, mais vous avez un service dont dépend le démarrage de vos applications. Les trois points ci-dessous sont ceux qui reviennent en production, avec les mitigations correspondantes.

Le workload ne peut pas démarrer si Vault est inaccessible. Mitigations :

  • Vault en HA
  • Cache local (Vault Agent)
  • TTL assez longs pour survivre à une panne courte

Chaque demande de credential ajoute un appel réseau. Mitigations :

  • Vault Agent avec cache
  • TTL suffisants pour éviter trop de renouvellements
  • Pooling de connexions côté app

Plus complexe qu'un Secret K8s statique. À justifier par :

  • Nombre de services
  • Exigences de sécurité/audit
  • Fréquence de rotation souhaitée

Vault ne se contente pas de vérifier l'identité : il peut aussi s'en servir pour construire les permissions à la volée.

Avec les templated policies, vous pouvez exploiter les métadonnées d'alias pour créer des ACL dynamiques :

policy-namespace-scoped.hcl
path "database/creds/{{identity.entity.aliases.auth_kubernetes_*.metadata.service_account_namespace}}-*" {
capabilities = ["read"]
}

Avantages :

  • Une seule policy pour plusieurs namespaces
  • Isolation automatique : chaque namespace n'accède qu'à ses propres credentials
  • Moins de policies à maintenir

Vault excelle comme courtier d'identité pour secrets dynamiques : le workload reçoit les credentials et gère la connexion lui-même.

Si vous voulez aller plus loin vers un accès interactif sans exposition directe des credentials (SSH, RDP, bases de données), HashiCorp Boundary couvre un autre niveau :

  • Brokering : le client reçoit les credentials (ce que fait Vault)
  • Credential injection : un worker intermédiaire cache les credentials au client final (ce que fait Boundary)

Boundary complète Vault pour les accès interactifs privilégiés.

  1. Vault = courtier d'identité : transforme une identité prouvée en accès temporaire vers une ressource cible
  2. Pas de credential permanent vers la cible : le workload conserve une preuve d'identité source (token K8s, OIDC, IAM) mais pas de secret vers la ressource finale
  3. Couche Identity : Vault associe les authentifications à des entities et aliases, permettant consolidation et policies dynamiques
  4. Auth native : K8s service account, cloud IAM, OIDC, pas de secret à distribuer au workload
  5. Credential unique : chaque workload obtient ses propres credentials
  6. Audit complet : qui (identité source) a demandé quoi (secret) et quand
  7. TTL courts réduisent le besoin de révocation : mais ne remplacent pas totalement la capacité à révoquer avant expiration
  8. Compromis plateforme : on gagne en sécurité et gestion, mais Vault devient une dépendance critique à opérer

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn