Interacting with IOS XE using Paramiko (part 1)
Introduction
In this blog post, we will explore Paramiko to interact with Cisco devices. We will do a couple of things: - Execute a single command against a Cisco IOS XE device - Execute a single command against multiple Cisco IOS XE devices - Execute a multiple commands against multiple Cisco IOS XE devices - Set interface descriptions on Cisco IOS devices
For all the examples, we will use a Cisco sandbox environment delivered by Cisco Devnet. Go check out Devnet, really brilliant. To get a list of all sandboxes, check out this link.
To use the sandbox for this blog post, refer to this page)
Installing Paramiko
We will start with instakllation of the Paramiko library. That’s pretty straigthforward, please refer to the below output. I’m recommending to work inside a virtual enviroment (see this post to check how to do this)
WAUTERW-M-65P7:Paramiko_Introduction wauterw$ python3 -m venv venv
WAUTERW-M-65P7:Paramiko_Introduction wauterw$ source venv/bin/activate
(venv) WAUTERW-M-65P7:Paramiko_Introduction wauterw$ pip3 install paramiko
Collecting paramiko
Downloading https://files.pythonhosted.org/packages/06/1e/1e08baaaf6c3d3df1459fd85f0e7d2d6aa916f33958f151ee1ecc9800971/paramiko-2.7.1-py2.py3-none-any.whl (206kB)
***Truncated***
Installing collected packages: pycparser, cffi, six, bcrypt, cryptography, pynacl, paramiko
Successfully installed bcrypt-3.1.7 cffi-1.14.0 cryptography-2.8 paramiko-2.7.1 pycparser-2.20 pynacl-1.3.0 six-1.14.0
Script 1: single command - single device
Below script will execute the show ip interface brief
against a Cisco IOS XE device. We start obviously with importing the Paramiko
library. Then we specify the required parameters to login to our device (this is all documented in the above ‘sandbox’ link).
The main part of this script is the instantiation of the ssh client object. We will first create the ssh client and then make a connection (via the connect
method) to our Cisco IOS XE device.
Next, we will execute the specified command using the ssh.exec_command
command. Our SSH client will now send this command to the IOS XE device which will execute the
Disclaimer: this is not production-grade code obviously. One should never store the username and password in the clear, not in the source code itself. The examples in the post are merely conceptual and for informational purposes.
import paramiko
import time
host = 'ios-xe-mgmt-latest.cisco.com'
username = '*****'
password = '*****'
port = 8181
command = 'show ip interface brief \n'
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(hostname=host,username=username, password=password, port=port, look_for_keys=False, allow_agent=False)
stdin, stdout, stderr = ssh.exec_command(command)
output = stdout.readlines()
print(' '.join(map(str, output)))
Running the script will show the interfaces overview.
(venv) WAUTERW-M-65P7:Paramiko_Introduction wauterw$ python3 ExecuteSingleCommand.py
Welcome to the DevNet Sandbox for CSR1000v and IOS XE
***Truncated***
Thanks for stopping by.
Interface IP-Address OK? Method Status Protocol
GigabitEthernet1 10.10.20.48 YES other up up
GigabitEthernet2 unassigned YES manual up up
GigabitEthernet3 unassigned YES NVRAM administratively down down
***Truncated***
Script 2: single command - multiple devices
This is a small addition to the above script. We want to run the same command against multiple IOS XE devices. We will take the opportunity to optimize our script a little bit.
For the next scripts, we will put the functionality to create an SSH client and make an SSH connection into a seperate file. Reason is that we will use this multiple times going forward.
We will create a file called connection.py
:
import paramiko
def get_connection(host, username, password, port):
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(hostname=host,username=username, password=password, port=port, look_for_keys=False, allow_agent=False)
return ssh
With that out of the way, we can continue. As we created this seperate connection
package, we need to import that into our code using from connection import get_connection
. Next, as we want to target multiple devices, we store the configuration into a dictionary specifying the IP, username, password and port. I will specifiy two devices, but because I only have one IOS XE device available here, I will run my commands against the same device.
Rest of the script is pretty self explanatory. We will loop over the devices dictionary and for each create a SSH client and make a connection. Next we will run the command against the device.
Disclaimer: this is not production-grade code obviously. One should never store the username and password in the clear, not in the source code itself. The examples in the post are merely conceptual and for informational purposes.
import paramiko
from connection import get_connection
import time
devices = {
'iosxe1': {
'ip': 'ios-xe-mgmt-latest.cisco.com',
'username': '***',
'password': '***',
'port': '8181'
},
'iosxe2': {
'ip': 'ios-xe-mgmt-latest.cisco.com',
'username': '***',
'password': '***',
'port': '8181'
}
}
command = 'show ip interface brief \n'
for device in devices.keys():
print(f"Executing on device: {devices[device]['ip']}\n\n")
ssh = get_connection(host=devices[device]['ip'], username=devices[device]['username'], password=devices[device]['password'], port=devices[device]['port'])
stdin, stdout, stderr = ssh.exec_command(command)
output = stdout.readlines()
print(' '.join(map(str, output)))
I won’t print the output here, but as you can imagine it will just print the output of the show ip interface brief
command twice.
Script 3: multiple commands - multiple devices
After the second script, you will surely think why we are limiting ourselves to a single command. Let’s get that fixed in this third script. Easy enough right? Let’s see…
We won’t go in very much detail as it’s mostly based on the script 2 from above. Instead of specifying command as a String
, we specify command as a list
.
import paramiko
from connection import get_connection
import time
devices = {
'iosxe1': {
'ip': 'ios-xe-mgmt-latest.cisco.com',
'username': '***',
'password': '***',
'port': '8181'
}
}
commands = ['show ip interface brief\n', 'show run\n']
for device in devices.keys():
ssh = get_connection(host=devices[device]['ip'], username=devices[device]['username'], password=devices[device]['password'], port=devices[device]['port'])
for command in commands:
stdin, stdout, stderr = ssh.exec_command(command)
output = stdout.readlines()
time.sleep(2)
print(' '.join(map(str, output)))
ssh.close()
Let’s run this script now…
(venv) WAUTERW-M-65P7:Paramiko_Introduction wauterw$ python3 ExecuteMultipleCommandsMultipleDevicesIssue
.py
Welcome to the DevNet Sandbox for CSR1000v and IOS XE
***Truncated***
Interface IP-Address OK? Method Status Protocol
GigabitEthernet1 10.10.20.48 YES other up up
GigabitEthernet2 unassigned YES manual up up
GigabitEthernet3 unassigned YES NVRAM administratively down down
***Truncated***
Traceback (most recent call last):
File "ExecuteMultipleCommandsMultipleDevicesIssue.py", line 19, in <module>
stdin, stdout, stderr = ssh.exec_command(command)
File "/Users/wauterw/Dropbox/Programming/blog-hugo-netlify-code/Paramiko_Introduction/venv/lib/python3.8/site-packages/paramiko/client.py", line 508, in exec_command
chan = self._transport.open_session(timeout=timeout)
File "/Users/wauterw/Dropbox/Programming/blog-hugo-netlify-code/Paramiko_Introduction/venv/lib/python3.8/site-packages/paramiko/transport.py", line 875, in open_session
return self.open_channel(
File "/Users/wauterw/Dropbox/Programming/blog-hugo-netlify-code/Paramiko_Introduction/venv/lib/python3.8/site-packages/paramiko/transport.py", line 969, in open_channel
raise SSHException("SSH session not active")
paramiko.ssh_exception.SSHException: SSH session not active
You will notice that the script returns an error SSH session not active
.
The reason for this is that the exec_command
on Cisco only allows a single command. In order to run multiple commands, you need to run the invoke_shell()
method. We’ll show this in the next script.
Script 4: multiple commands - multiple devices: fixed
As mentioned above, instead of using exec_command()
we need to use the invoke_shell()
method. Let’s change our script to accomodate that.
import paramiko
import time
commands = ['show version\n', 'show run\n']
max_buffer = 65535
devices = {
'iosxe1': {
'ip': 'ios-xe-mgmt-latest.cisco.com',
'username': '***',
'password': '***',
'port': '8181'
},
'iosxe2': {
'ip': 'ios-xe-mgmt-latest.cisco.com',
'username': '***',
'password': '***',
'port': '8181'
}
}
def get_connection(host, username, password, port):
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(hostname=host,username=username, password=password, port=port, look_for_keys=False, allow_agent=False)
return ssh
def clear_buffer(connection):
if connection.recv_ready():
return connection.recv(max_buffer)
for device in devices.keys():
connection = get_connection(host=devices[device]['ip'], username=devices[device]['username'], password=devices[device]['password'], port=devices[device]['port'])
new_connection = connection.invoke_shell()
output = clear_buffer(new_connection)
time.sleep(2)
new_connection.send("terminal length 0\n")
output = clear_buffer(new_connection)
for command in commands:
print(f"Executing command {command}")
new_connection.send(command)
time.sleep(2)
output = new_connection.recv(max_buffer)
print(output)
new_connection.close()
When you run this script, you will notice that it now works. We are looping over the devices and for each device, we create an SSH connection and execute both commands (before moving to the next device).
Script 5: multiple commands - multiple devices: write to file
If you are following this tutorial and you have executed script 4, you will notice that the terminal output is really messy. The output is just written to the terminal as it is received by the IOS XE device. A small improvement could be to write the output to a text file.
You can do this by changing the content in our for loop.
for device in devices.keys():
outputFileName = device + ' ' + command + '_output.txt'
connection = get_connection(host=devices[device]['ip'], username=devices[device]['username'], password=devices[device]['password'], port=devices[device]['port'])
new_connection = connection.invoke_shell()
output = clear_buffer(new_connection)
time.sleep(2)
new_connection.send("terminal length 0\n")
output = clear_buffer(new_connection)
with open(outputFileName, 'wb') as f:
for command in commands:
print(f"Executing command {command}")
new_connection.send(command)
time.sleep(2)
output = new_connection.recv(max_buffer)
f.write(output)
new_connection.close()
In this script, you can see that we are writing the output of the two commands to an output file per device. So there will be two files, one for each device. Each file containts the output for both these commands.
The code can be found here. Check out the following files for this tutorial:
- Script 1: ExecuteSingleCommand.py
- Script 2: ExecuteSingleCommandMultipleDevices.py
- Script 3: ExecuteMultipleCommandsMultipleDevicesIssue.py
- Script 4: ExecuteMultipleCommandsMultipleDevices.py
- Script 5: ExecuteMultipleCommandsMultipleDevices_toFile.py I