Automatically use jump proxy when initiating SSH connection from an external network.

Two years ago I have described how to match network inside SSH client configuration. Today, I will extend this technique to match specific network and perform DNS query to determine if a jump proxy is needed.

Prerequisites

Install utility to manage Python packages.

$ sudo apt install python3-pip

Install psutil Python package.

$ pip3 install psutil
Collecting psutil
  Downloading psutil-5.8.0-cp39-cp39-manylinux2010_x86_64.whl (293 kB)
     |████████████████████████████████| 293 kB 1.2 MB/s
Installing collected packages: psutil
Successfully installed psutil-5.8.0

Create helper script

Create ~/.ssh/ipnetext.py Python helper script.

#!/usr/bin/env python3
# SSH helper - Check if machine is in specific network and dns name is resolvable
# Usage: ipnetext.py network
# Example: ipnet.py 172.16.0.0/16 server.example.org
# Exit codes: 0 - true, 1 - false (different network), 2 - false (same network, dns query failed)
import ipaddress
import socket
import sys
import psutil
import dns.resolver

for interface, snicaddrs in psutil.net_if_addrs().items():
    for snicaddr in snicaddrs:
        if snicaddr.family is socket.AF_INET:
            if (ipaddress.ip_network(sys.argv[1], strict=False) == ipaddress.ip_network((snicaddr.address, snicaddr.netmask),strict=False)):
                try:
                    dns.resolver.query(sys.argv[2],rdtype=dns.rdatatype.A,lifetime=1)
                except:
                    exit(2)
                exit(0)
exit(1)

Ensure that the executable bit is set.

$ chmod +x ~/.ssh/ipnetext.py

This helper script will be used to perform match operation as it will return true when the computer is in the specific network (provided by the first argument) and DNS query for the remote server (provided the second argument) will succeed.

SSH client configuration

The pi-hole.example.org server is available and resolvable in 10.10.0.0/16 network, so the following configuration will ensure that the jump proxy will be defined when computer is outside of this network or the DNS query fails.

Match host pi-hole !exec "~/.ssh/ipnetext.py 10.10.0.0/16 %h.example.org"
  ProxyJump proxy.example.org
  User milosz

Host pi-hole
  Hostname pi-hole.example.org
  User milosz

Match user milosz
  IdentityFile ~/.ssh/milosz

Usage

Verify configuration within a defined network (do not use jump proxy).

