Migration vers Ansible Vault

Je l’avais évoqué plusieurs fois dans quelques articles, j’utilise ansible pour gérer l’installation automatique des services que j’auto-héberge. J’étais jusqu’à présent resté dans une gestion simple des mots de passe liés aux services (comprendre « exposé en clair dans les fichiers »). Vraiment pas terrible du point de vue sécurité, mais permettant de me concentrer sur la partie (ré-)installation automatique, là où réside le vrai gain de temps. Disposant de quelques jours de congés, je me suis donc attelé à ce sujet longtemps repoussé qu’est la migration des secrets vers le vault ansible.

Un vault ansible, en quelques mots, est un fichier chiffré contenant des pairs clé/valeur permettant de référencer un secret dans un script ansible.

Configuration

Côté implémentation, j’ai choisi de stocker mon vault dans le dossier group_vars/all/ et j’ai commencé en créant deux fichiers all.yml et vault_all.yml. Dans vault_all.yml, je stocke l’ensemble de mes secrets associés à un identifiant, par exemple, le mot de passe secretpassword associé à la clé vault_test_user_password donnera le fichier suivant :

---
vault_test_user_password: secretpassword

Ensuite, dans all.yml, je référence la variable du vault et l’expose dans une nouvelle variable. Cette étape n’est pas forcément nécessaire, il est tout à fait possible d’utiliser directement l’identifiant présent dans le vault. L’intérêt de cette méthode quelque peu répétitive au moment de la mise en place, réside dans le fait que l’on dispose d’un inventaire des variables stockées dans notre fichier vault, sans avoir besoin de déchiffrer le fichier vault pour parcourir son contenu. Voici donc, en reprenant l’exemple ci-dessus, le contenu de notre fichier de référence :

---
test_user_password: "{{ vault_test_user_password }}"

Maintenant que je dispose de mon dictionnaire de secrets, je peux passer à l’opération de chiffrement de ce dernier. Par défaut, ansible demandera le mot de passe à utiliser à chaque opération de chiffrement/déchiffrement du fichier, ce qui se révèle vite rébarbatif. Heureusement, il est possible de créer un fichier .vault_pass pour y stocker le mot de passe du vault. Évidemment, on prendra soin de l’ajouter à notre .gitignore, afin de ne pas commit cette information dans notre dépôt git. Dernière étape, référencer l’emplacement du mot de passe dans la configuration ansible, soit dans ansible.cfg, par ajout de la ligne :

[defaults]
vault_password_file = .vault_pass

Maintenant que tout est prêt, il convient de chiffrer le fichier vault. Pour effectuer cette opération, on utilisera la commande :

ansible-vault encrypt group_vars/all/vault_all.yml

Le fichier vault étant désormais chiffré, l’édition s’effectuera désormais avec :

ansible-vault edit group_vars/all/vault_all.yml

Parmi les autres opérations de la commande ansible-vault, on notera en particulier decrypt, view et rekey.

Utilisation

Maintenant que tout est configuré, je peux donc remplacer les références directes au mot de passe présent dans mes playbooks, par les nouvelles variables créées. Toujours avec l’exemple précédent, on passera de l’extrait de playbook suivant :

password: secretpassword

au nouveau contenu « sécurisé »:

password: "{{ test_user_password }}"

Il ne reste plus qu’à déclencher l’exécution de l’un des playbooks afin de vérifier que tout a été configuré correctement. Je pourrais m’arrêter là; les secrets de mes playbooks sont maintenant chiffrés dans un fichier dédié, tout va pour le mieux, mais un dernier point de faiblesse subsiste, le mot de passe du vault stocké en clair dans notre fichier .vault_pass. Évidemment, si un attaquant devait mettre la main sur ce fichier, il y a fort à parier que ce serait le cadet de mes soucis, mais voyons tout de même si je peux améliorer les choses.

Sécurisation du mot de passe Vault

En parcourant la documentation ansible liée au concept de vault, j’ai découvert que le paramètre de configuration vault_password_file est susceptible de prendre un fichier de script exécutable en entrée. Seule condition, le script doit renvoyer le mot de passe à utiliser. Étant donné que j’utilise déjà password-store en ligne de commande pour la gestion de mes mots de passe, j’y ai vu une occasion parfaite d’augmenter encore la sécurité en stockant le mot de passe du vault dans une entrée de mon password-store. Quelques recherches plus tard, j’obtiens donc le script suivant; script chargé de retourner le contenu de l’entrée ansible-vault.

#!/bin/bash

###################################
## Get password from password store
# Inspired by https://github.com/paulRbr/ansible-makefile/blob/master/pass.sh
###################################

if (command -v pass >/dev/null 2>&1)
then
    existingVault=$(pass "ansible-vault" || true)

    if [ -n "${existingVault}" ]
    then
        >&2 echo "Using passphrase found at 'ansible-vault' in your password store."
        echo "${existingVault}"
    else
        >&2 echo "No passphrase found at 'ansible-vault' in your password store."
        exit 0
    fi
fi

Ma configuration ansible devient donc (Ne pas oublier de rendre le fichier de script exécutable):

[defaults]
vault_password_file = ./get-vault-pass.sh

Autres améliorations

Il est possible d’avoir plusieurs vaults dans un projet ansible. Dans l’exemple précédent, toutes les entrées du vault sont partagées avec l’ensemble des playbooks, du fait d’appartenir au groupe all. Pour simplifier l’organisation, j’ai dans mon cas déplacé tous les secrets liés à unicoda et à son playbook de déploiement dans le dossier group_vars/unicoda/ et son vault dédié. De la même façon, on peut alors envisager d’améliorer le script pour utiliser un mot de passe différent pour chaque vault, chacun des mots de passe étant alors stocké de façon chiffrée dans password-store.

Par ailleurs, j’ai découvert pendant la mise en place de vault, qu’il existe un plugin ansible maintenu par la communauté et qui permet d’aller chercher directement le contenu d’un mot de passe dans password-store. Je n’ai pas poussé plus avant l’expérimentation, mais l’utilisation de ce plugin pourrait être une alternative valable à l’utilisation de vault.

Conclusion

Grâce à cette dernière amélioration, mes secrets ansible sont désormais stockés en lieu sûr et ne sont plus accessible au premier venu. C’est un élément de sécurité que j’encourage à mettre en place immédiatement, dès que le premier secret apparaît dans le dépôt de code.

Sources