Netmiko and SSH Proxies

Author: Kirk Byers
Date: 2021-09-23 (Updated)

Introduction

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.

SSH Proxy Diagram

The Setup

First of all my setup is as follows:

  • Netmiko = 4.0.0a4
  • Paramiko = 2.7.2

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 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 intermediate server
host jumphost
  # Use only the key specified in IdentityFile
  IdentitiesOnly yes
  # The SSH key to use to the intermediate server
  IdentityFile ~/.ssh/test_rsa
  User gituser
  HostName pynetqa.lasthop.io

host * !jumphost
  User pyclass
  # -F forces usage of this SSH config file
  ProxyCommand ssh -F ~/pynet_articles/netmiko_proxy/ssh_config -W %h:%p jumphost
  # Alternate solution using netcat
  # ProxyCommand ssh -F ~/pynet_articles/netmiko_proxy/ssh_config jumphost nc %h:%p

The ProxyCommand above says that when connecting to any host (besides the jumphost itself) do so by executing:

$ ssh -F ~/pynet_articles/netmiko_proxy/ssh_config -W %h:%p jumphost

In other words any SSH connection will be proxied through the jumphost (besides the SSH connection to the jumphost itself).

The '-W %h:%p jumpost' argument binds standard input and standard output through the jumphost. From 'man ssh':

-W host:port
Requests that standard input and output on the client be 
forwarded to host on port over the secure channel.

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 -F ./ssh_config jumphost
Last login: Thu Sep 23 09:58:43 2021

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

https://aws.amazon.com/amazon-linux-2/
(pynet_tools) [gituser@pynetqa ~]$

At this point a good initial test is to manually SSH to the network device using the SSH config file.

$ ssh -F ./ssh_config -l pyclass cisco1.lasthop.io
Password: 
cisco1#show users
    Line       User       Host(s)              Idle       Location
*  8 vty 0     pyclass    idle                 00:00:00 10.100.148.177

  Interface    User               Mode         Idle     Peer Address

cisco1#

The 10.100.148.177 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.

Testing SSH-Proxying with a Netmiko Script

Now let's test this using a Netmiko script.

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

if os.getenv("NETMIKO_PASSWORD"):
    password = os.getenv("NETMIKO_PASSWORD")
else:
    getpass()

device = {
    "device_type": "cisco_ios",
    "host": "cisco1.lasthop.io",
    "username": "pyclass",
    "password": password,
    "ssh_config_file": "./ssh_config",
}

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

This script defines a network device including a set of required Netmiko parameters. It then connects to that device using Netmiko.

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 
    Line       User       Host(s)              Idle       Location
*  8 vty 0     pyclass    idle                 00:00:00 10.100.148.177

  Interface    User               Mode         Idle     Peer Address

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

What if the SSH key is encrypted?

$ ssh -F ./ssh_config2 jumphost
Enter passphrase for key '/home/kbyers/.ssh/test_rsa_encr': 
Last login: Thu Sep 23 12:58:51 2021 from ec2-13-57-229-17.us-west-1.compute.amazonaws.com

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

https://aws.amazon.com/amazon-linux-2/
(pynet_tools) [gituser@pynetqa ~]$

Here I have a new SSH config file (ssh_config2) where the only changes are the SSH key and the reference to the SSH config file itself. This SSH key is encrypted and is already trusted by the intermediate server.

6c6
-   IdentityFile ~/.ssh/test_rsa
---
+   IdentityFile ~/.ssh/test_rsa_encr 
13c13
-   ProxyCommand ssh -F ~/pynet_articles/netmiko_proxy/ssh_config -W %h:%p jumphost
---
+   ProxyCommand ssh -F ~/pynet_articles/netmiko_proxy/ssh_config2 -W %h:%p jumphost

You can see here that I am prompted for the SSH key passphrase

$ ssh -F ./ssh_config2 jumphost
Enter passphrase for key '/home/kbyers/.ssh/test_rsa_encr':

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_encr.py 
Enter passphrase for key '/home/kbyers/.ssh/test_rsa_encr': 
    Line       User       Host(s)              Idle       Location
*  8 vty 0     pyclass    idle                 00:00:00 10.100.148.177

  Interface    User               Mode         Idle     Peer Address

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

Additional notes:

You could construct a more specific SSH config file as follows (i.e. replace the wildcard host with a specific destination host):

# The intermediate server
host jumphost
  IdentitiesOnly yes
  IdentityFile ~/.ssh/test_rsa
  User gituser
  HostName pynetqa.lasthop.io

host cisco1.lasthop.io
  User pyclass
  ProxyCommand ssh -F ~/pynet_articles/netmiko_proxy/ssh_config -W %h:%p jumphost

Kirk Byers

@kirkbyers

You might also be interested in: