Performing Infrastructure Audit and gathering facts using Netmiko and Genie

John Abat
8 min readJan 7, 2024

--

Use Case Intent

We have a scenario where the infrastructure devices needs to be audited for compliance and maintenance. Devices would be checked if they have the updated Software Version and Image, functioning power supply, line cards, and interfaces. All sorts of stuff that needs to be checked regularly in order to ensure that the devices remains operational for the business needs. Sure, there are solutions already offered by different vendors to perform the checks. But here, we’re going to write a script that is flexible to our use case — on top of that, free.

Network Diagram for Lab Demonstration

Actual Nodes in EVE-NG bridged to my physical network
Ubuntu Machine containing the Script

Explaining the Code

Full Code

from netmiko import *
import pprint

#edit updated_ver depending on audit requirements

updated_ver = '15.5(2)T'



#indicate devices in device_list.txt, show commands in sh_commands.txt
#this opens the files, reads, then turn them into a list.

with open('device_list.txt') as device_list:
hosts = device_list.read().splitlines()

with open('sh_commands.txt', "r") as sh_cmd_list:
sh_app_commands = sh_cmd_list.read().splitlines()

#iterating each host
#connect_param are the common parameters to be used by Netmiko, ConnectHandler to access the devices.

for host in hosts:
try:
connect_param = {
'device_type': 'cisco_ios',
'host': host,
'port': 22,
'username': 'cisco',
'password': 'cisco',
'secret': 'cisco'
}

print(f"================={host}=================\n")

#at the current device in iteration, the list of commands are iterated.
#connect_param as argument to ConnectHandler
#session_conn.enable() to go to privilege mode, secret is used.

for sh_app_command in sh_app_commands:
session_conn = ConnectHandler(**connect_param)
session_conn.enable()
out = session_conn.send_command(sh_app_command, use_genie=True)
#conditional, will perform a code block depending on what command in 'sh_app_command' is currently on the for loop iteration
if sh_app_command == 'show version':
print(f"{out['version']['hostname']} IOS version is: {out['version']['version']}")
print(f"Image ID is: {out['version']['image_id']}")
if out['version']['version'] == updated_ver:
print(f"{out['version']['hostname']} is currently running updated version {updated_ver}\n")
else:
print(f"Device version {out['version']['version']} outdated. Please update device version to {updated_ver}")
elif sh_app_command == 'show interfaces':
print("Router Interface Status:")
for interface, info in out.items():
if info['enabled'] == True:
print(f"{interface}: enabled")
else:
print(f"{interface}: shutdown")

print(f"\n===============================================\n\n\n")

except (NetMikoTimeoutException, NetMikoAuthenticationException) as error:
print(f"{error}\n")

