Working in security/operations in the tech industry, I use SSH a lot. To various different machines (some with hostnames, some without), using various different users and keys, and often (as was the case in my previous post) via a bastion host. Over the years, I’ve collected a number of SSH tricks that make my life easier.
Using ProxyCommand to automatically use / not use an SSH tunnel
First up, almost all of the machines that I need access to cannot be SSHed to directly when I’m not on a specific network. Instead, I need to log in via a bastion host. But if I do happen to already be within the network (SSHing from one machine to another for example), I don’t want to connect via the bastion host any longer. Luckily, you can use ProxyCommand
to do exactly that:
Host bastion
User jp
IdentityFile ~/.ssh/keys/work/jp.rsa
HostName bastion.example.com
Host work-server
ProxyCommand bash -c "dig +short myip.opendns.com @resolver1.opendns.com | egrep '^(10|50|52)\\.' && nc %h %p || ssh bastion nc %h %p"
This uses the same OpenDNS trick as my previous post) along with Bash conditionals. If my IP starts with 10.
, I will use the && nc %h %p
part of the command, which will use netcat locally to create the connection. If it does not, then I will set up an SSH tunnel using netcat instead (which will otherwise still use my local ssh config, so the bastion and final hosts can use different users/keys).
It’s quite lovely to just be able to type ssh work-server
and have things just work™ no matter if I’m on network or not.
Using ProxyCommand to dynamically resolve ‘fake’ hostnames
Finally, we can actually use the same basic idea to dynamically resolve hostnames. Say we have a bunch of servers that we don’t necessary known the IP for (and that don’t have a hostname) but have a way to look it up. Perhaps we can use my previous ec2 script combined with autoproxied to automatically look up hosts in EC2 without having to spawn a subshell explicitly:
Host *.aws
ProxyCommand bash -c "nc $(ec2 $(echo %h | sed "s/.aws//") --ip) %p"
What that will do is allow you to SSH to prod-frontend.aws
and automatically get logged into the private IP address of the first server with a tag matching prod-frontend
(assuming you have the credentials to access said machine). Even better, you could (and probably would) combine it with the automatic SSH tunnel above:
Host *.aws
ProxyCommand bash -c "dig +short myip.opendns.com @resolver1.opendns.com | egrep '^(10|50|52)\\.' && nc %h %p || nc $(ec2 $(echo %h | sed "s/.aws//") --ip) %p"
It’s ugly, but it totally works.
Note: .aws
is actually a valid TLD: ICANN .aws. If you need to actually SSH to a host using a .aws
TLD, you could easily choose a different suffix.
Automatically choosing SSH key based on user
Finally, as a non-ProxyCommand bonus, I have a number of different users that I use to SSH to various different machines, based on how limited access is to the machine or, in some cases, how old it is. In order to deal with that, I don’t always want to type ssh -i {key name} {user}@{host}
. Luckily, since version 4.4 SSH has has the ability to apply configuration sections conditionally based on various fields. One of those is the User
being used for the connection:
Match User jp
IdentityFile ~/.ssh/keys/work/jp.rsa
Match User automation
IdentityFile ~/.ssh/keys/work/automation.rsa
Match User superuser
IdentityFile ~/.ssh/keys/work/superuser.rsa
In this case, logging into a remote machine using the superuser
user and key is as easy as ssh superuser@{host}
. Better yet, if I do need to override this setting for some reason, I can. The -i
flag overrides the config file.
And… that’s it. There are a handful of other tricks I use to stay organized (like keeping my various SSH keys in folders), but those three tips save me the lion’s share of time.
Along the way, I came across an interesting alternative: advanced-ssh-config. Basically, it’s a ‘smarter’ system that can handle automatically proxying and some dynamic configuration for you. The one downside I’ve found is that I couldn’t figure out how to quickly use the SSH tunnel when I need to but not when I don’t. It has the ability to have fallback gateways, but the failover takes a long time. This just works.