Categories
Network Automation

Python: Paramiko

Networking automation is a god-send. I think that every Network Engineer, Voice too! Should learn it. It will make life as said roles much easier. Today we’ll run through a basic script using Paramiko, which is a ” Python implementation of the SSHv2 protocol”, it provides both client and server functionality. Today we will focus on the client-side.

Today’s demonstration of paramiko will be completed with the help of GNS3. At the time of this post, I do not have a post on how to install and setup GNS3, yet! So I will assume that you already have your GNS3 software installed and perfectly operational, ios, qemus and’all. I know how difficult it can be to run a GNS3 server and the GNS3 program on a single machine, so in the near future, I will also have a separate blog post on how to install and setup a remote GNS3 server.

HEADS UP: The installation will be done on a Linux system. However, I believe the same instructions can be followed in a Windows system.

Here is our topology:

Here is my setup:

Both F0/0 interfaces for the NY and Cali routers are running DHCP, so they’re pulling IP addresses from the 192.168.0.x network:

R1:

R2:

Both can ping anywhere on the internet without any issues ( Pinging both Level-3’s and Google’s dns servers:

R1:

R2:

Within my home network, I have a laptop running Linux, where I will be running my scripts from. I know that there is a way for me to utilize the python appliance for GNS3, however, I could care less about that right, I’ll stick to my laptop for now.

Let us start with the installation:

Open up your terminal and enter the following:

pip3 install paramiko

Next, verify that your installation took, by importing paramiko into a new python file.

I named my file “deviceo”; you can name yours whatever you like, just make sure the extension is “py”. Run the script, if you get no errors, then you’re good to go!

Now that we have paramiko installed, we will begin by configuring our routers with basic remote-access configuration:

username admin privilege 15 password 0 redEyeCoding123
!
!
ip domain name paramiko_lab.com
!
!
line vty 0 15
 exec-timeout 0 0
 logging synchronous
 login local
 transport input telnet ssh

Before saving your configuration, generate some RSA keys ( 1024 bit ):

Now, save your configuration on both routers and test connectivity from your home network to your GNS3 network. If you have another computer on your home network, test from there. I’ll use putty from my laptop to verify connectivity, you can use putty or your favorite SSH client:

Connectivity to R1 verified:

Connectivity to R2 verified

Let’s start using paramiko

Open up a new python file, naming it whatever you like, and import the paramiko module.

Create the SSH Client

ssh_client = paramiko.SSHClient()

Missing Server Host Keys

Before we attempt to connect to the routers, ( the SSH-Server ), we must configure our script to automatically insert and save the host key policy that the SSH server has on our system, otherwise python with throw an SSHExection error.

ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

Let’s build the dictionary containing our router’s login information

router_defaults = {
            'hostname': f'192.168.0.131',
            'username': f'admin',
            'password': f'redEyeCoding123',
            'port': '22',
            'allow_agent': False,
            'look_for_keys': False
        }

Now let’s connect to router R1

The paramiko “connect” module contains a set of parameters needed to establish a connection to a device. Those parameters were already pre-configured in the dictionary above. We will use python’s “Un-Packing” syntax to on upack the contents of the created dictionary into the “connect” function call; like so:

ssh_client.connect(**router_defaults)

I prefer the above method, its quick and easy, otherwise I’d have to do this:

ssh_client.connect(hostname='192.168.0.131',
                   port=22, username='admin', 
                   password='redEyeCoding123',
                  allow_agent=False, look_for_keys=False)

We need a way to test if the connection was established…

Then we will create a variable that tests whether the connection is active or not; it will return a boolean value, which we will use later to close the SSH session. Enter the following to test this. If ran correctly you should get a True within your terminal:

is_session_active = ssh_client.get_transport().is_active()
print(f"Is the session active?: {is_session_active}") # // True

Once completed, you can comment out the print code and move forward.

Now, lets start an interactive shell with the device.

command_shell = ssh_client.invoke_shell()

Every time we send commands using paramiko, we’re sending them as if we’re typing within the console of the router? What do I mean? I mean that we will need to send the signal for the “enter” key to the router so it knows to “hit enter” after every command. If we don’t , we’ll get a bunch of errors. It’s like trying to configure both an OSPF process and the OSPF network command all on the same line.

So how do we do this? We’ll use Python’s escape characters to get this done. I personally like to create a variable that will be assigned to the value I will need:

enter_key = "\n"

If you need to learn more information on Python’s string literals, you can go here: https://docs.python.org/2.0/ref/strings.html

( So far, your script should look like this )

import paramiko

#Build SSH Client
ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

#Build datastructure for device_info
router_defaults = {
            'hostname': f'192.168.0.131',
            'username': f'admin',
            'password': f'redEyeCoding123',
            'port': '22',
            'allow_agent': False,
            'look_for_keys': False
}

#Connect to Client
ssh_client.connect(**router_defaults)
is_session_active = ssh_client.get_transport().is_active()
print(f"Is the session active?: {is_session_active}") # // True

#Build The Shell
command_shell = ssh_client.invoke_shell()

Let’s send some commands:

command_shell.send(f"terminal length 0 {enter_key}")
command_shell.send(f"show ip interface brief {enter_key}")
command_shell.send(f"show clock {enter_key}")
command_shell.send(f"show run {enter_key}")

Before we print out the output, we have to give the router some time to output the information, otherwise we will get back zilch. To do this we’ll import the time module and use the sleep method, putting the program to sleep for about 2 seconds:

command_shell.send(f"terminal length 0 {enter_key}")
command_shell.send(f"show ip interface brief {enter_key}")
command_shell.send(f"show clock {enter_key}")
command_shell.send(f"show run {enter_key}")

# Go to sleep for 2 seconds and give the 
  # router time to respond!!
time.sleep(2)

Noticed the first command? We need that particularly for our Cisco device. Why? Well, because Cisco devices show a max of 24 lines of output for any given command entered, force you to hit the space bar or enter to see the rest of the output. So we put “terminal length 0” as the first command, to let the router know to display the output in full. If we did not put this command in , first, we would get bits and pieces of our commands. For instance here is a capture of the show run command without the terminal length being adjusted:

Nah… we don’t want that…we want the whole thing!

Ok, so we sent over some commands to the router, now what? How about we verify that it did send the commands over? Let us verify this by assigning the output kicked back from the router to a variable. We’ll do this using the paramiko recv method:

router_output = command_shell.recv(10000).decode("utf-8")
# we need to decode the output from the 
	# router so it is readable to us in terminal

Now let us print the output:

print(router_output)

By the End of this your script should look like this:

import paramiko

#Build SSH Client
ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
enter_key = "\n"

#Build datastructure for device_info
router_defaults = {
            'hostname': f'192.168.0.131',
            'username': f'admin',
            'password': f'redEyeCoding123',
            'port': '22',
            'allow_agent': False,
            'look_for_keys': False
}

#Connect to Client
ssh_client.connect(**router_defaults)
is_session_active = ssh_client.get_transport().is_active()
print(f"Is the session active?: {is_session_active}") # // True

#Build The Shell
command_shell = ssh_client.invoke_shell()

#Send Commands
command_shell.send(f"terminal length 0 {enter_key}")
command_shell.send(f"show ip interface brief {enter_key}")
command_shell.send(f"show clock {enter_key}")
command_shell.send(f"show run {enter_key}")

# Go to sleep for 2 seconds and give the 
  # router time to respond!!
time.sleep(2)

router_output = command_shell.recv(10000).decode("utf-8")
# we need to decode the output from the 
	# router so it is readable to us in terminal.
print(router_output)

If done correctly, you should see the output generated from the router. I know we only sent commands to one router while our topology has two devices. I did this on purpose so I can discuss how to send commands to multiple devices in different blog post.