Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

Chapitre 1218 minIntermédiaire

Automatiser les équipements réseau : Cisco et pfSense

Par Josselyn C., prof de BTS SIO. Mis à jour le .

À la fin de ce chapitre, tu sauras

  • expliquer pourquoi un équipement réseau se gère autrement qu'un serveur ;
  • écrire l'inventaire d'un groupe de switchs Cisco ;
  • créer des VLAN et configurer des ports de façon idempotente ;
  • sauvegarder automatiquement les configurations ;
  • situer les possibilités et les limites d'Ansible sur pfSense.

3 exercices corrigés

Ansible automatise aussi les switchs, les routeurs et les pare-feu : il se connecte à leur ligne de commande en SSH (ou à leur API) et envoie des commandes depuis le nœud de contrôle, puisque ces équipements n'ont pas de Python. Ce chapitre explique ce qui change avec le réseau, configure les VLAN et les ports des switchs Cisco de Sorbier de façon idempotente, sauvegarde leurs configurations, et fait le point sur pfSense.

Pourquoi le réseau est différent

Sur un serveur, Ansible copie un module Python et l'exécute sur place. Un switch Cisco ne sait pas exécuter de Python : le module tourne donc sur le nœud de contrôle, se connecte à l'équipement, lit sa configuration, calcule les commandes nécessaires et les envoie.

Serveur LinuxSwitch Cisco
Où tourne le moduleSur le serveurSur le nœud de contrôle
Connexionssh (un shell)ansible.netcommon.network_cli (la ligne de commande IOS en SSH), parfois netconf ou httpapi
Variable indispensable—ansible_network_os: cisco.ios.ios
Élévation de privilègessudoenable (ansible_become_method: enable), inutile avec un compte privilege 15
IdempotenceLe module compare l'état du systèmeLe module compare la configuration courante (running-config) et n'envoie que les lignes qui manquent
Factssetupcisco.ios.ios_facts

La collection cisco.ios (et sa dépendance ansible.netcommon) gère IOS et IOS XE, le système des Catalyst 9200 et 9300 de Sorbier. Des collections équivalentes existent pour d'autres constructeurs : arista.eos, junipernetworks.junos, cisco.nxos.

Ses modules sont de deux familles :

  • les modules de ressources (ios_vlans, ios_l2_interfaces, ios_interfaces, ios_static_routes…) décrivent un état structuré, en YAML, comme les modules d'un serveur ;
  • les modules génériques (ios_config, ios_command) envoient des lignes de configuration ou des commandes show telles quelles.

Préparer les switchs et le nœud de contrôle

Sur chaque switch, un compte local pour Ansible, en plus de la configuration SSH du cours Routage et commutation :

text
SW-ETAGE1(config)# username ansible privilege 15 algorithm-type scrypt secret Mdp-Switchs-2026!

privilege 15 fait arriver le compte directement en mode privilégié : pas besoin de mot de passe enable. Le mot de passe du compte va dans le coffre d'Ansible.

Sur le nœud de contrôle, la connexion network_cli a besoin d'une bibliothèque SSH en Python, ansible-pylibssh (recommandée) ou, à défaut, paramiko :

bash
pipx inject ansible-core ansible-pylibssh

Sans elle, la première tâche échoue avec « paramiko is not installed ». Connecte-toi ensuite une fois à chaque switch en SSH depuis le bastion pour vérifier et enregistrer son empreinte, comme pour les serveurs.

Les variables du groupe, dans inventaire/group_vars/switchs/vars.yml :

yaml
---
ansible_connection: ansible.netcommon.network_cli
ansible_network_os: cisco.ios.ios
ansible_user: ansible
ansible_password: "{{ vault_mdp_switchs }}"

et vault_mdp_switchs dans vault.yml, chiffré (chapitre 9).

Recueillir les informations

