Using 2 factor authentication with OpenSSH

Since OpenSSH can delegate the authentication to PAM, it's easy to extend a password-based authentication to a full 2 factor authentication. Since OpenSSH 6.2, it is also possible to check for a second factor after another method, for example using SSH keys, using AuthenticationMethods.

However, we want more, we want our users to be able to use any of their authentication methods (password, SSH keys, Kerberos, ...) and propose them a second factor authentication afterwards. But let's start step by step from the basis and build our solution

The basis: 2 factor authentication with pam

PAM is by design modulable. It's possible to build your own module, compile it and by simply adding it to the right folder, PAM will be able to use it (after some configuration). You can find some existing module online or build your own based on them, for example:

The PAM configurations is always found in the /etc/pam folder, and for sshd, this is /etc/pam.d/sshd. This configuration file obeys a very simple format, defined in its documentation. For most systems, /etc/pam.d/sshd will only contain an include control definition for the auth service like this one:

auth       include      password-auth

We need two changes in this configuration:

For CERN, this results in the following configuration:

auth       substack     password-auth
auth       required     pam_2fa.so    [CERN-specific configuration]

And Voila, we have successfully configured PAM to use 2 factor authentication for sshd. Now we only need to configure sshd to use PAM via the following configuration:

UsePAM yes # That's easy!
ChallengeResponseAuthentication yes # To allow the client to discuss with the remote PAM

RSAAuthentication no # We can't support any non-pam authentication
PubkeyAuthentication no # We can't support any non-pam authentication
PasswordAuthentication no # No basic password authentication
KerberosAuthentication no # We can't support any non-pam authentication

Just restarting our sshd service and we're done, we've protected our servers with 2 factor authentication! But this was only the beginning: we need to support Kerberos, not passwords.

AuthenticationMethods: supporting Kerberos

In OpenSSH 6.2, a new parameter was included: AuthenticationMethods. This new feature allows to define chains of authentication methods that are required. The design is relatively simple: the server exposes a list of method the client can use, the client pick one and use it, the servers then replies "good, now pick another one in this list", rinse&repeat until satisfied.

So we want to use Kerberos. Looking at the documentation, the corresponding method is gssapi-with-mic. Now, our second factor is using PAM, so we still need the corresponding method, keyboard-interactive:pam. We just have to chain the two, re-enable the corresponding authentication and here is our configuration:

UsePAM yes # That's easy!
ChallengeResponseAuthentication yes # To allow the client to discuss with the remote PAM
KerberosAuthentication yes # We support it!

AuthenticationMethods gssapi-with-mic,keyboard-interactive:pam

RSAAuthentication no # We don't support it
PubkeyAuthentication no # We don't support it
PasswordAuthentication no # No basic password authentication

We still have a small problem as we didn't change our PAM configuration: our users will be asked to type their password. We just need to remove the password authentication and simply keep our second factor module in PAM:

auth       required     pam_2fa.so    [CERN-specific configuration]

Restarting sshd, and done!

AuthenticationMethods: adding SSH keys support

On top of Kerberos, we also want to support SSH keys. Looking at OpenSSH naming convention, the method for it is publickey. We just need to enable it and chain it to keyboard-interactive:pam and add it to the list of supported methods in our configuration:

UsePAM yes # That's easy!
ChallengeResponseAuthentication yes # To allow the client to discuss with the remote PAM
KerberosAuthentication yes # We support it!
PubkeyAuthentication yes # We support it!

AuthenticationMethods gssapi-with-mic,keyboard-interactive:pam publickey,keyboard-interactive:pam

RSAAuthentication no # We don't support it
PasswordAuthentication no # No basic password authentication

AuthenticationMethods: adding PAM support ?

And now we are back to the beginning, supporting password-based authentication, but this time in addition to the other methods. Password-based authentication is using keyboard-interactive:pam, so if we were to follow the previous logic, we would use keyboard-interactive:pam,keyboard-interactive:pam. However, if we do so, we still only have one single PAM configuration in which we can:

There is no such thing as keyboard-interactive:pam:sshdpassword or keyboard-interactive:pam:2fa. There is only one configuration which is always called in the same environment. In the end, PAM can't differentiate the two cases we have: calling PAM after a successful authentication for the second factor or calling PAM for doing the whole authentication...

Exposing more information to PAM

In the end we have no choice but to patch OpenSSH to expose more information to PAM, allowing it to make the right decision. The patch was proposed to the OpenSSH bug tracker and RedHat agreed to include a version of this patch in CentOS 7.3+

This patch adds a new environment variable, SSH_USER_AUTH, which is exposed:

This list of successful authentication methods also contain:

Our patch has been adapted by upstream and a modified version is already included since 7.6. Unfortunately this patch is missing a key functionality and thus currently does not work for us.

Using a smart PAM configuration

With this patched version of OpenSSH, it is now possible to make decisions based on this environment variable. Using this variable is safe as it is only controlled by:

Let's start with a simple check: the existence of a non-empty SSH_USER_AUTH variable in the PAM environment. A straightforward version of such a module is available on our GitHub. This module will basically return two values: PAM_SUCCESS if there was any previously successful authentications and PAM_IGNORE if there was none. Based on this, we can write the following PAM configuration:

auth       [success=1 ignore=ignore default=die] pam_ssh_user_auth.so
auth       substack     password-auth
auth       required     pam_2fa.so    [CERN-specific configuration]

This configuration is simple, but not trivial to read, as the format is not simple, especially on the first line. This first line means that pam will call pam_ssh_user_auth.so and will:

We can ensure the behavior we want, that is running the 2nd factor after any first factor, by configuring SSHD in the following manner:

UsePAM yes # That's easy!
ChallengeResponseAuthentication yes # To allow the client to discuss with the remote PAM
KerberosAuthentication yes # We support it!
PubkeyAuthentication yes # We support it!

AuthenticationMethods gssapi-with-mic,keyboard-interactive:pam publickey,keyboard-interactive:pam keyboard-interactive:pam

RSAAuthentication no # We don't support it
PasswordAuthentication no # No basic password authentication

Protecting the second factor

At CERN, we have another requirement: the second factor should never be asked is there was no first factor. This is due to the fact that one of the second factor we are using is based on SMS and we don't want malicious attackers to spam our users with SMS...

Due to this constrain, we need a more complicated PAM configuration, which I won't explain here (consider it an exercise for the reader):

auth       [success=2 ignore=ignore default=die] pam_ssh_user_auth.so
auth       substack     password-auth
auth       [default=1]  pam_deny.so
auth       required     pam_2fa.so    [CERN-specific configuration]

This new configuration will result in a single pam call resulting in:

UsePAM yes # That's easy!
ChallengeResponseAuthentication yes # To allow the client to discuss with the remote PAM
KerberosAuthentication yes # We support it!
PubkeyAuthentication yes # We support it!

AuthenticationMethods gssapi-with-mic,keyboard-interactive:pam publickey,keyboard-interactive:pam keyboard-interactive:pam,keyboard-interactive:pam

RSAAuthentication no # We don't support it
PasswordAuthentication no # No basic password authentication

Acknowledgements

We would like to thanks: