Python SSH

There are multiple options to use SSH in Python but Paramiko is the most popular one. Paramiko is an SSHv2 protocol library for Python. In this lesson, I’ll show you how to use Paramiko to connect to a Cisco IOS router, run a show command, and return the output to us.

Configuration

Here is the topology:

Python H1 R1

I’ll use a Cisco IOS router running IOS Version 15.7(3)M3 and I’ll run the Python code from my computer.

Router

On the router, we need to enable SSH:

R1(config)#ip domain-name networklessons.local
R1(config)#crypto key generate rsa general-keys modulus 1024
R1(config)#ip ssh version 2

R1(config)#username admin privilege 15 secret admin

R1(config)#line vty 0 4
R1(config-line)#transport input ssh
R1(config-line)#login local

I configured an “admin” user with privilege level 15 so that we have full access to the router once we log in.

Python

We need to install Paramiko, which is easy with PIP:

pip install paramiko

We are now ready to try some code.

Sample Code

In our first example, here’s what we try to accomplish:

  • Connect to the router with username/password authentication.
  • Run the show ip route command.
  • Look for the default route in the output and show it to us.

Here is my code:

import paramiko

router_ip = "172.16.1.100"
router_username = "admin"
router_password = "admin"

ssh = paramiko.SSHClient()

# Load SSH host keys.
ssh.load_system_host_keys()
# Add SSH host key automatically if needed.
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# Connect to router using username/password authentication.
ssh.connect(router_ip, 
            username=router_username, 
            password=router_password,
            look_for_keys=False )

# Run command.
ssh_stdin, ssh_stdout, ssh_stderr = ssh.exec_command("show ip route")

output = ssh_stdout.readlines()
# Close connection.
ssh.close()

# Analyze show ip route output
for line in output:
    if "0.0.0.0/0" in line:
        print("Found default route:")
        print(line)

When we run this code, here’s what we get:

Found default route:
S*    0.0.0.0/0 [1/0] via 172.16.1.254

This is nice. We managed to connect to the router, run the show ip route command, and look for the default route.

Improved Sample Code

How could we improve this script with some of the things we learned? For example:

  • Our code runs a single command. How about we use a function so we can use our code to connect to different devices and run different commands?
  • What if our SSH connection fails? It would be nice to deal with this with a try/except block. How about we try to attempt to connect multiple times?

Let’s see what we can do. Here is my improved code:

import paramiko

router_ip = "172.16.1.100"
router_username = "admin"
router_password = "admin1"

ssh = paramiko.SSHClient()


def run_command_on_device(ip_address, username, password, command):
    """ Connect to a device, run a command, and return the output."""

    # Load SSH host keys.
    ssh.load_system_host_keys()
    # Add SSH host key when missing.
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    
    total_attempts = 3
    for attempt in range(total_attempts):
        try:
            print("Attempt to connect: %s" % attempt)
            # Connect to router using username/password authentication.
            ssh.connect(router_ip, 
                        username=router_username, 
                        password=router_password,
                        look_for_keys=False )
            # Run command.
            ssh_stdin, ssh_stdout, ssh_stderr = ssh.exec_command(command)
            # Read output from command.
            output = ssh_stdout.readlines()
            # Close connection.
            ssh.close()
            return output

        except Exception as error_message:
            print("Unable to connect")
            print(error_message)


# Run function
router_output = run_command_on_device(router_ip, router_username, router_password, "show ip route")

# Analyze show ip route output
# Make sure we didn't receive empty output.
if router_output != None:
    for line in router_output:
        if "0.0.0.0/0" in line:
            print("Found default route:")
            print(line)

What did I change?

  • I created a function to contain the code used to connect to the router.
  • I added a try/except block for the connection with multiple attempts. When the connection fails, it shows the reason.
  • An extra check to see if the router output contains any information.

Verification

Let’s run our code again to see if it works.

Wrong Password

When you supply a wrong password, you get this output:

Attempt to connect: 0
Unable to connect
Authentication failed.
Attempt to connect: 1
Unable to connect
Authentication failed.
Attempt to connect: 2
Unable to connect
Authentication failed.

This looks good. Our code attempted to connect three times and showed the reason why it was unable to connect.

Host unavailable

When your host is unavailable, you get this output:

Attempt to connect: 0
Unable to connect
[Errno 10060] A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond
Attempt to connect: 1
Unable to connect
[Errno 10060] A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond
Attempt to connect: 2
Unable to connect
[Errno 10060] A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond

Once again, three attempts to connect and it shows the reason why our script failed.

Conclusion

You have now learned how you can connect to a device using SSH and Python. Although this works, it might not be the best solution. In this example, I ran the show ip route command and looked for the default route. This is simple, but what if you want to parse show commands that have a lot of information?

It’s possible to parse the output of show commands like this using regular expressions but it’s a pain. Instead, if possible, it’s better to use a REST API where the device returns the output in JSON format. Parsing JSON in Python is much easier.

I hope you enjoyed this lesson. If you have any questions feel free to leave a comment!


Forum Replies

  1. Hi Rene

    Fantastic script.
    It works on my homelab and on GNS3 too, do you know how make more commands in a single session?

    Thanks

  2. Hello Giovanni

    In order to implement more commands in a single session, you simply input them in a similar manner in the # Run command. section of the script.

    I hope this has been helpful!

    Laz

  3. Hello Giovanni,

    This lesson is part of a Python course which I’m currently working on, it’s almost finished. In the course I explain everything, so you’ll know how to write an example like this yourself.

    As Lazaros explained, you can add multiple commands at the bottom of the script:

    # Run function
    router_output = run_command_on_device(router_ip, router_username, router_password, "show ip route")

    Rene

  4. So it doesn’t seem to work.

    It works using more channels like this …

    import paramiko
    import cmd
    import time
    import sys
    
    buff = ''
    resp = ''
    
    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    ssh.connect('192.168.99.10', username='test', password='test')
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    
    #run command
    chan = ssh.invoke_shell()
    
    # turn off paging
    chan.send('terminal length 0\n')
    time.sleep(1)
    resp = chan.recv(9999)      # recv(nbytes)
                                #Receive data from the channel. The return 
    ... Continue reading in our forum

  5. Hi.
    I’m try to make a script to automate some tasks with Fortigate Fw.
    When I try the connection , python sollevate this exception.

    >>> ssh.connect("192.168.1.99",
    	    22,
                username="admin", 
                password=None,
                look_for_keys=False )
    Traceback (most recent call last):
      File "<pyshell#7>", line 1, in <module>
        ssh.connect("192.168.1.99",
      File "C:\...\Python38-32\lib\site-packages\paramiko\client.py", line 435, in connect
        self._auth(
      File "C:\...\Python38-32\lib\site-packages\paramiko\client.py", line 765, in _auth
        r
    ... Continue reading in our forum

28 more replies! Ask a question or join the discussion by visiting our Community Forum