Comment générer automatiquement un inventaire et propager les clés SSH pour piloter un cluster complet depuis une seule VM de contrôle avec Vagrant et Ansible

Comment générer automatiquement un inventaire et propager les clés SSH pour piloter un cluster complet depuis une seule VM de contrôle avec Vagrant et Ansible

> ./deploy_cluster.sh --with-control-node_

Vagrant & Ansible : L'Art du "Control Node" Automatisé

Par Nicolas DELAHAYE | v.1974 | Architecte Solution

STATUS: SSH_KEYS_DISTRIBUTED


C'est un mur que tout ingénieur DevOps rencontre tôt ou tard. Vous voulez construire un lab réaliste : Une VM "Maître" (Control Node) qui configure plusieurs VMs "Esclaves" (Workers) via Ansible.

Le problème ? Vagrant est conçu pour isoler. Par défaut, il génère une clé SSH unique par machine. Résultat : votre VM Maître ne peut pas se connecter aux Workers. La solution naïve consiste à lancer ansible_local sur chaque machine indépendamment, mais c'est un anti-pattern qui ne simule pas la réalité d'un cluster.

// Pour comprendre en détail pourquoi l'architecture ansible_local est souvent incontournable (surtout sur Windows), je vous invite à lire mon comparatif : Vagrant : Ansible vs Ansible_Local et le mythe du Control Node.

Pour réussir une Vagrant Ansible Inventory propre et automatisée, nous devons inverser la logique : générer une clé de confiance unique et construire l'inventaire dynamiquement.

Au lieu de laisser Vagrant gérer ses clés, nous allons créer une paire de clés "Cluster" sur l'hôte (votre PC) et la distribuer.

Le Flux Architectural :
1. Host : Génère une paire de clés SSH (Privée/Publique).
2. Vagrant : Injecte la clé Publique dans tous les Workers.
3. Vagrant : Monte la clé Privée dans le Control Node.
4. Ruby : Génère le fichier d'inventaire .ini automatiquement.

Voici le code complet. Il fait tout le travail sale à votre place. Pas de bidouille manuelle.

Note : Ce script écrit le fichier d'inventaire sur votre machine hôte pour qu'il soit accessible dans la VM via le dossier partagé /vagrant. Si vous rencontrez des problèmes de performance ou de permissions sur ce partage, vérifiez votre configuration de montage (voir mon article : Vagrant Synced Folders : Le casse-tête Windows vs Linux).

# 1. Génération de la clé Cluster (si absente)
KEY_PATH = ".vagrant/cluster_key"
unless File.exist?(KEY_PATH)
  system("ssh-keygen -t ed25519 -f #{KEY_PATH} -N '' -C 'vagrant-cluster'")
end
CLUSTER_PUBKEY = File.read("#{KEY_PATH}.pub")

# 2. Préparation de l'inventaire dynamique
inventory_content = ["[all:vars]", 
                     "ansible_user=vagrant", 
                     "ansible_ssh_private_key_file=/vagrant/#{KEY_PATH}", 
                     "ansible_ssh_common_args='-o StrictHostKeyChecking=no'", 
                     "", "[controllers]", "control-node ansible_host=192.168.56.10",
                     "", "[workers]"]