yaml
---
- name: Inventorier les switchs
  hosts: switchs
  gather_facts: false

  tasks:
    - name: Lire les facts du switch et ses VLAN
      cisco.ios.ios_facts:
        gather_subset: min
        gather_network_resources: vlans

    - name: Afficher le modèle et la version d'IOS
      ansible.builtin.debug:
        msg: >-
          {{ inventory_hostname }} : {{ ansible_facts['net_model'] }},
          IOS {{ ansible_facts['net_version'] }}, numéro de série {{ ansible_facts['net_serialnum'] }}

    - name: Lancer une commande show
      cisco.ios.ios_command:
        commands:
          - show ip interface brief
      register: interfaces

    - name: Afficher la sortie
      ansible.builtin.debug:
        var: interfaces.stdout_lines[0]

gather_facts: false au niveau du play, parce que le module setup est fait pour les serveurs ; on appelle ios_facts à la place. Les facts réseau commencent par net_ : net_hostname, net_model, net_version, net_serialnum, net_interfaces. Avec gather_network_resources, la configuration des VLAN est aussi rendue sous forme structurée, dans ansible_facts['network_resources']['vlans']. Cet inventaire alimente utilement GLPI ou la documentation du réseau.

Créer les VLAN de façon idempotente

Le switch d'étage sw-etage2 doit avoir les VLAN 10, 50, 99, 998 et 999 du plan de Sorbier. Avec le module de ressources ios_vlans :

yaml
---
- name: Configurer les VLAN des switchs d'étage
  hosts: switchs
  gather_facts: false

  vars:
    vlans_etage:
      - vlan_id: 10
        name: POSTES
      - vlan_id: 50
        name: TELEPHONIE
      - vlan_id: 99
        name: ADMIN
      - vlan_id: 998
        name: PARKING
      - vlan_id: 999
        name: NATIF

  tasks:
    - name: Garantir les VLAN de l'étage
      cisco.ios.ios_vlans:
        config: "{{ vlans_etage }}"
        state: merged

Au premier passage, le module lit les VLAN existants, envoie seulement ce qui manque (des commandes comme vlan 10 puis name POSTES) et renvoie la liste des commandes envoyées dans commands. Au deuxième passage, tout est déjà en place : ok, aucune commande.

Le paramètre state dit comment traiter ce qui existe déjà sur l'équipement :

stateEffetUsage
mergedAjoute ou modifie ce qui est décrit, ne touche pas au resteLe plus prudent : ajouter des VLAN
replacedRemplace entièrement la configuration des VLAN décritsCorriger un VLAN dont des réglages ont dérivé
overriddenFait de la liste la seule configuration : les VLAN absents de la liste sont supprimésImposer un état complet ; dangereux si la liste est incomplète
deletedSupprime les VLAN décrits (ou tous, si config est vide)Retirer des VLAN
gatheredLit la configuration et la renvoie en YAML, sans rien modifierDocumenter, comparer
renderedProduit les commandes à partir de config, sans les envoyerRelire avant d'appliquer

Configurer les ports

Les ports 1 à 20 de sw-etage2 accueillent un poste et un téléphone IP. Avec ios_config, une boucle sur range génère la configuration de chaque port :

yaml
    - name: Configurer les ports des postes avec téléphone
      cisco.ios.ios_config:
        parents: interface GigabitEthernet1/0/{{ item }}
        lines:
          - description Poste et telephone
          - switchport mode access
          - switchport access vlan 10
          - switchport voice vlan 50
          - spanning-tree portfast
      loop: "{{ range(1, 21) | list }}"
      when: inventory_hostname == 'sw-etage2'

