Comment utiliser Python pour automatiser le réseau
Python dispose de trois bibliothèques – Paramiko, Netmiko et NAPALM – dédiées au pilotage des équipements réseau. Cet article explique comment s’en servir dans les scripts d’administration.
Python est le langage le plus courant pour écrire des scripts qui automatisent l’administration des équipements réseau. Les utilisateurs qui débutent ont juste besoin de comprendre la manière dont s’enchaînent les commandes et comment sont structurées les données, notamment les « dictionnaires Python ». L’intérêt de Python tient surtout à l’existence de trois bibliothèques de fonctions dédiées au réseau : Paramiko, Netmiko et NAPALM (pour Network Automation Programmability Abstraction Layer with Multivendor support). Chacune d’elle apporte une couche d’abstraction supplémentaire pour simplifier la communication avec les équipements réseau. Cet article montre comment les utiliser.
Paramiko
Paramiko sert à programmer l’envoi de commandes à un équipement réseau via le protocole SSH. Grâce à cette bibliothèque, les utilisateurs envoient des commandes que l’équipement réseau exécutera comme si elles avaient été saisies dans sa console CLI depuis un clavier qui lui aurait été directement relié. Le résultat de ces commandes sera récupéré par le script Python qui pourra les afficher sur l’écran de l’administrateur.
Le script Python ci-dessous, que nous nommerons paramiko-example.py, utilise la bibliothèque Paramiko pour demander à un routeur Cisco Catalyst 3560 sa table ARP, laquelle liste le couple adresse IP/adresse MAC des machines du réseau avec lesquelles il communique. Obtenir cette table est la première étape pour identifier sur quel port d’un switch une machine du réseau est connectée.
01) #!/usr/bin/env python3
02) import sys
03) from time import sleep
04) import paramiko
05) router="192.168.1.108"
06)
07) # Create an ssh connection and set terminal length 0
08) conn = paramiko.SSHClient()
09) conn.set_missing_host_key_policy(paramiko.AutoAddPolicy())
10) conn.connect(router, username="tester", password="foobar")
11) router_conn = conn.invoke_shell()
12) print('Successfully connected to %s' % router)
13) router_conn.send('terminal length 0\n')
14) sleep(1) # Wait for the cmd to be sent and processed
15)
16) # Send the command and wait for it to execute
17) router_conn.send("show arp\n")
18) sleep(2)
19)
20) # Read the output, decode into UTF-8 (ASCII) text, and print
21) print(router_conn.recv(5000).decode("utf-8"))
Les lignes un à cinq importent les bibliothèques supplémentaires dont nous avons besoin, et définissent l’adresse IP du routeur que nous allons interroger. Les lignes sept à quatorze créent une connexion SSH vers le routeur avec l’identifiant tester et le mot de passe foobar. Une commande est envoyée pour désactiver le scrolling du texte à l’écran. Les lignes 16 à 18 envoient la commande show arp directement interprétable par le routeur. La ligne 21 lit le résultat de cette commande et l’affiche. La connexion est fermée lorsque le script se termine.
Voici le résultat :
01) $ ./paramiko-example.py
02) Successfully connected to 192.168.1.108
03)
04) test-sw>terminal length 0
05) test-sw>show arp
06) Protocol Address Age (min) Hardware Addr Type Interface
07) Internet 192.168.1.1 1 0025.9c1a.4c89 ARPA Vlan1
08) Internet 192.168.1.3 0 0c84.dcc4.8569 ARPA Vlan1
09) Internet 192.168.1.108 - bcc4.933e.49c0 ARPA Vlan1
10) Internet 192.168.1.141 0 4c32.7596.b70b ARPA Vlan1
11) test-sw>
Paramiko se limite véritablement à la simple fourniture d’une interface SSH vers les machines du réseau. Les paramètres propres à l’appareil que l’on pilote à distance, y compris la syntaxe particulière de son système d’exploitation, doivent être écrits dans le script comme si l’on se trouvait directement sur cet appareil.
De la même manière, il faut avec Paramiko savoir par avance comment l’appareil va encoder les réponses qu’il nous renvoie ; ici nous savons qu’il fallait les décoder en ASCII. Et pour aller plus loin dans les possibilités d’administration automatisée par script, il faudrait savoir comment changer des paramètres sur ce modèle et comment vérifier que ces paramètres ont bien été pris en compte.
Réécrire intégralement chaque script pour chaque équipement réseau à automatiser n’est pas une bonne pratique. La bibliothèque Netmiko résout ce problème.
Netmiko
La bibliothèque Netmiko apporte une couche d’abstraction vers un grand nombre d’équipements réseau. Ici, plus besoin de connaître par avance les commandes propres à l’appareil que l’on pilote. À la place, on écrit dans le script des commandes génériques. Le premier bénéfice est que le script, que nous appellerons netmiko-example.py, est d’autant plus court à écrire :
01) #!/usr/bin/env python3
02) import sys
03) from time import sleep
04) from netmiko import ConnectHandler
05) dev={
06) 'device_type' : 'cisco_ios',
07) 'host' : '192.168.1.108',
08) 'username' : 'tester',
09) 'password' : 'foobar',
10) } # Use a dictionary to pass the login parameters
11)
12) # Connect to the device
13) router_conn = ConnectHandler(**dev)
14)
15) # Send the command and print the result
16) print(router_conn.send_command("show arp\n"))
Les lignes 1 à 4 importent les modules nécessaires. Les lignes 5 à 10 créent un « dictionnaire Python » qui contient les paramètres de connexion de l’équipement. Lorsqu’on pilote plusieurs appareils, il suffit de multiplier les dictionnaires. La connexion vers l’appareil se fait en une seule ligne. Il suffit aussi d’une ligne pour envoyer la commande et afficher son résultat.
Le résultat est aussi plus court avec Netmiko, car cette bibliothèque supprime les lignes qui correspondent aux commandes (les « invites ») :
01) $ ./netmiko-example.py
02) Protocol Address Age (min) Hardware Addr Type Interface
03) Internet 192.168.1.1 1 0025.9c1a.4c89 ARPA Vlan1
04) Internet 192.168.1.3 0 0c84.dcc4.8569 ARPA Vlan1
05) Internet 192.168.1.108 - bcc4.933e.49c0 ARPA Vlan1
06) Internet 168.1.141 0 4c32.7596.b70b ARPA Vlan1
Netmiko utilise la définition device_type qui puise dans sa base de connaissances interne pour savoir comment gérer les communications avec l’équipement cible, ce qui évite à l’administrateur de se hasarder à des spécificités dans les commandes et les lectures de résultat. Reste qu’il ne permet pas de changer les paramètres de la cible. C’est là qu’entre en scène NAPALM.
NAPALM
La bibliothèque NAPALM est conçue pour manipuler la configuration des équipements réseau. Il faut toutefois noter que si Netmiko dispose d’une base de connaissance très riche, NAPALM ne sait travailler qu’avec un nombre plus restreint d’équipements ; il reconnaît ceux de marques Arista, Juniper et Cisco (sous IOS, IOS XR, NX-OS et NX-OS SSH).
Pour commencer, NAPALM utilise des fonctions get pour récupérer les paramètres d’un équipement. Le script suivant utilise la fonction get_arp_table() pour faire la même chose que les scripts précédents :
01) #!/usr/bin/env python3
02) import sys
03) import time
04) import napalm
05) import os
06) import json
07) dev_type = 'ios'
08) dev_creds={
09) 'hostname': '192.168.1.108',
10) 'username': 'tester',
11) 'password': 'foobar',
12) 'optional_args': {'secret': 'foobar'}
13) } # Use a dictionary for the login parameters
14)
15) driver = napalm.get_network_driver(dev_type)
16) conn = driver(**dev_creds)
17) conn.open()
18) output = conn.get_arp_table()
19) print(json.dumps(output, indent=2))
20) close()
Le script à écrire est plus long avec NAPALM qu’il ne l’est avec Netmiko, car il nécessite d’inclure plus de bibliothèques de fonctions et que les paramètres de connexion soient spécifiés différemment, comme on le voit dans les lignes 2 à 13. La connexion est établie dans les lignes 15 à 17. Ensuite, les informations de la table ARP sont récupérées, mais contrairement à précédemment NAPALM les reformate, pour qu’elles se présentent comme un dictionnaire Python. La ligne 19 affiche les données formatées de la sorte :
01) $ ./napalm-example.py
02) [
03) {
04) "interface" : "Vlan1",
05) "mac" : "00:25:9C:1A:4C:89",
06) "ip" : "192.168.1.1",
07) "age" : 0.0
08) },
09) {
10) "interface" : "Vlan1",
11) "mac": "0C:84:DC:C4:85:69",
12) "ip" : "192.168.1.3",
13) "age" : 0.0
14) },
15) {
16) "interface" : "Vlan1",
17) "mac": "BC:C4:93:3E:49:C0",
18) "ip" : "192.168.1.108",
19) "age" : 0.0
20) },
21) {
22) "interface" : "Vlan1",
23) "mac" : "4C:32:75:96:B7:0B",
24) "ip" : "192.168.1.141",
25) "age" : 2.0
26) }
27) ]
Le mérite de ce format dictionnaire est qu’il s’agit d’une structure de données directement réutilisable dans Python. Il devient dès lors possible de remplacer, fusionner, comparer les données de ce dictionnaire afin de déployer, corriger ou adapter une configuration.
Plusieurs tutoriaux en ligne permettent d’aller au-delà de cette première approche de l’utilisation Python pour automatiser le réseau. Nous vous conseillons de suivre les cours (en anglais) de Nick Russo pour débuter, puis d’enchaîner sur les cours « Building Network Automations Solutions » et « Ansible for Networking Engineers » d’Ivan Pepelnjak.