Vault Database secrets engine génère des credentials de base de données à la demande, avec une durée de vie limitée. Chaque service obtient son propre user/password, révocable individuellement.
Ce guide couvre PostgreSQL, MySQL et MongoDB, mais le principe s'applique à tous les plugins database supportés par Vault.
Pourquoi des credentials dynamiques pour les bases de données
Section intitulée « Pourquoi des credentials dynamiques pour les bases de données »Le compte de service partagé pose deux problèmes que la rotation manuelle ne résout pas. D'abord la portée de la compromission : un mot de passe qui fuite donne accès à la base pour tous les services qui l'utilisent, et le changer implique de redéployer chacun d'eux. Ensuite l'imputabilité : les journaux de la base ne montrent qu'un seul nom d'utilisateur, impossible donc de rattacher une requête à un service précis. Le tableau ci-dessous oppose les deux modèles sur ces points.
| Modèle classique | Avec Vault Database |
|---|---|
Un compte partagé app_user | Un compte par workload v-token-app-xyz |
| Password dans le code/env | Password généré à la demande |
| Rotation manuelle annuelle | Dynamic role : émission éphémère + révocation |
| Compromission = tout exposé | Compromission = un seul workload |
| Audit : "app_user a fait X" | Audit : "v-token-svc-abc a fait X" |
Prérequis
Section intitulée « Prérequis »- Vault installé et démarré
- Accès admin pour configurer le moteur
- Base de données PostgreSQL, MySQL ou MongoDB accessible depuis Vault
- Compte de gestion dédié à Vault pour créer/supprimer des utilisateurs
Architecture du moteur Database
Section intitulée « Architecture du moteur Database »Le moteur repose sur trois objets distincts qu'il faut savoir nommer avant de taper la moindre commande, car ils apparaissent tels quels dans les chemins de l'API. La connection décrit comment joindre la base, le role décrit quel utilisateur créer et avec quels droits, le lease est la trace de chaque credential émis. Le schéma montre leur enchaînement, de la configuration jusqu'à la révocation.
Composants :
- Connection : configuration de l'accès à la DB (URL, admin credentials)
- Role : template SQL + TTL pour les credentials générés
- Lease : référence au credential émis, pour renouvellement/révocation
Étape 1 : activer le moteur et configurer la connexion
Section intitulée « Étape 1 : activer le moteur et configurer la connexion »Cette première étape se fait avec un token administrateur et ne concerne que l'opérateur Vault. Elle branche le moteur sur un chemin, puis lui donne de quoi ouvrir une session sur la base. Tant qu'elle n'est pas terminée, aucune application ne peut demander de credentials.
Activer le moteur
Section intitulée « Activer le moteur »Sans argument, le moteur est monté sur le chemin database/ : c'est ce
préfixe que l'on retrouve dans toutes les commandes qui suivent. Ajoutez
-path= si vous devez gérer plusieurs parcs de bases séparément.
vault secrets enable databaseConfigurer la connexion PostgreSQL
Section intitulée « Configurer la connexion PostgreSQL »Le paramètre allowed_roles est le contrôle d'accès le plus important de ce bloc : il
énumère les rôles autorisés à utiliser cette connexion, et un rôle absent de la
liste sera refusé même s'il existe. Les marqueurs {{username}} et
{{password}} dans connection_url sont remplis par Vault avec les
identifiants du compte de gestion, ce qui évite de les écrire deux fois.
vault write database/config/postgres \ plugin_name="postgresql-database-plugin" \ allowed_roles="readonly,readwrite" \ connection_url="postgresql://{{username}}:{{password}}@postgres.example.com:5432/myapp?sslmode=require" \ username="vault_admin" \ password="VaultAdminPassword123"vault write database/config/mysql \ plugin_name="mysql-database-plugin" \ allowed_roles="readonly,readwrite" \ connection_url="{{username}}:{{password}}@tcp(mysql.example.com:3306)/myapp" \ username="vault_admin" \ password="VaultAdminPassword123"vault write database/config/mongodb \ plugin_name="mongodb-database-plugin" \ allowed_roles="readonly,readwrite" \ connection_url="mongodb://{{username}}:{{password}}@mongo.example.com:27017/admin?tls=true" \ username="vault_admin" \ password="VaultAdminPassword123"Rotation du mot de passe admin
Section intitulée « Rotation du mot de passe admin »Après configuration, faites tourner le mot de passe admin pour que seul Vault le connaisse :
vault write -force database/rotate-root/postgresÉtape 2 : créer un rôle dynamique
Section intitulée « Étape 2 : créer un rôle dynamique »Un rôle est le contrat entre Vault et la base : il décrit l'utilisateur à créer, ce qu'il a le droit de faire et combien de temps il vit. C'est le seul endroit où appliquer le moindre privilège, puisqu'un credential émis hérite exactement des droits écrits ici. Prévoyez un rôle par niveau d'accès plutôt qu'un rôle unique très permissif.
Un rôle définit :
- Le template SQL pour créer l'utilisateur
- Les permissions accordées
- Le TTL (durée de vie)
vault write database/roles/readonly \ db_name="postgres" \ creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \ GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \ revocation_statements="REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA public FROM \"{{name}}\"; \ DROP ROLE IF EXISTS \"{{name}}\";" \ default_ttl="1h" \ max_ttl="24h"vault write database/roles/readwrite \ db_name="postgres" \ creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \ GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \ revocation_statements="REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA public FROM \"{{name}}\"; \ DROP ROLE IF EXISTS \"{{name}}\";" \ default_ttl="1h" \ max_ttl="8h"vault write database/roles/readonly \ db_name="mysql" \ creation_statements="CREATE USER '{{name}}'@'%' IDENTIFIED BY '{{password}}'; \ GRANT SELECT ON myapp.* TO '{{name}}'@'%';" \ revocation_statements="DROP USER IF EXISTS '{{name}}'@'%';" \ default_ttl="1h" \ max_ttl="24h"Variables disponibles :
| Variable | Description |
|---|---|
{{name}} | Username généré par Vault |
{{password}} | Password généré par Vault |
{{expiration}} | Date d'expiration (PostgreSQL VALID UNTIL) |
Étape 3 : émettre des credentials
Section intitulée « Étape 3 : émettre des credentials »C'est la seule commande que l'application exécute réellement. Chaque appel crée un nouvel utilisateur dans la base et retourne un couple identifiant/mot de passe qui n'existera nulle part ailleurs : Vault ne les conserve pas, il ne garde que le lease qui permettra de les révoquer. Relancer la commande ne redonne donc pas les mêmes valeurs, elle en génère d'autres.
vault read database/creds/readonlySortie :
Key Value--- -----lease_id database/creds/readonly/abcd1234-5678-90eflease_duration 1hlease_renewable truepassword A1B2-c3D4-e5F6-g7H8username v-token-readonly-xyz123abcCe qui se passe :
- Vault se connecte à PostgreSQL avec le compte admin
- Vault exécute le
creation_statementsavec un username/password générés - Vault retourne les credentials et crée un lease
- L'application utilise ces credentials
- À expiration (ou révocation), Vault exécute
revocation_statements
Vérifier côté PostgreSQL
Section intitulée « Vérifier côté PostgreSQL »La preuve que le mécanisme fonctionne se lit dans la base elle-même : les
utilisateurs créés par Vault portent un nom préfixé par v- et une date
d'expiration dans la colonne valuntil.
-- Lister les utilisateurs VaultSELECT usename, valuntil FROM pg_user WHERE usename LIKE 'v-%';
-- Résultat usename | valuntil--------------------------+------------------------ v-token-readonly-xyz123 | 2026-03-16 15:00:00+00Static role : rotation d'un compte existant
Section intitulée « Static role : rotation d'un compte existant »Pour les applications legacy qui ne supportent pas les credentials dynamiques, Vault peut gérer un compte existant et le faire tourner automatiquement.
Créer un static role
Section intitulée « Créer un static role »Le compte visé par username doit exister au préalable dans la base :
Vault ne le crée pas, il se contente d'en changer le mot de passe tous les
rotation_period. La première rotation a lieu dès la création du rôle.
vault write database/static-roles/legacy-app \ db_name="postgres" \ username="legacy_app_user" \ rotation_statements="ALTER ROLE \"{{name}}\" WITH PASSWORD '{{password}}';" \ rotation_period="24h"Lire les credentials
Section intitulée « Lire les credentials »La lecture se fait sur static-creds et non sur creds : elle ne génère rien,
elle renvoie le mot de passe courant du compte. Le champ ttl indique le temps
restant avant la prochaine rotation, pas la durée de vie d'un credential.
vault read database/static-creds/legacy-appSortie :
Key Value--- -----last_vault_rotation 2026-03-16T10:00:00Zpassword NewRotatedPass123rotation_period 24httl 23h45musername legacy_app_userDifférence avec dynamic role :
- Le username est toujours le même (
legacy_app_user) - Le password change automatiquement tous les
rotation_period - Pas de lease à renouveler, mais le password change
Choisir dynamic role ou static role
Section intitulée « Choisir dynamic role ou static role »Le critère de décision n'est pas la sécurité mais la contrainte applicative. Si l'application accepte de recevoir un identifiant différent à chaque démarrage, le dynamic role est toujours préférable. Le static role ne se justifie que lorsqu'un nom d'utilisateur fixe est imposé, par un outil tiers ou par des droits accordés manuellement sur ce compte précis.
| Cas d'usage | Choix | Raison |
|---|---|---|
| Nouvelle application | Dynamic role | Traçabilité par workload |
| Appli legacy avec user fixe imposé | Static role | Pas d'alternative |
| Besoin d'audit précis par service | Dynamic role | Username unique = audit clair |
| Outil sans support de relecture credentials | Static role | Password stable entre rotations |
| Connexions éphémères (jobs, CI) | Dynamic role | TTL très court possible |
| Connexion longue durée (pool) | Dynamic role + renouvellement | Ou static role si legacy |
Révocation et renouvellement
Section intitulée « Révocation et renouvellement »Un credential dynamique disparaît soit parce que son lease arrive à terme,
soit parce qu'on le révoque explicitement. Les deux chemins déclenchent les
mêmes revocation_statements, donc la suppression effective de l'utilisateur
dans la base. Savoir les piloter à la main est indispensable le jour d'un
incident, quand il faut couper l'accès d'un service sans attendre l'expiration
naturelle.
Renouveler un lease
Section intitulée « Renouveler un lease »Avant expiration, l'application peut prolonger :
vault lease renew database/creds/readonly/abcd1234-5678-90efLe renouvellement est limité par le max_ttl du rôle.
Révoquer un lease
Section intitulée « Révoquer un lease »La révocation est immédiate et définitive : les connexions ouvertes avec ce
compte tomberont dès que la base l'exigera à nouveau. L'option -prefix coupe
d'un coup tous les credentials émis par un rôle, c'est le geste à connaître en
cas de suspicion de fuite.
# Révoquer un credential spécifiquevault lease revoke database/creds/readonly/abcd1234-5678-90ef
# Révoquer tous les credentials d'un rôlevault lease revoke -prefix database/creds/readonlyAction réelle : Vault exécute les revocation_statements et supprime
l'utilisateur de la base de données.
Forcer une rotation (static role)
Section intitulée « Forcer une rotation (static role) »Cette commande change le mot de passe du compte sans attendre la fin du
rotation_period et remet le compteur à zéro. L'application devra relire
static-creds pour retrouver un accès valide.
vault write -force database/rotate-role/legacy-appPolicy pour l'accès database
Section intitulée « Policy pour l'accès database »Une policy Vault autorise des opérations sur des chemins. Celle-ci donne le
strict nécessaire à une application en lecture seule : demander des credentials
sur son propre rôle, et prolonger ses leases. Notez qu'elle ne mentionne ni
database/config/ ni database/roles/ : l'application n'a aucune raison de
lire la configuration du moteur ni de créer des rôles.
# Obtenir des credentials readonlypath "database/creds/readonly" { capabilities = ["read"]}
# Renouveler ses propres leasespath "sys/leases/renew" { capabilities = ["update"]}vault policy write app-readonly policy-app-readonly.hclIntégration applicative
Section intitulée « Intégration applicative »Passer aux credentials dynamiques change une habitude de développement : la configuration de connexion n'est plus lue une fois au démarrage, elle a une date de péremption. Deux approches existent, soit l'application interroge Vault elle-même, soit un agent local écrit un fichier de configuration à sa place. La première demande du code, la seconde s'applique à un logiciel qu'on ne peut pas modifier.
Exemple Python avec psycopg2
Section intitulée « Exemple Python avec psycopg2 »La bibliothèque hvac est le client Python officiel de Vault. Le point
important de cet exemple est l'ordre des opérations : on s'authentifie d'abord
auprès de Vault avec une identité durable, ici un AppRole, et c'est cette
identité qui donne droit aux credentials éphémères de la base.
import hvacimport psycopg2from contextlib import contextmanager
def get_db_credentials(): """Obtenir des credentials dynamiques depuis Vault.""" client = hvac.Client(url='https://vault.example.com') # Auth via AppRole, Kubernetes, etc. client.auth.approle.login( role_id='app-role-id', secret_id='app-secret-id' )
# Lire les credentials dynamiques creds = client.secrets.database.generate_credentials( name='readonly', mount_point='database' )
return { 'user': creds['data']['username'], 'password': creds['data']['password'], 'lease_id': creds['lease_id'], 'lease_duration': creds['lease_duration'] }
@contextmanagerdef get_db_connection(): """Context manager avec credentials dynamiques.""" creds = get_db_credentials()
conn = psycopg2.connect( host='postgres.example.com', database='myapp', user=creds['user'], password=creds['password'] )
try: yield conn finally: conn.close() # Optionnel : révoquer immédiatement si TTL court # client.sys.revoke_lease(creds['lease_id'])
# Utilisationwith get_db_connection() as conn: cursor = conn.cursor() cursor.execute("SELECT * FROM users LIMIT 10") rows = cursor.fetchall()Vault Agent avec template
Section intitulée « Vault Agent avec template »Pour les applications qui ne peuvent pas appeler Vault directement :
template { contents = <<EOF[database]host = postgres.example.comport = 5432database = myapp{{ with secret "database/creds/readonly" }}username = {{ .Data.username }}password = {{ .Data.password }}{{ end }}EOF destination = "/etc/myapp/db.conf" perms = "0600" command = "systemctl reload myapp"}Dépannage
Section intitulée « Dépannage »Les messages d'erreur du moteur Database viennent de deux mondes différents, et c'est ce qui rend le diagnostic déroutant : certains sont émis par Vault (policy, rôle, TTL), d'autres remontent tels quels de la base de données (connexion, authentification, syntaxe SQL). Identifier l'émetteur oriente immédiatement vers la bonne configuration à corriger.
| Symptôme | Cause probable | Solution |
|---|---|---|
permission denied | Policy manquante | Vérifier database/creds/<role> |
connection refused | Vault ne peut pas joindre la DB | Vérifier réseau/firewall |
authentication failed | Credentials admin invalides | Vérifier la config connection |
role not found | Rôle inexistant ou mal nommé | vault list database/roles |
TTL exceeds max | TTL demandé > max_ttl | Réduire le TTL ou augmenter max_ttl |
| User non supprimé après révocation | revocation_statements incorrect | Vérifier la syntaxe SQL |
Debug de la connexion
Section intitulée « Debug de la connexion »Ces trois commandes se lancent dans l'ordre et isolent la panne. La première ne montre jamais le mot de passe de gestion, seulement l'URL et les rôles autorisés ; la troisième liste les leases encore actifs, utile pour repérer des credentials qui auraient dû être révoqués.
# Vérifier la configurationvault read database/config/postgres
# Tester une émissionvault read database/creds/readonly
# Vérifier les leases actifsvault list sys/leases/lookup/database/creds/readonlyCe que le moteur Database ne fait pas
Section intitulée « Ce que le moteur Database ne fait pas »Vault gère le cycle de vie des comptes, rien d'autre. Le partage des responsabilités ci-dessous évite la déception classique en production : une application qui plante à l'expiration parce que personne n'a écrit le code de renouvellement. Tout ce qui touche au pool de connexions et à la surveillance des expirations reste à votre charge.
| Responsabilité | Vault | Vous |
|---|---|---|
| Créer des users dynamiques | ✅ | - |
| Révoquer à expiration | ✅ | - |
| Stocker les credentials | ❌ | ✅ (le temps d'un lease) |
| Gérer le pool de connexions | ❌ | ✅ |
| Migrer les apps existantes | ❌ | ✅ |
| Monitorer les expirations | ❌* | ✅ |
| Backup de la DB | ❌ | ✅ |
*Vault expose des métriques, mais le monitoring est à votre charge.
À retenir
Section intitulée « À retenir »- Dynamic role : nouveau credential par requête, traçabilité parfaite
- Static role : rotation périodique d'un compte existant (legacy uniquement)
- Connection : compte de gestion dédié à Vault (pas le superuser DB)
rotate-root: après config, seul Vault connaît le mot de passe de gestion- Lease : identifiant pour renouveler ou révoquer un dynamic credential
- Révocation : action réelle, l'user est supprimé de la DB
- Plugins : création_statements dépendent du moteur (PostgreSQL ≠ MongoDB)
- Intégration : l'app doit gérer renouvellement et rechargement des connexions