CentOS 8: Requiring a Yubikey OTP Press for SSH logins

Some 7 years back, I wrote a guide to requiring a Yubikey OTP for SSH logins on CentOS. In the time that's passed, the process has changed (a little), so this documentation provides an updated reference.

Although this is written (and tested) for CentOS 8, it should work equally well on CentOS 7 (and presumably also Rocky Linux) too.

The (increased compared to my previous post) flexibility of Yubikeys, along with their relative ubiquity makes them a fantastic candidate for two-factor authentication tokens. Modern Yubikeys can do U2F as well as using their proprietary mechanism, allowing them to be used with a wide range of software.

By the end of this documentation, we'll have configured a CentOS 8 server to require that a user provides a Yubikey press along with

  • Username AND
  • Account password, OR
  • Authorised SSH key

For brevities sake, the majority of this documentation assumes you want root to manage user's yubikeys - something Yubico call Administrative level managment - switching between the two is relatively straight forward, so details on how to switch "User Level" will be given at the end of the document.


Installing the Module

We require a PAM module, but it can be obtained from EPEL, so the process is as simple as

yum -y install epel-release
yum -y install pam_yubico


Getting an API key

We need a valid API key to talk to Yubico's servers, so visit https://upgrade.yubico.com/getapikey/ to get one - make a note of the details it issues you.

You should have something like

  • Client ID: nnnnn
  • Secret Key: <base64 string>


Editing PAM Settings

The next step is to configure PAM to require a key

cd /etc/pam.d/
nano sshd

At the top, add the following, remembering to replace API_ID with the Client ID you were given by Yubico

auth required pam_yubico.so id=API_ID authfile=/etc/yubikeys

Save and exit (Ctrl-X, y for those not familiar with nano)

Now, we need to configure SELinux so that the module is allowed to connect out to the API, this is as simple as

setsebool -P authlogin_yubikey on


Configuring a user to use a Yubikey

Next we'll configure account ben to use a Yubikey.

In order to do this, we need to know ben's Yubikey ID - this can be obtained by running the following and then pressing the Yubikey

read line; ID=$(echo $line | cut -c1-12)

We then write the username and ID out to the file

echo "ben:$ID" >> /etc/yubikeys

Multiple ID's can be specified on one line, seperate by a colon (:), so we might also do

echo "ben:$ID:$ID2" >> /etc/yubikeys

Configuring SSH

At this point, we need to choose a path, we can

  • Permit password authentication, and require a yubikey press. SSH keys will work without a keypress
  • Require a SSH key and a yubikey press, password auth won't work

There isn't a (good) way to achieve both.

Requiring a Yubikey press with password authentication

This option allows users to login with their account password, immediately followed by a Yubikey press.

It's also the default behaviour for the config changes we've made so far, so we need only double check that SSHD is configured both to use PAM and to allow password auth:

egrep "UsePAM|PasswordAuthentication" /etc/ssh/sshd_config | grep -v "#"
PasswordAuthentication yes
UsePAM yes

If the value of both isn't yes then edit /etc/ssh/sshd_config to correct and then run systemctl restart sshd

Opening a new terminal, if you now try and SSH to the box, when prompted for a password:

  • Enter your password
  • Press the yubikey

and you should be logged in


Requiring a Yubikey press with SSH keys

Users that have a SSH public key authorised on the server won't, by default, be prompted for MFA. We can change this, but will need to disable password based authentication (otherwise they'll get prompted for their password)

cd /etc/pam.d
nano sshd

Comment out the following line (put a # in front of it)

#auth       substack     password-auth

Save and exit (Ctrl-X, y)

Now we need to edit the SSH config

cd /etc/ssh/
nano sshd_config

# Ensure Challenge Response is enabled
ChallengeResponseAuthentication yes

# Set the auth methods
AuthenticationMethods publickey,keyboard-interactive

Save and exit (Ctrl-X, y)

Opening a new terminal, if you now try and SSH to your system, you should be prompted for a Yubikey press:

ben@optimus:~$ ssh ben@
YubiKey for `ben': 
Activate the web console with: systemctl enable --now cockpit.socket

Last login: Tue Jun 22 16:20:37 2021 from
[ben@centos-yubikey-test ~]$ 


Bonus: Applying to Sudo

If you're feeling particularly security conscious, you might decide that you want users using sudo to provide a 2nd factor (very slightly reducing the risk of terminals being left unattended)

To do so, make the same change you made to sshd to the sudo PAM file:

cd /etc/pam.d
nano sudo
# Remember to replace API_ID
auth required pam_yubico.so id=API_ID authfile=/etc/yubikeys

Save and exit (Ctrl-X, y)

This won't, however, work for users who have NOPASSWD set in the sudo config - again, there isn't really a good way to overcome that (other than to observe that if you're happy giving them NOPASSWD on a command, requiring 2FA for it seems slightly incompatible).


Allowing Users to Manage their Own Keys

So far in this document, we've implemented Administrative Level management of keys - only privileged users can manage which yubikeys can be used for which users. This, obviously, has it's place but entails an increase in workload for the sysadmin.

You can, instead, use User Level mgmt, in order to allow users to manage their own yubikeys

cd /etc/pam.d
nano sshd

Change the line we added in order to remove the authfile argument, so it now looks like

auth required pam_yubico.so id=API_ID

Keys can now be specified by adding them to a file in the user's home directory, so to authorise ben's key above, we'd do

mkdir ~ben/.yubico/
echo "ben:$ID" >> ~ben/.yubico/authorized_yubikeys
chown ben:ben -R ~ben/.yubico/