Wi-Fi, Cloud et Technologies Réseau

Automatisation réseau avec Python

Python est devenu le langage de référence de l'automatisation réseau. Environnement, connexions SSH avec Netmiko, analyse structurée des sorties, API REST, secrets et méthodes sûres : un guide pratique pas à pas.

Script Python se connectant à plusieurs équipements réseau pour collecter et analyser leur configuration

Répéter la même vérification sur cinquante commutateurs, collecter un inventaire, comparer des configurations à un standard : ces tâches sont longues et propices aux erreurs lorsqu'elles sont faites à la main. Python, grâce à sa syntaxe lisible et à un écosystème riche de bibliothèques réseau, est devenu le langage de référence pour les automatiser.

Objectif : mettre en place un environnement de travail, écrire des scripts sûrs pour collecter des informations par SSH et par API, analyser les résultats et appliquer de bonnes pratiques. La démarche globale (documentation, source de vérité, Ansible) est présentée dans Documentation technique et automatisation de l'administration réseau.

Les exemples utilisent Python 3.10 ou ultérieur et des équipements Cisco IOS XE de laboratoire. Commencez toujours par des scripts en lecture seule, sur un environnement de test, avec un compte aux droits limités.

Préparer l'environnement

Utilisez un environnement virtuel pour isoler les dépendances de chaque projet :

python3 -m venv .venv
source .venv/bin/activate            # sous Windows : .venv\Scripts\activate
pip install netmiko textfsm ntc-templates napalm requests python-dotenv
pip freeze > requirements.txt

Organisation de projet recommandée :

automatisation-reseau/
├── .venv/                 (non versionné)
├── .env                   (secrets locaux, non versionné)
├── inventaire.yml
├── scripts/
│   ├── collecte_versions.py
│   └── audit_ntp.py
├── requirements.txt
└── README.md

Versionnez le projet avec Git, en excluant .venv et .env dans le fichier .gitignore.

Ne jamais écrire les mots de passe dans le code

Les identifiants sont lus depuis des variables d'environnement, un fichier .env local non versionné, ou mieux, un gestionnaire de secrets :

# .env (exemple local, ne jamais le versionner)
RESEAU_UTILISATEUR=automatisation
RESEAU_MOTDEPASSE=change-moi
import os
from dotenv import load_dotenv

load_dotenv()
UTILISATEUR = os.environ["RESEAU_UTILISATEUR"]
MOTDEPASSE = os.environ["RESEAU_MOTDEPASSE"]

Se connecter en SSH avec Netmiko

Netmiko simplifie les connexions SSH aux équipements réseau : gestion des invites, de la pagination et des particularités de chaque système.

from netmiko import ConnectHandler

equipement = {
    "device_type": "cisco_xe",
    "host": "10.10.99.21",
    "username": UTILISATEUR,
    "password": MOTDEPASSE,
}

with ConnectHandler(**equipement) as connexion:
    sortie = connexion.send_command("show version")
    print(sortie)

Le gestionnaire de contexte with garantit la fermeture de la connexion, même en cas d'erreur.

Analyser les sorties : du texte aux données

Une sortie de commande est du texte destiné à un humain. Pour l'exploiter dans un programme, il faut la structurer. Les modèles TextFSM de la bibliothèque ntc-templates couvrent de nombreuses commandes :

with ConnectHandler(**equipement) as connexion:
    interfaces = connexion.send_command("show ip interface brief", use_textfsm=True)

for intf in interfaces:
    if intf["status"] == "up" and intf["proto"] == "down":
        print(f"Anomalie : {intf['interface']} est up/down")

interfaces devient une liste de dictionnaires : on peut filtrer, compter, comparer. Les noms exacts des champs dépendent du modèle TextFSM : affichez une première fois le résultat pour les connaître.

Lorsque l'équipement le permet, préférez une API qui renvoie directement des données structurées (JSON, XML) : c'est plus fiable que l'analyse de texte.

Exemple complet : inventaire des versions

"""Collecte la version logicielle de chaque équipement de l'inventaire (lecture seule)."""
import csv
import os
import sys
from concurrent.futures import ThreadPoolExecutor

import yaml
from dotenv import load_dotenv
from netmiko import ConnectHandler
from netmiko.exceptions import NetmikoAuthenticationException, NetmikoTimeoutException

load_dotenv()

def collecter(hote: dict) -> dict:
    params = {
        "device_type": hote["type"],
        "host": hote["adresse"],
        "username": os.environ["RESEAU_UTILISATEUR"],
        "password": os.environ["RESEAU_MOTDEPASSE"],
        "conn_timeout": 10,
    }
    try:
        with ConnectHandler(**params) as c:
            info = c.send_command("show version", use_textfsm=True)[0]
            return {"nom": hote["nom"], "version": info.get("version", "?"), "statut": "ok"}
    except (NetmikoTimeoutException, NetmikoAuthenticationException) as erreur:
        return {"nom": hote["nom"], "version": "", "statut": f"échec : {type(erreur).__name__}"}

