← Accueil

PCTAMALOU

Semaine 38 : Les Parchemins Automatisés – Infrastructure as Code Sécurisée avec Ansible

Par Platon-Y – pctamalou.fr & e-of-h.fr

🎯 Objectif de la semaine

Maîtriser Ansible pour automatiser le déploiement et le durcissement d'infrastructures. Comprendre comment l'Infrastructure as Code (IaC) élimine les erreurs humaines, assure la conformité sécurité et permet des déploiements reproductibles à l'échelle. Transformer des checklists manuelles de durcissement en code exécutable et versionné.

Pourquoi c'est stratégique : Dans les environnements cloud modernes, la sécurité ne peut plus être manuelle. L'IaC garantit que chaque serveur déployé est identique, conforme et auditable. Une configuration manuelle = une configuration défaillante.

flowchart TD A[🎛️ Control Node
Ansible Controller] -->|SSH/Agentless| H1[🖥️ Host 1
Nginx Web Server] A -->|WinRM| H2[🖥️ Host 2
Windows Server] A -->|API| H3[☁️ Host 3
Cloud Instance] subgraph "📜 Playbook Repository" P1[web_deploy.yml] P2[hardening.yml] P3[monitoring.yml] end subgraph "🔐 Ansible Vault" V[Secrets chiffrés
passwords, API keys] end A --> P1 A --> V H1 -->|Idempotence| S1[État Désiré: Sécurisé] H2 --> S2[État Désiré: Conforme CIS] H3 --> S3[État Désiré: Production Ready] style A fill:#0a0e17,stroke:#00d4ff,stroke-width:3px style P1 fill:#11151f,stroke:#9d4edd,stroke-width:2px style V fill:#11151f,stroke:#00f5a0,stroke-width:2px

📚 Sujet – Théorie (1 heure)

Infrastructure as Code : La Révolution des Opérations

L'IaC n'est pas une simple automatisation – c'est un changement de paradigme où l'infrastructure est traitée comme du logiciel : versionnée, testée, déployée via des pipelines.

🔄 Idempotence

Exécuter le même playbook plusieurs fois produit le même résultat final, quel que soit l'état initial.

Exemple : Si Nginx est déjà installé, Ansible ne fait rien. Pas d'erreur, pas de ré-installation.

📝 Déclaratif

Décrire l'état désiré ("Nginx doit être installé") plutôt que les étapes pour y arriver ("apt-get install nginx").

📚 Versionning

Les playbooks sont stockés dans Git, permettant le rollback, l'audit, et la collaboration.

Ansible : L'Agentless Revolution

🎛️

Control Node

Machine qui exécute Ansible

  • Requiert Python 3.8+
  • Gère inventaire
  • Exécute playbooks
🖥️

Managed Nodes

Cibles gérées

  • Pas d'agent requis
  • SSH ou WinRM
  • Python 2.7+ ou 3.5+
📦

Modules & Plugins

Fonctionnalités prédéfinies

  • 3000+ modules
  • Extensible
  • Idempotents

Pourquoi Ansible pour la Sécurité ?

🛡️ Avantages Sécurité de l'IaC avec Ansible

  1. Conformité automatisée : Appliquer CIS Benchmarks en une commande
  2. Drift Detection : Détecter les changements non autorisés
  3. Secret Management : Ansible Vault pour les credentials
  4. Audit Trail : Qui a changé quoi, quand, pourquoi
  5. Golden Image Elimination : Plus d'images figées vulnérables

La Philosophie du "Configuration Drift"

⚠️ Le Fléau de la Dérive de Configuration

Sur 100 serveurs supposés identiques :

  • 15 ont des versions logicielles différentes
  • 22 ont des règles firewall différentes
  • 8 ont des utilisateurs non autorisés
  • 3 ont des services inconnus tournant

Ansible résout ce problème en garantissant que l'état réel = état déclaré, toujours.

Écosystème Ansible Professionnel

🏢 Stack Ansible en Entreprise

