Automatisation de la sauvegarde des configurations de commutateurs réseau via SSH avec Python

La gestion d'un parc hétérogène de commutateurs réseau nécessite souvent une approche flexible pour la sauvegarde des configurations. Lorsque les protocoles standards comme TFTP ou SNMP ne sont pas uniformément configurables, l'automatisation via SSH avec la bibliothèque Paramiko devient une solution robuste. Ce guide présente un script Python capable de se connecter à plusieurs équipements, de gérer la pagination des terminaux et d'archiver les données extraites.

Implémentation du script de sauvegarde

Le script suivant utilise une session shell interactive pour simuler la saisie humaine, ce qui permet de contourner les limitations de certains terminaux réseau qui n'acceptent pas l'exécution de commandes directes.

import paramiko
import time
import re
import tarfile
import os
from datetime import datetime

def filtrer_flux_texte(flux_brut):
    # Suppression des séquences d'échappement ANSI
    regex_ansi = re.compile(r'\x1b\[[0-9;]*[mGK]')
    texte_propre = regex_ansi.sub('', flux_brut)
    # Conservation uniquement des caractères imprimables et des sauts de ligne
    return ''.join(c for c in texte_propre if c.isprintable() or c == '\n')

def extraire_config_equipement(adresse_ip, login, mot_de_passe, dossier_destination):
    nom_fichier = os.path.join(dossier_destination, f"{adresse_ip}.cfg")
    print(f"Extraction en cours pour : {adresse_ip}")
    
    try:
        ssh_client = paramiko.SSHClient()
        ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh_client.connect(adresse_ip, username=login, password=mot_de_passe, timeout=10)

        session_shell = ssh_client.invoke_shell()
        # Commande standard pour afficher la configuration (type Huawei/H3C)
        session_shell.send("display current-configuration\n")
        
        buffer_final = ""
        indicateur_pagination = "---- More ----"
        
        while True:
            time.sleep(1)
            while session_shell.recv_ready():
                fragment = session_shell.recv(65535).decode('latin-1')
                buffer_final += fragment

            # Nettoyage des codes de contrôle spécifiques
            buffer_final = re.sub(r'\x1b\[\d+D', '', buffer_final)
            
            if indicateur_pagination in buffer_final:
                # Envoi d'une pression sur la barre d'espace pour la suite
                buffer_final = buffer_final.replace(indicateur_pagination, "")
                session_shell.send(" ")
            else:
                break

        # Finalisation du contenu
        contenu_propre = filtrer_flux_texte(buffer_final)
        
        with open(nom_fichier, "w", encoding='utf-8') as f:
            f.write(contenu_propre)
            
        ssh_client.close()
    except Exception as e:
        print(f"Erreur sur {adresse_ip} : {e}")

def generer_archive(liste_fichiers):
    horodatage = datetime.now().strftime("%Y%m%d_%H%M%S")
    nom_archive = f"backup_reseau_{horodatage}.tar.gz"
    print(f"Création de l'archive : {nom_archive}")
    
    with tarfile.open(nom_archive, "w:gz") as tar:
        for f in liste_fichiers:
            if os.path.exists(f):
                tar.add(f, arcname=os.path.basename(f))
    return nom_archive

def purger_fichiers_temporaires(liste_fichiers):
    for f in liste_fichiers:
        try:
            os.remove(f)
        except OSError:
            pass

if __name__ == '__main__':
    # Inventaire des équipements [IP, User, Password]
    inventaire = [
        ["10.0.0.1", "admin", "P@ssw0rd1"],
        ["10.0.0.2", "admin", "P@ssw0rd2"]
    ]
    
    repertoire_travail = "./temp_configs"
    if not os.path.exists(repertoire_travail):
        os.makedirs(repertoire_travail)

    fichiers_generes = []
    for switch in inventaire:
        extraire_config_equipement(*switch, repertoire_travail)
        fichiers_generes.append(os.path.join(repertoire_travail, f"{switch[0]}.cfg"))

    generer_archive(fichiers_generes)
    purger_fichiers_temporaires(fichiers_generes)

Analyse des problématiques techniques

Gestion de la pagination (Pagination Terminal)

Sur de nombreux équipements réseau, l'affichage d'une configuration longue s'arrête périodiquement avec une mention type ---- More ----. Le script doit détecter cette chaîne de caractères et envoyer une instruction de défilement (généralement une espace). Une alternative consiste à désactiver temporairemetn la pagination sur le commutateur si les privilèges le permettent via la commande :

<Device> system-view
[Device] user-interface vty 0 4
[Device-ui-vty0-4] screen-length 0

Cependant, cette modification peut impacter le confort d'utilisation pour les administrateurs humains, d'où l'intérêt de la gestion logicielle dans le script.

Encodage et caractères spéciaux

Le transfert de flux via SSH peut inclure des caractères non-ASCII ou des séquences de couleurs ANSI (utilisées pour la coloration syntaxique dans certains terminaux). L'utilisation de l'encodage latin-1 lors de la réception des données (decode('latin-1')) est souvent plus permissive que l'UTF-8 pour capturer le flux brut sans erreur de décodage. Le nettoyage est ensuite effectué par une expression régulière pour garantir un fichier texte final propre et lisible.

Synchronisation du flux

La latence réseau et la vitesse de traitement du processeur du commutateur varient. L'utilisation de time.sleep() associée à recv_ready() permet de s'assurer que l'intégralité du buffer est lue avant de paser à l'étape suivante, évitant ainsi de tronquer la configuration récupérée.

Étiquettes: Python SSH paramiko networking automation

Publié le 26 juin à 17h07