def main() -> int:
    with open("inventaire.yml", encoding="utf-8") as f:
        hotes = yaml.safe_load(f)["equipements"]
    with ThreadPoolExecutor(max_workers=10) as pool:
        resultats = list(pool.map(collecter, hotes))
    with open("versions.csv", "w", newline="", encoding="utf-8") as f:
        ecrivain = csv.DictWriter(f, fieldnames=["nom", "version", "statut"])
        ecrivain.writeheader()
        ecrivain.writerows(resultats)
    echecs = [r for r in resultats if r["statut"] != "ok"]
    print(f"{len(resultats) - len(echecs)} réussites, {len(echecs)} échecs")
    return 1 if echecs else 0

if __name__ == "__main__":
    sys.exit(main())
# inventaire.yml
equipements:
  - { nom: sw-acc-e2-01, adresse: 10.10.99.21, type: cisco_xe }
  - { nom: sw-acc-e3-01, adresse: 10.10.99.22, type: cisco_xe }
  - { nom: rt-paris, adresse: 10.10.99.1, type: cisco_xe }

Points notables : gestion des erreurs par équipement (un échec n'interrompt pas le reste), parallélisme limité (10 connexions simultanées), résultat exploitable (CSV) et code de retour utilisable par un planificateur.

Auditer la conformité

L'automatisation est particulièrement utile pour vérifier qu'un parc respecte un standard :

ATTENDU = {"ntp server 10.10.99.40", "ntp server 10.10.99.41"}

def auditer_ntp(connexion) -> set[str]:
    config = connexion.send_command("show running-config | include ^ntp server")
    presentes = {ligne.strip() for ligne in config.splitlines() if ligne.strip()}
    return ATTENDU - presentes        # lignes manquantes

Un rapport des écarts, exécuté chaque nuit, détecte les dérives de configuration sans rien modifier.

NAPALM : une abstraction multi-constructeurs

NAPALM fournit des fonctions identiques quel que soit le système (IOS, IOS XE, NX-OS, Junos, EOS…) :

from napalm import get_network_driver

pilote = get_network_driver("ios")
with pilote("10.10.99.21", UTILISATEUR, MOTDEPASSE) as equipement:
    faits = equipement.get_facts()
    voisins = equipement.get_lldp_neighbors()
    print(faits["hostname"], faits["os_version"])

NAPALM permet aussi de comparer une configuration candidate à la configuration active (load_merge_candidate, compare_config) avant de l'appliquer, puis de valider ou d'abandonner : un mécanisme précieux pour des changements maîtrisés.

Les API : REST, RESTCONF, NETCONF

Les équipements et contrôleurs modernes exposent des API qui renvoient des données structurées selon des modèles YANG :

  • RESTCONF (RFC 8040) : HTTP et JSON/XML ;
  • NETCONF (RFC 6241) : SSH et XML, avec des transactions et une validation de configuration.

Exemple de lecture RESTCONF (laboratoire, après activation de RESTCONF sur l'équipement) :

import requests

url = "https://10.10.99.21/restconf/data/ietf-interfaces:interfaces"
reponse = requests.get(
    url,
    auth=(UTILISATEUR, MOTDEPASSE),
    headers={"Accept": "application/yang-data+json"},
    timeout=10,
    verify="/chemin/vers/ac-interne.pem",   # vérifier le certificat
)
reponse.raise_for_status()
for intf in reponse.json()["ietf-interfaces:interfaces"]["interface"]:
    print(intf["name"], intf.get("enabled"))

Ne désactivez pas la vérification des certificats (verify=False) en dehors d'un laboratoire isolé : déployez plutôt l'autorité de certification interne.

Modifier des configurations : précautions

Lorsque vous passez à l'écriture :

  1. Sauvegarder la configuration avant toute modification.
  2. Prévisualiser les changements (comparaison, mode simulation).
  3. Commencer par un seul équipement, puis un petit groupe, avant le parc entier.
  4. Vérifier après chaque modification (tests automatisés : interface active, voisin présent, service joignable).
  5. Prévoir un retour arrière.
  6. Journaliser ce que le script a fait, sur quel équipement et quand.
  7. Faire relire le code par un collègue, comme pour tout changement de production.
with ConnectHandler(**equipement) as c:
    sortie = c.send_config_set([
        "interface GigabitEthernet1/0/10",
        "description Bureau 210",
    ])
    c.save_config()

Bonnes pratiques

  • Lecture d'abord : inventaires, audits, sauvegardes.
  • Secrets hors du code, compte dédié aux droits minimaux.
  • Gestion des erreurs et délais d'attente sur chaque connexion.
  • Données séparées du code : inventaire en YAML, idéalement issu d'une source de vérité.
  • Code versionné, relu, testé sur un laboratoire.
  • Documentation : un README qui explique ce que fait chaque script et comment l'exécuter.

Erreurs courantes

  • Mots de passe en clair dans les scripts ou dans Git.
  • Scripts qui modifient la production sans test ni sauvegarde.
  • Analyse fragile de texte au lieu de modèles TextFSM ou d'API.
  • Connexions massives sans limite, saturant les équipements ou déclenchant les protections contre la force brute.
  • verify=False utilisé en production.

Conclusion

Python transforme les tâches répétitives en scripts fiables et reproductibles. Commencez par la collecte et l'audit en lecture seule avec Netmiko et TextFSM, adoptez les API dès que possible, protégez vos secrets et appliquez aux scripts la même rigueur qu'à tout changement de production. Pour exploiter les données collectées en continu, voir Télémétrie, SNMP et observabilité.

Références