Better ssh client setup

🗓️ 5 min

You probably already know how to create and register an ssh key pair.

sh
ssh-keygen -t ed25519 -C "email@example.com"
# Press enter a bunch of times
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519

Simple enough.
You can then copy the pub key into [insert relevant UI here] or just send it to your sever with ssh-copy-id -i ~/.ssh/id_ed25519.pub user@server.

However, when using more than one service or handling more than one server, it might be best to use different keys.

Why use multiple ssh keys

On one hand, it limits the impact of a compromised key.
If a machine gets compromised, but the ssh keys it used only granted access to a limited set of services, one can rest assured that the other services are probably fine.
This is especially relevant when separating work from personal keys.

It’s also useful for revocation. You can cut access to one key/machine without affecting others.
Or set the ssh key for an untrustworthy machine to expire on a short cycle.

Fine, you create multiple ssh keys, and now find yourself having to run your ssh commands like ssh -i ~/.ssh/key user@server or even worse, having to ssh-add ~/.ssh/key before managing your remote git repo.

This kinda sucks, but there is a better solution.

Config file

You might not be aware that ssh will look for a client config under ~/.ssh/config.
The file follows this basic structure:

Host [address or alias]
IdentityFile [path to the ssh key]

You can have as many of these as you want.
The file is read up-to-down and the first match is used, so the least specific sections should be at the bottom.

This config for example, would use the first section for that IP, while using the wildcard at the end for all other ssh connections:

Host 192.168.1.69
IdentityFile ~/.ssh/nice_key
Host *
IdentityFile ~/.ssh/other_key

Handling multiple services and keys

In practice, you might end up with a config that looks like this:

Host github.com
IdentityFile ~/.ssh/github
Host gitlab.com
IdentityFile ~/.ssh/gitlab
Host vps
HostName 209.85.231.104
User vps_usr

Note here that, since a HostName is provided, the Host in the last section acts as the alias one would use to refer to that service.
So for that section, you would run ssh vps instead of ssh vps_usr@209.85.231.104, much less verbose.

Since the first two sections will be used by git, adding a HostName and a User doesn’t make much sense.
For starters, git will always default to the git user, no need to explicitly set that.
Plus when working on GitHub/GitLab hosted repos, one would clone something like git@github.com:ORG/REPO.git, so the Host would always be the same as the HostName, thus making it redundant.

Of course, other options can be set in the config file, like Port or ConnectTimeout.
But there are more clever things that can be done.

Advanced options

There are, of course, many more options than these.
These are just used to show the usefulness of an ssh config file.

Proxy Jump

Depending on the security requirements of an organization, a ProxyJump might be needed when connecting to a server.

This simply means that the outgoing connection must go through a dedicated server before being redirected to final one, which might for example not be exposed to the internet.
To do this, one would ssh -J jump_host target_host.

As you might imagine, each might have different settings, so that command is bound to get messy.
You could create a shell alias, or you could use the ssh config:

Host target_host
HostName target_host.com
User target_user
IdentityFile ~/.ssh/target_key
ProxyJump jump_host
Host jump_host
HostName jump_host.com
User jump_user
IdentityFile ~/.ssh/jump_key

This allows the ssh command to look like ssh target_host, no need to worry about who jumps where and with which credentials.

Port Forwarding

Another security-related config is ssh tunneling or port forwarding.

This is done with something like:

sh
ssh -L localhost:3000:localhost:3306 example.com

Which means ‘please take all traffic going to localhost:3000 and send it to example.com on port 3306
The second localhost is from the perspective of the remote server.

This might seem a bit silly. Why not just point directly to example.com:3306?
The interesting bit here is that the traffic is being sent through the ssh connection (so port 22 by default).
The server would receive the traffic on :22 and re-route it to :3306.

This might be interesting not only to reduce the number of exposed ports in a server, but also to ensure cryptographic security. There’s no need to use SSL/TLS here, OpenSSH is plenty secure and comes for free with no work needed on either side of the communication.

Of course, one could route more than one ports, and point to more than the remote “localhost”, with a designated user, etc.:

sh
ssh -L localhost:8000:[IP_ADDRESS]:8000 -L localhost:8001:localhost:8001 username@example.com

This is not exactly easy to type, but a config like this might help:

Host what_a_mess
HostName example.com
User username
LocalForward localhost:8000 [IP_ADDRESS]:8000
LocalForward localhost:8001 localhost:8001

One would only need to run ssh what_a_mess.

As you might tell, LocalForward implies there is also a RemoteForward, which indeed is used to send traffic the other way around (from the server to the client).

Just be careful when committing this config to your dotfiles repo: make sure no sensitive information is public!


Other posts you might like