Let’s start from the 1st portion:

  • import Netmiko and all of its components. Netmiko is a library written by Kirk Byers. It simplifies the process and code on how to connect to devices of different vendors by utilizing the library already established. (see more in https://pynet.twb-tech.com/blog/netmiko-python-library.html)
  • device_list.txt and sh_commands.txt are text files located in the same directory as the python script. They contain the list of devices and commands to be executed for our use case. These files will be opened, read, and stored in a list for iteration later in the code.
  • for loop iterates each host indicated in device_list.txt. connect_param contains the dictionary that will be used by ConnectHandler later as parameters to be used when accessing devices. It usually contains the platform, host, port, credentials, secrets.
  • ‘host’: host, — contains the current host being iterated from the list of host. With this, I can conduct a series of action exclusively applied to the current host on the for loop. Once done, the same set of actions will be conducted to the next host in the for loop.
  • for sh_app_command in sh_app_commands: — on the current host, a for loop will be initiated again. This time, for the commands to be executed. For the current command being iterated, the series of actions will be conducted.
  • session_conn = ConnectHandler(**connect_param) — This will store the instance of connection towards the device being accessed. **connect_param is the dictionary serving as an argument with the ConnectHandler().
  • session_conn.enable() — This will elevate the access to privileged mode since devices being accessed are in user mode. The ‘secret’ portion is used for the enable password.
  • out = session_conn.send_command(sh_app_command, use_genie=True) — to send the show commands, it will send the currently iterated command in the for loop as sh_app_command is the argument. use_genie=True will return the output in a structured form of data (in Dictionary format)

The returned data in raw dictionary form looks like this for show version and show interfaces respectively:

  • the benefit of this is not having to rely in regular expressions when parsing the intended information needed from the show command. This makes the data returned more structured. The process still depends on what data needs to be parsed and how the data should be presented.

This is the output when after the script is ran

re-pasting as reference
  • the action will depend on what is the command being iterated in the for loop.
  • As an example, in show version: ‘print(f”{out[‘version’][‘hostname’]} IOS version is: {out[‘version’][‘version’]}”)’ — this will access the dictionary stored in “out” variable when show version is being iterated. This will display the hostname, IOS version, Image ID, then it will check if the IOS version is up to date with the comparison operator in line 47–50.
  • The same will go for show interfaces for line 51–57. This will check which router interfaces are shutdown and which are enabled.
  • f string is a great way to utilize customized output of the audit to make the output more presentable and compiled as the audit process is completed.
  • Again, the key takeaway here is that we don’t need to resort into regex just to parse the intended data that we need to access. Instead, we use Genie to have a return output in a dictionary structure, which makes it easier to access and present the data intended.

Lastly, we’re going to indicate an action on how we handle exceptions

  • First, notice that there’s an indicated “try” statement at line 23. try statement scopes out the whole process of logging in to the current iterated host and attempting to gather facts as discussed.
  • When the process of logging in to the device fails, exception block comes in. It handles the error by executing the code block and continues on executing the rest of the script, instead of the whole script failing as a whole when 1 login fails.
  • I’ve indicated 2 possible reasons on why the login attempt for the SSH process to the iterated host would fail. One reason is when we’re not getting any response from the host. Another one is when we can access the host but we’re getting an authentication error. I’ve assigned the value in ‘error’ to be printed.

To demonstrate the exception, I’ve shutdown R1’s interface

  • If we don’t use an exception process here, then 1 failed login attempt would fail the whole script execution.
  • Here, since R1’s interface is shutdown, our terminal won’t get any responses and will return an error value of ‘Connection to device timed-out…’. Then continue the script execution for the rest of the devices in the list.

I’ll edit the credentials into an incorrect one to see the authentication error exception

here’s the view in my Ubuntu text editor
  • since the credentials defined are incorrect, R2 and R3 will have an authentication error and the exception would return a value implying that it was able to reach to the device prompt but it was failing to authenticate.

Thoughts and recap

  • Genie is only supported in Linux-based systems. For more info regarding genie and its parsers, you may refer to: https://pubhub.devnetcloud.com/media/genie-feature-browser/docs/#/parsers
  • An error that I’ve encountered when installing genie is that it occasionally experiences time outs when installing. I just repeated the process ‘python3 -m pip install pyats genie’ over and over again until the process is done. After all, once the component is done installing, it’s already cached in the system so you won’t have to redownload it.
  • In Genie, I tried ‘show interface’ in the command and returned the output in raw form, not in dictionary. ‘show interfaces’ does return the intended output structure. I tried ‘sh ver’ and ‘show ver’ and it worked as intended. The reason seems like ‘show interface’ can further be completed by ‘show interface ethernet0/0’ or ‘show interfaces’ whereas ‘sh ver’ didn’t experience that issue. It seems like Genie doesn’t want a vague command input that’s why I have to be as specific as possible with no other possible completion for that command.
  • Since the commands for execution are being iterated, the structure of the dictionary being returned isn’t constant. So, putting an all-around dictionary access would fail as ‘out[‘version’][‘hostname’]}’ would be an invalid dictionary access when it iterates to the ‘show interfaces’ command. As such, I used conditionals to match what actions / dictionary access I’m going to use depending on which command is currently being executed.
  • In the conditional portion, in lines 44 and 51, I’ve came up with the thought process that it’s easier and simpler if I should just match the input command being iterated instead of matching the dictionary output, for instance matching if the key value has ‘Ethernet’ or ‘version’ — which leads to complexity. That’s why I just stick to a simpler solution that I should specify the specific commands in the text file, so that it’s matched properly in the conditional.
  • Room for improvement for this code / project is to utilize Nornir or Multithreading to simultaneously log on to the list of devices instead of doing the process 1 by 1. I plan to make this improvement on my future projects.

--

--

John Abat
John Abat

Written by John Abat

Network Engineer specializing in Enterprise, Service Provider, Security, and Data Center Networks. Network Automation Enthusiast.

No responses yet