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 tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn