Today I was helping a customer establish a baseline configuration on all their devices and decided it would be a good time to write a little bit of Python.
Our goal for the day was to connect to about 60 devices, setup SNMP, then turn around and add those same 60 devices to the monitoring system. All-in-all this would usually be about 800 command line entries and 400 buttons pressed to get everything setup. Our switches are Juniper QFX and EX which require logging in, entering configuration mode, setting four items under the SNMP configuration, commiting the configuration, and logging out of the device. LibreNMS requires us to navigate to the “Add Device” page and input the hostname of the device, the SNMP community and version the device is configured to use, then press the “Add” button and wait.
I am by no means a professional Python developer but I can hack my way around the basic constructs. My goal here is not to write a multithreaded, asynchronous, PEP8 compliant application. My goal is to save some time and get out the door by 3PM.
After the first couple switches we determined the set of steps we needed to properly configure SNMP were:
cli configure set snmp community MY_COMMUNITY authorization read-only set snmp contact “FirstName LastName” set snmp location “SOME PLACE” set snmp routing-instance-access commit exit exit
For testing we are connecting to a single device. This device is 172.31.33.73 which as a hostname of qfx3
So there are a few variables need to configure the above:
Username
Password
SNMP Community
Location
Contact
We start by statically setting our host. We put our host in a list to rewrite less code later.
device = [‘qfx3’]
Then we write some code that will allow us to prompt the user for this information. We use the getpass library to allow us prompt the user for the password without having them type it in cleartext.
import getpass username = raw_input('Input Username: ') password = (getpass.getpass()) snmp_community = raw_input(str(('Input SNMP v2 Community: '))) snmp_location = raw_input(str(('Input SNMP Location: '))) snmp_contact = raw_input(str(('Input Device Contact: ')))
These 5 five lines print to the terminal windows and request the user for information then store that information in variable for the script to use later.
Next we connect to the switch using Paramiko. Paramiko is a library built for handling SSH connections. There are a lot of guides on what each line does but when you put it all together these lines connect you to the switch, authenticate you and land you on the first prompt of the device. We use the time library to sleep the script as a crude method of handling a laggy CLI.
import paramiko import time #Connect to switch and build ssh connection remote_conn_pre = paramiko.SSHClient() remote_conn_pre.set_missing_host_key_policy(paramiko.AutoAddPolicy()) remote_conn_pre.connect(switch, username=username, password=password, look_for_keys=False, allow_agent=False) print('SSH connection established to ' + switch) remote_conn = remote_conn_pre.invoke_shell() print('Interactive SSH session established') #Print terminal to screen output = remote_conn.recv(1000) remote_conn.send('\n') time.sleep(2) print(output)
Because this is a Juniper device.. If you log in as the root user you must pass the command ‘cli’ to navigate into operational mode. If we are logged as any other user we are dropped directly into operational mode. Again, if user is root we use the time library to sleep the script as this process can take some time depending on the platform.
#Username root requires getting into the cli if username == 'root': remote_conn.send('cli\n') time.sleep(3) output = remote_conn.recv(1000) print(output) else: pass
The basis of running any command using Paramiko is as follows.
remote_conn.send('show route\n') ##### Wrap the command you want to send in quotes time.sleep(2) ##### Wait for device to return the output of the CLI output = remote_conn.recv(1000) ##### Save 1000 bytes of the returned output to variable output print(output) ##### Print the variable output
Now we just copy and paste the above until we have built a script of the commands we want to run. snmp_contact and snmp_location should be wrapped in quotes as the devices allow them to wrapped in quotes and multiple words with spaces.
#Enter configuration mode remote_conn.send('configure\n') time.sleep(2) output = remote_conn.recv(1000) print(output) #SNMP Configuration remote_conn.send('set snmp location \"' + snmp_location + '\"' + '\n') time.sleep(1) output = remote_conn.recv(1000) print(output) remote_conn.send('set snmp contact \"' + snmp_contact + '\"' + '\n') time.sleep(1) output = remote_conn.recv(1000) print(output) remote_conn.send('set snmp community ' + snmp_community + ' authorization read-only\n') time.sleep(1) output = remote_conn.recv(1000) print(output) remote_conn.send('set snmp routing-instance-access\n') time.sleep(1) output = remote_conn.recv(1000) print(output) #Save configuration remote_conn.send("commit\n") time.sleep(4) output = remote_conn.recv(1000) print(output) #Exit configuration mode remote_conn.send('exit\n') time.sleep(1) output = remote_conn.recv(1000) print(output) #Exit operational mode remote_conn.send('exit\n') time.sleep(1) output = remote_conn.recv(1000) print(output)
As mentioned before – because we create a list (with just a single item in that list) we must loop over the list then run all of the above code for each device in the list. This list and loop over the list now ends up saving us time and prevents us from having to rewrite code later when we add additional devices to our list. The code ends up looking like this:
for switch in devices: #Connect to switch and build ssh connection remote_conn_pre = paramiko.SSHClient() remote_conn_pre.set_missing_host_key_policy(paramiko.AutoAddPolicy()) remote_conn_pre.connect(switch, username=username, password=password, look_for_keys=False, allow_agent=False) print('SSH connection established to ' + switch) remote_conn = remote_conn_pre.invoke_shell() print('Interactive SSH session established') #Print terminal to screen output = remote_conn.recv(1000) remote_conn.send('\n') time.sleep(2) print(output) #Username root requires getting into the cli if username == 'root': remote_conn.send('cli\n') time.sleep(3) output = remote_conn.recv(1000) print(output) else: pass #Enter configuration mode remote_conn.send('configure\n') time.sleep(2) output = remote_conn.recv(1000) print(output) #SNMP Configuration remote_conn.send('set snmp location \"' + snmp_location + '\"' + '\n') time.sleep(1) output = remote_conn.recv(1000) print(output) remote_conn.send('set snmp contact \"' + snmp_contact + '\"' + '\n') time.sleep(1) output = remote_conn.recv(1000) print(output) remote_conn.send('set snmp community ' + snmp_community + ' authorization read-only\n') time.sleep(1) output = remote_conn.recv(1000) print(output) remote_conn.send('set snmp routing-instance-access\n') time.sleep(1) output = remote_conn.recv(1000) print(output) #Save configuration remote_conn.send("commit\n") time.sleep(4) output = remote_conn.recv(1000) print(output) #Exit configuration mode remote_conn.send('exit\n') time.sleep(1) output = remote_conn.recv(1000) print(output) remote_conn.send('exit\n') time.sleep(1) output = remote_conn.recv(1000) print(output)
Now we have to glue it all together. Import all the needed libraries. Define your list of devices. Prompt the user for variables and then loop over the devices in the list and configure them using Paramiko.
import paramiko import time import getpass devices = [ 'qfx3', ] username = raw_input('Input Username: ') password = (getpass.getpass()) snmp_community = raw_input(str(('Input SNMP v2 Community: '))) snmp_location = raw_input(str(('Input SNMP Location: '))) snmp_contact = raw_input(str(('Input Device Contact: '))) for switch in devices: #Connect to switch and build ssh connection remote_conn_pre = paramiko.SSHClient() remote_conn_pre.set_missing_host_key_policy(paramiko.AutoAddPolicy()) remote_conn_pre.connect(switch, username=username, password=password, look_for_keys=False, allow_agent=False) print('SSH connection established to ' + switch) remote_conn = remote_conn_pre.invoke_shell() print('Interactive SSH session established') #Print terminal to screen output = remote_conn.recv(1000) remote_conn.send('\n') time.sleep(2) print(output) #Username root requires getting into the cli if username == 'root': remote_conn.send('cli\n') time.sleep(3) output = remote_conn.recv(1000) print(output) else: pass #Enter configuration mode remote_conn.send('configure\n') time.sleep(2) output = remote_conn.recv(1000) print(output) #SNMP Configuration remote_conn.send('set snmp location \"' + snmp_location + '\"' + '\n') time.sleep(1) output = remote_conn.recv(1000) print(output) remote_conn.send('set snmp contact \"' + snmp_contact + '\"' + '\n') time.sleep(1) output = remote_conn.recv(1000) print(output) remote_conn.send('set snmp community ' + snmp_community + ' authorization read-only\n') time.sleep(1) output = remote_conn.recv(1000) print(output) remote_conn.send('set snmp routing-instance-access\n') time.sleep(1) output = remote_conn.recv(1000) print(output) #Save configuration remote_conn.send("commit\n") time.sleep(4) output = remote_conn.recv(1000) print(output) #Exit configuration mode remote_conn.send('exit\n') time.sleep(1) output = remote_conn.recv(1000) print(output) #Exit operational mode remote_conn.send('exit\n') time.sleep(1) output = remote_conn.recv(1000) print(output)
Now save this as some juniper.py in your directory of choice then execute it using Python. At first the user is prompted for information then our script connects to the device and executes our command.
python /home/justin/juniper.py Input Username: root Password: Input SNMP v2 Community: A_COMMUNITY Input SNMP Location: MY_HOUSE Input Device Contact: JUSTIN OEDER SSH connection established to qfx3 Interactive SSH session established --- JUNOS 18.4R1.8 built 2018-12-17 03:30:15 UTC root@:RE:0% root@:RE:0% cli {master:0} root> configure Entering configuration mode Users currently editing the configuration: root terminal d0 (pid 1867) on since 2019-02-06 01:52:28 UTC {master:0}[edit] {master:0}[edit] root# set snmp location "MY_HOUSE" {master:0}[edit] root# set snmp contact "JUSTIN OEDER" {master:0}[edit] root# set snmp community A_COMMUNITY authorization read-only {master:0}[edit] root# set snmp routing-instance-access {master:0}[edit] root# commit configuration check succeeds commit complete {master:0}[edit] root# exit Exiting configuration mode {master:0} root> exit root@:RE:0%
Now, to expand on this script and ACTUALLY save time with it our list of devices needs to get bigger! We can use hostnames or IP addresses.
devices = [ 'qfx1', '172.31.33.72', 'qfx3', '172.31.33.74', ]
Now just keep adding devices to this list and run the script again!
Next came our real time sucker. Manually adding devices to the monitoring system. LibreNMS has a pretty well documented API
https://docs.librenms.org/API/Devices/#add_device
Here they tell you exactly how to add a device using a curl command.
curl -X POST -d '{"hostname":"localhost.localdomain","version":"v1","community":"public"}' -H 'X-Auth-Token: YOURAPITOKENHERE' https://librenms.org/api/v0/devices
We can test that this does what we expect by adding our varables to it and running it.
curl -X POST -d '{"hostname":"qfx1","version":"v2c","community":"MY_COMMUNITY"}' -H 'X-Auth-Token: LIBRENMS_API_TOKEN' https://librenms.chewonice.net/api/v0/devices
Now we can’t run a curl command from Python directly but the requests library can help us.
import requests
Then navigate to this little site https://curl.trillworks.com/
curl -X POST -d '{"hostname":"qfx1","version":"v2c","community":"MY_COMMUNITY"}' -H 'X-Auth-Token: LIBRENMS_API_TOKEN' https://librenms.chewonice.net/api/v0/devices
Gives us:
import requests headers = {'X-Auth-Token': 'LIBRENMS_API_TOKEN',} data = '{"hostname":"qfx1","version":"v2c","community":"MY_COMMUNITY"}' response = requests.post('https://librenms.chewonice.net/api/v0/devices', headers=headers, data=data)
And once again we stitch it all together.
import paramiko import time import getpass import requests devices = [ 'qfx1', '172.31.33.72', 'qfx3', '172.31.33.74', ] username = raw_input('Input Username: ') password = (getpass.getpass()) snmp_community = raw_input(str(('Input SNMP v2 Community: '))) snmp_location = raw_input(str(('Input SNMP Location: '))) snmp_contact = raw_input(str(('Input Device Contact: '))) for switch in devices: #Connect to switch and build ssh connection remote_conn_pre = paramiko.SSHClient() remote_conn_pre.set_missing_host_key_policy(paramiko.AutoAddPolicy()) remote_conn_pre.connect(switch, username=username, password=password, look_for_keys=False, allow_agent=False) print('SSH connection established to ' + switch) remote_conn = remote_conn_pre.invoke_shell() print('Interactive SSH session established') #Print terminal to screen output = remote_conn.recv(1000) remote_conn.send('\n') time.sleep(2) print(output) #Username root requires getting into the cli if username == 'root': remote_conn.send('cli\n') time.sleep(3) output = remote_conn.recv(1000) print(output) else: pass #Enter configuration mode remote_conn.send('configure\n') time.sleep(2) output = remote_conn.recv(1000) print(output) #SNMP Configuration remote_conn.send('set snmp location \"' + snmp_location + '\"' + '\n') time.sleep(1) output = remote_conn.recv(1000) print(output) remote_conn.send('set snmp contact \"' + snmp_contact + '\"' + '\n') time.sleep(1) output = remote_conn.recv(1000) print(output) remote_conn.send('set snmp community ' + snmp_community + ' authorization read-only\n') time.sleep(1) output = remote_conn.recv(1000) print(output) remote_conn.send('set snmp routing-instance-access\n') time.sleep(1) output = remote_conn.recv(1000) print(output) #Save configuration remote_conn.send("commit\n") time.sleep(4) output = remote_conn.recv(1000) print(output) #Exit configuration mode remote_conn.send('exit\n') time.sleep(1) output = remote_conn.recv(1000) print(output) remote_conn.send('exit\n') time.sleep(1) output = remote_conn.recv(1000) print(output) #Add device to LibreNMS api_token = 'LIBRENMS_API_TOKEN' headers = {'X-Auth-Token': api_token,} data = '{"hostname":"' + switch + '","version":"v2c","community":"' + snmp_community + '"}' print(data) response = requests.post('http://172.31.33.66/api/v0/devices', headers=headers, data=data) print(response)
Now run the script, wait a minute for LibreNMS to discover the devices and enjoy the remainder of your day! Out the door by 1:15. Even better than expected.
This script can also be used to update the community on both the device and the monitoring software simultaneously! Just rerun the script with another community.
If you look, there is a lot of repeated code. I am hoping to clean this code up in later blog posts. Juniper also has a native API which would provide us with the ability push configuration changes without having to “screen scrape” the SSH session.