I maintain my systems mostly via SSH and although SSH is secure enough with it’s encryption I want to secure it a bit more. I do use keys a lot and is the proper way to log in via SSH as passwords are not needed anymore. But since I work remotely sometimes and not having my workstation with me, I have to keep allowing password based logins for SSH but don’t want to rely on the password itself, so I started the implementation of 2 factor authentication for SSH using the Google Authenticator. This post will describe the steps necessary to enable 2FA using the Google Authenticator app for the SSH service.
Notes on the Google Authenticator:
As Google has a reputation to scrape literally every piece of information about you and your behaviour online, the authenticator does not send any data to Google (as they say themselves). The numbers generated by the app are processed on your phone itself instead of coming from a server of Google and the data exchanged is only performed between your computer and the server you are connecting to.
Installing the Google Authenticator software on the server:
In order to enable 2FA we need to install the Google Authenticator software on the server as well because we will be using PAM for the authentication process and the software delivers the appropriate module for use with PAM.
To install it op Debian based systems, do:
apt-get install libpam-google-authenticator
On Arch based systems you need to run:
yaourt -Sy google-authenticator-libpam-git
For Arch you need the AUR repo, therefore have yaourt installed on your system!
For Redhat/CentOS based systems:
There is a rpm for the authenticator module, but it seriously outdated, so we need to compile it ourself. First install the required depencies:
sudo yum install make gcc pam-devel
Download and unzip the sourcecode:
wget -O google-authenticator.zip https://github.com/google/google-authenticator/archive/master.zip
unzip google-authenticator.zip
Now enter the directory and start the build:
cd google-authenticator-master/libpam/
./bootstrap.sh
./configure
make
sudo make install
Installing the Google Authenticator app:
Open the app store on your phone and search for the Google Authenticator app and install it.
Configuring the Authenticator on the server:
Login on your server with the user you want to enable the Google Authenticator for and run the following command:
google-authenticator
You’ll have to answer the question if you want tokens to be time-based, for extra security I strongly advise you to set it to yes as this will set a expiry time on the token itself.
After this process a URL and a huge QR code will be spewed out on the terminal. The URL shows the same QR code, but in your browser which may be more convenient.
A set of keys and emergency codes are provided, save them in a safe place! You will need these emergency codes in case you lose access to the Google Authenticator app.
3 additional questions will be asked like below:
Your new secret key is: 2IKBRFIFF5FRB7JB
Your verification code is 245699
Your emergency scratch codes are:
52818155
31686839
29008669
66823031
59636947
Do you want me to update your "/home/jeffrey/.google_authenticator" file (y/n) y
Do you want to disallow multiple uses of the same authentication
token? This restricts you to one login about every 30s, but it increases
your chances to notice or even prevent man-in-the-middle attacks (y/n) y
By default, tokens are good for 30 seconds and in order to compensate for
possible time-skew between the client and the server, we allow an extra
token before and after the current time. If you experience problems with poor
time synchronization, you can increase the window from its default
size of 1:30min to about 4min. Do you want to do so (y/n) y
If the computer that you are logging into isn't hardened against brute-force
login attempts, you can enable rate-limiting for the authentication module.
By default, this limits attackers to no more than 3 login attempts every 30s.
Do you want to enable rate-limiting (y/n) y
The questions are simple and straightforward and don’t need any extra explanation, for the best security with the tokens you will want to answer yes on all questions except the 3rd question. Note that it says that it will increase the lifetime of a token from 90 seconds to 240 seconds. If you want the best security out of this system you should answer no here but relies heavily on the current system time and your device generating the token, they may not differ more than 90 seconds. If you use NTP on your server the 90 seconds should not be an issue but if it is you should answer yes here.
Setting up the app:
Open the Google Authenticator app on your device and you have the choice to enable via QR code or enter the tokens manually. For this example I will use the manual process, but for faster configuration the QR code is best.
After the name and key is inserted, add the account. After adding the account the app starts generating tokens:
Configuring PAM and SSH:
In order to activate the Google Authenticator module using PAM we need to open the PAM config for SSH:
sudo nano /etc/pam.d/sshd
Add the following line to the top of the file and save it:
auth required pam_google_authenticator.so
Open the SSH config:
sudo nano /etc/ssh/sshd_config
Search for the keyword ChallengeResponseAuthentication
If it's commented out like:
# ChallengeResponseAuthentication no
You need to change it to:
ChallengeResponseAuthentication yes
If the line does not exist within the configuration file, just add it (which is the same as above) and save your changes:
ChallengeResponseAuthentication yes
Now restart SSH to activate the settings changed:
For Debian based systems:
sudo service ssh restart
For Arch based systems:
sudo systemctl restart sshd.service
For Redhat/CentOS based systems:
sudo service sshd restart
Logging in via SSH with the authenticator:
Now that the settings are active we want to test it. I've done it on my local machine, so in this example I'll connect to localhost:
[jeffrey@jeffrey ~]$ ssh localhost
Verification code:
Password:
[jeffrey@jeffrey ~]$
As you can see it's working!
Note on SSH-keys:
If you already use SSH-keys to login without password on your server this will bypass the Google Authenticator. Looking at the way keys work and how they need to be deployed you basically have performed a 2FA when logging in to the server and placing your pubkey there. If you work from your private desktop this will work just fine but in my opinion for mobile devices that can be used on-the-go it's not.
For mobile devices that may get stealed or might get lost it's absolutely a pre to sign your SSH-key with a password. this way you will have to enter a password every time to login to the server, but that's only for decrypting the key and then will login with that key.
You can create a key with password via the following line:
ssh-keygen -t rsa -N 'YOUR_PASSWORD'
This will overwrite your current keyfile! Once generated place the publickey on the server and you're done. This will function as 2FA as well and will bypass the Google Authenticator module.
Small problem I found with your post,
“On Arch based systems you need to run:
yaourt -Sy google-authenticator-libpam-git
For Arch you need the AUR repo, therefore have yaourt installed on your system!”
Just because you are using the AUR, doesn’t mean you have yaourt installed. You can build and install packages using no AUR helper, with just makepkg and pacman (https://wiki.archlinux.org/index.php/Arch_User_Repository#Installing_packages) or you could use any of the AUR helpers avalable to Arch users (https://wiki.archlinux.org/index.php/AUR_helpers).
Also any reason for using the git package https://aur.archlinux.org/packages/google-authenticator-libpam-git/ (tracks git master), over the non-git one https://aur.archlinux.org/packages/libpam-google-authenticator/ (stable releases). For my system I installed the latter and everything is working as expected.
sudo service restart ssh
should be
sudo service ssh restart
Thanks! I’ve fixed it and added the service restart part for CentOS as well as that was missing 🙂
How well does this work with SSH key pairs? Also, If I’m on my local network I’d like to forgo 2FA, know of a convention for that?
Hi! When using keys 2FA will be used as well but in a different manner as you already had to login to that server and place your key on it. I strongly suggest however to use keys with passwords on them, especially when working from mobile devices that may get stealed.
You can create a new key with password using:
ssh-keygen -t rsa -N 'YOUR_PASSWORD'
You will then need to transfer it to your server again in order to use the key with password.
I’m using SSH keys and other than this google auth thing I’m 99% sure that it’s otherwise default ubuntu 14.04 configs. Using passwords, the 2FA verification code works fine. If I have my keys loaded in an ssh agent, it skips straight through with no verification code.
Hi! I’ve checked this and the behavior you see is the correct one. Basically you are already have passed 2FA as you had to login to that server once with your password and add your public key to it’s authorized_keys file. If you want to play safe you want to create a key with a password on it, using it that way you always have 2FA and won’t need the authenticator.
You can create a new key with password using:
ssh-keygen -t rsa -N 'YOUR_PASSWORD'
You will then need to transfer it to your server again in order to use the key with password.
I have that working. Put your trusted machines in /etc/security/access.conf, then in /etc/pam.d/sshd put in the line:
auth sufficient pam_access.so
So if you aren’t in access.conf you need 2FA, but if you are you just need your keys.
When trying to log back in I’m getting, “Using keyboard-interactive authentication.” and then a prompt to put in my password. Only my password isn’t being accepted. Any idea what’s going on? Perhaps something in PAM.
Hi!
Can you paste the contents of the /etc/pam.d/ssh file? This file is responsible for the allowed/required methods for SSH logins.
My default is:
#%PAM-1.0
auth required pam_sepermit.so
auth include password-auth
account required pam_nologin.so
account include password-auth
password include password-auth
# pam_selinux.so close should be the first session rule
session required pam_selinux.so close
session required pam_loginuid.so
# pam_selinux.so open should only be followed by sessions to be executed in the user context
session required pam_selinux.so open env_params
session optional pam_keyinit.so force revoke
session include password-auth
Hi, it seems okay to me, although I don’t use SELinux nor have experience with. My PAM config for SSH is:
#%PAM-1.0
#auth required pam_securetty.so #disable remote root
auth required pam_google_authenticator.so
auth include system-remote-login
account include system-remote-login
password include system-remote-login
session include system-remote-login
And I see some differences compared with our configs, maybe the pam_nologin is the showstopper here. You may as well try out my config to see if that helps.
Note: your statement “for the best security with the tokens you will want to answer yes on all questions” is not quite true – the time window question (1m30s -> 4m) decreases security if you answer yes. If you want absolutely the maximum potential security, you should answer “no” to that question as it reduces the number of simultaneously valid OTP codes from 8 to 3.
Hi! You are correct but it relies heavily on the correct time on the server and the device generating the token, if they differ more than 1:30 it will not work. If you use NTP this is not an issue but I sometimes see servers at work that are behind a couple of minutes making the 4:00 option key. I’ve changed the article with this note, thanks for informing me!