Aller au contenu
Sécurité medium

Vault Database : credentials dynamiques pour PostgreSQL, MySQL, MongoDB

22 min de lecture

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 classiqueAvec Vault Database
Un compte partagé app_userUn compte par workload v-token-app-xyz
Password dans le code/envPassword généré à la demande
Rotation manuelle annuelleDynamic 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"
  • 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

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.

Architecture du moteur Database secrets de Vault : connexion, rôle et génération de credentials

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.

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.

Fenêtre de terminal
vault secrets enable database

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.

Fenêtre de terminal
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"

Après configuration, faites tourner le mot de passe admin pour que seul Vault le connaisse :

Fenêtre de terminal
vault write -force database/rotate-root/postgres

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)
Fenêtre de terminal
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"

Variables disponibles :

VariableDescription
{{name}}Username généré par Vault
{{password}}Password généré par Vault
{{expiration}}Date d'expiration (PostgreSQL VALID UNTIL)

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.

Fenêtre de terminal
vault read database/creds/readonly

Sortie :

Key Value
--- -----
lease_id database/creds/readonly/abcd1234-5678-90ef
lease_duration 1h
lease_renewable true
password A1B2-c3D4-e5F6-g7H8
username v-token-readonly-xyz123abc

Ce qui se passe :

  1. Vault se connecte à PostgreSQL avec le compte admin
  2. Vault exécute le creation_statements avec un username/password générés
  3. Vault retourne les credentials et crée un lease
  4. L'application utilise ces credentials
  5. À expiration (ou révocation), Vault exécute revocation_statements

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 Vault
SELECT usename, valuntil FROM pg_user WHERE usename LIKE 'v-%';
-- Résultat
usename | valuntil
--------------------------+------------------------
v-token-readonly-xyz123 | 2026-03-16 15:00:00+00

Pour les applications legacy qui ne supportent pas les credentials dynamiques, Vault peut gérer un compte existant et le faire tourner automatiquement.

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.

Fenêtre de terminal
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"

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.

Fenêtre de terminal
vault read database/static-creds/legacy-app

Sortie :

Key Value
--- -----
last_vault_rotation 2026-03-16T10:00:00Z
password NewRotatedPass123
rotation_period 24h
ttl 23h45m
username legacy_app_user

Diffé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

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'usageChoixRaison
Nouvelle applicationDynamic roleTraçabilité par workload
Appli legacy avec user fixe imposéStatic rolePas d'alternative
Besoin d'audit précis par serviceDynamic roleUsername unique = audit clair
Outil sans support de relecture credentialsStatic rolePassword stable entre rotations
Connexions éphémères (jobs, CI)Dynamic roleTTL très court possible
Connexion longue durée (pool)Dynamic role + renouvellementOu static role si legacy

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.

Avant expiration, l'application peut prolonger :

Fenêtre de terminal
vault lease renew database/creds/readonly/abcd1234-5678-90ef

Le renouvellement est limité par le max_ttl du rôle.

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.

Fenêtre de terminal
# Révoquer un credential spécifique
vault lease revoke database/creds/readonly/abcd1234-5678-90ef
# Révoquer tous les credentials d'un rôle
vault lease revoke -prefix database/creds/readonly

Action réelle : Vault exécute les revocation_statements et supprime l'utilisateur de la base de données.

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.

Fenêtre de terminal
vault write -force database/rotate-role/legacy-app

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.

policy-app-readonly.hcl
# Obtenir des credentials readonly
path "database/creds/readonly" {
capabilities = ["read"]
}
# Renouveler ses propres leases
path "sys/leases/renew" {
capabilities = ["update"]
}
Fenêtre de terminal
vault policy write app-readonly policy-app-readonly.hcl

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.

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 hvac
import psycopg2
from 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']
}
@contextmanager
def 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'])
# Utilisation
with get_db_connection() as conn:
cursor = conn.cursor()
cursor.execute("SELECT * FROM users LIMIT 10")
rows = cursor.fetchall()

Pour les applications qui ne peuvent pas appeler Vault directement :

agent-config.hcl
template {
contents = <<EOF
[database]
host = postgres.example.com
port = 5432
database = 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"
}

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ômeCause probableSolution
permission deniedPolicy manquanteVérifier database/creds/<role>
connection refusedVault ne peut pas joindre la DBVérifier réseau/firewall
authentication failedCredentials admin invalidesVérifier la config connection
role not foundRôle inexistant ou mal nommévault list database/roles
TTL exceeds maxTTL demandé > max_ttlRéduire le TTL ou augmenter max_ttl
User non supprimé après révocationrevocation_statements incorrectVérifier la syntaxe SQL

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.

Fenêtre de terminal
# Vérifier la configuration
vault read database/config/postgres
# Tester une émission
vault read database/creds/readonly
# Vérifier les leases actifs
vault list sys/leases/lookup/database/creds/readonly

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éVaultVous
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.

  1. Dynamic role : nouveau credential par requête, traçabilité parfaite
  2. Static role : rotation périodique d'un compte existant (legacy uniquement)
  3. Connection : compte de gestion dédié à Vault (pas le superuser DB)
  4. rotate-root : après config, seul Vault connaît le mot de passe de gestion
  5. Lease : identifiant pour renouveler ou révoquer un dynamic credential
  6. Révocation : action réelle, l'user est supprimé de la DB
  7. Plugins : création_statements dépendent du moteur (PostgreSQL ≠ MongoDB)
  8. Intégration : l'app doit gérer renouvellement et rechargement des connexions

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