Ansible Vault : chiffrer vos secrets dans Git
Découvrez ce qu’Ansible Vault protège réellement : fichiers vars ou valeurs inline chiffrés, séparation staging/production et rekey propre dans Git.
Ce que protège Ansible Vault, et ce qu’il ne protège pas
Ansible Vault chiffre les secrets dans votre dépôt de playbooks. Git stocke donc du texte chiffré au lieu d’un mot de passe en clair. La commande ansible-vault chiffre un fichier entier ou une seule valeur dans un fichier, avec une clé symétrique dérivée du mot de passe que vous choisissez. Ansible déchiffre ces données en mémoire pendant l’exécution du playbook. La variable se comporte alors comme n’importe quelle autre variable.
Ce modèle a une limite claire. Vault protège un secret au repos dans le dépôt, et rien de plus. Dès qu’une tâche s’exécute, la valeur est en clair en mémoire, dans le template généré, dans les arguments du module et dans la sortie d’exécution, sauf si vous empêchez leur affichage. Toute personne qui peut exécuter le playbook possède le mot de passe du vault. Vault assure donc la confidentialité vis-à-vis des personnes extérieures à l’équipe, mais ne fournit pas de contrôle d’accès individuel au sein de l’équipe.
Si vous n’avez pas encore écrit de playbook, commencez par un premier playbook Ansible sur un VPS, puis revenez ici lorsque ce playbook aura besoin d’un mot de passe.
Chiffrer un fichier entier ou une seule chaîne ?
ansible-vault encrypt remplace un fichier par du texte chiffré. Le fichier devient un bloc de texte base64 sous une ligne d’en-tête qui commence par $ANSIBLE_VAULT. Utilisez-le lorsque le fichier ne contient que des secrets.
ansible-vault encrypt_string chiffre une valeur et affiche un extrait YAML que vous pouvez coller dans un fichier vars ordinaire. Le nom de la variable reste lisible et seule la valeur est chiffrée. Utilisez-le lorsque les secrets sont placés à côté de paramètres en clair.
La différence importante au quotidien concerne le diff. Un fichier vault est rechiffré avec un nouveau salt aléatoire chaque fois que vous l’enregistrez. Chaque octet du texte chiffré change donc. git diff affiche alors un bloc illisible remplacé par un autre bloc illisible. Un reviewer ne peut pas savoir si vous avez renouvelé un mot de passe ou réécrit le fichier. Avec encrypt_string, chaque secret constitue son propre bloc dans un fichier en clair. Le diff montre donc exactement quelle variable a changé et laisse le reste du fichier intact.
Le format inline a un inconvénient, qui apparaît au moment de la rotation : ansible-vault rekey ne modifie pas les blocs inline. Choisissez le format fichier lorsque la liste des secrets est longue et change rarement. Choisissez le format inline lorsque le fichier mélange des secrets et des variables normales et que vous voulez que la code review soit exploitable.
La structure de group_vars qui indique ce qui est protégé
Ansible charge group_vars/<group>.yml et charge également tous les fichiers présents dans un répertoire group_vars/<group>/. La forme avec répertoire est celle qu’il faut utiliser, car elle permet à un même groupe de contenir côte à côte un fichier en clair et un fichier chiffré.
inventory/
hosts.ini
group_vars/
all/
vars.yml
vault.yml
web/
vars.yml
vault.yml
host_vars/
db01/
vars.yml
vault.yml
playbooks/
site.ymlChaque vault.yml est chiffré. Chaque vars.yml est en clair. Le nom du fichier indique les valeurs qui sont protégées, sans qu’il soit nécessaire d’ouvrir le fichier.
La deuxième partie du modèle repose sur l’indirection. Dans le fichier chiffré, préfixez chaque variable avec vault_.
vault_db_password: "a real password"
vault_grafana_admin_token: "a real token"Référencez ensuite ces noms depuis le fichier en clair situé à côté.
db_password: "{{ vault_db_password }}"
grafana_admin_token: "{{ vault_grafana_admin_token }}"Les rôles et les templates utilisent db_password et ne savent jamais d’où vient la valeur. La séparation entre un playbook et un rôle reste ainsi claire : la séparation entre un playbook et un rôle. Le fichier vars.yml en clair sert également d’index consultable : grep -r vault_ group_vars/ répertorie tous les secrets attendus par le dépôt, sans rien déchiffrer. Le coût est d’ajouter un nom par secret. Une faute de frappe dans un nom vault_ apparaît à l’exécution comme une variable non définie, et non comme une erreur de syntaxe.
Chiffrer une variable avec encrypt_string
ansible-vault encrypt_string --vault-id prod@~/.ansible/vault-prod.txt \
--stdin-name 'vault_db_password'Saisissez le secret, puis appuyez sur Ctrl-D. --stdin-name lit la valeur depuis l’entrée standard, ce qui évite de l’enregistrer dans l’historique de votre shell. L’autre forme place la valeur sur la ligne de commande, où le shell l’enregistre :
ansible-vault encrypt_string --vault-id prod@~/.ansible/vault-prod.txt \
'a real password' --name 'vault_db_password'Dans les deux cas, la commande affiche un bloc YAML. Collez-le dans le fichier vars exactement tel qu’il est affiché, car l’indentation sous le tag !vault fait partie de la valeur.
vault_db_password: !vault |
$ANSIBLE_VAULT;1.2;AES256;prod
6638643965323633646262656665306333616466396630323136393465356136396436383331
3131303163306665326539353837343663313762616561306534373963383531613664393332Le tag !vault indique au chargeur YAML que le scalaire est un texte chiffré et non du texte brut. L’en-tête contient la version du format, le cipher et le libellé de vault ID qui a servi à le chiffrer. Une valeur chiffrée sans vault ID contient un en-tête 1.1 sans libellé. Elle fonctionne également, mais fournit simplement moins d’informations sur l’origine du mot de passe.
Où le mot de passe du vault est-il stocké ?
En dehors du dépôt. C’est la seule règle qui ne souffre aucune exception.
--ask-vault-pass demande le mot de passe une fois par exécution et ne stocke rien. Cette méthode convient à un ordinateur portable, mais pas à une tâche cron ou à un runner CI.
Un fichier de mot de passe est un fichier texte brut dont la première ligne contient le mot de passe. Créez-le vide avec des permissions restrictives, puis renseignez-le dans un éditeur. Le mot de passe n’apparaîtra ainsi jamais dans l’historique de votre shell :
mkdir -p ~/.ansible
install -m 600 /dev/null ~/.ansible/vault-prod.txt
$EDITOR ~/.ansible/vault-prod.txtIndiquez ce fichier à n’importe quelle commande avec --vault-password-file :
ansible-playbook -i inventory/hosts.ini playbooks/site.yml \
--vault-password-file ~/.ansible/vault-prod.txtRépéter cette option pour chaque commande est facile à oublier. Définissez-la donc une seule fois dans ansible.cfg, à la racine du dépôt.
[defaults]
inventory = inventory/hosts.ini
vault_password_file = ~/.ansible/vault-prod.txtLe même paramètre lit la variable d’environnement ANSIBLE_VAULT_PASSWORD_FILE. C’est ainsi qu’un job CI fournit généralement le mot de passe. Le job écrit le mot de passe provenant de son propre credential store dans un fichier d’un répertoire temporaire, exporte la variable, puis supprime le fichier à la fin de l’exécution. Ajoutez également le pattern de nom de fichier à .gitignore, car le chemin de ansible.cfg est commité et quelqu’un finira tôt ou tard par créer le véritable fichier dans le checkout.
Si le fichier de mot de passe est exécutable, Ansible l’exécute et lit le mot de passe sur sa sortie standard au lieu de lire le fichier comme du texte. Cela permet de récupérer le mot de passe du vault depuis un keyring système ou un gestionnaire de secrets cloud sans l’écrire sur disque. Un script utilisé avec --vault-id doit respecter des exigences supplémentaires : son nom doit se terminer par -client ou par -client suivi d’une extension, il doit être exécutable, il doit accepter une option --vault-id et il doit afficher le mot de passe sur la sortie standard.
Deux identifiants de vault : staging et production
Un identifiant de vault est un libellé associé à un mot de passe de vault, écrit sous la forme label@source. La source est prompt : le chemin vers un fichier de mots de passe ou vers un script client. Les libellés permettent à un même dépôt de conserver des secrets protégés par plusieurs mots de passe. Ainsi, le mot de passe staging ne permet pas d’ouvrir le fichier de production.
ansible-vault encrypt --vault-id staging@~/.ansible/vault-staging.txt \
group_vars/staging/vault.yml
ansible-vault encrypt --vault-id prod@~/.ansible/vault-prod.txt \
group_vars/prod/vault.ymlTransmettez tous les identifiants nécessaires à une exécution :
ansible-playbook playbooks/site.yml \
--vault-id staging@~/.ansible/vault-staging.txt \
--vault-id prod@~/.ansible/vault-prod.txtVous pouvez aussi les définir une seule fois dans ansible.cfg :
[defaults]
vault_identity_list = staging@~/.ansible/vault-staging.txt, prod@~/.ansible/vault-prod.txtUn comportement peut surprendre. Par défaut, le libellé est un indice, pas un verrou. Ansible essaie chaque secret actuellement chargé sur le fichier, jusqu’à ce que l’un d’eux le déchiffre. Ainsi, un fichier portant le libellé staging reste accessible si le mot de passe de production est la bonne clé. Définissez vault_id_match = True sous [defaults], ou utilisez la variable d’environnement ANSIBLE_VAULT_ID_MATCH. Ansible utilise alors uniquement le secret dont le libellé correspond à l’en-tête du fichier. Ce contrôle nécessite l’en-tête 1.2. Il s’applique donc uniquement au contenu chiffré avec un identifiant de vault.
Lorsque plusieurs identifiants sont chargés, ansible-vault encrypt ne sait plus avec quel mot de passe chiffrer. Indiquez-le avec --encrypt-vault-id prod, ou définissez vault_encrypt_identity dans ansible.cfg afin que le dépôt dispose d’une valeur par défaut.
L’intérêt concerne le périmètre des déploiements. Un job CI qui déploie staging reçoit uniquement le mot de passe staging. Ainsi, un runner compromis ne peut pas lire les identifiants de production. Lorsque vous exécutez des plays sur un parc de serveurs Linux depuis une seule machine de contrôle, cette séparation fait la différence entre un incident limité et un incident de grande ampleur.
Rechiffrer le vault lorsqu’une personne quitte l’équipe
Le rechiffrement modifie le mot de passe du vault et rechiffre son contenu avec le nouveau mot de passe. Il n’annule rien. Toute personne qui a détenu l’ancien mot de passe peut toujours déchiffrer toute copie du repository qu’elle a conservée, y compris chaque ancien commit présent dans cette copie. Considérez donc le mot de passe du vault comme compromis dès qu’un détenteur quitte l’équipe, puis effectuez la rotation dans cet ordre.
- Modifiez les vrais identifiants sur les serveurs et dans les services tiers. C’est cette étape qui révoque réellement l’accès.
- Ajoutez les nouvelles valeurs dans les fichiers du vault avec
ansible-vault edit. - Rechiffrez chaque fichier chiffré avec un nouveau mot de passe de vault.
- Transmettez le nouveau mot de passe du vault aux personnes qui en ont encore besoin, via un canal distinct du repository.
ansible-vault rekey --vault-id prod@~/.ansible/vault-prod-old.txt \
--new-vault-id prod@prompt \
group_vars/prod/vault.yml host_vars/db01/vault.ymlrekey accepte plusieurs fichiers dans une seule commande, et --new-vault-id prod@prompt demande le nouveau mot de passe une seule fois au lieu de le lire depuis le disque. Conservez le même label, sauf raison particulière de le modifier, car le label est inscrit dans l’en-tête de chaque fichier réécrit par la commande.
C’est ici que la forme inline vous impose une étape supplémentaire. ansible-vault rekey agit sur des fichiers entièrement chiffrés. Un bloc !vault placé dans un fichier vars en clair reste donc inchangé. Recherchez d’abord ces blocs, puis régénérez chaque fichier avec encrypt_string en utilisant le nouveau mot de passe :
grep -rl '!vault' group_vars/ host_vars/C’est tout le compromis. Les blocs inline permettent de lire facilement les différences, mais nécessitent une intervention manuelle au moment de la rotation. Les fichiers entièrement chiffrés sont renouvelés avec une seule commande, mais n’apportent aucune information utile lors de la review.
Pourquoi le secret apparaît encore dans votre sortie
Vault intervient dès que la valeur est déchiffrée. Ansible affiche le résultat d’une tâche, et un module qui réaffiche ses arguments inclut l’identifiant dans ce résultat. Une exécution verbeuse, un --diff sur une tâche de template, une tâche en échec qui affiche ses arguments ou un plugin de callback qui écrit la sortie dans un fichier conserveront chacun la valeur en clair. Chiffrer le fichier n’empêche aucune de ces situations.
no_log: true est l’option à utiliser. Définissez-la sur toute tâche qui reçoit un identifiant.
- name: Write the application environment file
ansible.builtin.template:
src: app.env.j2
dest: /etc/myapp/app.env
owner: myapp
group: myapp
mode: "0600"
no_log: trueAnsible masque alors le résultat de cette tâche dans la sortie. Le journal indique que la tâche a été exécutée, sans enregistrer les données traitées. Définissez cette option notamment sur les boucles, car une boucle affiche un résultat par élément. Une boucle parcourant une liste d’identifiants afficherait la liste entière.
Quatre autres emplacements peuvent exposer un secret déchiffré. no_log ne les couvre pas :
- Un fichier généré à partir d’un template hérite des
modeetownerque vous lui avez attribués. Définissezmode: "0600"et un propriétaire précis pour tout fichier contenant un identifiant. Sinon, le secret sera lisible par tous sur l’hôte cible. - Un secret transmis à
ansible.builtin.commandouansible.builtin.shellapparaît dans la liste des processus de l’hôte cible pendant l’exécution de la commande. Tout utilisateur local peut alors le lire. Transmettez-le plutôt dans un fichier ou une variable d’environnement. - Le fact caching écrit les facts collectés sur le disque de la machine de contrôle. Une variable enregistrée contenant un secret peut donc finir dans un fichier de cache que personne ne considère comme sensible.
- Le même secret se trouve généralement dans un second emplacement, par exemple dans un fichier d’environnement lu par un conteneur. Les règles applicables sont distinctes. La section conserver les identifiants hors des fichiers env de Compose couvre cet aspect.
no_log complique le débogage, ce qui est précisément son rôle. Supprimez-le temporairement sur un hôte de test lorsqu’une tâche se comporte mal, puis réactivez-le avant de déployer la modification en production.
Lire et modifier des fichiers chiffrés sans laisser de texte en clair
ansible-vault view group_vars/prod/vault.yml déchiffre le fichier dans un pager sans rien écrire sur le disque. ansible-vault edit déchiffre le fichier dans un fichier temporaire, ouvre votre $EDITOR, puis le rechiffre lorsque vous le fermez. Préférez ces deux commandes à ansible-vault decrypt, qui laisse un fichier en clair dans l’arborescence de travail. Un fichier de coffre déchiffré ajouté par accident à l’index est le moyen le plus courant par lequel un identifiant réel se retrouve dans un dépôt public.
Git peut afficher un diff lisible pour les fichiers entièrement chiffrés en les déchiffrant à la volée :
git config --local diff.ansible-vault.textconv "ansible-vault view --vault-password-file ~/.ansible/vault-prod.txt"
printf '%s\n' 'group_vars/**/vault.yml diff=ansible-vault' >> .gitattributesComprenez ce que fait cette configuration avant de l’activer. git diff affichera désormais les secrets de production dans votre terminal, où ils resteront dans l’historique de défilement et dans tout partage d’écran. Il s’agit d’une commodité locale pour une seule personne sur une seule machine. Gardez donc le git config local et attendez-vous à ce que les clones des autres utilisateurs se comportent différemment, sauf s’ils configurent la même chose.
Quand Vault cesse d’être l’outil adapté
Vault est un format de fichier avec un mot de passe par label. Cette structure détermine ses limites. Passez à un véritable secret store dès que l’un des cas suivants se présente.
- Vous avez besoin d’un accès par personne. Toutes les personnes qui exécutent le playbook détiennent le même mot de passe. Les vault IDs séparent les accès par environnement, jamais par personne.
- Vous avez besoin d’une piste d’audit. Vault n’enregistre ni qui a déchiffré quoi, ni à quel moment.
- Vous avez besoin d’une rotation planifiée. Vault ne gère ni expiration ni versioning. Rien ne vous indique donc qu’un credential n’a pas changé depuis deux ans.
- L’application elle-même doit accéder au secret au moment de l’exécution. Un service qui lit son mot de passe de base de données au démarrage ne devrait pas le lire dans votre dépôt de déploiement.
Le modèle s’inverse alors. Ansible cesse de stocker les secrets et commence à les récupérer au moment de l’exécution via un lookup plugin, auprès de HashiCorp Vault (un autre produit au nom prêtant à confusion), du secret manager d’un cloud provider ou d’un keyring sur la control machine. Le dépôt contient un chemin, le store contient la valeur et le store conserve le journal des accès. Pour une petite équipe, un password manager auto-hébergé avec une API, comme un serveur Vaultwarden, couvre le même besoin à plus petite échelle.
Un credential reste en dehors de tout cela. La clé SSH utilisée par votre control machine pour accéder aux serveurs ne relève pas de Vault, car Ansible en a besoin avant l’exécution du moindre play. Gérez-la avec un agent et une passphrase, comme expliqué dans les bases de la gestion des clés SSH.
FAQ
Dois-je chiffrer l’ensemble du fichier vars ou uniquement la chaîne secrète ?
Chiffrez l’ensemble du fichier lorsqu’il ne contient que des secrets, car une seule commande permet de tout renouveler et la structure reste simple. Utilisez ansible-vault encrypt_string lorsque les secrets se trouvent à côté de variables ordinaires : seule la valeur chiffrée change alors dans le diff, et le reviewer peut voir quelle variable a été modifiée. Le compromis concerne le renouvellement. ansible-vault rekey prend en charge les fichiers complets et laisse les blocs !vault intégrés dans le texte inchangés. Vous devez donc les régénérer manuellement avec le nouveau mot de passe.
Où stocker le fichier de mot de passe Ansible Vault ?
En dehors du dépôt, avec le mode 0600, à un chemin tel que ~/.ansible/vault-prod.txt. Indiquez ce fichier avec --vault-password-file, définissez vault_password_file sous [defaults] dans ansible.cfg, ou définissez ANSIBLE_VAULT_PASSWORD_FILE dans l’environnement. En CI, faites écrire par le job le mot de passe provenant de son propre credential store dans un fichier temporaire, exportez la variable, puis supprimez le fichier à la fin du job. Si le fichier est exécutable, Ansible l’exécute et lit le mot de passe sur la sortie standard. Vous pouvez ainsi le récupérer depuis un keyring au lieu de le stocker sur le disque.
Comment utiliser des mots de passe Vault différents pour la staging et la production ?
Attribuez un label à chaque mot de passe avec --vault-id staging@/path/to/file et --vault-id prod@/path/to/file, puis chiffrez les fichiers de chaque environnement avec son propre label. Transmettez les deux identifiants au moment de l’exécution, ou indiquez-les dans vault_identity_list sous [defaults]. Par défaut, Ansible essaie chaque secret dont il dispose jusqu’à ce que l’un d’eux déchiffre le fichier. Définissez donc vault_id_match = True si vous voulez qu’il essaie uniquement le secret dont le label correspond à l’en-tête du fichier. Lorsque plusieurs identifiants sont chargés, choisissez celui utilisé pour le chiffrement avec --encrypt-vault-id.
Ansible Vault empêche-t-il l’apparition d’un mot de passe dans la sortie d’exécution ?
Non. Vault protège le secret au repos dans le dépôt uniquement. Lorsqu’une tâche s’exécute, la valeur est en clair, et une exécution verbeuse ou une tâche en échec peut l’inclure dans le journal. Ajoutez no_log: true à chaque tâche qui manipule un credential, définissez un mode et un owner restrictifs sur tout fichier généré avec un template, et évitez de transmettre des secrets comme arguments de commande, car ils sont visibles dans la liste des processus sur l’hôte cible pendant l’exécution de la commande.