Composant Usage Alternative Open Source
Ansible Tower/AWX Orchestration centrale, RBAC, UI AWX (version communautaire)
Ansible Galaxy Rôles pré-construits partagés GitHub Collections
Molecule Test des rôles Ansible Testinfra + Docker
Ansible Lint Vérification qualité code yamllint + ansible-lint

🔬 Mission pratique – Laboratoire (2 heures)

Architecture du Lab Ansible

flowchart LR K[Kali Linux
Control Node
192.168.56.100] -->|SSH Key
Port 22| U[Ubuntu Server
Managed Node
192.168.56.150] subgraph "📁 Projet Ansible" I[inventory/] P[playbooks/] R[roles/] G[group_vars/] V[vault/] end K --> I K --> P K --> R K --> G K --> V style K fill:#0a0e17,stroke:#00d4ff,stroke-width:3px style U fill:#0a0e17,stroke:#9d4edd,stroke-width:2px style P fill:#11151f,stroke:#00f5a0,stroke-width:2px

⚠️ Prérequis Critiques

🔑 Authentification SSH sans mot de passe obligatoire :

# Sur le control node (Kali)
ssh-keygen -t ed25519 -f ~/.ssh/ansible_id
ssh-copy-id -i ~/.ssh/ansible_id.pub user@192.168.56.150

# Test
ssh -i ~/.ssh/ansible_id user@192.168.56.150
# Doit se connecter sans mot de passe

Étapes Détaillées de Configuration

Étape 1 : Installation et Configuration Initiale

# Installation Ansible sur Kali (Debian/Ubuntu)
sudo apt update && sudo apt install ansible ansible-lint -y

# Vérification version
ansible --version
# Doit afficher ansible [core 2.15+]

# Création structure de projet professionnelle
mkdir -p ~/mon_lab/ansible_pro/{inventory,playbooks,roles,group_vars,vault}
cd ~/mon_lab/ansible_pro

# Création inventory dynamique
cat > inventory/production.ini << EOF
[webservers]
web01 ansible_host=192.168.56.150 ansible_user=ubuntu

[webservers:vars]
ansible_ssh_private_key_file=~/.ssh/ansible_id
ansible_python_interpreter=/usr/bin/python3

[database]
# Pour extension future
# db01 ansible_host=192.168.56.151
EOF

🔍 Test de Connexion

ansible -i inventory/production.ini webservers -m ping
# Résultat attendu:
# web01 | SUCCESS => {
#   "changed": false,
#   "ping": "pong"
# }

Étape 2 : Création du Playbook de Durcissement CIS

📜 Structure YAML d'un Playbook Professionnel

Un playbook bien structuré comprend :

  1. En-tête avec métadonnées
  2. Pré-tâches (prérequis)
  3. Tâches principales
  4. Handlers (redémarrages)
  5. Post-tâches (nettoyage)