parents désigne le sous-mode (ici l'interface), lines les commandes à y trouver. ios_config compare ces lignes à la configuration courante et n'envoie que celles qui manquent : la tâche est idempotente si les lignes sont écrites exactement comme IOS les affiche dans show running-config. range(1, 21) va de 1 à 20 : la borne de fin est exclue.

Les modifications ne sont faites que dans la running-config. Pour qu'elles survivent à un redémarrage, ajoute à la dernière tâche save_when: modified : le module copie la running-config dans la startup-config si elle a changé.

Sauvegarder les configurations

Sauvegarder chaque soir la configuration de tous les équipements, c'est l'automatisation réseau la plus simple et l'une des plus utiles : après une panne ou une erreur, on sait ce qui a changé et on peut reconstruire.

yaml
---
- name: Sauvegarder la configuration des switchs
  hosts: switchs
  gather_facts: false

  tasks:
    - name: Enregistrer la running-config sur le bastion
      cisco.ios.ios_config:
        backup: true
        backup_options:
          dir_path: "{{ playbook_dir }}/sauvegardes/reseau"
          filename: "{{ inventory_hostname }}.cfg"

Chaque exécution réécrit sauvegardes/reseau/sw-etage1.cfg et sw-etage2.cfg sur le bastion. Planifié chaque nuit (une tâche cron sur le bastion, ou un pipeline), et suivi d'un git commit dans un dépôt réservé aux sauvegardes, il donne un historique des configurations : git diff montre ce qui a changé entre deux nuits. Les configurations contiennent des secrets (hachages des mots de passe, clés) : ce dépôt doit être privé et protégé.

Et pfSense ?

pfSense se configure par son interface web, et toute sa configuration est rangée dans un seul fichier XML, /cf/conf/config.xml. Il n'a pas de ligne de commande de configuration comparable à IOS, et Netgate ne publie pas de collection Ansible officielle.

La collection communautaire pfsensible.core comble ce manque : ses modules se connectent en SSH, modifient config.xml et appellent les fonctions PHP de pfSense pour appliquer le changement. Elle gère notamment les alias, les règles de filtrage, la NAT, les VLAN, les interfaces, les utilisateurs et IPsec. Les modules doivent s'exécuter en root : on se connecte avec un compte qui l'est, ou on installe le paquet pfSense-pkg-sudo pour pouvoir utiliser become, que pfSense ne propose pas par défaut. L'inventaire doit aussi indiquer l'interpréteur Python fourni par pfSense (ansible_python_interpreter), dont le chemin dépend de la version. L'interface d'une règle se désigne par son nom affiché dans pfSense (ADMIN) ou son identifiant interne (lan, opt1…).

Un alias et une règle pour autoriser le bastion à joindre les serveurs Windows en WinRM :

yaml
    - name: Garantir l'alias des serveurs Windows
      pfsensible.core.pfsense_alias:
        name: SRV_WINDOWS
        type: host
        address: 192.168.10.177 192.168.10.178 192.168.10.181
        descr: Serveurs Windows gérés par Ansible
        state: present

    - name: Autoriser le bastion vers les serveurs Windows en WinRM HTTPS
      pfsensible.core.pfsense_rule:
        name: Bastion vers SRV_WINDOWS (WinRM)
        action: pass
        interface: ADMIN
        ipprotocol: inet
        protocol: tcp
        source: 192.168.10.210
        destination: SRV_WINDOWS
        destination_port: 5986
        state: present

Les règles de filtrage elles-mêmes, la NAT et les VPN de pfSense sont expliqués dans le cours consacré au pare-feu pfSense ; l'architecture des zones, dans Cybersécurité des infrastructures.

Exercices

Exercice 1 sur 3 Les VLAN du cœur Réussi À revoir

Le switch de cœur sw-coeur (adresse de gestion à définir) doit porter les VLAN 10, 20, 30, 40, 50 et 99 du plan de Sorbier, avec leurs noms (POSTES, SERVEURS, ENTREPOT, VISITEURS, TELEPHONIE, ADMIN), plus 998 (PARKING) et 999 (NATIF). Écris la tâche, et dis quel state choisir pour une première mise en place sur un switch déjà en service.

Voir la réponseMasquer la réponse de l'exercice 1
yaml
    - name: Garantir les VLAN du cœur
      cisco.ios.ios_vlans:
        config:
          - vlan_id: 10
            name: POSTES
          - vlan_id: 20
            name: SERVEURS
          - vlan_id: 30
            name: ENTREPOT
          - vlan_id: 40
            name: VISITEURS
          - vlan_id: 50
            name: TELEPHONIE
          - vlan_id: 99
            name: ADMIN
          - vlan_id: 998
            name: PARKING
          - vlan_id: 999
            name: NATIF
        state: merged

merged : il ajoute ce qui manque sans rien supprimer. Sur un switch en service, overridden supprimerait un VLAN oublié dans la liste et couperait les ports qui l'utilisent. Une fois la liste vérifiée (avec state: gathered pour lire l'existant), on peut passer à overridden pour imposer exactement le plan.

