SSH: Conditional host address based on network or location

I have been searching extensively to find a solution to ease management and administration of my servers from multiple location. Let me present my environment and then my use cases

Environment

  • Site One (10.0.1.0\24)
    • Server A
    • Server B
    • Server C
  • Site Two (10.0.2.0\24)
    • Server D
    • Server E
  • Site Three (10.0.3.0\24)
    • Server F

Use cases

I manage these servers from any of the sites and even other external networks and the two main issues I have had are

  1. Reaching any server from any other server, workstation or laptop in the field with:
    ssh server-x
  2. Be able to bridge multiple server jumps. Normally, only “A”, “D” and “F” have an externally port forwarded to it.
    For example, sending a file from “B” to “E” is troublesome with

    rsync local_file server-e:.

My solution

Perhaps my Google skills are lacking, but finding a solution that is efficient across all locations and dynamic was hard. There were quite a few samples of having two settings in the ssh config file for each host, i.e. server-x-local and server-x-remote. Sure, doable but a pain to maintain across all sites and not transparent enough.

The solution to this problem was the Match keyword for the ssh config. This is the configuration for Server A:

Match Originalhost server-a Exec "ifconfig | grep 10.0.1"
     Hostname 10.0.1.2
Host server-a
     Hostname external.siteone.com

Let’s explain this construct. The match clause executes the ifconfig and pipes the output to grep in order to see if we are on this subnet. This will evaluate to true if I’m located on any host on subnet One. If the match is true it will set the hostname to the local IP of the server. If the clause evaluates to false, the default (external DNS) hostname of the server will be used. According to the ssh_config man page, the first match of a setting will be used. Therefor, the Match clause needs to be before the Host.

This works great from my initial testing and makes it possible to use the very same ssh config file regardless of client.

The second issue: Jump/Proxy/Bastion setup.

In order to reach other servers behind the externally connected servers easily I had previously been using SSH tunneling to solve some tricky cases. Now that I had some Google flow I attempted to take a stab at that challenge as well.

As it turns out, this was an easier fix thanks to the ProxyCommand feature in later SSH versions. From my understanding, this executes a custom command as a precondition to the final SSH connection.

Expanding on the previous solution, this is what I came up with. First, the complete Server A ssh configuration, same as before.

Match Originalhost server-a Exec "ifconfig | grep 10.0.1"
     Hostname 10.0.1.2
Host server-a 
     Hostname external.siteone.com

Then, the Server B configuration:

Match Originalhost server-b Exec "ifconfig | grep 10.0.1"
     ProxyCommand none
Host server-b
     Hostname 10.0.1.3
     ProxyCommand ssh -W %h:%p server-a

The idea here is the same. The default case is connecting from an external site and the ProxyCommand initiates an ssh connection to server-a first and uses it as a proxy to then connect to server-b. If, on the other hand, we are located on the local subnet already, the ProxyCommand is disabled and no proxy connection to server-a is made.

All that remains now is to configure all different hosts at one client and the roll out the very same config file to any host that is going to connect to any of the servers.

Conclusion

I’m really happy with this solution and it has greatly improved my efficiency when connecting to the different servers. Now the connections are really transparent and flexible!

Let me know if you have any success with this solution!

Credits

http://sshmenu.sourceforge.net/articles/transparent-mulithop.html

https://unix.stackexchange.com/questions/150002/only-apply-match-keyword-to-single-host-in-ssh-config/

 

