Many infrastructure teams need to route SSH connections through different bastion hosts based on IP ranges. While simple wildcards (10.1.*
) work for basic cases, they fail when dealing with non-contiguous or non-octet-aligned CIDR blocks like 10.2.0.0/18
.
The OpenSSH client doesn't natively support CIDR notation in ~/.ssh/config
host patterns. The wildcard matching only works on:
- Whole octets (
10.1.*.*
) - Simple patterns (
192.168.?
)
We can implement CIDR matching using a script in the ProxyCommand directive:
Host 10.2.*
ProxyCommand bash -c '[[ $(printf "%02x" ${(s:.:)1}) =~ ^0a02(00|40) ]] &&
ssh -q %r@bastion-%1 -W %h:%p || exit 1'
This zsh/bash solution:
- Converts IP to hex (10.2.0.1 → 0a020001)
- Uses regex to match CIDR ranges (first 18 bits for /18)
- Selects bastion host based on matched group
For more complex scenarios, a Python script provides better CIDR handling:
Host 10.3.*
ProxyCommand python3 -c '
import ipaddress, sys, subprocess
ip = ipaddress.ip_address(sys.argv[1].split("%h")[1])
if ip in ipaddress.ip_network("10.3.0.0/18"):
subprocess.run(["ssh","-q","bastion-foo","-W",sys.argv[1]])
elif ip in ipaddress.ip_network("10.3.64.0/18"):
subprocess.run(["ssh","-q","bastion-bar","-W",sys.argv[1]])
else:
sys.exit(1)
' %h:%p
While these solutions work, they add latency to the SSH connection startup. For high-performance needs, consider:
- Pre-compiling IP ranges in the script
- Using compiled languages like Go for the proxy command
- Implementing connection multiplexing
For Linux systems, network namespaces can provide cleaner routing:
ip netns add vpc1
ip -n vpc1 route add 10.1.0.0/16 via $BASTION_IP
ssh -o "ProxyCommand=ip netns exec vpc1 nc %h %p" 10.1.5.6
While SSH config files support wildcards for host matching, they don't natively understand CIDR notation. The typical approach using patterns like 10.1.*
works for simple cases but fails when you need:
- Non-octet-aligned subnets (e.g., /18, /22)
- Precise network boundary matching
- Multiple bastion hosts for different subnets
Use SSH's Match exec
to run a script that checks IP ranges:
Match host 10.* exec "ipcalc -n %h | grep -q '^NETWORK=10.2.0.0/18$'"
ProxyCommand ssh -q bastion-foo -W %h:%p
Match host 10.* exec "ipcalc -n %h | grep -q '^NETWORK=10.2.64.0/18$'"
ProxyCommand ssh -q bastion-bar -W %h:%p
Create a reusable script (~/bin/ssh-cidr-check
):
#!/bin/bash
ip=$1
cidr=$2
# Install ipcalc if needed: apt-get install ipcalc
ipcalc -n "$ip" | grep -q "^NETWORK=${cidr}$"
exit $?
Then reference it in your config:
Match host 10.* exec "/bin/bash ~/bin/ssh-cidr-check %h 10.2.0.0/18"
ProxyCommand ssh -q bastion-foo -W %h:%p
For systems without ipcalc, use this Python one-liner:
Match host 10.* exec "python3 -c 'import ipaddress,sys; sys.exit(0 if ipaddress.ip_address(sys.argv[1]) in ipaddress.ip_network(sys.argv[2]) else 1)' %h 10.2.0.0/18"
ProxyCommand ssh -q bastion -W %h:%p
For environments with thousands of hosts:
- Cache CIDR calculations
- Use compiled languages (Go/Rust) for the check script
- Limit Match directives to broad host patterns first