(updated May 2024 for RHEL 9)
2FA for ssh
2 Factor Authentication is a great way to improve security on your systems. Adding 2FA for secure ssh on CentOS/RHEL9 is simple enough, and there are plenty of ‘how-to’ guides out there. For example: https://www.digitalocean.com/community/tutorials/how-to-set-up-multi-factor-authentication-for-ssh-on-centos-7 was written for CentOS7, but is still broadly applicable.
This particular install needed a tweak though, and I had to dig through information all over the web to work out what was needed to get it configured as required. What I needed to achieve was:
- external ssh logins by one specified user only, secured with ssh keys AND 2FA
- internal SSH logins by that user and another, with just password authentication
To do this I assume you already have setup:
- sshd running on a port externally visible
- 2 x pre-existing users, user1 and user2, who can log on to ssh already
- user1 who already access the ssh server externally using secure keys and no password (IMPORTANT – THIS GUIDE WILL NOT WORK IF PUBLIC KEY SSH IS NOT ALREADY WORKING)
- external access, port forwarding, secure keys etc., already working – these are not in scope for this post
The end goal is to get:
- user1 – access ssh from the internal network, and is the ONLY user granted external access to ssh
- user2 – access ssh from the internal network only
Google-authenticator from EPEL
The 2FA part uses the google-authenticator package, so install this from the EPEL repo.
Once it is installed ‘su’ to user1 and run ‘google-authenticator’ – make sure it completes through and writes the .google_authenticator file in the user’s home directory, picking appropriate options and entering the generated 2FA code into the app of your choosing. Make sure you record the resultant recovery codes somewhere safe! We don’t need to run it as user2, as user2 will not be using 2FA (internal access only).
Configure PAM
/etc/pam.d/sshd
PAM is the module sshd uses to manage authentication. We need to tweak its config on how it will manage auth for sshd:
nano /etc/pam.d/sshd
Firstly comment out the line:
auth substack password-auth
so it reads:
# auth substack password-auth
…and add the following at the end of the file (3 lines):
auth [success=done default=ignore] pam_access.so accessfile=/etc/security/access-local.conf
auth [success=done ignore=die default=die] pam_google_authenticator.so
auth requisite pam_deny.so
/etc/security/access-local.conf
Create and edit the noted ‘/etc/security/access-local.conf’ file and add the following (editing as needed for your local IP setup):
+ : ALL : 10.0.0.0/24 - : ALL : ALL
sshd configuration
ChallengeResponse Authentication
You WILL need to set ChallengeResponseAuthentication to yes to enable ssh coming back and asking for your 2FA key. Trying to set this inside match blocks fails (I found different ‘man’ entries for this option, with one saying it can be set inside a MATCH block, and one that it cannot; on CentOS8 version, it cannot and must be set at the sshd level).
In RedHat 9, this needs to be changed in the file /etc/ssh/sshd_config.d/50-redhat.conf, with the default for ChallengeResponseAuthentication changed from no to yes. This setting .
In this setup’s example there is very little set in sshd’s config other than ChallengeResponseAuthentication before the MATCH blocks – your local configuration may of course vary, but you will need to have setup key auth.
Match blocks in /etc/ssh/sshd_config
To get SSHD working we are managing most of our security configuration (defined in /etc/ssh/sshd_config) in MATCH blocks, one for the external network, one for internal. Note, if you have PasswordAuthentication set to yes or no in the file, comment it out – we will manage it on a network address basis within MATCH blocks (but do make sure your MATCH blocks catch *all* possible network addresses, as the default is ‘yes’).
Outside Network MATCH:
Match Address *,!10.0.0.0/24
AllowUsers user1
AuthenticationMethods publickey,keyboard-interactive
PasswordAuthentication no
The first MATCH block applies to external network users only.
Note the leading asterisk in the address match, it is important – you are saying “mark any (‘*’) address except (‘!’) 10.0.0.0/24” as external in this block.
Within the block, the options are saying: if the block is triggered by the network address match, only allow user1 to try and login, and only succeed if they have secure ssh keys setup and working *and* google-authenticator configured and successfully passed. Password authentication is definitely NO here!
Local Network MATCH:
The second MATCH block in sshd_config then applies to local network matches only.
Note I have used specific users here, as I am only enabling two – you can use AllowGroups if you prefer. Note these users do not have to have google_authenticator run *unless* they are also in the external MATCH AllowUsers list.
Match Address 10.0.0.0/24
AllowUsers user1 user2
AuthenticationMethods password
PasswordAuthentication yes
Restart sshd – BUT keep your ssh session active in case you have messed something up and cannot log back in.
Logging in!
OK – we are ready to test 2FA for secure ssh on CentOS9/RHEL9…
Local Network first!
You should be able to login from the local network as user1 and user2, with passwords only.
- user1 tries to log in to ssh from a local network address, so the first MATCH block is ignored
- sshd_config’s second MATCH block triggers on the local network
- In that MATCH block user1 is on the AllowUsers list, so we proceed
- PAM checks ‘access-local.conf’ file, and the ‘+’ entry says “OK, you are any user (“ALL”) coming in from my local network subnet, skip bringing in google-authenticator”
- user1 is prompted for their password, enters it
- sshd is happy (‘PasswordAuthentication yes’) and PAM are both happy – login!
External Network
You should be able to login from an external network as user1 only, and only using (already working) ssh keys, and only with the newly configured 2FA code.
- user1 tries to log in to ssh from an external network address
- sshd_config’s MATCH block triggers for external network
- In that MATCH block user1 is on the AllowUsers list, so we proceed
- But now we are proceeding with ‘PasswordAuthentication No’ and ‘AuthenticationMethods publickey,keyboardinteractive’
- PAM checks ‘access-local.conf’ and its sshd file, and the ‘-‘ entry says “You are not a user coming in from my local network subnet, so we need google-authenticator”
- For PAM and sshd to accept this user, the secure keys have to have been accepted, and user1 is now also prompted for their 2FA code
- If 2FA is successful sshd and PAM are both happy – login
From an internal or external network location try to login to ssh as a user not in the AllowUsers list and sshd should immediately reject it.
Hopefully this guide on setting 2FA for secure ssh on CentOS/RHEL was useful!
Discover more from SimonandKate.net
Subscribe to get the latest posts sent to your email.