Vagrant.configure("2") do |config|
  
  # --- DEFINITION DES WORKERS ---
  (1..2).each do |i|
    ip = "192.168.56.2#{i}"
    name = "worker#{i}"
    
    config.vm.define name do |node|
      node.vm.box = "ubuntu/jammy64"
      node.vm.network "private_network", ip: ip
      node.vm.hostname = name
      
      # Injection de la clé publique
      node.vm.provision "shell", inline: "echo '#{CLUSTER_PUBKEY}' >> /home/vagrant/.ssh/authorized_keys"
      
      # Ajout à l'inventaire en mémoire
      inventory_content << "#{name} ansible_host=#{ip}"
    end
  end

  # --- DEFINITION DU CONTROL NODE ---
  config.vm.define "control-node" do |node|
    node.vm.box = "ubuntu/jammy64"
    node.vm.network "private_network", ip: "192.168.56.10"
    node.vm.hostname = "control-node"

    # Injection de la clé publique (pour qu'il puisse se connecter à lui-même si besoin)
    node.vm.provision "shell", inline: "echo '#{CLUSTER_PUBKEY}' >> /home/vagrant/.ssh/authorized_keys"

    # Installation d'Ansible sur le Control Node uniquement
    node.vm.provision "shell", inline: "apt-add-repository ppa:ansible/ansible -y && apt-get update && apt-get install ansible -y"

    # 3. Écriture du fichier d'inventaire sur le disque Hôte
    # Il sera accessible dans la VM via /vagrant/inventory.ini
    File.write("inventory.ini", inventory_content.join("\n"))

    # 4. Lancement du Playbook (Mode Parallel Execution)
    node.vm.provision "ansible_local" do |ansible|
      ansible.playbook = "playbook.yml"
      ansible.inventory_path = "/vagrant/inventory.ini"
      ansible.limit = "all"
      ansible.verbose = true
    end
  end
end

Avec cette configuration :

  • Zéro Friction : Un simple vagrant up construit le cluster, les clés et lance Ansible.
  • Isolation Propre : Votre machine Windows/Mac ne lance pas Ansible. C'est la VM "control-node" qui le fait, via le montage /vagrant.
  • Scalabilité : Changez (1..2) en (1..10) et votre inventaire se met à jour automatiquement.