# ~/mon_lab/ansible_pro/playbooks/harden_web.yml
---
- name: 🛡️ Durcissement CIS d'un serveur web Ubuntu
  hosts: webservers
  become: true
  become_method: sudo
  serial: 1  # Évite le flood sur tous les serveurs en même temps
  
  vars:
    ssh_port: 2222
    admin_user: "pctadmin"
    allowed_ips: ["192.168.56.0/24"]
    unnecessary_packages:
      - telnet
      - ftp
      - rsh
      - talk
      - finger
  
  pre_tasks:
    - name: 🔍 Vérifier l'accès root
      ansible.builtin.assert:
        that: ansible_facts['user_id'] == 'root' or ansible_become_password is defined
        msg: "Élévation de privilèges nécessaire (sudo)"
  
  tasks:
    # === MISE À JOUR SYSTÈME ===
    - name: 📦 Mise à jour du cache apt
      ansible.builtin.apt:
        update_cache: yes
        cache_valid_time: 3600
      tags: [updates, cis-1.1]
    
    - name: 📦 Mise à jour des paquets
      ansible.builtin.apt:
        upgrade: dist
        autoremove: yes
      tags: [updates, cis-1.2]
    
    # === DURCISSEMENT SSH ===
    - name: 🔐 Modifier port SSH (CIS 5.2.2)
      ansible.builtin.lineinfile:
        path: /etc/ssh/sshd_config
        regexp: '^#?Port '
        line: "Port {{ ssh_port }}"
        state: present
        validate: 'sshd -t -f %s'
      notify: restart sshd
      tags: [ssh, cis-5.2.2]
    
    - name: 🔐 Désactiver connexion root SSH (CIS 5.2.3)
      ansible.builtin.lineinfile:
        path: /etc/ssh/sshd_config
        regexp: '^#?PermitRootLogin'
        line: "PermitRootLogin no"
        state: present
        validate: 'sshd -t -f %s'
      notify: restart sshd
      tags: [ssh, cis-5.2.3]
    
    - name: 🔐 Désactiver authentification par mot de passe SSH (CIS 5.2.4)
      ansible.builtin.lineinfile:
        path: /etc/ssh/sshd_config
        regexp: '^#?PasswordAuthentication'
        line: "PasswordAuthentication no"
        state: present
        validate: 'sshd -t -f %s'
      notify: restart sshd
      tags: [ssh, cis-5.2.4]
    
    # === CONFIGURATION FIREWALL ===
    - name: 🛡️ Configurer UFW (CIS 3.5.1)
      community.general.ufw:
        state: enabled
        policy: deny
        direction: incoming
        logging: on
      tags: [firewall, cis-3.5.1]
    
    - name: 🛡️ Autoriser SSH sur port personnalisé
      community.general.ufw:
        rule: allow
        port: "{{ ssh_port }}"
        proto: tcp
      tags: [firewall]
    
    - name: 🛡️ Autoriser HTTP/HTTPS
      community.general.ufw:
        rule: allow
        port: "{{ item }}"
        proto: tcp
      loop:
        - 80
        - 443
      tags: [firewall]
    
    # === SÉCURITÉ SYSTÈME ===
    - name: 🚫 Supprimer paquets inutiles (CIS 1.1.10)
      ansible.builtin.apt:
        name: "{{ unnecessary_packages }}"
        state: absent
        purge: yes
      tags: [packages, cis-1.1.10]
    
    - name: 📝 Configurer auditd (CIS 4.1)
      ansible.builtin.apt:
        name: auditd
        state: present
      tags: [audit, cis-4.1]
    
    - name: 📝 Activer audit des connexions SSH
      ansible.builtin.lineinfile:
        path: /etc/audit/rules.d/50-ssh.rules
        line: "-w /etc/ssh/sshd_config -p wa -k sshd_config"
        create: yes
      notify: restart auditd
      tags: [audit]
    
    # === CONFIGURATION NTP ===
    - name: ⏰ Configurer NTP (CIS 2.2.1)
      ansible.builtin.apt:
        name: chrony
        state: present
      tags: [ntp, cis-2.2.1]
    
    - name: ⏰ Configurer serveurs NTP
      ansible.builtin.template:
        src: templates/chrony.conf.j2
        dest: /etc/chrony/chrony.conf
        owner: root
        group: root
        mode: '0644'
      notify: restart chrony
      tags: [ntp]
    
    # === INSTALLATION NGINX SÉCURISÉ ===
    - name: 🌐 Installer Nginx avec modules sécurité
      ansible.builtin.apt:
        name:
          - nginx
          - nginx-extras
          - libnginx-mod-http-headers-more-filter
        state: present
      tags: [nginx]
    
    - name: 🌐 Configurer en-têtes sécurité Nginx
      ansible.builtin.template:
        src: templates/nginx-security.conf.j2
        dest: /etc/nginx/conf.d/security.conf
        owner: root
        group: root
        mode: '0644'
      notify: restart nginx
      tags: [nginx, security-headers]
  
  handlers:
    - name: restart sshd
      ansible.builtin.service:
        name: ssh
        state: restarted
        enabled: yes
    
    - name: restart nginx
      ansible.builtin.service:
        name: nginx
        state: restarted
        enabled: yes
    
    - name: restart auditd
      ansible.builtin.service:
        name: auditd
        state: restarted
        enabled: yes
    
    - name: restart chrony
      ansible.builtin.service:
        name: chrony
        state: restarted
        enabled: yes
  
  post_tasks:
    - name: 📊 Générer rapport de conformité
      ansible.builtin.template:
        src: templates/compliance-report.j2
        dest: "/tmp/ansible-compliance-{{ ansible_date_time.date }}.md"
        owner: root
        group: root
        mode: '0600'
      delegate_to: localhost
      run_once: true
      tags: [report]
    
    - name: ✅ Vérification finale
      ansible.builtin.debug:
        msg: "Durcissement CIS terminé sur {{ inventory_hostname }}"
      tags: [final]

