Two Factors Are Better Than One
Although I've always been interested in security, there are just some security
measures I've never liked. SSH brute-force attacks end up being a major way that
attackers compromise Linux systems, but when it comes to securing SSH, I've never
been a fan of changing your SSH port to something obscure, nor have I
liked scripts like fail2ban that attempt to detect brute-force attacks and block
attackers with firewall rules. To me, those measures sidestep the real issue:
brute-force attacks require password authentication. If you disable password
authentication (set PasswordAuthentication
to no in your sshd_config) and
use only SSH keys, you can relax about all those brute-force attacks knocking on your
door.
In a past article ("Secret Agent Man", December 2013), I wrote about why you should set a passphrase on your SSH keys and how to use SSH Agent to make password-protected keys a bit less annoying. In one respect, you can think of password-protected SSH keys as a form of two-factor authentication. The key is something you have, and the password is something you know. The problem, however, is that if you host a system with multiple users, you can't enforce password-protected SSH keys from the server side. So in this article, I discuss how to add two-factor authentication to an SSH server that accepts only keys.
These days, more services on-line offer two-factor authentication (2FA) as an extra layer of security on top of a user name and password. After you perform your normal authentication, you provide your 2FA token (usually a string of digits) that authenticates you. Although in the past, 2FA required you to carry around a special hardware dongle, these days, a number of software approaches can use your cell phone instead. Some approaches use TOTP (Time-based One-Time Password), so your phone just needs accurate time but no network to function. Other approaches use push notifications, SMS or even a phone call to share the 2FA token, and some implementations can use all of the above.
Some 2FA SSH implementations work via the ForceCommand
directive placed in the
SSH configuration for a particular user and let you enable 2FA on a per-user
basis. Others offer a PAM module you can add system-wide (and use for sudo
authentication as well as SSH). Although a number of excellent 2FA SSH
implementations exist for Linux, I've chosen Google Authenticator for a few reasons:
-
It's free, and the source is available.
-
It's been available and tested for a number of years.
-
Packages are available for a number of distributions.
-
Clients are available for a number of phone operating systems.
-
It uses a custom PAM module, so it's easy to add 2FA system-wide.
-
It provides a backup in the form of backup codes in case users lose or wipe their phones.
As I mentioned, Google Authenticator is packaged for a number of distributions, so, for instance, on Debian-based systems, you can install it with:
$ sudo apt-get install libpam-google-authenticator
If for some reason it isn't packaged for your distribution, you also can just go here, download the software and make and install it according to the documentation there. You also will need to install the Google Authenticator app on your phone.
Configure User AccountsI recommend setting up Google Authenticator for all of your user accounts (or at least all of the sysadmin accounts) before enforcing 2FA in SSH to make it easier to enroll all of the users and avoid the risk of locking people out. To configure Google Authenticator, each user needs to log in and run google-authenticator. You will be presented with a series of questions where it's safe to answer "y"; however, I generally answer no to extending the time window to four minutes, and I also answer no to rate limiting, since as I disable password authentication, I'm less concerned with brute-force attacks. The output looks something like this:
$ google-authenticator
Do you want authentication tokens to be time-based (y/n) y
https://www.google.com/chart?chs=200x200&chld=M|0&cht=qr&chl
↪=otpauth://totp/username@debian%3Fsecret%3D4SK2LTLCTLCEV757
QR Code Removed
Your new secret key is: 4SK2LTLCTLCEV757
Your verification code is 221544
Your emergency scratch codes are:
53267360
44975412
59302752
36003899
64736155
Do you want me to update your "/home/username/.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) n
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) n
If you have libqrencode installed, the output also will contain a QR code in the console you can scan with the Google Authenticator app on your phone. Otherwise, you simply can enter the secret key into your Google Authenticator application on your phone. Also, be sure to write down those backup codes and store them in a safe place. These are one-time-use codes you can use to get back in to the system in case you ever lose or wipe your phone. Once you are logged back in, you can run google-authenticator again.
Configure PAM and SSHOnce your phone and user accounts are configured with Google Authenticator, you are ready to enforce 2FA in PAM and SSH. To do this, edit your /etc/pam.d/sshd file and add the following to the top of the file:
auth required pam_google_authenticator.so
On my Debian system, I noticed that once I finished the configuration process, I would not only be prompted for my 2FA token, I'd also be prompted for my local system password. Because I wasn't interested in three-factor authentication (two-and-a-half factor authentication?), I noticed I needed to comment out the following further down in the file:
@include common-auth
Of course, if you aren't on a Debian-based system, this extra step may not be necessary.
The final step is to configure SSH. Hopefully you already have disabled password
authentication for SSH in the past, and if not, I recommend you consider it. Most
of the SSH 2FA guides out there (this one included) will tell you to enable
ChallengeResponseAuthentication
in your /etc/ssh/sshd_config:
ChallengeResponseAuthentication yes
I noticed, however, that when you are using key-based authentication instead of passwords, you need to add an additional setting to the config file:
AuthenticationMethods publickey,keyboard-interactive
Once these settings are in place, you can enable them by restarting your SSH service, which depending on your system may be one of the following:
$ sudo service ssh restart
$ sudo service sshd restart
After SSH has restarted, you should get an additional prompt the next time you SSH to the server:
$ ssh kyle@server1.example.com
Authenticated with partial success.
Verification code:
Type in the verification code that shows up in your Google Authenticator phone app, and you can log in. The nice thing about adding 2FA to SSH is that it provides an additional means of protection in case your computer is ever compromised or stolen. Attackers also would have to compromise or steal your phone before they could access your systems.