Script pour la sauvegarde automatique des configurations

I. Description


Dans un réseau, on peut rencontrer de nombreux problèmes dus à la disponibilité des données. Par exemple, on peut faire face à une perte ou une suppression des configurations d’un routeur. Pour garantir un bon fonctionnement de notre réseau, il est nécessaire de faire des sauvegardes très souvent. Etant un travail répétitif, nous allons l’automatiser à travers un script écrit en python.

Considérons le réseau suivant :

Nous allons vous montrer comment automatiser la vérification de la conformité et la sauvegarde des configurations des routeurs et du pare-feu ASA, à travers des scripts écrits en Python. La sauvegarde se fait sur un serveur, appelé dans notre cas SYSLOG-FTP (machine Linux). Les scripts seront lancés à partir de la machine de l’administrateur (PC-ADMIN) qui est aussi une machine Linux.

Chaque routeur a une interface de management Netx, x étant le numéro du routeur. Les Netx ont dans le même Subnet (192.168.3.0/24) que les machines SYSLOG-FTP et PC-ADMIN.

II. Script pour la sauvegarde des configurations


Voici ci-dessous le script, nommé backup_script.py, qui effectue la sauvegarde automatique des configurations.

Version 1 :

import monlibparamiko
import threading


def backup_configs(router):
    client = monlibparamiko.connect(**router)
    shell = monlibparamiko.get_shell(client)

    monlibparamiko.send_command(shell, 'enable')
    monlibparamiko.send_command(shell, '\n')

    from datetime import datetime
    now = datetime.now()
    year = now.year
    month = now.month
    day = now.day
    hour = now.hour
    minute = now.minute

    # Création du fichier ou mettre le backup du routeur
    nom_du_fichier = '{}-{}-{}_{}h{}_{}.txt'.format(day,month,year,hour,minute,router["ip_address"])

    monlibparamiko.send_command(shell, 'sh version')
    sortie = monlibparamiko.show(shell)
    # Envoie des backups au serveur tftp
     # Pour vérifier si c'est un routeur Cisco
    if "Cisco IOS Software" in sortie:
        monlibparamiko.send_command(shell, 'copy run tftp:')
        monlibparamiko.send_command(shell, '192.168.3.254')
        monlibparamiko.send_command(shell, nom_du_fichier)
     # Pour vérifier si c'est un pare-feu Cisco ASA
    elif "Cisco Adaptive Security Appliance" in sortie:
        monlibparamiko.send_command(shell, 'copy run tftp:')
        monlibparamiko.send_command(shell, 'running-config')
        monlibparamiko.send_command(shell, '192.168.3.254')
        monlibparamiko.send_command(shell, nom_du_fichier)

    monlibparamiko.close(client, router["ip_address"])