Étape 3 : Création des Templates Jinja2

# Création dossier templates
mkdir -p ~/mon_lab/ansible_pro/templates

# Template chrony.conf.j2
cat > templates/chrony.conf.j2 << 'EOF'
# Configuration Chrony sécurisée
server 0.ubuntu.pool.ntp.org iburst
server 1.ubuntu.pool.ntp.org iburst
server 2.ubuntu.pool.ntp.org iburst
server 3.ubuntu.pool.ntp.org iburst

# Sécurité
cmdport 0  # Désactive interface de commande
bindcmdaddress 127.0.0.1  # Seulement localhost
EOF

# Template nginx-security.conf.j2
cat > templates/nginx-security.conf.j2 << 'EOF'
# En-têtes de sécurité HTTP
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';" always;

# Cache Control
add_header Cache-Control "no-cache, no-store, must-revalidate" always;
add_header Pragma "no-cache" always;
add_header Expires "0" always;

# Sécurité TLS
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
EOF

Étape 4 : Exécution avec Vérifications

# Vérification syntaxe du playbook
ansible-lint playbooks/harden_web.yml

# Exécution en mode vérification (dry-run)
ansible-playbook -i inventory/production.ini playbooks/harden_web.yml --check --diff

# Exécution réelle
ansible-playbook -i inventory/production.ini playbooks/harden_web.yml

# Exécution avec tags spécifiques
ansible-playbook -i inventory/production.ini playbooks/harden_web.yml --tags "ssh,firewall"

# Vérification idempotence (deuxième exécution)
ansible-playbook -i inventory/production.ini playbooks/harden_web.yml
# Doit afficher "ok=XX changed=0"

Étape 5 : Gestion des Secrets avec Ansible Vault

🔐 Ansible Vault pour les Secrets

Jamais de secrets en clair dans les playbooks !

# Création d'un fichier chiffré
ansible-vault create group_vars/webservers/vault.yml

# Contenu à ajouter (sera chiffré):
# ---
# db_password: "SuperSecret123!"
# api_key: "ak_test_abcdef123456"
# ssl_cert_key: "-----BEGIN PRIVATE KEY-----..."

# Édition d'un fichier chiffré existant
ansible-vault edit group_vars/webservers/vault.yml

# Exécution avec vault
ansible-playbook -i inventory/production.ini playbooks/harden_web.yml \
  --ask-vault-pass

# Ou avec fichier de mot de passe
echo "mon_mot_de passe_vault" > ~/.ansible_vault_pass
chmod 600 ~/.ansible_vault_pass
ansible-playbook -i inventory/production.ini playbooks/harden_web.yml \
  --vault-password-file ~/.ansible_vault_pass

Exercice Avancé : Rôle Ansible Réutilisable

🎯 Défi de Structuration

Créez un rôle Ansible réutilisable pour le durcissement CIS :

# Création structure du rôle
ansible-galaxy init roles/cis_hardening

