Managing SSH Key Pairs for server access
posted by Ayush Newatia
13 November, 2024
I’ve recently dipped my toe into deploying Ruby and Rails apps to a VPS, mainly to improve the deployment story for Bridgetown. No PaaS is all the rage with Rails 8, and server management is a huge gap in my skillset anyway so I figured it’s worth investing some time into it.
Using password authentication to SSH into servers is considered insecure. The preferred method is to use SSH Key Pairs which led me down the rabbit hole of the best practices to manage these keys and keep them safe.
In this blog post, I’ll describe what I’ve learned.
What is an SSH key-pair?
An SSH Key Pair consists of a public key and a private key. The private key should never leave the system it’s generated on. It is incredibly sensitive and must be kept safe.
The public key is installed on the server we want to securely connect to. The ~/.ssh/authorized_keys
file on a server contains all the public keys authorized to create SSH connections.
The SSH handshake uses your local private key along with the public key on the server to authenticate access. This video is a great explainer: https://www.youtube.com/watch?v=dPAw4opzN9g.
A key serves to identify a single entity. If you’re deploying from your machine to multiple servers, then it’s your personal key on all the servers. You can also use the same key to write to GitHub repositories and sign your git commits because it uniquely identifies you.
Generating a key pair
We can generate a key pair using the ssh-keygen
CLI utility. The ED25519 is the most secure key type available today, so we’ll use that.
We’ll also pass in a comment using the -C
flag which is appended to the public key. That gives us context about each public key installed on a server.
$ ssh-keygen -t ed25519 -C "your_email@example.com"
You’ll be asked to enter a filename for the key. Press enter
as the default is fine.
Enter file in which to save the key (/Users/[USER]/.ssh/id_ed25519):
Next, you’ll be asked for a passphrase to encrypt your private key on disk. This prevents people with access to your disk from reading your private key unless they know your passphrase.
You should generate a secure passphrase and store it in a password manager for security.
Enter passphrase (empty for no passphrase):
After confirming your passphrase, your key pair will be generated:
Your identification has been saved in /Users/[USER]/.ssh/id_ed25519
Your public key has been saved in /Users/[USER]/.ssh/id_ed25519.pub
The key fingerprint is:
SHA256:GcsT23BKVXAugQoK0z+gWidXIqqzyg7O9KlrOQWRdNY your_email@example.com
The key's randomart image is:
+--[ED25519 256]--+
|.o.o. .+oo |
|oo* oE. .. + |
| =.= + .= o . |
|o.+ = .o @ . |
|o..+ . S . |
|+ . . |
|.+o |
|B+. . |
|=B+o |
+----[SHA256]-----+
You can now view your public key:
$ cat ~/.ssh/id_ed25519.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHD9BSFIEWuUq0ZCnlwRG/68rSbV0LcEAzLTYjOUqf2L your_email@example.com
Private keys for automated deployments
Earlier in this post, I said that a private key should never leave the system it’s generated on. When setting up automated deployments as part of a CI/CD pipeline, we need to break this rule.
The CI server is a different entity to yourself meaning it needs its own key pair. It is also a trusted entity so we can safely generate the private key on our machine and transfer it to the CI server.
CI services have a secure mechanism to store sensitive information. GitHub Actions calls these secrets, and they can be referenced in your workflows. Other services will have an equivalent. Store the private key on the CI service securely using this technique.
Ensure you treat the private key with utmost care and never store it unencrypted. If you choose to keep a copy of the deployment key pair on your machine, ensure that it is stored safely. 1Password or another password manager can help here.
The SSH Agent
As we saw earlier, the private key is encrypted on disk. Constantly entering the passphrase to decrypt the private key every time we connect to a server is tedious, which is where SSH agents come in.
The SSH agent holds your keys and certificates in memory, unencrypted, and ready for use by ssh
. This is safe because the unencrypted data is only ever held in memory, never written to disk.
Add your key to the SSH agent using:
$ ssh-add ~/.ssh/id_ed25519
On a Mac, you can store your passphrase in the Keychain so it’s automatically used to decrypt the key when you need it:
$ ssh-add --apple-use-keychain ~/.ssh/id_ed25519
1Password has its own SSH Agent using which you can access keys stored within your 1Password vaults.
SSH Config
There’s an SSH config file you can use to streamline your SSH usage. If you haven’t set it up before, it won’t exist:
$ touch ~/.ssh/config
$ cat ~/.ssh/config
Within it, you can configure options for different hosts. The below examples shows a config file which uses the user’s personal key by default, but uses the 1Password agent for GitHub only.
Host *
IdentityFile ~/.ssh/id_ed25519
Host github.com
HostName github.com
IdentityAgent ~/.1password/agent.sock
IdentityFile none
Here’s a more in-depth explanation of the config file: https://www.ssh.com/academy/ssh/config.
Conclusion
To sum up: each SSH key pair uniquely identifies a single entity. You should have one for yourself to sign git commits and SSH into servers. Use separate keys for automated deployments.
Use a password manager like 1Password to manage your SSH keys. Alternatively, ensure your private keys are encrypted with a secure passphrase and store that passphrase safely.
Finally, use the SSH config file to streamline your workflow.
P.S.: Massive thanks to Will Jessop for answering my silly questions about this stuff and for reviewing this blog post.
Further reading
-
Connecting to GitHub using SSH: https://docs.github.com/en/authentication/connecting-to-github-with-ssh/about-ssh.
-
GitHub’s guide to generate SSH keys: https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent.
-
SSH agent forwarding (A technique to use your local private key when connecting to a another server from a remote server. For example, to check out a git repo during an SSH session): https://docs.github.com/en/authentication/connecting-to-github-with-ssh/using-ssh-agent-forwarding.
-
How SSH knows which public key to use on a server: https://security.stackexchange.com/questions/182804/how-does-ssh-know-which-public-key-to-use-from-authorized-keys.
-
Installing public keys on a server: https://linuxhandbook.com/add-ssh-public-key-to-server/.