10 thoughts on “SSH: Conditional host address based on network or location

  1. Thanks for the detailed writeup. I just need to get to my home servers when I’m traveling, and this is great solution. I use iTerm with Profiles, and this way, I don’t have to have duplicate profiles set up. Very clean.

  2. I have done something similar in the past but for a different reason. When I want to access my home server I’m either at home, or not. If I’m at home I have to use my local IP, this is because my home router does not support NAT Loopback (https://support.google.com/wifi/answer/6274503?hl=en). By using the same method you have above, but with a different cmd, I’m able to detect if I’m on my home network. (IP addresses changed for example)
    “`
    Match OriginalHost server-a Exec “curl -s ‘https://api.ipify.org?format=text’ | grep ‘10.2.1.1’”
    Hostname 192.168.1.2
    Host server-a
    Hostname 10.2.1.1
    “`
    I went with this solution because many home routers use the same subnet so simply checking if I’m on “192.168.1.0/24” wouldn’t work as I’d get too many false positives.

  3. THANKS!

    I love this solution. I found it more useful for my setup to check WIFI hotspot name in the condition using nmcli command.

    Match host some-host Exec “nmcli -g NAME connection show –active | grep accelico”

  4. I have been doing something different, but I’m going to experiment with your ideas, they seem interesting and a better solution that I came up with.

    For my solution I did something like this:

    Host *.example.com # No final period
    ProxyJump gateway.example.com. # Note the final period!

    For the “switch” for inside example.com vs outside, I manually appended a final period or not. Thus from outside:

    ssh my_desktop.example.com

    or from inside:

    ssh my_desktop.example.com. # final period

    My solution was not automatic, I had to be aware of my location and add the final period. I’m implementing your solution now.

    Thanks much!

  5. I also used the SSH ‘Match’ option to figure out which network I’m on, and set either ‘Hostname’ (for my home network gateway machine) or ‘ProxyJump’ (to reach any machine inside my home network by using my gateway machine as a proxy if I’m on the outside). I also added some ‘LocalCommand’ to alert me of what’s going on when I’m connecting via SSH (piped to STDERR with ‘>&2’ so as to not disrupt rsyncing), and a special rule for when I ssh:ing to localhost.

    The following shell command “{ ip neigh; ip link; }|grep -Fw 60:0e:56:36:c4:ca” returns true if it is run on a machine any in my network, since my gateway machine (luthor) has the MAC address 60:0e:56:36:c4:ca. The ‘ip neigh’ will list the machines that localhost is aware of on the local network, except localhost itself, while ‘ip link’ will display information about localhost. Grepping over both of these the IP of the gateway machine will be found in the output of ‘ip link’ if the SSH command was run on the gateway machine itself, or in the output of ‘ip neigh’ if the SSH command was run on one of the other machines in my home network.

    Is a shell command “[ %h = %L ]” (expanded by SSH) which returns true if the local hostname (%L) is the same as the remote hostname (%h). (The %-options are documented in manpage ‘ssh_config’.)

    # For gateway machine.
    # If ‘ssh luthor’ when SSH:ing from outside of my home network, the ‘Hostname’
    # is set to my dyndns-domain, otherwise ‘Hostname’ will be unmodified (i.e.
    # just ‘luthor’).
    Match originalhost luthor exec “[ %h = %L ]”
    LocalCommand echo “SSH %n: To localhost” >&2
    Match originalhost luthor !exec “[ %h = %L ]” !exec “{ ip neigh; ip link; }|grep -Fw 60:0e:56:36:c4:ca”
    LocalCommand echo “SSH %n: From outside network, to %h” >&2
    Hostname luthor.dyndns.org
    Host luthor
    PermitLocalCommand yes
    LocalCommand echo “SSH %n: From home network, to %h” >&2

    For the other machines on my local network instead set the ‘ProxyJump’ option to my gateway (luthor) if I’m SSH:ing in from outside my home network. If I’m at home, this won’t work (because my router will block the connection) but then there is no need for a ‘ProxyJump’ as I can connect directly.

    The gateway ‘luthor’ is the only machine on my home network which visible to the internet. The machine ‘vorlon’ is behind the NAT, and from outside my home network it can only be reached through ‘luthor’ (either by running ‘ssh -t luthor ssh vorlon’, or as below by setting ‘ProxyJump’ – One can also use the SSH ‘ProxyCommand’ setting, but I haven’t tested that).

    Match originalhost vorlon exec “[ %h = %L ]”
    LocalCommand echo “SSH %n: To localhost” >&2
    Match originalhost vorlon !exec “[ %h = %L ]” !exec “{ ip neigh; ip link; }|grep -Fw 60:0e:56:36:c4:ca”
    LocalCommand echo “SSH %n: From outside network, via proxy luthor” >&2
    ProxyJump luthor
    Host vorlon
    PermitLocalCommand yes
    LocalCommand echo “SSH %n: From home network” >&2

    • Oh, yes, originally I experimented by using ‘iwgetid -r|grep SSID’ to detect which wifi I was connected to, but I sometimes use an ethernet cable, and I wanted something that would allow for that too (which the above stuff do, at least on my setup).

  6. I like the match!
    I’ve also used a shell script for the ProxyCommand
    The shell script can examine it’s environment do more than one line.
    Then a case statement can invoke ssh or nc (netcat)
    The downside with netcat is that you need to be careful of ssh fingerprinting (your end box should have the same key deterministically)
    Thank you for the write up (yes 6 years later).

Leave a Reply

Your email address will not be published. Required fields are marked *

 

This site uses Akismet to reduce spam. Learn how your comment data is processed.