$ ssh -A pi-hole -vv
OpenSSH_8.4p1 Ubuntu-5ubuntu1.1, OpenSSL 1.1.1j  16 Feb 2021
debug1: Reading configuration data /home/milosz/.ssh/config
debug2: checking match for 'host pi-hole !exec "~/.ssh/ipnetext.py 10.10.0.0/16 %h.example.org"' host pi-hole originally pi-hole
debug1: Executing command: '~/.ssh/ipnetext.py 10.10.0.0/16 pi-hole.example.org'
debug2: match not found
debug1: /home/milosz/.ssh/config line 136: Applying options for pi-hole
debug2: checking match for 'user milosz' host pi-hole.example.org originally pi-hole
debug2: match found
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: /etc/ssh/ssh_config line 19: include /etc/ssh/ssh_config.d/*.conf matched no files
debug1: /etc/ssh/ssh_config line 21: Applying options for *
debug2: resolving "pi-hole.example.org" port 22
debug2: ssh_connect_direct
debug1: Connecting to pi-hole.example.org [10.10.2.1] port 22.
debug1: Connection established.
debug1: identity file /home/milosz/.ssh/milosz type 0
debug1: identity file /home/milosz/.ssh/milosz-cert type -1
debug1: Local version string SSH-2.0-OpenSSH_8.4p1 Ubuntu-5ubuntu1.1
debug1: Remote protocol version 2.0, remote software version OpenSSH_7.9p1 Raspbian-10+deb10u2+rpt1
debug1: match: OpenSSH_7.9p1 Raspbian-10+deb10u2+rpt1 pat OpenSSH* compat 0x04000000
debug2: fd 3 setting O_NONBLOCK
debug1: Authenticating to pi-hole.example.org:22 as 'milosz'
[...]
Authenticated to pi-hole.example.org ([10.10.2.1]:22).
[...]
Linux DietPi 5.10.63-v7+ #1457 SMP Tue Sep 28 11:25:31 BST 2021 armv7l

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Thu Oct 21 00:51:58 2021 from 10.10.10.254
milosz@DietPi:~ $

Verify configuration outside of a defined network (set jump proxy).

$ ssh -A pi-hole -vv
OpenSSH_8.4p1 Debian-5, OpenSSL 1.1.1k  25 Mar 2021                                                                                                                                                            [195/1965]
debug1: Reading configuration data /home/milosz/.ssh/config
debug2: checking match for 'host pi-hole !exec "~/.ssh/ipnetext.py 10.10.0.0/16 %h.example.org"' host pi-hole originally pi-hole
debug1: Executing command: '~/.ssh/ipnetext.py 10.10.0.0/16 pi-hole.example.org'
debug2: match found
debug1: /home/milosz/.ssh/config line 5: Applying options for pi-hole
debug2: checking match for 'user milosz' host pi-hole.example.org originally pi-hole
debug2: match found
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: /etc/ssh/ssh_config line 19: include /etc/ssh/ssh_config.d/*.conf matched no files
debug1: /etc/ssh/ssh_config line 21: Applying options for *
debug1: Setting implicit ProxyCommand from ProxyJump: ssh -vv -W '[%h]:%p' proxy.example.org
debug1: Executing proxy command: exec ssh -vv -W '[pi-hole.example.org]:22' proxy.example.org
debug1: identity file /home/milosz/.ssh/milosz type -1
debug1: identity file /home/milosz/.ssh/milosz-cert type -1
debug1: Local version string SSH-2.0-OpenSSH_8.4p1 Debian-5
OpenSSH_8.4p1 Debian-5, OpenSSL 1.1.1k  25 Mar 2021
debug1: Reading configuration data /home/milosz/.ssh/config
debug2: checking match for 'host pi-hole !exec "~/.ssh/ipnetext.py 10.10.0.0/16 %h.example.org"' host proxy.example.org originally proxy.example.org
debug2: match not found
debug2: checking match for 'user milosz' host proxy.example.org originally proxy.example.org
debug2: match found
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: /etc/ssh/ssh_config line 19: include /etc/ssh/ssh_config.d/*.conf matched no files
debug1: /etc/ssh/ssh_config line 21: Applying options for *
debug2: resolving "proxy.example.org" port 22
debug2: ssh_connect_direct
debug1: Connecting to proxy.example.org [10.10.10.254] port 22.
debug1: Connection established.
debug1: identity file /home/milosz/.ssh/milosz type -1
debug1: identity file /home/milosz/.ssh/milosz-cert type -1
debug1: Local version string SSH-2.0-OpenSSH_8.4p1 Debian-5
debug1: Remote protocol version 2.0, remote software version OpenSSH_8.4p1 Ubuntu-5ubuntu1.1
debug1: match: OpenSSH_8.4p1 Ubuntu-5ubuntu1.1 pat OpenSSH* compat 0x04000000
debug2: fd 3 setting O_NONBLOCK
debug1: Authenticating to proxy.example.org:22 as 'milosz'
[...]
Authenticated to proxy.example.org ([10.10.10.254]:22).
debug1: channel_connect_stdio_fwd pi-hole.example.org:22
[...]
debug1: Authenticating to pi-hole.example.org:22 as 'milosz'
[...]
Authenticated to pi-hole.example.org (via proxy).
[...]
Linux DietPi 5.10.63-v7+ #1457 SMP Tue Sep 28 11:25:31 BST 2021 armv7l

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Thu Oct 21 01:37:47 2021 from 10.10.10.254
milosz@DietPi:~ $

What a neat idea!

ko-fi