Two-Factor-Authentication with SSH
SSH can use a variety of authentication methods, such as passwords and key pairs. Less common, and not particularly user friendly, are methods like OPIE. The latter is available per default in FreeBSD (and a few other BSDs I believe), but I can't recall a single case where I've seen it being used in the wild. Other implementations, like Google Authenticator make MFA easy to integrate and use, without being too disruptive for the user.
Now, some people argue that a password-protected SSH key pair consitutes MFA, with the key pair being the "what you have" and the password protecting it being the "what you know". Well, it is wrong and dangerous!
MFA is only useful, if all parts of it can be enforced server-side!
Consider this: A user may choose to store a 8192 bit key pair on an unecrypted device, a laptop or maybe even mobile phone, which is easily lost or stolen. The user may choose a ridiculously weak password, or not to password-protect that key pair at all. There's nothing the sysadmin can do to prevent them from doing so. There's no way the sysadmin would even know about it. How secure is your exceptionally long SSH key pair now? Would you -as a sysadmin- want the user's key pair to be the only security check before gaining access to your server? Thought not!
(And there are even cases, where SSH key pairs -including the private bit- are shared among individuals, stored off-site in services like Dropbox or emailed in plain text...)
Of course it's advisable to encrypt SSH keys with a password. However, the lack of control from the company's perspective renders it useless as far as policies are concerned. Hence, when I refer to a password in the second part of the article, I mean the server-controlled SSH password challenge (typically the user account's password, LDAP or any password challenge that may be configured server-side), not the private key decryption password.
It's increasingly common that employees and contractors use all sorts of devices to log into company services to do their job, and it's increasingly common to use arbitrary (read: not company-provided) devices for that. BYOD (bring your own device) is one reason why MFA has become more and more important over the last couple of years.
So, let's jump right in. There are two very easy ways to accomplish it (among several others, for example the aforementioned OPIE).
When you play with sshd's or PAM's configuration remotely, you want to make sure that you keep one logged-in root session open while experimenting with another user account. Otherwise you might just find that you have set up three or four factor authentication and locked yourself out!
Option 1: SSH Key Pair + Server-Side Password
OpenSSH has enhanced the configuration setting called AuthenticationMethods recently (version 6.2, I believe) so that it now allows to chain several authentication methods, meaning that all of them need to be satisfied in order to successfully log in.
Let me give an example, which -coincidentally- does what we want to achieve anyway. To be applied to /etc/ssh/sshd_config (works on FreeBSD 10-STABLE, RHEL/CentOS 6.x, possibly most other *nix variants too):
Match User johndoe AuthenticationMethods publickey,keyboard-interactive
Read the commas as logical AND. On login, johndoe's key pair will be checked first and if it's a match, you'll see this:
Authenticated with partial success.
Then, he will be asked for his password. So without realising, you have just set up MFA. Your key pair being what you have, the account password being what you know. This is possibly the simplest way of setting up MFA with SSH, and already better than single-factor authentication. However, it's not the best by any stretch of imagination, because the what you have and what you know don't change until a password and key pair rotation is forced upon users. So this wouldn't protect against replay attacks (using the same set of credentials for subsequent logins).
It is arguably better to use a physical what you have, which spits out one-time codes that are time-limited, aka TOTP. (Physical can be an actual hardware key fob like many banks have used in recent years, or an application on a phone)
Well, let's not get religious about it or start an argument. Let me show you the second, in my opinion better, option...
Option 2: Server-Side Password + Google Authenticator
Google Authenticator can be considered a synonym for a number of apps across all sorts of platforms, which in fact implement the same protocol. Indeed, the majority of Time-based One-Time Password (TOTP) implementations -including Google- follow RFC 6238. If you already use something other than Google Authenticator, you may find that you can just stick with it in the context of this article.
In this article, I will show how to set up Google Authenticator on FreeBSD. (If there's any interest, I might follow up with a short howto for RHEL/CentOS or Debian, though the steps are roughly the same.)
First, let's install the authenticator and libqrencode, which produces a scannable QR code on the console:
pkg install pam_google_authenticator-20140826_1 pkg install libqrencode
Next, execute google-authenticator as the user you want to protect with MFA:
su - johndoe -c google-authenticator
There are a number of questions to answer, but generally speaking y will be a good choice for all of them. A configuration file is then written to ~/.google-authenticator.
Make sure you scan the code with your authenticator software on the mobile phone (Google Authenticator or otherwise, see note above), or alternatively enter the secret key provided if your phone/app doesn't support scanning of QR codes. It's also a good idea to store the five keys provided in a very secure place. They grant access in absence of the authenticator app (if you lose your phone, for example); each of them can be used only once.
Next, root needs to enable Google Authenticator in PAM. Add this to your auth list in /etc/pam.d/sshd:
auth required /usr/local/lib/pam_google_authenticator.so
So the last two lines starting with auth would be these:
auth required pam_unix.so no_warn try_first_pass auth required /usr/local/lib/pam_google_authenticator.so
This effectively tells PAM, that after asking for a password on SSH log in, a Google Authenticator verification code should be entered. Only if both are successful, PAM will be satisfied. If one of them is unsuccessful, we're not getting in, and PAM wouldn't even tell the user whether it was the password or the code which was incorrect.
Finally make sure the user's login is handled by PAM:
Match User johndoe AuthenticationMethods keyboard-interactive
Now issue service sshd restart and that's it.
Of course, you could add the directives from above to the global section in sshd_config. I would recommend not to do that, though, unless you are absolutely certain that every user (especially root) has set up Google Authenticator properly. If they haven't, they would not be able to log in any more.
Also, personally I prefer to have a very restrictive global section and then remove contraints on a per-user or per-group basis, so Match User sections work best for me. Your mileage may vary.
Can I use publickey + password + verification code?
Of course you can. You'd change the directive from above to add key pair authentication:
Match User johndoe AuthenticationMethods publickey,keyboard-interactive
With this, the key pair is matched first, and only if that succeeds, we're moving on to password and verification code. It's very much the same as Option 1 above, except that PAM (keyboard-interactive) has been taught to use Google Authenticator in the meantime :-)
How about publickey + verification code, without password?
This is also possible, but not a terribly good idea. To make it happen, you'd need to comment the following line in /etc/pam.d/sshd:
# auth required pam_unix.so no_warn try_first_pass
Then you'd use the same AuthenticationMethods as in the previous question. However, I'd advise not to do this. There are authenticator apps for computers (not only for mobile phones and tablets) out there, and your user might just choose to go with that. This could possibly mean that both the private SSH key and the configured authenticator end up on the very same device. You can tell them not to do that, but you can't enforce that recommendation. If the device is lost, your security may be seriously compromised, and the sysadmin may not even be aware of it.
Should I set up MFA for all my servers? Quite inconvenient!
Of course not! You should not publicly expose SSH on any server, if you can avoid it! The vast majority of your estate would of course be locked away and protected by some sort of proper VPN (IPSec, OpenVPN etc), wouldn't it? And if that's the case, you'd probably implement MFA at the "door" to your VPN.
However, sometimes exceptions are required where SSH has to be publicly accessible. Only if that's the case, you need to integrate MFA with SSH. You can expect an increasing number of companies to require MFA for a lot of things very soon (if not already). As a sysadmin, you don't want to be caught with trousers down if your $employer suddenly mandates it for remote server access, too.
Other Protective Measures
Although I can probably be described as moderately paranoid (they are coming after us after all!), I deliberately didn't mention any other SSH-related security measures. The article would just get way too long and they are sufficiently covered elsewhere. Some of them are also debatable. Let me give you some pointers for further research, with very brief remarks:
fail2ban -- add security measures like firewall rules based on the number of failed login attempts, definitely worth looking into (not only for SSH)
- security by obscurity
- some people suggest that moving SSH to a different port might be useful; I think that's pointless nonsense, unless you accompany this by other measures like redirecting port 22 into honey pots or adding firewall rules to lock source IPs out when they try port 22 first
- changing SSH's banner -- doesn't help if anybody actually performs a check against a vulnerability rather than trusting that your system reports a correct version to help them with it
enforcing password length and complexity policies with cracklib and friends; common sense
enforcing regular password rotation/expiry; common sense
port-knocking; not particularly useful in multi-user environments, and not very user friendly
Update 27 June 2015: The following, originally missing items, have been added in response to comments below. How could I miss them? Thanks guys!
- IP whitelisting; if your users do use static IPs (or connect through tunnels with static IPs), this is obviously the best protection against everybody else
- chroot'ing users to confine them to a limited area on the host
This list is still not complete, and not meant to be. There are even more ways of adding security to SSH (or a server as a whole), including commercial solutions. They are beyond the scope of what I initially wanted to demonstrate here.
I hope this article was of some use to you. At least, if anybody in your company shouts MFA!, you are now aware that it's actually quite easy to implement. Feel free to give feedback below!