Tu avais trouvé ?

Exercice 2 sur 3 Pourquoi toujours changed ? Réussi À revoir

Moussa a écrit la tâche ci-dessous. À chaque exécution, elle est changed sur sw-etage1, alors que la configuration ne change pas. Explique et corrige.

yaml
    - name: Désactiver les ports inutilisés
      cisco.ios.ios_config:
        parents: interface range GigabitEthernet1/0/23 - 47
        lines:
          - sw acc vlan 998
          - shut
Voir la réponseMasquer la réponse de l'exercice 2

Deux causes. Les commandes sont abrégées : IOS les accepte mais les enregistre en entier (switchport access vlan 998, shutdown), donc ios_config ne les retrouve jamais dans la configuration et les renvoie. Et interface range n'apparaît pas dans show running-config, qui affiche chaque interface séparément : le parent n'est jamais trouvé. Correction : une boucle sur chaque interface, commandes complètes.

yaml
    - name: Désactiver les ports inutilisés
      cisco.ios.ios_config:
        parents: interface GigabitEthernet1/0/{{ item }}
        lines:
          - switchport access vlan 998
          - shutdown
      loop: "{{ range(23, 48) | list }}"

range(23, 48) donne les ports 23 à 47.

Tu avais trouvé ?

Exercice 3 sur 3 La sauvegarde nocturne Réussi À revoir

Écris la ligne de crontab du compte de Chloé sur le bastion qui lance le playbook sauvegarde-reseau.yml du projet ~/sorbier-infra chaque nuit à 2 h 30, puis commite les configurations dans le dépôt ~/sauvegardes-reseau. Le playbook écrit dans ce dépôt (dir_path: /home/clambert/sauvegardes-reseau). Que faut-il prévoir pour le mot de passe du coffre et pour la clé SSH, puisque personne n'est là pour les taper ?

Voir la réponseMasquer la réponse de l'exercice 3
text
30 2 * * * cd ~/sorbier-infra && ansible-playbook sauvegarde-reseau.yml --vault-password-file ~/.config/ansible/coffre-sorbier.txt >> ~/journaux/sauvegarde-reseau.log 2>&1 && cd ~/sauvegardes-reseau && git add -A && git commit -q -m "Sauvegarde du $(date +\%F)"

Dans une crontab, % a un sens spécial (fin de commande) et doit être échappé en \%. Le cd est nécessaire pour que le bon ansible.cfg soit lu. Le PATH de cron est réduit : il peut falloir le chemin complet de la commande (~/.local/bin/ansible-playbook). Sans personne pour taper les secrets : un fichier de mot de passe du coffre protégé (chmod 600), et, pour SSH, le mot de passe du compte des switchs vient déjà du coffre (connexion par mot de passe). Si le commit ne trouve rien à valider (configuration inchangée), git commit renvoie une erreur sans gravité. Une solution plus propre est de lancer la sauvegarde depuis un ordonnanceur (pipeline planifié, AWX, Semaphore), qui garde les secrets et l'historique des exécutions.

Tu avais trouvé ?

Chapitre terminé !

Fais les 3 exercices avant de passer à la suite : c'est en t'entraînant que tout se fixe.

chapitres
–
de série
–
du cours
–
Chapitre suivant : Créer des VM Proxmox avec AnsibleRevoir le sommaire du cours