
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.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Structure
defaults/main.yml+vars/main.ymldans un rôle. - Indirection
secured_app_db_password = "{{ vault_secured_app_db_password }}". - Quand
vars/plutôt quedefaults/(précédence Ansible). - Chiffrer uniquement
vars/main.yml, laisserdefaults/main.ymlen clair. - Surcharger un secret depuis le playbook consommateur.
- Pourquoi pas
vars/vault.yml: Ansible ne charge automatiquement quevars/main.yml.
Prérequis
Section intitulée « Prérequis »- Avoir lu Introduction Vault et Playbooks mixtes.
- Comprendre la structure d’un rôle Ansible (voir Créer un premier rôle).
- Comprendre la précédence des variables (voir Précédence variables).
Structure cible
Section intitulée « Structure cible »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.mdRè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.
Le pattern d’indirection — anatomie
Section intitulée « Le pattern d’indirection — anatomie »defaults/main.yml — l’interface
Section intitulée « defaults/main.yml — l’interface »# roles/secured_app/defaults/main.yml (CLAIR)secured_app_user: "appuser"secured_app_port: 8080secured_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.
vars/main.yml — l’implémentation
Section intitulée « vars/main.yml — l’implémentation »# 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 :
ansible-vault encrypt roles/secured_app/vars/main.yml \ --vault-password-file=.vault_passwordL’utilisateur du rôle ne voit jamais ces valeurs claires sans le mot de passe.
tasks/main.yml — utilisation transparente
Section intitulée « tasks/main.yml — utilisation transparente »- 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é.
Étape par étape
Section intitulée « Étape par étape »-
Créer la structure du rôle :
Fenêtre de terminal ansible-galaxy init roles/secured_appcd roles/secured_app/ -
Définir l’interface publique dans
defaults/main.yml:secured_app_user: "appuser"secured_app_port: 8080secured_app_db_password: "{{ vault_secured_app_db_password }}"secured_app_api_token: "{{ vault_secured_app_api_token }}" -
Définir les secrets dans
vars/main.yml:vault_secured_app_db_password: "RoleDBPasswordLab81!"vault_secured_app_api_token: "role_api_tok_lab81_xyz" -
Chiffrer
vars/main.yml:Fenêtre de terminal ansible-vault encrypt vars/main.yml \--vault-password-file=../../.vault_password -
Utiliser les variables publiques dans
tasks/main.yml(pas lesvault_*directement, pour préserver la possibilité de surcharge). -
Consommer le rôle depuis un playbook :
- hosts: db1.labbecome: trueroles:- role: secured_appvars:secured_app_port: 9999 # surcharge OKLancer :
Fenêtre de terminal ansible-playbook -i inventory/hosts.yml playbook.yml \--vault-password-file=.vault_password
Surcharger un secret depuis l’extérieur
Section intitulée « Surcharger un secret depuis l’extérieur »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.
Pourquoi vars/main.yml et pas vars/vault.yml
Section intitulée « Pourquoi vars/main.yml et pas vars/vault.yml »Source de confusion classique.
Ansible charge automatiquement :
defaults/main.ymlvars/main.ymltasks/main.ymlhandlers/main.ymlmeta/main.yml
Ansible ne charge PAS automatiquement :
vars/vault.ymlvars/secrets.ymldefaults/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é.
Lab pratique
Section intitulée « Lab pratique »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.ymlest bien chiffré. - L’indirection
secured_app_db_password = vault_secured_app_db_passwordfonctionne. - Une surcharge
secured_app_port: 9999depuis le playbook prend bien le pas surdefaults/. - Le secret déchiffré arrive correctement dans le template final.
Suivre : labs/vault/dans-roles/README.md.
Sécurité — bonnes pratiques
Section intitulée « Sécurité — bonnes pratiques »- Chiffrer
vars/main.yml, jamaisdefaults/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 devars/. - Galaxy : un rôle publié sur Galaxy ne contient jamais son
vars/main.ymlchiffré (ce serait une fuite). Le consommateur fournit ses propres secrets via--extra-varsou songroup_vars/.
Pièges courants
Section intitulée « Pièges courants »vars/vault.ymlau lieu devars/main.yml: Ansible ne charge pasvault.yml, variablesundefined.- 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 danstasks/: empêche la surcharge externe ; toujours passer par la variable d’interface.
À retenir
Section intitulée « À retenir »- Pattern
defaults/main.yml+vars/main.yml: interface publique vs implémentation chiffrée. - Indirection
secured_app_xxx = "{{ vault_secured_app_xxx }}"dansdefaults/. vars/main.ymlest chiffré entièrement avecansible-vault encrypt.- Ansible charge automatiquement
vars/main.yml(pasvars/vault.ymlni 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.