Python // Misc: Netmiko and using SSH Proxies

Netmiko supports SSH proxies. By this I mean you can ‘bounce’ through an intermediate server while connecting to a remote network device.

This article will demonstrate how to use this feature.

First of all my setup is as follows:

  • Netmiko = 2.2.2
  • Paramiko = 2.4.1

My lab environment also has two Linux AWS servers. The first server is the script server; this server has both Netmiko and Paramiko installed. The second server is the intermediate server; this is the server that we will be proxying through.

OpenSSH, which is running on the two Linux servers, supports obtaining connection parameters from a file. The standard location for this file is ~/.ssh/config.

My SSH config file is configured as follows (the comments added here are for clarity and aren’t in the actual file):

host jumphost                   # The intermediate server
  IdentityFile ~/.ssh/id_rsa    
  IdentitiesOnly yes            # Use only id_rsa key
  user gituser
  hostname 10.10.10.159

host * !jumphost                # Only use the SSH proxy
  ProxyCommand ssh jumphost nc %h %p  

The ProxyCommand above says that when connecting to any host (besides the jumphost itself) do so by executing ‘ssh jumphost nc %h %p’. In other words, any SSH connection will be proxied through the jumphost (besides the SSH connection to the jumphost itself). The ‘nc’ (netcat) command will be executed on the intermediate server and will continue the SSH connection to the end network device. The %h and %p will resolve to the end host and port respectively.
 

I have also set up an SSH trust between the script server and the intermediate server (jumphost). Consequently, I am able to SSH into the intermediate server without any password (i.e. only using SSH keys).

$ ssh jumphost
Last login: Fri Jul 20 12:30:12 2018 from 10.10.10.58

       __|  __|_  )
       _|  (     /   Amazon Linux AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-ami/2018.03-release-notes/ 
(lha) [gituser@ip-10-161-115-202 ~]$  

At this point, a good initial test is to manually SSH to the network device using the SSH config file. Note, for testing purposes I am just using a file named ‘ssh_config’ (i.e. my SSH config file is not in the default ~/.ssh/config location).

$ ssh -F ssh_config -l pyclass cisco1.domain.com
Password: 

pynet-rtr1#show users
    Line       User       Host(s)      Idle       Location
*  8 vty 0     pyclass    idle         00:00:00 10.10.10.159

  Interface    User       Mode         Idle     Peer Address

pynet-rtr1# 

The 10.10.10.159 address is the IP address of the intermediate server (well it was actually a public IP address that I hid, but it was the intermediate server’s public IP). Consequently, I have verified that I am proxying through the intermediate server.

Now let’s test this using a Netmiko script.

#!/usr/bin/env python
from netmiko import ConnectHandler
from getpass import getpass

device = {
    'device_type': 'cisco_ios',
    'host': 'cisco1.domain.com',
    'username': 'pyclass',
    'password': getpass(),
    'ssh_config_file': './ssh_config',
} 

net_connect = ConnectHandler(**device)
output = net_connect.send_command("show users")
print(output)       

This is a basic Netmiko script. It defines a network device named ‘device’ with some arguments that are required to create a Netmiko connection. One item of note, I must specifically identify the ‘ssh_config_file’ (Netmiko requires this for SSH proxy support). Netmiko will not automatically use the SSH config file in ~/.ssh/config.

Once the connection is established, the script will execute the ‘show users’ command.


Now let’s execute this:

$ python ssh_proxy.py 
Password: 
    Line       User       Host(s)      Idle       Location
*  8 vty 0     pyclass    idle         00:00:00  10.10.10.159

  Interface    User       Mode         Idle     Peer Address

Once again we see that the SSH connection came via the .159 server (i.e. we connected through the intermediate server).

What if the SSH key is encrypted?

Here I have re-generated a new id_rsa key that is now encrypted using a password and I have added the new public key to the authorized_keys on the intermediate server.

$ ssh jumphost
Enter passphrase for key '/home/gituser/.ssh/id_rsa': 
Last login: Fri Jul 20 19:55:08 2018

       __|  __|_  )
       _|  (     /   Amazon Linux AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-ami/2018.03-release-notes/
(lha) [gituser@ip-10-161-115-202 ~]$ 

At this point, if we re-execute our Python script, it will operate the same except that we will now be automatically prompted to decrypt our SSH key.

$ python ssh_proxy.py
Password: 
Enter passphrase for key '/home/gituser/.ssh/id_rsa': 
    Line       User       Host(s)      Idle       Location
*  8 vty 0     pyclass    idle         00:00:00 10.10.10.159

  Interface    User       Mode         Idle     Peer Address

Once again we see that the connection came from the .159 server.

Additional notes:

You could construct a more specific SSH config file as follows:

host jumphost
  IdentityFile ~/.ssh/id_rsa
  IdentitiesOnly yes
  user gituser
  hostname 10.10.10.159

host cisco1.domain.com
  ProxyCommand ssh jumphost nc %h %p  



Netmiko reference code on SSH Proxy