Best practices today require at least considering the use of Bastion hosts or VPN-type network restriction as well as, per the focus of your question, central credential management. I recommend making use of Bastions and using CA-based Key-signing.
Why you should (but don't have to) do bastions anyway
We would like it so when an employee leaves, even if he keeps a copy of his SSH keys, he cannot reach the SSH endpoints. This is a task for bastion hosts, VPNs, and firewalls. Bastion hosts are also a great help in terms of infrastructure in that if you can restrict access to the Bastions (for example by removing the ex-employee's ssh keys) then the user is automatically restricted from accessing the other endpoints.
While Bastion hosts may well make the practice of doing key management easier, it isn't technically essential. Still, I highly recommend making use of bastion hosts: It improves security; simplifies infrastructure; and aids with auditing.
Managing keys
Basic copying of pubkeys into authorized_keys
Copying the public keys as part of an automated task will work. Ansible, SaltStack, cloud-specific deployment tools, cron, and so forth are examples of how that might be implemented. But it doesn't have the obvious advantages of Key-signing below. If you already have something automated that is working for you, it can continue to work well. There are advantages - but you've already put the extra work into making it work, and it isn't broken (right?).
FreeIPA
FreeIPA, if configured on all servers and workstations/laptops, allows you to manage keys centrally. I've found some tools that apparently work with it to support using CA-signed keys, though I'm yet to explore this option.
Key-Signing
Built-in support for signed SSH key certificates has apparently been available in openssh since at least 2010*, though I've only recently learned of it. SSH key-signing seems to be superior to every other key-management mechanism available. Facebook recently (2016) posted on how they manage certificates - and signing SSH keys is the main ingredient.
The core idea is that you have your own internal CA certify your public keys and have all your ssh endpoints trust the CA. When you connect to an endpoint, ssh presents your public key and the signed certificate, the endpoint verifies that the cert is good, and you are granted access.
Advantages of key-signing:
- Your "personal" keys become ephemeral, meaning you don't have to worry about having a backup of your keys
- Actual keys aren't trusted, meaning compromised keys aren't important
- Certs expire, meaning a compromised key-cert combination has a time limit whereafter it no longer has access to your infrastructure
- Server keys can also be signed, meaning you don't have to manually approve or manage conflicting fingerprints when you connect to signed endpoints (FreeIPA can also handle this, though it technically still does "old-school" copying of the public keys)
How do I use Key signing?
There unfortunately isn't a well-known turn-key system built specifically for orchestrating CA trust and key-signing - but there are tools available you can put together to do the job. For a team, you might be willing to write your own system to sign the keys, though I'm currently looking at a few toolsets. Top among them are HashiCorp's Vault which promises to solve the problem with Signed SSH Certificates using it's Secrets Engine. CommerceHub's ssh-ca-server, promises to allow your CA to sign keys via web interface, whilst using LDAP auth to ensure only valid users' keys are signed. As mentioned, I'm not sure if either of these are full solutions - but they do at least cover a lot of ground. There do seem to be many others as well - though I'm not yet aware of any that seem to be the "best" toolset. See also the informed comments on this answer.
I want to do it manually - or I want to understand how it's done
In a personal capacity, as long as you keep your CA key passphrased, you can use CA signing as below. In a small business setting, I recommend partitioning the key away so no user has direct access to it, or put together existing tools (mentioned above) to do the grunt-work for you. You should also put in place appropriate backup mechanisms - this one key governs access to everything:
Example manual use case
In my example, we're going to use the .ssh/.ca
folder for the ca data on the test client. Note of course this is merely an example. The best way to do this is via a remote service:
tricky@testclient $ export CAPATH=~/.ssh/.ca
Generate the CA key pair
You can use rsa
for the type - I just prefer ed25519
:
tricky@testclient $ ssh-keygen -C CA -f ${CAPATH}/ca -t ed25519
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/tricky/.ssh/.ca/ca.
Your public key has been saved in /home/tricky/.ssh/.ca/ca.pub.
The key fingerprint is:
SHA256:qGSZbssuQOV0TR8WrTV/gzXBSyTO4uAKUO7q+fJUDYw CA
The key's randomart image is:
+--[ED25519 256]--+
| .o. +o .oo.|
| oo.o.o .+o .= |
| +..E o oo.oo+ o|
| . .oo .+.o .o + |
|. =o..So . . .|
|. +..o . |
| . .+. . |
| o+o. |
| =Bo |
+----[SHA256]-----+
tricky@testclient $ cat ${CAPATH}/ca.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKg9nxnaBLOQKnfuxUQgb3Y1hiqIaMEtXwr8WsX5bLlv CA
Distribute the public part
Distribute the ca.pub
file to your remote endpoints in a location accessible to the ssh daemon such as /etc/ssh/ca.pub
. This only has to be done once on each server. Make sure it is owned by root and not writeable by non-root users. It is fine to be readable by other users.
Configure the remote endpoint(s)
Add the following line to your sshd_config
file on your remote endpoints, referring to where you added the ca.pub
file. Again, this only has to be done once on each server:
TrustedUserCAKeys /etc/ssh/ca.pub
Test the config file. You might find "Deprecated option" warnings for unrelated things in the config, especially if you are using a non-stock generated config, but as long as there are no actual errors, you can restart the sshd service:
root@testserver $ sshd -t
/etc/ssh/sshd_config line 23: Deprecated option UsePrivilegeSeparation
/etc/ssh/sshd_config line 26: Deprecated option KeyRegenerationInterval
/etc/ssh/sshd_config line 27: Deprecated option ServerKeyBits
/etc/ssh/sshd_config line 45: Deprecated option RSAAuthentication
/etc/ssh/sshd_config line 54: Deprecated option RhostsRSAAuthentication
root@testserver $ systemctl restart sshd
root@testserver $
Ensure you're not using your old keys
To test, rename your existing keys so you are no longer using explicitly trusted keys:
for type in dsa rsa ed25519 ; do
mv ~/.ssh/id_${type}{,.bak}
mv ~/.ssh/id_${type}.pub{,.bak}
done
See notes** at the end to restore these original keys (after all you will need your existing keys until your CA is trusted by all endpoints).
As a sanity check, if you are using a service such as keychain or ssh-agent that remembers your keys, you might want to stop the service so you know your test is not using your old keys:
tricky@testclient $ killall ssh-agent
Create a new user keypair
You can use rsa
for the type - I just prefer ed25519
:
tricky@testclient $ ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519
Generating public/private ed25519 key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in testkey.
Your public key has been saved in testkey.pub.
The key fingerprint is:
SHA256:0cKlJxYQrizfd5FDboyVYhn9RK7oHkpsBbarZuv+DoQ tricky@testclient
The key's randomart image is:
+--[ED25519 256]--+
| ooo.... |
| . . B.o. |
| + @ *o. |
| o o = & o. |
| E + . S O |
| + o + . o |
| o * + . |
| o= + o |
| ==++ . |
+----[SHA256]-----+
tricky@testclient $
Sign the new user keypair
Keysigning makes use of serial numbers which can be used to aid in auditing. I'm not bothering with recording this in the example. You would at least increment the serial number on subsequent signing events. The serial must be a number:
tricky@testclient $ export serial=1
Identity can theoretically be anything - but client username or user@hostname is a good starting point
tricky@testclient $ export IDENTITY="${USER}@$(hostname)"
The key needs to grant access to specific remote user(s). With a typical bastion configuration, you wouldn't have 'root' here:
tricky@testclient $ export users="root,${USER}"
For a validity period, I generally recommend using 4 hours (14400 seconds) - but you may want a larger time-span. Facebook's post recommends a full day (86400 seconds). Others with automated authentication recommend as little as five minutes.
tricky@testclient $ export VALIDITY_PERIOD=86400
Note that you sign the public key, as it is the public portion that needs to be accepted by the endpoint:
tricky@testclient $ ssh-keygen -s ${CAPATH}/ca -I ${IDENTITY} -n ${users} -V +${VALIDITY_PERIOD} -z ${serial} ~/.ssh/id_ed25519.pub
Enter passphrase:
Signed user key /home/tricky/.ssh/id_ed25519-cert.pub: id "tricky@testclient" serial 1 for root,tricky valid from 2018-10-09T20:06:00 to 2018-10-10T20:06:00
Connect to the endpoint:
tricky@testclient $ ssh root@testserver
Last login: Mon Oct 8 20:46:31 2018 from testclient.localdomain
root@testserver ~ #
* Thanks, @PatrickMevzek, for the ref info:
TrustedUserCAKeys ... was added first in a commit on March 4 2010
openssh.com/txt/release-5.4 "Add support for certificate authentication of users and hosts using a new, minimal OpenSSH certificate format (not X.509)."
**
for type in dsa rsa ed25519 ; do
mv ~/.ssh/id_${type}{.bak,}
mv ~/.ssh/id_${type}.pub{.bak,}
done
Once restored, I recommend signing your existing keys but also to start treating them as ephemeral. Keep a backup of your original keys in case you believe they may be needed for services that trust them but don't support CA-based Key-signing.
there is no way to revoke access
. In fact, EC2 ssh key can be replaced.