Le secrets engine KV (Key-Value) est le plus utilisé dans Vault. Il stocke des paires clé/valeur comme des mots de passe, clés API ou tokens. La version KV v2 ajoute le versioning : chaque modification crée une nouvelle version, permettant de récupérer ou restaurer des valeurs précédentes.
Ce guide couvre toutes les opérations sur les secrets KV v2, du stockage basique au contrôle de concurrence avec Check-and-Set.
Prérequis
Section intitulée « Prérequis »- Vault installé et démarré (mode dev ou production)
- Variables d'environnement configurées (
VAULT_ADDR,VAULT_TOKEN)
Comprendre KV v1 vs KV v2
Section intitulée « Comprendre KV v1 vs KV v2 »Vault propose deux versions du secrets engine KV, et elles ne diffèrent pas
seulement par des options : ce sont deux moteurs distincts, avec des chemins
API différents et des policies incompatibles. Une policy écrite pour un
mount v1 ne fonctionne pas sur un mount v2. La ligne décisive du tableau est
Versioning : c'est elle qui entraîne tout le reste, le soft delete comme le
Check-and-Set. Retenez la dernière ligne comme règle de choix : KV v2 est le
défaut, KV v1 ne se justifie que sur un mount hérité ou sur un volume
d'écriture où le coût du stockage des versions devient un problème mesuré.
| Aspect | KV v1 | KV v2 |
|---|---|---|
| Versioning | Non | Oui (conserve les versions) |
| Soft delete | Non | Oui (récupérable) |
| Check-and-Set | Non | Oui (évite les écrasements) |
| Métadonnées | Basiques | Complètes (timestamps, custom) |
| Cas d'usage | Legacy, très haute performance | Standard, recommandé |
Activer KV v2 (production)
Section intitulée « Activer KV v2 (production) »Le mount point que vous choisissez ici est définitif dans les faits : il
apparaît dans toutes vos policies, dans les URL de l'API et dans les
configurations de vos applications. Le renommer plus tard signifie réécrire
tout cela. Prenez donc un nom explicite (secret, kv, ou un nom par domaine
métier) plutôt que le nom par défaut du mode dev par habitude. La commande de
vérification confirme deux choses : que le moteur est monté au bon endroit, et
que la colonne Options de la sortie détaillée contient bien version:2. Sans
ce -detailed, la liste affiche seulement le type kv et vous ne pouvez pas
distinguer une v1 d'une v2.
# Activer KV v2 sur le path "secret/"vault secrets enable -path=secret kv-v2
# Vérifier l'activationvault secrets list -detailed | grep '^secret/'Stocker un secret
Section intitulée « Stocker un secret »vault kv put sert aussi bien à la création qu'à la mise à jour : la
commande est la même, c'est Vault qui décide selon l'existence du chemin. Un
point mérite d'être posé tout de suite car il surprend souvent : l'écriture
remplace l'intégralité du secret, elle ne fusionne pas. Réécrire un seul
champ efface donc tous les autres. Pour ne modifier qu'une clé sans toucher aux
autres, la commande à utiliser est vault kv patch.
Syntaxe de base
Section intitulée « Syntaxe de base »Deux écritures coexistent pour désigner un secret, et elles ne se valent pas.
L'aide intégrée de vault kv put qualifie la première de syntaxe dépréciée
(« deprecated path-like syntax ») justement parce qu'elle ressemble à un chemin
API sans en être un : secret/chemin/du/secret n'existe pas côté serveur, le
vrai chemin est secret/data/chemin/du/secret. La seconde forme sépare
explicitement le mount point du chemin relatif et supprime cette
ambiguïté. Utilisez -mount= partout où vous le pouvez.
# Forme dépréciée pour KV v2 : ressemble à un chemin API sans en être unvault kv put secret/chemin/du/secret clé1="valeur1" clé2="valeur2"
# Forme recommandée avec -mountvault kv put -mount=secret chemin/du/secret clé1="valeur1" clé2="valeur2"Exemple concret
Section intitulée « Exemple concret »Imaginons une application web qui a besoin de se connecter à une base de données PostgreSQL :
vault kv put -mount=secret apps/webapp/database \ host="db.example.com" \ port="5432" \ username="webapp_user" \ password="SuperSecret123!"Sortie :
========== Secret Path ==========secret/data/apps/webapp/database
======= Metadata =======Key Value--- -----created_time 2026-03-16T13:32:40.176016777Zcustom_metadata <nil>deletion_time n/adestroyed falseversion 1Comprendre les chemins API (path réel vs logique)
Section intitulée « Comprendre les chemins API (path réel vs logique) »Vous écrivez sur secret/apps/webapp/database, mais Vault stocke sur
secret/data/apps/webapp/database. Pourquoi ?
KV v2 utilise des sous-chemins API pour séparer les opérations :
| Opération | Chemin API | Exemple |
|---|---|---|
| Données (CRUD) | secret/data/... | secret/data/apps/webapp/database |
| Métadonnées | secret/metadata/... | secret/metadata/apps/webapp/database |
| Suppression (versions) | secret/delete/... | secret/delete/apps/webapp/database |
| Restauration | secret/undelete/... | secret/undelete/apps/webapp/database |
| Destruction | secret/destroy/... | secret/destroy/apps/webapp/database |
La CLI masque cette complexité, mais vous la verrez dans :
- les policies Vault (capabilities sur
secret/data/*) - l'API HTTP directe
- les intégrations tierces (Terraform, Kubernetes operator...)
Écrire des valeurs depuis un fichier
Section intitulée « Écrire des valeurs depuis un fichier »Vault accepte plusieurs formats d'entrée. Pour des secrets complexes, vous pouvez lire depuis un fichier.
Option 1 : Paires clé=valeur depuis stdin
# L'argument "-" seul demande à la CLI de lire un objet JSON sur stdinecho '{"api_key":"sk-abc123","api_secret":"secret-xyz789"}' | \ vault kv put -mount=secret apps/webapp/api-config -Le contenu JSON est interprété comme plusieurs paires clé=valeur. Vault
stockera deux champs : api_key et api_secret. L'intérêt dépasse le confort :
les valeurs ne figurent plus dans les arguments du processus vault, donc
plus dans la sortie de ps que n'importe quel utilisateur de la machine peut
lire. Le echo de la démonstration reste, lui, écrit dans l'historique du
shell ; en usage réel, faites arriver le JSON depuis un fichier à permissions
0600 ou depuis la sortie d'un autre outil.
Option 2 : Stocker un blob entier
Si vous voulez stocker un fichier entier (certificat, clé privée) comme une seule valeur :
vault kv put -mount=secret apps/webapp/tls-cert \ cert="$(cat server.crt)" \ key="$(cat server.key)"Option 3 : Fichier JSON avec @
# Le fichier doit contenir un objet JSON avec les paires clé-valeurinstall -m 0600 /dev/null creds.jsoncat > creds.json << 'EOF'{ "username": "admin", "password": "secret123"}EOF
vault kv put -mount=secret apps/webapp/creds @creds.json
# Effacer le contenu avant de retirer le fichiershred -u creds.jsonCette commande crée un secret avec deux champs : username et password.
Le install -m 0600 crée le fichier vide avec les bonnes permissions avant
d'y écrire quoi que ce soit : créer puis chmod laisse une fenêtre pendant
laquelle le secret est lisible par tout le monde. shred -u écrase le contenu
avant de supprimer l'entrée de répertoire, ce que rm seul ne fait pas. Sa
propre page d'aide prévient toutefois qu'il suppose une réécriture sur
place : sur un système de fichiers journalisé, copy-on-write ou sur un SSD
avec wear leveling, la garantie n'est pas totale. Le vrai réflexe reste
d'écrire ce type de fichier dans un tmpfs (/dev/shm, jamais persisté sur
disque).
Lire un secret
Section intitulée « Lire un secret »Lecture simple
Section intitulée « Lecture simple »Sans option, vault kv get renvoie la version la plus récente et affiche
les valeurs en clair dans le terminal. Deux blocs se succèdent dans la
sortie : Metadata, qui décrit l'état de la version (numéro, dates, drapeau
destroyed), et Data, qui contient les champs. Le premier réflexe devant un
secret qui « ne marche pas » est de lire version et deletion_time : un
secret supprimé en douceur affiche encore ses métadonnées mais plus aucune
donnée.
vault kv get -mount=secret apps/webapp/databaseSortie :
========== Secret Path ==========secret/data/apps/webapp/database
======= Metadata =======Key Value--- -----created_time 2026-03-16T13:32:40.176016777Zcustom_metadata <nil>deletion_time n/adestroyed falseversion 1
====== Data ======Key Value--- -----host db.example.compassword SuperSecret123!port 5432username webapp_userExtraire une valeur spécifique
Section intitulée « Extraire une valeur spécifique »Pour l'intégration dans des scripts, extrayez une seule valeur :
# Extraire le mot de passevault kv get -mount=secret -field=password apps/webapp/database# SuperSecret123!Format JSON
Section intitulée « Format JSON »Pour le parsing automatisé, utilisez le format JSON :
vault kv get -mount=secret -format=json apps/webapp/database | \ jq -r '.data.data.password'# SuperSecret123!Éviter les écrasements avec Check-and-Set
Section intitulée « Éviter les écrasements avec Check-and-Set »Quand plusieurs processus écrivent sur le même secret, il y a un risque d'écrasement silencieux. Check-and-Set (CAS) résout ce problème.
Le problème
Section intitulée « Le problème »Sans CAS, si deux processus modifient le même secret en parallèle :
- Processus A lit la version 5
- Processus B lit la version 5
- Processus A écrit → version 6
- Processus B écrit → version 7, écrasant les changements de A
La solution : -cas
Section intitulée « La solution : -cas »L'option -cas=<version> impose que l'écriture ne réussisse que si la
version courante correspond :
# Écrire seulement si la version actuelle est 2vault kv put -mount=secret -cas=2 apps/webapp/database \ password="NewSecurePassword!"Si un autre processus a modifié le secret entre-temps :
Error writing data to secret/data/apps/webapp/database: Error making API request.
Code: 400. Errors:* check-and-set parameter did not match the current versionCréer seulement si n'existe pas
Section intitulée « Créer seulement si n'existe pas »Le cas particulier -cas=0 signifie « version courante attendue : aucune ». Il
transforme l'écriture en création exclusive et échoue si le chemin existe
déjà. C'est l'outil du provisionnement : un script de bootstrap qui pose un mot
de passe initial doit refuser d'écraser celui d'une installation déjà en
service, plutôt que de découvrir la casse au premier redémarrage
d'application.
# Échoue si le secret existe déjàvault kv put -mount=secret -cas=0 apps/webapp/new-secret \ api_key="sk-newkey123"Forcer CAS au niveau métadonnées
Section intitulée « Forcer CAS au niveau métadonnées »Vous pouvez imposer l'usage de CAS pour tous les écrivains :
# Toute écriture sans -cas échoueravault kv metadata put -mount=secret -cas-required=true apps/webapp/databaseTentative d'écriture sans CAS :
Error writing data to secret/data/apps/webapp/database: Error making API request.
Code: 400. Errors:* check-and-set parameter required for this callVersioning
Section intitulée « Versioning »Chaque modification d'un secret crée une nouvelle version. C'est l'un des avantages majeurs de KV v2.
Mettre à jour un secret
Section intitulée « Mettre à jour un secret »Quand vous modifiez un secret, la version incrémente automatiquement :
# Mise à jour du mot de passe (crée version 2)vault kv put -mount=secret apps/webapp/database \ host="db.example.com" \ port="5432" \ username="webapp_user" \ password="NewPassword456!"Sortie :
...version 2Lire une version spécifique
Section intitulée « Lire une version spécifique »Toutes les versions restent lisibles tant qu'elles n'ont pas été détruites ou
évincées par max-versions. C'est ce qui permet de répondre en quelques
secondes à « quelle valeur ce secret avait-il avant le déploiement de mardi »,
sans restaurer une sauvegarde. Attention à la contrepartie : un mot de passe
révoqué reste lisible dans l'historique par quiconque a read sur le
chemin, ce qui rend max-versions et delete-version-after nécessaires sur
les secrets sensibles.
# Lire la version 1 (ancien mot de passe)vault kv get -mount=secret -version=1 apps/webapp/databaseVoir l'historique des versions
Section intitulée « Voir l'historique des versions »vault kv metadata get liste toutes les versions sans jamais afficher leurs
valeurs. C'est la commande à donner à quelqu'un qui doit auditer un secret
sans avoir le droit de le lire : elle ne demande la capability read que sur
secret/metadata/.... Dans la sortie, current_version indique ce que renvoie
un get sans option, oldest_version la plus ancienne version encore
récupérable, et chaque bloc Version N porte son deletion_time et son drapeau
destroyed. Une version détruite reste listée : c'est la métadonnée qui
subsiste, pas la donnée.
vault kv metadata get -mount=secret apps/webapp/databaseSortie (extrait) :
======= Metadata =======Key Value--- -----cas_required falsecreated_time 2026-03-16T13:32:40.176016777Zcurrent_version 2delete_version_after 0smax_versions 0oldest_version 0updated_time 2026-03-16T13:45:12.234567890Z
====== Version 1 ======Key Value--- -----created_time 2026-03-16T13:32:40.176016777Zdeletion_time n/adestroyed false
====== Version 2 ======Key Value--- -----created_time 2026-03-16T13:45:12.234567890Zdeletion_time n/adestroyed falseRevenir à une version antérieure
Section intitulée « Revenir à une version antérieure »Si une mise à jour pose problème, vous pouvez "rollback" :
# Restaurer la version 1 comme nouvelle version (crée version 3)vault kv rollback -mount=secret -version=1 apps/webapp/databaseSupprimer des secrets
Section intitulée « Supprimer des secrets »KV v2 distingue plusieurs niveaux de suppression. Le modèle mental :
| Action | Effet | Réversible ? |
|---|---|---|
delete | Masque la version (soft delete) | Oui (undelete) |
undelete | Rend la version visible à nouveau | Sans objet |
destroy | Supprime les données de la version | Non |
metadata delete | Supprime tout : versions + métadonnées | Non |
Delete (soft delete)
Section intitulée « Delete (soft delete) »Marque la version comme supprimée mais conserve les données :
vault kv delete -mount=secret apps/webapp/databaseLes données ne sont plus accessibles par un get normal :
vault kv get -mount=secret apps/webapp/database# Affiche uniquement les métadonnées, pas les donnéesUndelete (restauration)
Section intitulée « Undelete (restauration) »undelete efface le deletion_time d'une version et la rend de nouveau
lisible. Deux différences avec delete méritent l'attention. La restauration
exige la liste explicite des versions via -versions=, il n'y a pas de
comportement par défaut sur la dernière. Et elle n'a d'effet que sur une
version simplement supprimée : sur une version détruite, la commande réussit
sans rien ramener, puisque les données n'existent plus.
# Restaurer la version 2vault kv undelete -mount=secret -versions=2 apps/webapp/databaseLes données sont à nouveau accessibles.
Destroy (suppression définitive des données)
Section intitulée « Destroy (suppression définitive des données) »Supprime définitivement les données d'une ou plusieurs versions :
# Supprimer définitivement les versions 1 et 2vault kv destroy -mount=secret -versions=1,2 apps/webapp/databaseSupprimer l'objet secret entier
Section intitulée « Supprimer l'objet secret entier »Pour supprimer complètement un secret (toutes versions + métadonnées) :
vault kv metadata delete -mount=secret apps/webapp/databaseCette commande supprime tout : le secret n'existe plus dans Vault.
Organiser vos secrets pour les policies
Section intitulée « Organiser vos secrets pour les policies »Une bonne organisation facilite la gestion des policies et la maintenance. La structure de vos chemins détermine directement vos policies de sécurité.
Structure recommandée
Section intitulée « Structure recommandée »L'arborescence ci-dessous n'est pas une convention Vault : le moteur accepte
n'importe quel chemin. Elle est construite pour que chaque branche corresponde
à une policy. Regardez le premier niveau (apps/, infra/, teams/) : c'est
lui qui portera vos wildcards. Un secret rangé au mauvais endroit oblige soit à
élargir une policy existante, soit à en écrire une pour une seule clé, et c'est
ainsi que les autorisations dérivent.
secret/├── apps/ # Secrets applicatifs│ ├── webapp/│ │ ├── database # Credentials DB│ │ ├── api-keys # Clés API tierces│ │ └── config # Config sensible│ └── mobile-app/│ └── firebase├── infra/ # Secrets infrastructure│ ├── aws/│ │ └── credentials│ └── docker-registry/│ └── credentials└── teams/ # Secrets par équipe ├── dev/ └── ops/Pourquoi cette structure ?
Section intitulée « Pourquoi cette structure ? »Cette hiérarchie permet d'écrire des policies précises sans wildcards trop larges :
# Policy pour l'application webapp (lecture seule)path "secret/data/apps/webapp/*" { capabilities = ["read"]}
# Policy pour l'équipe ops (lecture/écriture sur infra)path "secret/data/infra/*" { capabilities = ["create", "update", "read", "delete"]}
path "secret/metadata/infra/*" { capabilities = ["list", "read"]}Critères d'organisation
Section intitulée « Critères d'organisation »Quatre axes de découpage sont possibles et vous devrez en choisir un comme niveau le plus haut de l'arborescence, puis combiner les autres en dessous. Le critère à placer en premier est celui sur lequel vous voulez la frontière d'isolation la plus stricte. Sur la plupart des installations c'est l'environnement : une policy de production ne doit jamais pouvoir déraper vers un chemin de développement à cause d'un wildcard mal placé.
| Critère | Exemple de chemin | Avantage |
|---|---|---|
| Par application | secret/apps/webapp/... | Isolation par service |
| Par environnement | secret/prod/..., secret/dev/... | Séparation prod/dev |
| Par équipe | secret/teams/backend/... | Ownership clair |
| Par type | secret/databases/..., secret/api-keys/... | Gestion transverse |
Combinez ces critères selon votre contexte :
secret/prod/apps/webapp/database ou secret/apps/webapp/prod/database.
Lister les secrets
Section intitulée « Lister les secrets »vault kv list explore un niveau de l'arborescence à la fois : il ne descend
pas récursivement. Les entrées terminées par / sont des répertoires
intermédiaires qu'il faut lister à leur tour, celles sans slash sont des
secrets. Cette commande frappe le chemin secret/metadata/... et non
secret/data/... : une application qui a le droit de lire ses secrets peut
donc très bien recevoir un permission denied sur un list, ce qui est le
comportement souhaité.
# Lister les secrets sous un cheminvault kv list -mount=secret apps/Sortie :
Keys----webapp/mobile-app/Métadonnées personnalisées
Section intitulée « Métadonnées personnalisées »custom_metadata attache des paires clé/valeur au secret entier, pas à une
version. Ces données sont lisibles par quiconque a read sur
secret/metadata/..., donc sans accès aux valeurs : c'est exactement ce qu'il
faut pour qu'une équipe d'exploitation sache qui possède un secret et depuis
quand il n'a pas tourné, sans lui donner le secret. N'y mettez jamais de donnée
sensible, elles ne bénéficient d'aucune protection supplémentaire.
vault kv metadata put -mount=secret \ -custom-metadata="owner=team-backend" \ -custom-metadata="environment=production" \ -custom-metadata="rotation=90days" \ apps/webapp/databaseLimiter les versions conservées
Section intitulée « Limiter les versions conservées »Par défaut, Vault conserve toutes les versions. Cela peut poser problème :
- Croissance du stockage
- Conservation d'anciennes valeurs sensibles plus longtemps que nécessaire
Limitez avec -max-versions :
# Conserver uniquement les 10 dernières versionsvault kv metadata put -mount=secret -max-versions=10 apps/webapp/databaseLa même limite se définit une fois pour tout le moteur, via son endpoint de
configuration secret/config. Les clés qui ne fixent pas leur propre
max_versions héritent alors de cette valeur :
vault write secret/config max_versions=10vault read secret/configSuppression automatique après soft delete
Section intitulée « Suppression automatique après soft delete »max-versions borne le nombre de versions, delete-version-after borne
leur durée de vie. Les deux répondent à des besoins différents : le premier
contient la volumétrie, le second répond à une exigence de rétention (« aucun
mot de passe révoqué ne doit rester récupérable plus de 24 heures »). Le délai
s'applique aux nouvelles versions écrites après ce réglage : Vault leur
inscrit un deletion_time calculé à l'écriture. Attention au piège : ce que
déclenche l'échéance est un soft delete, pas un destroy. Les données
restent présentes dans le stockage et un undelete les ramène. Pour une
véritable exigence d'effacement, combinez ce réglage avec max-versions ou un
destroy explicite.
# Marquer les nouvelles versions pour un soft delete 24h après leur écriturevault kv metadata put -mount=secret -delete-version-after=24h apps/webapp/databaseConsommation par les applications
Section intitulée « Consommation par les applications »Ce guide montre la gestion via CLI. En production, vos applications consomment les secrets via d'autres méthodes :
| Méthode | Usage typique |
|---|---|
| API HTTP | Intégration directe dans le code |
| Agent + templates | Injection dans fichiers de config |
| CSI driver | Montage comme volume Kubernetes |
| Vault Secrets Operator | Synchronisation vers Secrets K8s |
Ces quatre méthodes ne changent rien au moteur KV, elles changent qui s'authentifie. Aucune ne doit dépendre d'un token écrit dans un fichier de configuration : l'application prouve son identité (compte de service Kubernetes, rôle IAM, certificat) et Vault lui rend un token à durée de vie courte. Le guide Vault comme courtier d'identité décrit ce mécanisme, et Authentification détaille les méthodes disponibles.
Ce que KV v2 ne fait pas
Section intitulée « Ce que KV v2 ne fait pas »KV v2 améliore énormément la gestion des secrets statiques, mais il a des limites structurelles :
| Capacité | KV v2 | Alternative Vault |
|---|---|---|
| Générer des secrets dynamiques | Non | Database, AWS, Azure... |
| Rotation automatique côté cible | Non | Database secrets engine |
| Chiffrement de données applicatives | Non | Transit |
| Certificats X.509 | Non | PKI |
| Clés SSH signées | Non | SSH secrets engine |
Règle simple : si vous pouvez éviter un secret statique, faites-le.
- Base de données → Database secrets engine (credentials éphémères)
- Cloud provider → AWS/Azure/GCP secrets engine
- Certificats → PKI secrets engine
- SSH → SSH secrets engine avec certificats
KV v2 reste indispensable pour :
- Clés API tierces (imposées par le fournisseur)
- Secrets legacy (systèmes non intégrables)
- Tokens partagés (Slack webhooks, API tokens...)
- Bootstrap secrets (premiers credentials pour accéder au reste)
Dépannage
Section intitulée « Dépannage »Deux familles d'erreurs dominent sur KV v2, et elles se distinguent au message.
Tout ce qui parle de chemin (no value found, permission denied) vient
presque toujours d'un oubli du segment data/ ou metadata/ dans une policy :
la CLI l'ajoute pour vous, le moteur de policies non. Tout ce qui parle de
check-and-set vient d'une écriture concurrente ou d'un cas-required posé
sur la clé ; dans les deux cas la réponse est la même, relire la version
courante avec vault kv get puis rejouer l'écriture avec le bon -cas.
| Symptôme | Cause probable | Solution |
|---|---|---|
no value found at secret/data/... | Secret inexistant ou supprimé | Vérifier le path, utiliser undelete |
permission denied | Policy insuffisante | Vérifier les capabilities read, list sur secret/data/... |
check-and-set parameter did not match | Écriture concurrente | Relire la version actuelle, utiliser -cas=<version> |
check-and-set parameter required | cas-required=true sur la clé | Ajouter -cas=<version> à la commande |
| Version non trouvée | Version détruite | Vérifier avec metadata get |
| Versions anciennes inaccessibles | max-versions atteint | Augmenter la limite ou accepter la perte |
À retenir
Section intitulée « À retenir »- KV v2 est le standard pour les secrets statiques, préférez les secrets dynamiques quand c'est possible
- Check-and-Set (
-cas) évite les écrasements concurrents, activez-cas-requiredsur les secrets critiques - La CLI masque les chemins API, vos policies doivent cibler
secret/data/*etsecret/metadata/* delete= soft delete récupérable,destroy= définitif,metadata delete= suppression totale- Organisez vos secrets pour faciliter les policies : par application/environnement/équipe
- Les métadonnées personnalisées documentent mais ne déclenchent rien
- Limitez les versions avec
-max-versionspour éviter la croissance infinie et la rétention excessive