The SSH protocol offers multiple authentication options: passwords, public keys and certificates. Certificate-based authentication is the most secure of them all, but historically, it has been the most complicated to set up. This tutorial guides you through simple steps to configure certificate-based authentication for an OpenSSH server.
First, let’s consider the differences between certificates and keys.
As you can see, an SSH key is a binary proposition. Either you have it or you don’t, and a server will grant access to whomever happens to be in possession of the key, very similar to a physical door key. There is very little data about the owner of the key. Even the comment field is not mandatory, and a client does not pass it to a server.
A certificate, on the other hand, contains much more data, and therefore:
SSH certificates are built using SSH public keys and don’t offer anything extra from a cryptography engineering standpoint. First, you will need to set up a certificate authority (CA). The same ssh-keygen
command can be used to create a CA. In OpenSSH, the CA certificate is just public and private key pairs with additional identity and constraints data. The private key of the CA is used to sign user and host (SSH server) certificates. Once the keys are signed, they are distributed to users and hosts, respectively. The public key of the CA is copied to the SSH server, which is used to verify the user’s certificate.
For user authentication, the SSH client presents the user certificate (signed certificate by CA) to the SSH server in each new SSH connection. The SSH server validates the certificate by checking it against the CA’s public key. Along with the signature validation, the server will also check if the user certificate is not expired and if it violates security constraints. Access is granted upon successful validation of the certificate.
Generate SSH CA keypair using the ssh-keygen
command:
The host_ca
file is the host CA’s private key and should be protected. Don’t give it out to anyone, don’t copy it anywhere and make sure that as few people have access to it as possible. Ideally, it should live on a machine which doesn’t allow direct access, and all certificates should be issued by an automated process.
In addition, it’s best practice to generate and use two separate CAs — one for signing host certificates, one for signing user certificates. This is because you don’t want the same processes that add hosts to your fleet to also be able to add users (and vice versa). Using separate CAs also means that in the event of a private key being compromised, you only need to reissue the certificates for either your hosts or your users, not both at once.
As such, we’ll also generate a user_ca
with this command:
The user_ca
file is the user CA’s private key and should also be protected in the same way as the host CA’s private key.
Generate new host key and sign it with the CA key:
ssh_host_rsa_key-cert.pub
contains the signed host certificate.
Here’s an explanation of the flags used:
-s host_ca
: specifies the filename of the CA private key that should be used for signing.-I host.example.com
: the certificate’s identity — an alphanumeric string that will identify the server. I recommend using the server’s hostname. This value can also be used to revoke a certificate in future if needed.-h
: specifies that this certificate will be a host certificate rather than a user certificate.-n host.example.com
: specifies a comma-separated list of principals that the certificate will be valid for authenticating — for host certificates, this is the hostname used to connect to the server. If you have DNS set up, you should use the server’s FQDN (for example host.example.com
) here. If not, use the hostname that you will be using in an ~/.ssh/config
file to connect to the server.-V +52w
: specifies the validity period of the certificate, in this case 52 weeks (one year). Certificates are valid forever by default — expiry periods for host certificates are highly recommended to encourage the adoption of a process for rotating and replacing certificates when needed.First, copy the three files you just generated to the server, store them under the /etc/ssh
directory, set the permissions to match the other files there, then add this line to your/etc/ssh/sshd_config
file:
Once this is done, restart sshd
with systemctl restart sshd
.
Your server is now configured to present a certificate to anyone who connects. For your local ssh
client to make use of this (and automatically trust the host based on the certificate’s identity), you will also need to add the CA’s public key to your known_hosts
file.
You can do this by taking the contents of the host_ca.pub
file, adding @cert-authority *.example.com
to the beginning, then appending the contents to ~/.ssh/known_hosts
:
@cert-authority *.example.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDwiOso0Q4W+KKQ4OrZZ1o1X7g3yWcmAJtySILZSwo1GXBKgurV4jmmBN5RsHetl98QiJq64e8oKX1vGR251afalWu0w/iW9jL0isZrPrmDg/p6Cb6yKnreFEaDFocDhoiIcbUiImIWcp9PJXFOK1Lu8afdeKWJA2f6cC4lnAEq4sA/Phg4xfKMQZUFG5sQ/Gj1StjIXi2RYCQBHFDzzNm0Q5uB4hUsAYNqbnaiTI/pRtuknsgl97xK9P+rQiNfBfPQhsGeyJzT6Tup/KKlxarjkMOlFX2MUMaAj/cDrBSzvSrfOwzkqyzYGHzQhST/lWQZr4OddRszGPO4W5bRQzddUG8iC7M6U4llUxrb/H5QOkVyvnx4Dw76MA97tiZItSGzRPblU4S6HMmCVpZTwva4LLmMEEIk1lW5HcbB6AWAc0dFE0KBuusgJp9MlFkt7mZkSqnim8wdQApal+E3p13d0QZSH3b6eB3cbBcbpNmYqnmBFrNSKkEpQ8OwBnFvjjdYB7AXqQqrcqHUqfwkX8B27chDn2dwyWb3AdPMg1+j3wtVrwVqO9caeeQ1310CNHIFhIRTqnp2ECFGCCy+EDSFNZM+JStQoNO5rMOvZmecbp35XH/UJ5IHOkh9wE5TBYIeFRUYoc2jHNAuP2FM4LbEagGtP8L5gSCTXNRM1EX2gQ== host_ca
The value *.example.com
is a pattern match, indicating that this certificate should be trusted for identifying any host which you connect to that has a domain of *.example.com
— such as host.example.com
above. This is a comma-separated list of applicable hostnames for the certificate, so if you’re using IP addresses or SSH config entries here, you can change this to something like host1,host2,host3
or 1.2.3.4,1.2.3.5
as appropriate.
Once this is configured, remove any old host key entries for host.example.com
in your ~/.ssh/known_hosts
file, and start an ssh
connection. You should be connected straight to the host without needing to trust the host key. You can check that the certificate is being presented correctly with a command like this:
$ ssh -vv host.example.com 2>&1 | grep "Server host certificate"
debug1: Server host certificate: ssh-rsa-cert-v01@openssh.com SHA256:dWi6L8k3Jvf7NAtyzd9LmFuEkygWR69tZC1NaZJ3iF4, serial 0 ID "host.example.com" CA ssh-rsa SHA256:8gVhYAAW9r2BWBwh7uXsx2yHSCjY5OPo/X3erqQi6jg valid from 2020-03-17T11:49:00 to 2021-03-16T11:50:21
debug2: Server host certificate hostname: host.example.com
At this point, you could continue by issuing host certificates for all hosts in your estate using your host CA. The benefit of doing this is twofold: you no longer need to rely on the insecure trust on first use (TOFU) model for new hosts, and if you ever redeploy a server and therefore change the host key for a certain hostname, your new host could automatically present a signed host certificate and avoid the dreaded WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!
message.
Generate user keypair and sign it with our user CA. It’s up to you whether you use a passphrase or not.
user-key-cert.pub
contains the signed user certificate. You’ll need both this and the private key (user-key
) for logging in.
Here’s an explanation of the flags used:
-s user_ca
: specifies the CA private key that should be used for signing-I honda@remoteler.com
: the certificate’s identity, an alphanumeric string that will be visible in SSH logs when the user certificate is presented. I recommend using the email address or internal username of the user that the certificate is for — something which will allow you to uniquely identify a user. This value can also be used to revoke a certificate in future if needed.-n ec2-user,honda
: specifies a comma-separated list of principals that the certificate will be valid for authenticating, i.e. the *nix users which this certificate should be allowed to log in as. In our example, we’re giving this certificate access to both ec2-user
and honda
.-V +1d
: specifies the validity period of the certificate; in this case +1d
means 1 day. Certificates are valid forever by default, so using an expiry period is a good way to limit access appropriately and ensure that certificates can’t be used for access perpetually.If you need to see the options that a given certificate was signed with, you can use ssh-keygen -L
:
Once you’ve signed a certificate, you also need to tell the server that it should trust certificates signed by the user CA. To do this, copy the user_ca.pub
file to the server and store it under /etc/ssh
, fix the permissions to match the other public key files in the directory, then add this line to /etc/ssh/sshd_config
:
Once this is done, restart sshd
with systemctl restart sshd
.
Your server is now configured to trust anyone who presents a certificate issued by your user CA when they connect. If you have a certificate in the same directory as your private key (specified with the -i
flag, for example ssh -i /home/honda/user-key ec2-user@host.example.com
), it will automatically be used when connecting to servers.
In this tutorial, we showed you how to generate and configure OpenSSH hosts and clients with certificate-based authentication. Certificate-based authentication is the most secure form of authentication for SSH. In fact, at Remoteler, we recommend certificates as a passwordless authentication method for all infrastructure access requirements, including SSH, RDP, Kubernetes clusters, web applications, and database access. Remoteler automatically manages the CA for user and host certificate issuance. Learn how Remoteler works, or try Remoteler now.