Reputation: 2569
I need to jump through an intermediate host to reach my destination when I'm on a certain network (subnet is 10.10.11.x) because of a destination port I can't change and limited ports on which I can exit the restricted network. I use a SSH config like the following with success:
Host web-direct web
HostName web.example.com
Port 1111
Host web-via-jump jweb
HostName web.example.com
Port 1111
ForwardAgent yes
ProxyCommand ssh -p 110 -q relay.example.com nc %h %p
Going through the jumpbox is a significant performance hit, so I need to avoid it for the majority of times that it is not needed. Switching the ssh
/scp
/rsync
host nickname is fine for interactive use, but there are some automated/scripted tasks which it is very painful.
My shell stays open across network transitions, so startup (.zshrc) mechanisms don't help.
I've thought of running a script to poll for the restricted subnet and automating the switch by modifying the .ssh/config file, but I'm not even sure there would be a caching issue. Before I implement that, I thought I would ask if there is a better approach.
What's the best approach for swapping out SSH config based on origin host subnet detection?
In pseudo-config, something like:
if <any-active-local-interface> is on 10.10.11.x:
Host web
HostName web.example.com
Port 1111
ForwardAgent yes
ProxyCommand ssh -p 110 -q relay.example.com nc %h %p
else:
Host web
HostName web.example.com
Port 1111
endif
Upvotes: 27
Views: 16967
Reputation: 165
OpenSSH 9.4 added a new match predicate match localnetwork
that can match a list of CIDR-format addresses. So to matching if the client is on the subnet 10.10.12.0/24
while defaulting to use a VPN IP address otherwise can be done with:
Match Host pi localnetwork 10.10.12.0/24
HostName 10.10.12.4
Host pi
HostName 10.0.0.2
User turtvaiz
Identityfile ~/.ssh/id_pi
You can also match multiple subnets with a list, e.g. localnetwork 10.10.12.0/24,10.0.0.0/24
.
Upvotes: 8
Reputation: 9333
Based on the answer by Fedor Dikarev, Mike created a bash script named onsubnet
:
#!/usr/bin/env bash
if [[ "$1" == "--help" ]] || [[ "$1" == "-h" ]] || [[ "$1" == "" ]] ; then
printf "Usage:\n\tonsubnet [ --not ] partial-ip-address\n\n"
printf "Example:\n\tonsubnet 10.10.\n\tonsubnet --not 192.168.0.\n\n"
printf "Note:\n\tThe partial-ip-address must match starting at the first\n"
printf "\tcharacter of the ip-address, therefore the first example\n"
printf "\tabove will match 10.10.10.1 but not 110.10.10.1\n"
exit 0
fi
on=0
off=1
if [[ "$1" == "--not" ]] ; then
shift
on=1
off=0
fi
regexp="^$(sed 's/\./\\./g' <<<"$1")"
if [[ "$(uname)" == "Darwin" ]] ; then
ifconfig | grep -F 'inet ' | grep -Fv 127.0.0. | cut -d ' ' -f 2 | grep -Eq "$regexp"
else
hostname -I | tr -s " " "\012" | grep -Fv 127.0.0. | grep -Eq "$regexp"
fi
if [[ $? == 0 ]]; then
exit $on
else
exit $off
fi
Then in his .ssh/config
file, he uses Match exec
like Jakuje's answer:
Match exec "onsubnet 10.10.1." host my-server
HostName web.example.com
Port 1111
ForwardAgent yes
ProxyCommand ssh -p 110 -q relay.example.com nc %h %p
Match exec "onsubnet --not 10.10.1." host my-server
HostName web.example.com
Port 1111
Upvotes: 9
Reputation: 1920
The easiest way to match an IP is the use of a regex like this:
Match exec "[[ '%h' =~ ^10\.10\.11\. ]]"
...
You can expand it to match additional IPs:
Match exec "[[ '%h' =~ ^10\.10\.1(1|2)\. ]]"
...
Upvotes: 4
Reputation: 21
My solution to this problem is the following:
Host myserver
HostName [internal IP]
...
Match Host [internal IP] !Exec "nc -w1 -q0 %h %p < /dev/null"
ProxyCommand ssh jumphost -W %h:%p
It's important to have the Host myserver
lines first, so the SSH client will know the IP address.
In the Match
expression,
Host
option matches on that IP. (It accepts *
, so you can match to /8, /16 or /24 subnets too.)Exec
option executes a netcat
with a 1 second timeout to test if the SSH port is open. If not, the ProxyCommand
is used.This is the clearest way I found to actually test if you need a jumphost or not. If your network is lagging, you can set higher timeouts, of course. See man ssh_config
for more details.
Upvotes: 2
Reputation: 9333
Instead of checking the subnet CIDR, you can check what domain suffix DHCP has given you.
If you're trying to use a jump box when outside the intranet, this approach is more robust than checking for IP ranges in the reserved private allocation (e.g. your home network or remote-work location uses the same block as example.com
's intranet).
This approach is not useful if your intranet uses the same DNS suffix everywhere and you're trying to traverse subnets within the intranet. If that's your situation, use Jakuje's solution.
Match Host web Exec "hostname -d | ! grep -q -E '^example\.com'"
ForwardAgent yes
ProxyCommand ssh -p 110 -q relay.example.com nc %h %p
Host web
HostName web.example.com
Port 1111
If you don't have a recent version of hostname
that has the -d
option (e.g. you're on MacOS), you can just query resolve.conf
directly:
Match Host web Exec "! grep -q -E '^\s*search[ \t]+example\.com' /etc/resolv.conf"
...
...
Upvotes: 1
Reputation: 25956
You can use Match
's exec
option to execute shell commands, so you can write something like this:
Match host web exec "hostname -I | grep -qF 10.10.11."
ForwardAgent yes
ProxyCommand ssh -p 110 -q relay.example.com nc %h %p
Host web
HostName web.example.com
Port 1111
The Match
option boolean logic can short-circuit, so put host
first to skip the exec
term for other hosts. Try ssh web -vvv
to see the Match
logic in action.
Upvotes: 30
Reputation: 506
I'm using the following function for that:
function ssh() {
network=`networksetup -getairportnetwork en0 | cut -d: -f2 | tr -d [:space:]`
if [ -n "$network" -a -f $HOME/.ssh/config.$network ]; then
/usr/bin/ssh -F $HOME/.ssh/config.$network "$@"
else
/usr/bin/ssh "$@"
fi
}
export -f ssh
So I need a separate configuration file for each WiFi network where I want a custom solution. It works for me right now, but it's ugly. I can recommend it only as an idea, not as the best solution.
I'd be glad to know any better solution.
Upvotes: 1