C'est la différence entre "bidouiller des scripts" et faire de l'Infrastructure as Code. Vous respectez le principe DRY (Don't Repeat Yourself) et vous préparez un environnement qui ressemble à 99% à une production réelle (Bastion + Private Subnet).

Vagrant Synced Folders : Pourquoi la configuration change entre Windows et Linux ?

Vagrant Synced Folders : Pourquoi la configuration change entre Windows et Linux ?

> cat Vagrantfile | grep "synced_folder" --explain_

Vagrant Synced Folders : Le casse-tête Windows vs Linux résolu

Par Nicolas DELAHAYE | v.1974 | Architecte Solution

STATUS: FILESYSTEM_OPTIMIZATION


Si vous travaillez dans une équipe mixte, vous avez sûrement remarqué ce comportement étrange concernant les Vagrant Synced Folders. Sur Linux ou macOS, vous devez écrire des blocs de configuration complexes (spécifiant NFS, versions, UDP, etc.), alors que sur Windows, une simple ligne suffit souvent.

Est-ce que Windows est "plus intelligent" ? Non. Au contraire, cette différence cache un compromis technique majeur entre performance et compatibilité. Décortiquons ce qui se passe sous le capot de vos montages de fichiers.

Il faut comprendre que Vagrant est un chef d'orchestre, pas un ouvrier. Quand vous demandez un dossier partagé, Vagrant délègue cette tâche au "Provider" (VirtualBox, Hyper-V, VMWare) et aux capacités du système hôte.

Les Vagrant Synced Folders n'utilisent pas la même technologie selon l'OS sur lequel ils tournent :

  • Sur Windows : Utilise par défaut VirtualBox Shared Folders (vboxsf).
  • Sur Linux/macOS : Peut utiliser vboxsf, mais les développeurs forcent quasi-systématiquement NFS.

Linux et macOS sont des systèmes POSIX. Ils gèrent les permissions (chmod, chown), les liens symboliques et les sockets de manière standardisée.

Pour obtenir des performances décentes (surtout avec des projets contenant des milliers de fichiers comme Symfony, Laravel ou node_modules), on utilise le protocole NFS (Network File System).

Cependant, NFS n'est pas "magique". Pour qu'il fonctionne via Vagrant, il faut être explicite :
👉 Quelle version de NFS ? (souvent la 4)
👉 UDP ou TCP ? (TCP est plus stable)
👉 Quelles options de montage ?

C'est pour cela que votre configuration Linux est verbeuse. Vous demandez de la performance, et Vagrant a besoin de détails pour configurer le serveur NFS localement.

Windows n'est pas POSIX. Son système de fichiers (NTFS) ne gère pas les permissions comme Linux. Installer un serveur NFS sur Windows est possible mais fastidieux et instable.

Par défaut, Vagrant sur Windows se rabat donc sur le plus petit dénominateur commun : VirtualBox Shared Folders.

Le piège :
Vous n'avez rien à configurer, ça "juste marche". MAIS :
1. C'est beaucoup plus lent (I/O disque catastrophique sur les gros projets).
2. Les permissions sont "simulées" (tout appartient souvent à root ou vagrant).
3. Les événements de fichiers (inotify) pour le hot-reload passent mal.

Votre configuration actuelle avec des if/else imbriqués fonctionne, mais elle viole le principe DRY (Don't Repeat Yourself). Elle est difficile à maintenir si vous ajoutez un troisième dossier.

Voici une approche "DevSecOps" plus propre. Nous allons créer une méthode Ruby qui génère la configuration adaptée selon l'OS détecté.

# Au début de votre Vagrantfile ou dans un fichier require séparé
def get_mount_options(is_nfs_capable)
  if is_nfs_capable
    {
      type: "nfs",
      nfs_version: 4,
      nfs_udp: false,
      mount_options: ["rw", "actimeo=1"] # actimeo booste le cache NFS
    }
  else
    # Fallback Windows / vboxsf
    {
      mount_options: ["rw", "dmode=777", "fmode=777"] # On force les perms sur Windows
    }
  end
end

# Détection simple
IS_UNIX = !Vagrant::Util::Platform.windows?

Vagrant.configure("2") do |config|
  # Configuration dynamique et propre
  config.vm.synced_folder ".", "/home/dev", **get_mount_options(IS_UNIX)

  if PERSONNAL_CONFIG[:add_project_source_code]
     config.vm.synced_folder PERSONNAL_CONFIG[:src_path], "/home/project", **get_mount_options(IS_UNIX)
  end
end

// NOTE : L'opérateur ** en Ruby permet d'éclater le hash retourné par la fonction directement en arguments pour Vagrant.

Ne cherchez pas à avoir une configuration identique à l'octet près entre Windows et Linux pour vos Vagrant Synced Folders. C'est un combat perdu d'avance à cause des différences d'architecture OS.

L'approche recommandée est celle-ci :
Linux/Mac : Forcez NFS pour la vitesse.
Windows : Acceptez vboxsf pour la simplicité (ou passez à WSL2).
Le Code : Abstraire cette complexité dans une fonction Ruby pour garder un Vagrantfile lisible.

Vagrant sur Windows : Le mystère des variables BOX_VERSION et BOX_ARCH obligatoires

Vagrant sur Windows : Le mystère des variables BOX_VERSION et BOX_ARCH obligatoires

> vagrant up --provider=virtualbox --debug_

Vagrant : Pourquoi Windows exige BOX_VERSION et BOX_ARCH ?

Par Nicolas DELAHAYE | v.1974 | Architecte Solution

ERROR: AMBIGUOUS_BOX_METADATA_FOUND


Voici un scénario que j'ai vécu (et vous aussi probablement) : je pousse mon code sur Git. Mon Vagrantfile est propre. Je fais un vagrant up sur mon Mac M3 : tout fonctionne.

Mon collègue sous Windows clone le repo, lance la même commande et... CRASH.
> No matching provider found. Please specify box_version and box_arch.

Pourquoi cette différence de traitement ? Pourquoi Windows est-il l'élève difficile de la classe ? La réponse se trouve dans les entrailles de l'OS.

Pour comprendre, il faut revenir à la base. Linux et macOS sont des cousins : ils sont POSIX-compliant. Ils parlent la même langue (chemins de fichiers /, permissions, gestion des processus via fork/exec).

Windows est une bête différente (NTFS, chemins C:\, API Win32). Quand Vagrant essaie d'analyser le système pour "deviner" quoi faire, il se heurte à un mur de complexité sur Windows que Linux n'a pas.

Quand vous ne précisez rien dans le Vagrantfile, Vagrant doit faire un travail d'enquête (Inférence) :

Sur Linux / macOS :
Vagrant demande au noyau : "T'es qui ?".
Le noyau répond : "Je suis un Darwin arm64".
Vagrant regarde la Box : "J'ai une version compatible, je la prends."
👉 Succès automatique.
Sur Windows :
Vagrant fait face à plusieurs Hyperviseurs possibles (Hyper-V, VirtualBox, VMWare, WSL2). Les métadonnées système sont plus floues pour un outil né dans le monde Ruby/Linux.
Vagrant hésite : "Quelle architecture ? Quel Provider ? Quelle version exacte ?".
👉 Échec par prudence. Il vous demande de préciser.
  • Les Chemins de fichiers : Les métadonnées des Boxes sont stockées dans C:\Users\VotreNom\.vagrant.d\.... Les espaces ou caractères spéciaux dans les chemins Windows cassent souvent la lecture automatique des fichiers JSON de configuration.
  • L'Architecture : Windows ne rapporte pas son architecture (amd64 vs arm64) de la même manière standardisée que uname -m sous Unix. Vagrant ne peut pas garantir que la Box téléchargée tournera sur votre CPU.
  • Les Providers : Sur Windows, la cohabitation Hyper-V et VirtualBox est notoirement complexe. Vagrant a besoin de savoir explicitement quelle version de Box correspond à quel moteur de virtualisation.

Plutôt que de voir cela comme une contrainte Windows, voyez-le comme une discipline de reproductibilité.

En définissant ces variables, vous verrouillez votre environnement. Plus de "mise à jour magique" qui casse tout le lundi matin.

# Dans votre Vagrantfile
config.vm.box = "ubuntu/jammy64"
# Obligatoire pour Windows, recommandé pour tous :
config.vm.box_version = "202401.01.0"
config.vm.box_arch    = "amd64"

// RÉFÉRENCES OFFICIELLES :

[ EOF - Configuration Saved ]

Vagrant : Ansible vs Ansible_Local et le mythe du Control Node Windows

Vagrant : Ansible vs Ansible_Local et le mythe du Control Node Windows

> diff vagrant_provisioners.txt --side-by-side_

Vagrant : Le duel Ansible vs Ansible_Local (et la vérité sur Windows)

Par Nicolas DELAHAYE | v.1974 | Architecte & Automation

STATUS: ARCHITECTURE_DECISION_REQUIRED


Dans un Vagrantfile, la différence tient à six lettres et un underscore : _local. Pourtant, choisir entre config.vm.provision "ansible" et config.vm.provision "ansible_local" n'est pas une question de goût. C'est une décision d'architecture qui définit qui peut (ou ne peut pas) exécuter votre code.

C'est l'approche puriste. Ici, l'Hôte est le Maître, la VM est l'Esclave.

Le Flux :
[ VOTRE MAC/LINUX ] --(SSH)--> [ VM CIBLE ]

Vagrant va chercher l'exécutable ansible-playbook installé sur votre machine physique et lui demande de se connecter à la VM pour la configurer.

  • ✅ Les avantages : C'est propre. Vous utilisez votre Ansible centralisé, vos secrets (Vault), vos clés SSH et vous ne polluez pas la VM avec l'installation d'Ansible.
  • ❌ Le problème : Cela exige qu'Ansible soit installé sur la machine hôte. Et si votre collègue est sous Windows... vous êtes dans une impasse.

C'est l'approche "Contained". Ici, la VM est à la fois Maître et Esclave.

Le Flux :
1. Vagrant démarre la VM.
2. Vagrant installe Ansible DANS la VM (Guest).
3. Vagrant monte vos playbooks dans /vagrant.
4. La VM exécute Ansible sur elle-même (localhost).
  • ✅ Les avantages : Zéro dépendance. Que vous soyez sur Mac, Linux ou Windows, ça marche. Le lab est 100% autonome et reproductible.
  • ❌ Le problème : Le premier démarrage est plus lent (il faut installer Ansible) et c'est conceptuellement étrange pour un puriste de voir un serveur se configurer lui-même.

J'entends souvent : "Mais pourquoi on n'installe pas juste Ansible sur Windows pour utiliser le mode classique ?".

La réponse est brutale : C'est impossible. Ce n'est pas un bug, c'est une conception.

Pourquoi ? (Le point technique)

Ansible est écrit en Python, mais son cœur repose sur des primitives système POSIX : le fork() de processus, la gestion des descripteurs de fichiers, les pseudo-terminaux (pty) et un modèle de permissions SSH spécifique.

L'API Win32 de Windows fonctionne différemment. Elle ne possède pas ces concepts nativement. Résultat : Ansible ne peut pas fonctionner en tant que Control Node (le cerveau) sur Windows. Il peut seulement gérer des cibles Windows.

Ce que dit la documentation officielle

"Running Ansible from a Windows control machine is not a goal of the project."

"Ansible cannot run on Windows as the control node due to API limitations on the platform."

-- Docs officielles Ansible & FAQ

// Note : Oui, WSL (Windows Subsystem for Linux) existe. Mais cela ajoute une couche de complexité réseau avec Vagrant et n'est pas "supporté officiellement" pour la production par Red Hat.

Dans mon contexte de préparation aux certifications CKA/CKS, je dois pouvoir casser et reconstruire mon environnement en 5 minutes, que je sois sur mon MacBook Pro M3 ou que je partage le code avec un collègue sous Windows.

Mon choix est donc sans appel : ansible_local.

Recommandation
Équipe 100% Linux/Mac ansible (plus rapide)
Équipe mixte (Win/Lin/Mac) ansible_local (obligatoire)
Lab pédagogique / Formation ansible_local (zéro pré-requis)
CI/CD Pipeline ansible (l'agent CI est sous Linux)

// SYNTHÈSE :

Utiliser ansible_local n'est pas un "hack" ou une "verrue". C'est un pattern de portabilité. En DevSecOps, la reproductibilité de l'environnement prime sur la pureté de l'implémentation. Mieux vaut un script qui tourne partout qu'un script "parfait" qui ne tourne que sur ma machine.

CVE, CVSS, EPSS : Le Guide Ultime pour Prioriser vos Vulnérabilités

CVE, CVSS, EPSS : Le Guide Ultime pour Prioriser vos Vulnérabilités

> ./explain_vuln_metrics.sh --verbose --deep-dive_

CVE, CVSS et EPSS : Comprendre, Trier et Prioriser

Par Nicolas DELAHAYE | v.1974 | DevSecOps & Architecture

STATUS: CRITICAL_KNOWLEDGE_LOADED


Le monde de la cybersécurité ne manque pas d'acronymes. Mais s'il y a bien une trinité que tout ingénieur, développeur ou architecte doit maîtriser pour ne pas sombrer sous le poids de la dette technique, c'est celle-ci : CVE, CVSS et EPSS.

Au quotidien, nos scanners CI/CD crachent des rapports rouges sang. Sans une compréhension fine de ces métriques, vous allez patcher des fantômes pendant que votre infra brûle par une porte dérobée que vous aviez classée "Medium". Voici le décodage complet.

Définition : CVE signifie Common Vulnerabilities and Exposures.

Imaginez le CVE comme le numéro de sécurité sociale d'un bug de sécurité. C'est un identifiant unique, international, géré par le programme CVE (MITRE) et ses partenaires (CNA - CVE Numbering Authorities).

Structure d'un CVE :
CVE-2024-12345
  • CVE : Le préfixe standard.
  • 2024 : L'année d'attribution de l'ID (pas forcément l'année de découverte).
  • 12345 : Un numéro séquentiel unique.

À quoi ça sert ? À parler le même langage. Quand je dis "Log4Shell", c'est flou. Quand je dis CVE-2021-44228, tous les outils de la planète (firewalls, scanners, bases de données) savent exactement de quelle ligne de code on parle.

Avoir un nom (CVE), c'est bien. Savoir si c'est dangereux, c'est mieux. C'est le rôle du Common Vulnerability Scoring System.

Attention : Le CVSS mesure la sévérité technique, pas le risque pour VOTRE entreprise. Il répond à la question : "Si cette faille est exploitée, quels sont les dégâts potentiels ?"

Le thermomètre (Score v3.1 / v4.0) :

  • [0.0 - 3.9] FAIBLE : Impact minime.
  • [4.0 - 6.9] MOYEN : Nécessite souvent une action locale ou des privilèges.
  • [7.0 - 8.9] ÉLEVÉ : Impact sérieux, souvent exploitable à distance.
  • [9.0 - 10.0] CRITIQUE : "Game Over". Prise de contrôle totale, souvent sans authentification.

Un score CVSS n'est pas un chiffre magique, c'est une formule complexe basée sur un Vecteur. Exemple pour Log4Shell :

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H

Décryptage : AV:N (Réseau/Network) + AC:L (Complexité Faible) + PR:N (Aucun privilège requis) + C/I/A:H (Confidentialité/Intégrité/Dispo : Impact Haut). C'est le pire scénario possible.

C'est ici que la modernité entre en jeu. Vous avez 100 vulnérabilités "Critiques" (CVSS 9.8). Par laquelle commencer ?

Le CVSS a une limite : il ne vous dit pas si des pirates exploitent réellement la faille. Peut-être que la faille est théoriquement catastrophique, mais techniquement impossible à exploiter sans un alignement des planètes improbable.

L'EPSS (Exploit Prediction Scoring System), géré par FIRST.org, répond à la question : "Quelle est la probabilité que cette faille soit exploitée dans les 30 prochains jours ?"

La logique de triage moderne :
Une CVE avec un CVSS de 9.8 mais un EPSS de 0.01% (peu probable) est MOINS prioritaire qu'une CVE avec un CVSS de 7.5 mais un EPSS de 95% (attaque en cours massive).
La question posée Type de donnée
CVE "C'est quoi le problème ?" Identifiant unique (Catalogue)
CVSS "À quel point ça fait mal ?" Score de gravité (0-10)
EPSS "Vais-je me faire attaquer ?" Probabilité (0-100%)

Pour extraire ces données de vos images Docker ou de vos serveurs, vous avez besoin d'outils. Voici le Top 3 Open Source.

A. Aqua Trivy (Le Couteau Suisse)

C'est mon favori. Rapide, complet, sans base de données à gérer.

$ trivy image python:3.9-alpine
# Scan aussi le filesystem, les repos git et Kubernetes
$ trivy fs --scanners vuln,secret,config .

B. Grype & Syft (Le Duo Précis)

Développé par Anchore. Syft génère le SBOM (Software Bill of Materials) et Grype le scanne.

$ syft packages docker:nginx:latest -o json > sbom.json
$ grype sbom:./sbom.json

C. Clair (Le Pionnier)

Plus lourd, nécessite une base de données Postgres, mais très utilisé dans les registres d'entreprise comme Quay.io.

Vous scannez votre image de production. Trivy remonte la CVE-2023-XXXX.

Analyse :

  • CVSS : 9.1 (Critique) -> Panique à bord ?
  • Vecteur : AV:N (Réseau) mais AC:H (Complexité Haute).
  • EPSS : 0.02% (Très faible).
  • Contexte : La librairie vulnérable est présente dans l'image, mais elle n'est utilisée que pour compiler des assets statiques et n'est pas chargée au runtime.

Décision DevSecOps :

Malgré le CVSS rouge vif, le risque réel est quasi nul. On peut accepter le risque temporairement ou supprimer la librairie de l'image finale (multi-stage build), plutôt que de bloquer la mise en production pour un patch urgent.
C'est ça, la puissance de l'analyse contextuelle combinée à l'EPSS.

// RESSOURCES OFFICIELLES :

[ EOF - Security Scan Complete ]