router1 = {'ip_address':''192.168.3.1', 'port_num': '22', 'user': 'SSHadmin', 'passwd': 'ciscossh123'}
router2 = {'ip_address':''192.168.3.2', 'port_num': '22', 'user': 'SSHadmin', 'passwd': 'ciscossh123'}
router3 = {'ip_address':''192.168.3.3', 'port_num': '22', 'user': 'SSHadmin', 'passwd': 'ciscossh123'}
router4 = {'ip_address':''192.168.3.4', 'port_num': '22', 'user': 'SSHadmin', 'passwd': 'ciscossh123'}


# Liste des routeurs
routers = [router1, router2, router3, router4]



# Liste vide pour stocker les threads
threads = list()
for router in routers:
    # Thread pour chaque routeur qui exécute la fontion backup_configs
    th = threading.Thread(target=backup_configs, args=(router,))
    threads.append(th)

for th in threads:
    th.start()

for th in threads:
    th.join()

Dans ce script, nous avons une fonction nommée backup_configs() qui prend en entrée les information d’un routeur (adresse IP, numéro de port SSH, nom d’utilisateur SSH et mot de passe SSH).

Cette fonction a pour but de se connecter par SSH sur le routeur, de prendre sa configuration et de la sauvegarder sur le serveur de sauvegarde SYSLOG-FTP.

Le nom du fichier où la configuration va être mise contiendra l’adresse IP du routeur, la date et l’heure de sauvegarde.

Les équipements qui sont dans le réseau peuvent ne pas être de même type, ce qui signifie que les commandes peuvent être différentes.

La ligne monlibparamiko.send_command(shell, 'sh version') permet de connaitre le type d’équipement.

C’est en fonction du type d’équipement que la sauvegarde sera lancée :

sortie = monlibparamiko.show(shell)
    # Envoie des backups au serveur tftp
    if "Cisco IOS Software" in sortie:
 
        monlibparamiko.send_command(shell, 'copy run tftp:')
        monlibparamiko.send_command(shell, '192.168.3.254')
        monlibparamiko.send_command(shell, nom_du_fichier)
    elif "Cisco Adaptive Security Appliance" in sortie:
 
        monlibparamiko.send_command(shell, 'copy run tftp:')
        monlibparamiko.send_command(shell, 'running-config')
        monlibparamiko.send_command(shell, '192.168.3.254')
        monlibparamiko.send_command(shell, nom_du_fichier)

Les informations des routeurs sont données manuellement dans le code :

router1 = {'ip_address':''192.168.3.1', 'port_num': '22', 'user': 'SSHadmin', 'passwd': 'ciscossh123'}
router2 = {'ip_address':''192.168.3.2', 'port_num': '22', 'user': 'SSHadmin', 'passwd': 'ciscossh123'}
router3 = {'ip_address':''192.168.3.3', 'port_num': '22', 'user': 'SSHadmin', 'passwd': 'ciscossh123'}
router4 = {'ip_address':''192.168.3.4', 'port_num': '22', 'user': 'SSHadmin', 'passwd': 'ciscossh123'}

La liste routers = [router1, router2, router3, router4] va être parcourue pour récupérer les configurations de l’ensemble des routeurs.

Mais si on veut prendre les routeurs un à un, attendre de terminer l’un avant de passer à l’autre, cela risque de prendre énormément de temps (au cas où votre réseau contient des milliers de routeurs).

Raison pour laquelle des Threads sont créés pour chaque routeur et lancés pour une exécution parallèle :

for router in routers:
    # Thread pour chaque routeur qui exécute la fontion backup_configs
    th = threading.Thread(target=backup_configs, args=(router,))
    threads.append(th)

for th in threads:
    th.start()

for th in threads:
    th.join()

Version 2 :

import monlibparamiko
import threading


def backup_configs(router):
    client = monlibparamiko.connect(**router)
    shell = monlibparamiko.get_shell(client)

    monlibparamiko.send_command(shell, 'enable')
    monlibparamiko.send_command(shell, '\n')

    from datetime import datetime
    now = datetime.now()
    year = now.year
    month = now.month
    day = now.day
    hour = now.hour
    minute = now.minute

    # Création du fichier ou mettre le backup du routeur
    nom_du_fichier = '{}-{}-{}_{}h{}_{}.txt'.format(day,month,year,hour,minute,router["ip_address"])

    monlibparamiko.send_command(shell, 'sh version')
    sortie = monlibparamiko.show(shell)
    # Envoie des backups au serveur tftp
     # Pour vérifier si c'est un routeur Cisco
    if "Cisco IOS Software" in sortie:
 
        monlibparamiko.send_command(shell, 'copy run tftp:')
        monlibparamiko.send_command(shell, '192.168.3.254')
        monlibparamiko.send_command(shell, nom_du_fichier)
     # Pour vérifier si c'est un pare-feu Cisco ASA
    elif "Cisco Adaptive Security Appliance" in sortie:
        monlibparamiko.send_command(shell, 'copy run tftp:')
        monlibparamiko.send_command(shell, 'running-config')
        monlibparamiko.send_command(shell, '192.168.3.254')
        monlibparamiko.send_command(shell, nom_du_fichier)

    monlibparamiko.close(client, router["ip_address"])

# Liste contenant les adresses se trouvant dans un fichier
with open('IP_Addresses.txt', 'r') as f:
    addresses = f.read().splitlines()

# Liste contenant les routeurs
routers = list()
for address in addresses:
    router = {'ip_address': address, 'port_num': '22', 'user': 'SSHadmin', 'passwd': 'ciscossh123'}
    routers.append(router)


# Liste vide pour stocker les threads
threads = list()
for router in routers:
    # Thread pour chaque routeur qui exécute la fontion backup_configs
    th = threading.Thread(target=backup_configs, args=(router,))
    threads.append(th)

for th in threads:
    th.start()

for th in threads:
    th.join()

Le backup_script (v1) a été amélioré et rendu beaucoup plus automatisé pour donner la v2.

L’amélioration se trouve au niveau de l’ajout manuel des adresses IP des routeurs.

Lorsqu’un routeur est ajouté dans le réseau, il est possible d’oublier de l’ajouter dans le script pour que celui-ci le tienne en compte. En plus si un routeur est Down, cela ne sert à rien de le contacter pour essayer d’y connecter.

Pour pallier à ceux-là, un script qui lance des Ping sur une plage d’adresse a été créé.

Ce script a pour but de lancer des Ping sur chaque adresse de la plage donnée. Si un nœud ayant une adresse dans cette plage est actif, il va répondre au Ping. Dans ce cas, son adresse va être ajoutée dans un fichier nommé IP_Addresses.txt.

A la sortie de ce script de Ping, le fichier va contenir l’ensemble des adresses IP actives dans la plage.

Ce fichier est utilisé dans le script backup_script (v2) pour rendre plus automatisé le travail :

with open('IP_Addresses.txt', 'r') as f:
    addresses = f.read().splitlines()

Ensuite chaque adresse va être utilisée pour ajouter son routeur dans la liste de routeurs à parcourir :

routers = list()
for address in addresses:
    router = {'ip_address': address, 'port_num': '22', 'user': 'SSHadmin', 'passwd': 'ciscossh123'}
    routers.append(router)