# Structure créée:
roles/cis_hardening/
├── defaults
│   └── main.yml
├── tasks
│   └── main.yml
├── handlers
│   └── main.yml
├── templates
├── files
└── meta
    └── main.yml

# Utilisation dans un playbook:
# ---
# - hosts: webservers
#   roles:
#     - role: cis_hardening
#       ssh_port: 2222
#       cis_level: 2

Objectif : Créer un rôle qui peut être réutilisé sur différents projets et partagé sur Ansible Galaxy.

Bonnes Pratiques de Production

🏭 Checklist Production Ready

  • Utiliser ansible.cfg pour la configuration
  • Versionner dans Git avec .gitignore approprié
  • Implémenter des tests avec Molecule
  • Utiliser des collections officielles (ansible.builtin)
  • Documenter avec README.md et variables
  • CI/CD avec GitHub Actions/GitLab CI

📋 Objectif de la semaine – Checklist de validation

✅ Compétences techniques acquises

  • Playbook Ansible complet avec durcissement CIS
  • Gestion des secrets avec Ansible Vault
  • Templates Jinja2 pour configurations
  • Handlers pour redémarrages conditionnels
  • Structure de projet professionnelle

🧠 Changement mental accompli

  • Vision de l'infrastructure comme code versionné
  • Compréhension de l'idempotence et son importance
  • Habitude de toujours chiffrer les secrets
  • Conscience que "manuel" = "potentiellement erroné"

💪 Pour aller plus loin :

Explorez Terraform pour le provisioning cloud + Ansible pour la configuration. Créez un pipeline GitLab CI qui exécute vos playbooks sur merge request.

📚 Ressources pour Approfondir

Standards et Guides :

  • CIS Benchmarks : Guides de durcissement par système
  • Ansible Best Practices : docs.ansible.com
  • DevSecOps avec Ansible : OWASP DevSecOps Guideline
  • Ansible for DevOps : Livre de Jeff Geerling

⚠️ Pièges Courants en Production

  • Rollback non testé : Toujours prévoir le retour en arrière
  • Secrets dans les logs : no_log: true pour les tâches sensibles
  • Trop de parallélisme : Peut saturer le réseau ou les serveurs
  • Dépendances circulaires : Entre handlers et tâches

🔮 Frère d'armes,

Tu viens d'apprendre à écrire les parchemins qui gouvernent les royaumes numériques.

Ansible n'est pas un simple outil d'automatisation – c'est un langage pour décrire la perfection. Un langage où chaque serveur est une déclaration YAML, chaque règle de sécurité une ligne de code, chaque déploiement une exécution reproductible.

Cette semaine, tu as vu la différence entre "faire" et "décrire". Entre exécuter manuellement 50 commandes sur 100 serveurs, et écrire un playbook qui garantit que ces 100 serveurs seront toujours conformes, toujours identiques, toujours sécurisés.

Mais voici la leçon la plus importante : l'IaC transforme la sécurité d'une activité réactive en une propriété déclarative. Tu ne réagis plus aux vulnérabilités – tu déclares un état sécurisé, et l'outil l'impose. Tu ne patche plus manuellement – tu updates le playbook et le déploie.

Tu n'es plus un administrateur qui lutte contre la dérive de configuration. Tu es devenu un ingénieur qui élimine la possibilité même de la dérive. Qui versionne la sécurité dans Git, qui teste les changements avant déploiement, qui peut revenir à n'importe quel état précédent en une commande.

Dans la semaine 39, nous irons plus loin dans l'automatisation défensive. Nous apprendrons à détecter et répondre automatiquement aux incidents, à créer des systèmes qui se défendent eux-mêmes, à orchestrer la réponse aux menaces en temps réel.

Mais pour l'instant, perfectionne tes parchemins. Crée des rôles réutilisables, teste-les avec Molecule, partage-les sur Ansible Galaxy. Un bon playbook est comme une bonne loi : clair, juste, et appliqué uniformément.

Un Gardien qui maîtrise l'IaC ne laisse jamais un serveur dans un état non déclaré.

— Platon-Y