
Ansible amplifie tout : les bonnes pratiques comme les mauvaises. Un playbook mal sécurisé peut exposer des secrets à toute votre équipe, propager une mauvaise configuration à 200 serveurs en 30 secondes, ou ouvrir une porte dérobée via une collection Galaxy compromise. Cette page consolide les 6 axes de sécurité à appliquer transversalement à tout projet Ansible — en complément des sections spécialisées (secrets-vault, auditer un rôle, collections requirements).
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Identifier les 6 axes de sécurité d'un projet Ansible et leurs anti-patterns courants.
- Choisir la bonne stratégie de gestion de secrets (Ansible Vault, HashiCorp Vault, Passbolt) selon le contexte.
- Auditer la supply chain Galaxy (collections, rôles tiers, signatures GPG).
- Appliquer le moindre privilège côté contrôle (compte, clés SSH) et côté managé (sudoers ciblés).
- Durcir les managed nodes via les modules Ansible (SELinux enforcing, sysctl, firewalld, OpenSCAP).
- Tracer et auditer l'exécution (logs, callback plugins, AAP/AWX RBAC).
Prérequis
Section intitulée « Prérequis »- Comprendre la structure d'un playbook et la précédence des variables.
- Avoir manipulé Ansible Vault au moins une fois (premier vault).
- Connaître les bases supply chain (signatures GPG, pinning sémantique).
Axe 1 — Gestion des secrets
Section intitulée « Axe 1 — Gestion des secrets »Tous les secrets (mots de passe, tokens API, clés privées TLS, certificats) doivent être chiffrés avec Ansible Vault avant d'entrer dans Git, et masqués au runtime avec no_log: true. Trois patterns selon le contexte :
| Type de secret | Outil recommandé | Raison |
|---|---|---|
| Token API stable, mot de passe d'app | Ansible Vault encrypt_string | Versionné Git, inline, diffs lisibles |
| Secrets multi-environnements (dev/prod) | vault-id par environnement | Mots de passe distincts, rotation isolée |
| Credentials BDD dynamiques (TTL court) | HashiCorp Vault / OpenBao | Génération on-demand, lease automatique |
| Mots de passe partagés entre humains | Passbolt | UI navigateur, OpenPGP, audit par utilisateur |
| Secrets cloud (AWS, Azure, GCP) | IAM / Workload Identity | Pas de credential statique |
Les 5 anti-patterns à proscrire absolument
Section intitulée « Les 5 anti-patterns à proscrire absolument »| Anti-pattern | Risque | Solution |
|---|---|---|
Mot de passe en clair dans group_vars/all.yml | Exposition Git, logs, historique navigateur | Ansible Vault |
vault_password_file commité | Tout le monde peut déchiffrer | .gitignore + variable d'environnement |
| Secret affiché dans la sortie verbose | Visibilité dans CI/CD, AWX, ELK | no_log: true sur les tâches sensibles |
| Un seul mot de passe Vault pour dev + prod | Compromission totale en cas de fuite | Vault IDs par environnement |
Credentials dans --extra-vars CLI | Visible dans ps aux, history shell | Fichier chiffré ou variable d'env |
Utiliser no_log correctement
Section intitulée « Utiliser no_log correctement »- name: Créer un user avec mot de passe (à masquer) ansible.builtin.user: name: alice password: "{{ vault_alice_password_hash }}" no_log: true # OBLIGATOIRE — sinon le hash apparaît en clair dans les logs
- name: Appel API authentifié (à masquer) ansible.builtin.uri: url: "{{ api_url }}/v1/users" headers: Authorization: "Bearer {{ vault_api_token }}" method: POST body: "{{ payload }}" body_format: json no_log: true register: api_responseno_log: true masque toute la sortie de la tâche (args, stdout, stderr) — y compris en -v ou en cas d'échec.
Intégration avec un secret manager externe
Section intitulée « Intégration avec un secret manager externe »Quand Ansible Vault ne suffit plus (équipe > 5, audit fin requis, secrets dynamiques) :
- name: Récupérer un secret KV depuis HashiCorp Vault ansible.builtin.set_fact: db_password: "{{ lookup('community.hashi_vault.vault_kv2_get', 'kv/myapp/db', url='https://vault.example.com:8200', auth_method='approle', role_id=lookup('env', 'VAULT_ROLE_ID'), secret_id=lookup('env', 'VAULT_SECRET_ID')).data.password }}" no_log: trueDétails dans le guide intégration HashiCorp Vault.
Axe 2 — Supply chain Galaxy
Section intitulée « Axe 2 — Supply chain Galaxy »Les collections Galaxy ne sont pas exemptes des attaques supply chain (typosquatting, takeover de package, dépendances malicieuses). Trois mesures s'imposent :
Pinning strict
Section intitulée « Pinning strict »# requirements.yml — versionné dans Git---collections: - name: ansible.posix version: "2.0.0" # exact, pas >=2.0.0
- name: community.general version: ">=8.0.0,<10.0.0" # range majeur locké
- name: stephrobert.webapp source: https://github.com/stephrobert/ansible-collection-webapp.git type: git version: v1.2.3 # tag, JAMAIS "main" ou brancheJamais latest ni de branche Git — le drift est garanti et silencieux. Toujours un tag ou un SHA Git pour la reproductibilité.
Vérification d'intégrité
Section intitulée « Vérification d'intégrité »# Dans la CI, après install :ansible-galaxy collection verify -r requirements.yml -p collections/verify compare les checksums + signatures GPG du tarball installé avec ce que le mirror déclare. Un mirror compromis qui substitue un tarball malveillant est détecté.
Mirror privé en entreprise
Section intitulée « Mirror privé en entreprise »Pour les fleets sensibles, déployer un Galaxy NG ou Automation Hub privé qui mirror Galaxy public. Avantages : audit centralisé, scan Trivy automatique sur les nouveaux uploads, possibilité de bloquer une collection compromise avant qu'elle ne touche la prod.
Auditer un rôle avant install
Section intitulée « Auditer un rôle avant install »Cf. la checklist d'audit en 7 axes : auteur, activité, tests, README, code, permissions, dépendances. Un rôle malveillant qui tourne en root sur 50 serveurs = compromission totale. Le coût d'audit (10 min) est négligeable face au coût d'incident.
Axe 3 — Moindre privilège
Section intitulée « Axe 3 — Moindre privilège »Côté control node
Section intitulée « Côté control node »- Compte dédié Ansible (jamais root SSH direct sur les cibles).
- Clé SSH Ed25519 générée spécifiquement pour le contrôle, distincte de votre clé personnelle.
ssh-agent+ControlPersistpour éviter de retaper la passphrase à chaque tâche.- Pas de
--ask-pass— c'est un signal qu'une clé manque.
Côté managed node
Section intitulée « Côté managed node »-
Compte non-root avec
sudociblé viacommunity.general.sudoers:- community.general.sudoers:name: ansible-deployuser: ansiblenopassword: truecommands:- /usr/bin/systemctl restart nginx- /usr/bin/systemctl reload nginx- /usr/local/bin/deploy-app.shstate: presentbecome: trueLa règle ci-dessus permet à l'utilisateur
ansiblede redémarrer nginx et lancer un script précis sans mot de passe — mais pas d'avoir un shell root complet. Bien plus restrictif quecommands: ALL. -
Pas de
become: trueau niveau du play par défaut — uniquement sur les tâches qui en ont besoin. Réduit la surface d'attaque en cas de bug.
Credentials par environnement
Section intitulée « Credentials par environnement »inventory/├── hosts.yml├── group_vars/│ ├── all.yml # variables publiques globales│ ├── all/vault.yml # secrets globaux (chiffrés Vault)│ ├── prod/main.yml # variables publiques prod│ ├── prod/vault.yml # secrets prod (vault-id "prod")│ ├── staging/main.yml│ └── staging/vault.yml # vault-id "staging" (mot de passe différent)Avec vault-id distincts pour staging et prod, un développeur qui a accès à staging ne peut pas déchiffrer les secrets prod. La rotation est aussi locale (changer le mot de passe prod sans toucher à staging).
Axe 4 — Modules à risque (shell, command, raw)
Section intitulée « Axe 4 — Modules à risque (shell, command, raw) »Ces 3 modules exécutent des commandes arbitraires sur la cible. Sans précaution, ils sont une porte d'entrée pour l'injection :
# ❌ DANGEREUX — injection si user_input contient "; rm -rf /"- ansible.builtin.shell: "rm /tmp/{{ user_input }}"
# ✅ SÉCURISÉ — module dédié, validation côté Ansible- ansible.builtin.file: path: "/tmp/{{ user_input | regex_replace('[^a-zA-Z0-9._-]', '') }}" state: absentRègle : avant d'écrire command: ou shell:, chercher le module dédié avec ansible-doc -l | grep <thème>. Si c'est vraiment nécessaire, échapper les variables avec | quote :
# Si shell est inévitable- ansible.builtin.shell: "grep {{ pattern | quote }} /var/log/auth.log" changed_when: falseQuand command:/shell: sont légitimes
Section intitulée « Quand command:/shell: sont légitimes »- Aucun module dédié n'existe (cas rare en 2026, vérifier deux fois).
- Bootstrap d'un host sans Python :
rawest obligatoire. - Lecture-seule pour récupérer une valeur : avec
register:+changed_when: false.
Détails dans raw / command / shell.
Axe 5 — Validation du code
Section intitulée « Axe 5 — Validation du code »Stack 2026
Section intitulée « Stack 2026 »| Outil | Rôle | Fréquence |
|---|---|---|
yamllint | Syntaxe YAML stricte (interdire yes/no ambigus) | Pre-commit |
ansible-lint --profile production | 80+ règles : FQCN, idempotence, anti-patterns | Pre-commit + CI bloquante |
ansible-test sanity --docker | Validation collection (FQCN, types, doc) | CI sur push |
| Molecule + testinfra | Tests fonctionnels rôle multi-distros | CI sur PR |
ansible-playbook --check --diff | Détection de drift sur fleet | Cron quotidien |
Intégration CI/CD durcie
Section intitulée « Intégration CI/CD durcie »# .github/workflows/ansible-ci.yml — extraitpermissions: {} # GLOBAL : aucune permission par défaut
jobs: lint: runs-on: ubuntu-24.04 permissions: contents: read steps: - uses: actions/checkout@<SHA40> with: persist-credentials: false - run: pipx install ansible-lint - run: ansible-lint --profile=production . # bloquant - run: ansible-galaxy collection verify -r requirements.ymlRègles non négociables 2026 :
permissions: {}global + permissions minimales par job.- Actions pinnées par SHA, jamais par tag.
persist-credentials: falsesuractions/checkout.- Lint bloquant, pas seulement informatif — sinon les warnings s'accumulent.
Axe 6 — Durcissement OS et traçabilité
Section intitulée « Axe 6 — Durcissement OS et traçabilité »Modules de durcissement
Section intitulée « Modules de durcissement »# SELinux enforcing (mandatory en prod RHEL — objectif RHCE)- ansible.posix.selinux: state: enforcing policy: targeted become: true
# Durcissement réseau (anti-spoofing, syncookies)- ansible.posix.sysctl: name: "{{ item.name }}" value: "{{ item.value }}" sysctl_file: /etc/sysctl.d/99-hardening.conf reload: true loop: - { name: net.ipv4.tcp_syncookies, value: '1' } - { name: net.ipv4.conf.all.rp_filter, value: '1' } - { name: kernel.kptr_restrict, value: '2' } - { name: kernel.dmesg_restrict, value: '1' } become: true
# Firewalld avec règle d'or permanent + immediate- ansible.posix.firewalld: service: ssh permanent: true immediate: true state: enabled zone: public become: trueOpenSCAP pour la conformité CIS / ANSSI / STIG
Section intitulée « OpenSCAP pour la conformité CIS / ANSSI / STIG »Pour les environnements soumis à CIS / ANSSI / STIG, déclencher OpenSCAP via Ansible :
- name: Audit CIS via OpenSCAP ansible.builtin.command: > oscap xccdf eval --profile xccdf_org.ssgproject.content_profile_cis --results /var/log/oscap-cis-{{ inventory_hostname }}.xml /usr/share/xml/scap/ssg/content/ssg-rhel10-ds.xml register: scap_result changed_when: false failed_when: scap_result.rc not in [0, 2] # rc=2 = "des règles ont échoué", pas une erreur fatale become: trueLogs et audit
Section intitulée « Logs et audit »ANSIBLE_LOG_PATH=/var/log/ansible.logdansansible.cfgpour persister chaque run.- Callback plugins (
profile_tasks,timer,json) pour des sorties structurées. - AAP / AWX en prod : audit trail centralisé, RBAC fin, secrets stockés isolés, intégration LDAP/SAML.
# ansible.cfg — config sécurité[defaults]log_path = /var/log/ansible.logno_log = False # explicite, contrôlé par tâchehost_key_checking = True # ne JAMAIS désactiver en proddisplay_skipped_hosts = Falsecallbacks_enabled = ansible.posix.profile_tasks, ansible.posix.timer
[privilege_escalation]become = Truebecome_method = sudobecome_ask_pass = False
[ssh_connection]ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=accept-newpipelining = Truehost_key_checking = True est non négociable en production — sans lui, vous acceptez aveuglément n'importe quelle clé hôte (MITM possible). Pour le bootstrap initial, préférer accept-new (accepte le premier hôte mais refuse un changement ultérieur) plutôt que False.
Sécurité non négociable — récapitulatif
Section intitulée « Sécurité non négociable — récapitulatif »- Tous les secrets dans Vault, jamais en clair.
no_log: truesur les tâches sensibles. - Pinning strict +
ansible-galaxy collection verifydans la CI. become: trueciblé,sudoersprécis (commandes, pasALL).- Modes quoté + restrictifs :
"0600"secrets,"0640"configs sensibles,"0644"public. ansible-lint --profile productionen CI bloquante.- SELinux enforcing, firewalld permanent + immediate,
host_key_checking = True. - Audit trail :
ANSIBLE_LOG_PATHou AAP/AWX en prod.