OpenSSH Two Factor Authentication (But Not Service Accounts)03 Mar 2018 in Security
This post contains Amazon Affiliate links.
Very often, people hear “SSH” and “two factor authentication” and assume you’re talking about an SSH keypair that’s got the private key protected with a passphrase. And while this is a reasonable approximation of a two factor system, it’s not actually two factor authentication because the server is not using two separate factors to authenticate the user. The only factor is the SSH keypair, and there’s no way for the server to know if that key was protected with a passphrase. However, OpenSSH has supported true two factor authentication for nearly 5 years now, so it’s quite possible to build even more robust security.
In OpenSSH version 6.2, the OpenSSH developers added the
configuration parameter which specifies one or more sets of authentication
methods that must succeed. For example, to configure that an SSH public key and
the correct password for the account must be presented, you can specify:
1 AuthenticationMethods publickey,password
For each method listed, it must also be enabled in the configuration, so this would also require:
1 2 PubkeyAuthentication yes PasswordAuthentication yes
This presents a true two factor authentication configuration. It requires both something the user has (the private key in the keypair) and something the user knows (the password). This way, neither password compromise (via phishing, password reuse, etc.) nor key compromise (via stolen computer, accidental commit to a git repository, etc.) is sufficient to compromise the protected account on the server.
Perhaps you’d like to require that your users must present a publickey and either come from a trusted host (host-based authentication, which is an extremely poor choice on its own) or have the account password. In that case you could specify:
1 AuthenticationMethods publickey,password publickey,hostbased
publickey method has succeeded, the client will attempt both password
and hostbased authentication. If the host is trusted, the user will immediately
be allowed access, otherwise they will be prompted for their password.
OpenSSH enforces that the authentication methods listed must complete in the order listed in the configuration directive. (More precisely, only the first incomplete method in each group is offered at any time.) Consequently, you almost certainly want to make anything involving passwords be the last step, or else your SSH server can be used as a password oracle, even if they can’t log in. This is especially notable if your SSH server is joined to a domain or connected to an LDAP server. I repeat, put the password methods last to avoid leaking whether or not a password is correct without the other mechanisms.
I personally prefer
keyboard-interactive authentication to
former can use PAM to perform the authentication, allowing me to run the PAM
authentication stack. The latter does a direct password check by the
itself. Either is fine for most installations, but having the flexibility of
PAM can be useful. You can even enforce that only PAM will be used with a
configuration like the following:
1 AuthenticationMethods publickey,keyboard-interactive:pam
Of course, if there’s one thing I’ve learned, it’s that SSH public key
authentication is good for automating things. Depending on the scenario, I can
store the key on disk unencrypted, or manually load it into an SSH agent after
each reboot to keep it only in-memory. Either way, I don’t want these automated
processes to be blocked by the
sshd_config change. These users are restricted
in various ways, so the risk posed by their compromise is less than that of
For example, this blog is maintained by pushing to a git repository on the host.
Let’s call the user
blog, and since it’s fairly restricted (it uses
git-shell for its shell and has very
restricted ACLs), I’m happy to continue to allow it to use only an SSH public
key to push. To configure this, we can use a
Match User block:
1 2 3 4 5 AuthenticationMethods publickey,keyboard-interactive:pam Match User blog AuthenticationMethods publickey ForceCommand /usr/bin/git-shell -c "$SSH_ORIGINAL_COMMAND"
This requires both PAM (password) and public keypair for most users, but allows the automated push to the blog to proceed with only the publickey.
Alternatively, maybe you want to keep things easy for most of your users, and you don’t consider their compromise a risk. This would be typical for, say, a shared hosting provider. You’d like them to continue to be able to use either a password or an SSH public key. On the other hand, you’d like your admins to be forced to use two factors to authenticate to the server. If all of your admins are in a single POSIX group (a good practice anyway) you can use something like this:
1 2 3 4 5 PubkeyAuthentication yes PasswordAuthentication yes Match Group admins AuthenticationMethods publickey,password
If you don’t already do this, and you have a mix of trusted and untrusted (i.e.,
customers) users on your host, it might also be a good time to look at
restricting features like TCP Port Forwarding (
PermitOpen), pseudo-device tunneling (
PermitTunnel), and X12 Forwarding
X11Forwarding) are permitted for general users.
While I’ve focused only on SSH options here, there’s never been a better time to
look at options for requiring 2 factor authentication. Note that if you own
some models of Yubikey, like the Yubikey 4 (or
Nano), you can also use the PGP functionality of the
device to authenticate
to SSH servers, thanks to functionality in
gpg-agent that allows you to use a
GPG keypair to perform SSH auth. These devices also act as hardware tokens for
sites like Google, Dropbox, GitHub, Facebook, and BitBucket with Universal
2-Factor (U2F) support.