Chapitre 9. Le moteur KV
Objectifs pédagogiques¶
Distinguer KV v1 et v2. Écrire et lire sans exposer les valeurs. Expliquer la structure data/ et metadata/ et son enveloppe. Dérouler le cycle de vie d'une version. Employer le check-and-set. Configurer la rétention. Organiser une arborescence qui prépare le moindre privilège.
Prérequis¶
Chapitre 8 pour un serveur descellé avec audit actif, chapitre 4 pour la table des montages.
Cours¶
9.1 KV v1, KV v2, et pourquoi la v2¶
KV est le moteur de secrets statiques : il stocke ce qu'on lui confie derrière la barrière et le restitue aux identités autorisées. Ni génération, ni bail, ni rotation : du rangement, bien fait.
Deux versions, qui sont deux moteurs distincts. La v1 : un chemin, une valeur ; l'écriture écrase, la suppression est définitive, l'API est plate. La v2 : des versions conservées, une suppression douce distincte d'une destruction, des métadonnées par secret, le check-and-set, et une API dédoublée.
La v2 est le défaut de ce cours pour une raison de fond : le chapitre 2 a fait de la rotation une propriété de conception, et la rotation suppose des versions, un recouvrement entre l'ancienne et la nouvelle valeur, un retour arrière possible.
[Laboratoire]
Sur un serveur de production, rien n'est monté d'office hors des montages système : secret/ n'existe que parce qu'on vient de le créer.
9.2 Écrire et lire sans s'exposer¶
La forme naïve est une fuite :
# NE PAS FAIRE : le mot de passe part dans l'historique du shell
# et dans la liste des processus
vault kv put secret/billing/prod/db username=billing password=H7q...
Les formes correctes. [Laboratoire]
# Forme 1 : la valeur sensible lue sur l'entree standard
vault kv put secret/billing/prod/db username=billing password=-
# Forme 2 : depuis un fichier JSON a permissions strictes, supprime ensuite
vault kv put secret/billing/prod/db @db.json
# Lecture ; -field extrait une valeur seule, -format=json donne la structure
vault kv get secret/billing/prod/db
vault kv get -field=password secret/billing/prod/db
Une seconde écriture crée la version 2 ; la première reste archivée et se lit par vault kv get -version=1.
La correspondance API révèle la mécanique :
POST /v1/secret/data/billing/prod/db corps : {"data": {"username": "...", "password": "..."}}
GET /v1/secret/data/billing/prod/db?version=1
Deux pièges classiques y sautent aux yeux. Le chemin d'API intercale data/ entre le montage et le chemin logique, que la CLI ajoute pour vous. Et la réponse emboîte data.data : le premier est l'enveloppe standard des réponses, le second le conteneur des paires du secret, à côté de son frère metadata. Tout code qui consomme l'API doit viser .data.data.password.
9.3 data/ et metadata/¶
KV v2 expose deux familles de chemins pour chaque secret. secret/data/<chemin> porte les valeurs. secret/metadata/<chemin> porte le reste : liste des versions et leur état, configuration de rétention, métadonnées personnalisées comme le propriétaire ou la criticité, et effacement complet. C'est aussi sur metadata/ que travaille l'énumération.
Une phrase à emporter vers la partie IV : les policies s'écrivent sur les chemins d'API réels, pas sur ceux que la CLI affiche. Une règle sur secret/billing/* n'autorise rien d'utile ; les requêtes visent secret/data/billing/* et secret/metadata/billing/*. C'est sur ces préfixes que le moindre privilège se découpe : tel rôle lit les données, tel autre ne voit que l'inventaire, tel autre peut détruire.
9.4 Le cycle de vie d'une version¶
Trois degrés d'effacement. Se tromper de degré, dans un sens, perd des données ; dans l'autre, laisse vivre ce qu'on croyait détruit.
La suppression douce marque une version comme supprimée sans rien effacer :
vault kv delete secret/billing/prod/db # la version courante
vault kv delete -versions=1 secret/billing/prod/db # une version precise
vault kv undelete -versions=1 secret/billing/prod/db # marche arriere
La destruction efface définitivement les données d'une version ; les métadonnées demeurent. [Laboratoire]. Irréversible
L'effacement des métadonnées supprime tout : versions, historique, existence du chemin. [Laboratoire]. Irréversible et total
S'y ajoute la rétention automatique, par montage ou par secret : max_versions, dix versions conservées par défaut, et delete_version_after, durée au terme de laquelle chaque version passe en suppression douce.
Grille de décision : la valeur peut-elle encore servir, pour un recouvrement de rotation ou un retour arrière ? Alors suppression douce. Est-elle compromise ou échue ? Alors destruction, car une vieille version contenant un secret compromis est une copie de plus de ce secret, servie à quiconque peut lire les anciennes versions.
Le retour arrière propre existe : vault kv rollback -version=3 crée une nouvelle version reprenant le contenu de la troisième, sans réécrire l'historique.
9.5 Le check-and-set¶
Deux processus mettent à jour le même secret et le second écrase silencieusement le premier. KV v2 résout ce cas par une écriture conditionnée à la version que l'on croit courante :
# "J'ecris la version qui succede a la 4 -- si la courante n'est plus la 4, refuse."
vault kv put -cas=4 secret/billing/prod/db username=billing password=-
Si la version courante a changé, l'écriture échoue explicitement au lieu de réussir sur le dos d'autrui. Le paramètre -cas=0 signifie « n'écris que si le secret n'existe pas encore » : création stricte. Pour les chemins où l'écrasement aveugle est inacceptable, le mode se rend obligatoire :
Signalons pour la suite l'opération de patch, qui modifie un champ sans réécrire les autres et mobilise une capacité d'autorisation dédiée que le chapitre 11 rangera avec les autres.
9.6 L'arborescence¶
Une arborescence de secrets n'est pas un rangement esthétique : c'est la matière première des policies, qui découpent les droits par préfixes. Une arborescence bien pensée rend le moindre privilège naturel à exprimer ; une arborescence confuse le rend impossible.
Convention du fil rouge : secret/<application>/<environnement>/<objet>.
secret/
billing/
prod/
db (credentials PostgreSQL, db-pg-01)
payment-api (cle du prestataire de paiement)
preprod/
db
L'ordre des niveaux est motivé. L'application d'abord, parce que le consommateur naturel d'un préfixe est une application : la policy billing-kv dira en une règle que billing lit secret/data/billing/prod/*. L'environnement ensuite, parce que la séparation production contre préproduction est une exigence relevée dès l'audit de la partie I et qu'elle doit être découpable.
Trois anti-modèles se déduisent : le fourre-tout plat où aucun préfixe ne signifie rien ; le rangement par personne ou par équipe, qui épouse l'organigramme et se périme à chaque réorganisation ; les environnements mélangés au même niveau qu'un objet.
Règle d'hygiène complémentaire : les noms de chemins ne sont pas confidentiels. Ils circulent dans l'audit, dans les policies et dans les erreurs. Un chemin ne doit jamais contenir de valeur ni d'indice.
Exemple : les premiers secrets d'Astrea¶
Le premier geste du chantier n'est pas une écriture dans Vault, mais une rotation côté PostgreSQL. Le mot de passe du compte billing est celui que l'audit de la partie I a trouvé dans l'historique Git : le déplacer dans Vault n'y changerait rien. Un secret compromis rangé dans un coffre reste un secret compromis.
Séquence consignée : nouveau mot de passe généré et posé côté base ; écriture dans secret/billing/prod/db par l'entrée standard, l'historique du shell restant vide ; bascule de l'application ; et le vieux mot de passe cesse de fonctionner. Le constat 1 de la partie I est traité, pas déménagé.
La préproduction reçoit son mot de passe, distinct, en secret/billing/preprod/db : le tableau du chapitre 2 exigeait la séparation, l'arborescence la rend exprimable. Enfin, la clé du prestataire de paiement est écrite avec une métadonnée de propriétaire et une échéance de rotation, ce qui prépare la revue trimestrielle du chapitre 15.
Sécurité : les règles du statique¶
La ligne de commande est un canal de fuite. Toute valeur passée en argument traverse l'historique et la liste des processus. Les formes par entrée standard et par fichier existent, et le TP 3 n'accepte qu'elles.
Les versions sont des copies. Chaque version conservée est une instance de plus du secret. La rétention se configure en conscience, et une valeur compromise se détruit dans toutes ses versions une fois la rotation stabilisée.
Les chemins parlent. Noms de chemins et métadonnées circulent en clair dans l'audit et les policies. Aucune valeur, aucun indice.
Le statique reste du statique. KV ne fait ni rotation ni révocation : ces propriétés viendront des moteurs dynamiques de la partie VI. Ranger correctement est un progrès, pas une solution complète.
Erreurs fréquentes¶
Le secret en argument de kv put. L'historique le garde, la liste des processus l'expose (section 9.2).
Écrire les policies sur secret/billing/*. Les requêtes réelles visent secret/data/... et secret/metadata/.... C'est l'erreur la plus fréquente de tout l'écosystème KV v2 : semée à la section 9.3, piégée au TP 4, récoltée au chapitre 11.
Confondre delete et destroy. L'un retire de la circulation et se répare, l'autre efface sans retour (section 9.4).
Oublier l'enveloppe data.data en API. Le code qui lit .data.password reçoit un champ vide (section 9.2).
Croire qu'un secret compromis est réglé une fois rangé dans Vault. Il faut le faire tourner, pas le déménager (Exemple).
Laisser la rétention par défaut sans y penser. Dix versions conservées, donc dix copies, y compris d'une valeur qu'on croyait retirée (section 9.4).
Ranger par équipe plutôt que par application. L'arborescence épouse l'organigramme et se périme à la première réorganisation (section 9.6).
Mise en pratique : trois missions¶
Sur le serveur du TP si vous en disposez, sur papier sinon. Réponses commentées en fin de partie.
- Créer
secret/billing/preprod/dbsans que la valeur apparaisse dans l'historique, puis vérifier en lecture en n'affichant que le champpassword. - Mettre à jour ce secret en étant certain de ne pas écraser une écriture concurrente, puis rendre cette précaution obligatoire pour toutes les écritures futures sur ce chemin.
- La version 1 contenait une valeur désormais compromise : la rendre définitivement illisible tout en conservant la version courante et l'historique d'existence.
Questions de compréhension¶
- Donnez trois différences entre KV v1 et v2, et la raison de fond qui fait de la v2 le défaut de ce cours.
- Pourquoi la réponse d'une lecture contient-elle
data.data, et que contient le second bloc à côté du premier ? - Expliquez le dédoublement
data/etmetadata/, et sa conséquence pour les policies. - Distinguez suppression douce, destruction et effacement des métadonnées : effets, réversibilité, cas d'usage.
- Quel problème le check-and-set résout-il, et quel est le sens de
-cas=0? - Que fait
kv rollback, et pourquoi est-il préférable à une réécriture manuelle ? - Justifiez l'ordre application puis environnement de la convention d'arborescence.
- Pourquoi les noms de chemins ne doivent-ils contenir aucun indice ?
- Pourquoi ranger un secret compromis dans Vault ne le règle-t-il pas ?
Synthèse¶
KV v2 est le rangement bien fait : des secrets statiques versionnés, dont l'API dédoublée réserve deux pièges à connaître avant d'écrire la moindre policy, l'enveloppe de réponse et le préfixe des chemins de données. S'y ajoutent un cycle de vie à trois degrés complété par la rétention automatique et le retour arrière propre, le check-and-set pour que les écritures concurrentes échouent bruyamment, et une arborescence par application puis environnement qui est la matière première du moindre privilège. Les règles d'hygiène du statique s'y ajoutent : rien en argument de commande, rien d'indicatif dans les chemins, et la conscience que chaque version conservée est une copie. Déclenche le TP 3 (énoncé en partie XV).