Netmiko and SSH Proxies
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.
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