Aller au contenu
Infrastructure as Code medium

Ansible Vault dans un rôle : pattern defaults + vars chiffré et indirection vault_*

10 min de lecture

Logo Ansible

Un rôle Ansible bien conçu sépare interface publique et implémentation des secrets. Le pattern recommandé en 2026 : defaults/main.yml expose des variables au nom "normal" (ex: secured_app_db_password), mais leur valeur par défaut pointe vers une variable vault_* définie dans vars/main.yml chiffré. Cette indirection permet au consommateur du rôle de surcharger une variable normale, tout en gardant la vraie valeur chiffrée et hors de portée par défaut.

À la fin, vous saurez structurer un rôle qui chiffre ses secrets sans casser l'expérience de consommation, et comprendre pourquoi ce pattern est devenu standard dans les rôles publiés sur Galaxy.

  • Structure defaults/main.yml + vars/main.yml dans un rôle.
  • Indirection secured_app_db_password = "{{ vault_secured_app_db_password }}".
  • Quand vars/ plutôt que defaults/ (précédence Ansible).
  • Chiffrer uniquement vars/main.yml, laisser defaults/main.yml en clair.
  • Surcharger un secret depuis le playbook consommateur.
  • Pourquoi pas vars/vault.yml : Ansible ne charge automatiquement que vars/main.yml.
roles/secured_app/
├── defaults/
│ └── main.yml ← variables PUBLIQUES (clair) : interface du rôle
├── vars/
│ └── main.yml ← secrets vault_* (CHIFFRÉ) : implémentation
├── tasks/
│ └── main.yml
├── meta/
│ └── argument_specs.yml
└── README.md

Règle clé : Ansible charge automatiquement vars/main.yml (pas vars/vault.yml, pas vars/secrets.yml, uniquement main.yml ou main.yaml). Le fichier vars/main.yml est chiffré dans son intégralité ; les autres fichiers du rôle restent en clair.

# roles/secured_app/defaults/main.yml (CLAIR)
secured_app_user: "appuser"
secured_app_port: 8080
secured_app_db_password: "{{ vault_secured_app_db_password }}"
secured_app_api_token: "{{ vault_secured_app_api_token }}"

Ce que voit l'utilisateur du rôle : trois variables au nom "normal", documentables, surchargeables. Les deux dernières prennent leur valeur par défaut depuis des vault_* chiffrées.

# roles/secured_app/vars/main.yml (CHIFFRÉ COMPLET)
vault_secured_app_db_password: "RoleDBPasswordLab81!"
vault_secured_app_api_token: "role_api_tok_lab81_xyz"

Chiffrer ce fichier :

Fenêtre de terminal
ansible-vault encrypt roles/secured_app/vars/main.yml \
--vault-password-file=.vault_password

L'utilisateur du rôle ne voit jamais ces valeurs claires sans le mot de passe.

- name: Déployer l'application sécurisée
ansible.builtin.template:
src: app.conf.j2
dest: /etc/secured_app/app.conf
owner: "{{ secured_app_user }}"
mode: "0600"
vars:
rendered_db_password: "{{ secured_app_db_password }}"
rendered_api_token: "{{ secured_app_api_token }}"

Le code du rôle utilise secured_app_db_password (la variable publique). Ansible résout la chaîne defaults → vars, déchiffre vault_* à la volée, et substitue la valeur.

Pourquoi cette indirection plutôt que définir directement dans defaults/

Section intitulée « Pourquoi cette indirection plutôt que définir directement dans defaults/ »

Trois raisons.

Précédence variables : defaults/main.yml est le niveau de précédence le plus bas. N'importe quelle variable host_vars, group_vars ou --extra-vars l'écrase. Si on met le secret directement dans defaults/, n'importe quelle inattention l'écrase. En revanche, vars/main.yml est de précédence plus haute : seul un override explicite dans le playbook (ou --extra-vars) peut le remplacer.

Lisibilité : defaults/main.yml documente l'interface publique du rôle. Mettre directement secured_app_db_password: "RealPassword2026!" mélange interface et secret. Avec l'indirection, le defaults/ reste propre, le secret est isolé dans vars/.

Rotation : changer le mot de passe DB ne touche que vars/main.yml. defaults/main.yml reste stable, donc le diff Git est uniquement sur le fichier chiffré.

  1. Créer la structure du rôle :

    Fenêtre de terminal
    ansible-galaxy init roles/secured_app
    cd roles/secured_app/
  2. Définir l'interface publique dans defaults/main.yml :

    secured_app_user: "appuser"
    secured_app_port: 8080
    secured_app_db_password: "{{ vault_secured_app_db_password }}"
    secured_app_api_token: "{{ vault_secured_app_api_token }}"
  3. Définir les secrets dans vars/main.yml :

    vault_secured_app_db_password: "RoleDBPasswordLab81!"
    vault_secured_app_api_token: "role_api_tok_lab81_xyz"
  4. Chiffrer vars/main.yml :

    Fenêtre de terminal
    ansible-vault encrypt vars/main.yml \
    --vault-password-file=../../.vault_password
  5. Utiliser les variables publiques dans tasks/main.yml (pas les vault_* directement, pour préserver la possibilité de surcharge).

  6. Consommer le rôle depuis un playbook :

    - hosts: db1.lab
    become: true
    roles:
    - role: secured_app
    vars:
    secured_app_port: 9999 # surcharge OK

    Lancer :

    Fenêtre de terminal
    ansible-playbook -i inventory/hosts.yml playbook.yml \
    --vault-password-file=.vault_password

Cas d'usage : CI prod qui doit utiliser un mot de passe différent de celui chiffré dans le rôle (récupéré depuis HashiCorp Vault par exemple).

- hosts: prod_dbservers
roles:
- role: secured_app
vars:
# Surcharge directe — bypass complet de l'indirection vault_*
secured_app_db_password: "{{ lookup('community.hashi_vault.vault_kv2_get',
'prod-db',
engine_mount_point='secret').secret.password }}"

Comme secured_app_db_password est exposé en defaults/, il peut être surchargé par n'importe quelle source de variables de précédence supérieure. La valeur chiffrée par défaut sert de fallback ou de configuration locale ; la prod externalise.

Source de confusion classique.

Ansible charge automatiquement :

  • defaults/main.yml
  • vars/main.yml
  • tasks/main.yml
  • handlers/main.yml
  • meta/main.yml

Ansible ne charge PAS automatiquement :

  • vars/vault.yml
  • vars/secrets.yml
  • defaults/anything-other-than-main.yml

Si vous nommez votre fichier vars/vault.yml, vous obtiendrez une erreur 'vault_secured_app_db_password' is undefined. Il faut soit l'importer explicitement dans une tâche (include_vars), soit le nommer main.yml.

Convention 2026 : utiliser vars/main.yml chiffré. C'est plus court, ça marche out-of-the-box, ça évite l'ambiguïté.

Le lab vault/dans-roles du repo ansible-training reproduit ce pattern avec un rôle complet secured_app et 5 tests pytest qui valident :

  • Le fichier vars/main.yml est bien chiffré.
  • L'indirection secured_app_db_password = vault_secured_app_db_password fonctionne.
  • Une surcharge secured_app_port: 9999 depuis le playbook prend bien le pas sur defaults/.
  • Le secret déchiffré arrive correctement dans le template final.

Suivre : labs/vault/dans-roles/README.md.

  • Chiffrer vars/main.yml, jamais defaults/main.yml. L'interface publique doit rester lisible et diffable.
  • Convention vault_* systématique pour les variables sensibles, même dans les rôles privés.
  • Pas de secret en dur dans tasks/ : toujours via la variable d'interface.
  • README du rôle : documenter l'interface publique (defaults/) sans mentionner les valeurs chiffrées de vars/.
  • Galaxy : un rôle publié sur Galaxy ne contient jamais son vars/main.yml chiffré (ce serait une fuite). Le consommateur fournit ses propres secrets via --extra-vars ou son group_vars/.
  • vars/vault.yml au lieu de vars/main.yml : Ansible ne charge pas vault.yml, variables undefined.
  • Oubli du préfixe vault_* : marche techniquement, mais on perd la lecture rapide de la sensibilité.
  • Définir le secret directement dans defaults/ : trop bas en précédence, n'importe quelle source l'écrase silencieusement.
  • Chiffrer defaults/main.yml : casse le pattern, l'interface publique devient illisible.
  • Référencer vault_* directement dans tasks/ : empêche la surcharge externe ; toujours passer par la variable d'interface.
  • Pattern defaults/main.yml + vars/main.yml : interface publique vs implémentation chiffrée.
  • Indirection secured_app_xxx = "{{ vault_secured_app_xxx }}" dans defaults/.
  • vars/main.yml est chiffré entièrement avec ansible-vault encrypt.
  • Ansible charge automatiquement vars/main.yml (pas vars/vault.yml ni autre nom).
  • Surcharger un secret depuis le consommateur reste possible via vars: du play.
  • Le README du rôle documente l'interface publique sans révéler les secrets.

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