This article has been updated for Ubuntu 16.04. The updated version also includes additional information on how to go beyond basic setup.
An authentication factor is a single piece of information used to to prove you have the rights to perform an action, like logging into a system. An authentication channel is the way an authentication system delivers a factor to the user or requires the user to reply. Passwords and security tokens are examples of authentication factors; computers and phones are examples of channels.
SSH uses passwords for authentication by default, and most SSH hardening instructions recommend using an SSH key instead. However, this is still only a single factor. If a bad actor has compromised your computer, then they can use your key to compromise your servers as well.
To combat that, in this tutorial, we’ll set up multi-factor authentication. Multi-factor authentication (MFA) requires more than one factor in order to authenticate, or log in. This means a bad actor would have to compromise multiple things, like both your computer and your phone, to get in. The different type of factors are often summarized as:
One common factor is an OATH-TOTP app, like Google Authenticator. OATH-TOTP (Open Authentication Time-Based One-Time Password) is an open protocol that generates a one-time use password, commonly a 6 digit number that is recycled every 30 seconds.
This article will go over how to enable SSH authentication using an OATH-TOTP app in addition to an SSH key. Logging into your server via SSH will then require two factors across two channels, thereby making it more secure than a password or SSH key alone.
To follow this tutorial, you will need:
One Ubuntu 14.04 Droplet.
A sudo non-root user with an SSH key added, which you can set up by following this Initial Server Setup tutorial.
A smartphone or tablet with an OATH-TOTP app installed, like Google Authenticator (iOS, Android).
In this step, we’ll install and configure Google’s PAM.
PAM, which stands for Pluggable Authentication Module, is an authentication infrastructure used on Linux systems to authenticate a user. Because Google made an OATH-TOTP app, they also made a PAM that generates TOTPs and is fully compatible with any OATH-TOTP app.
First, update Ubuntu’s repository cache.
Next, install the PAM.
With the PAM installed, we’ll use a helper app that got installed with the PAM to generate a TOTP key for the user you want to add a second factor to. This key is generated on a user by user basis, not system wide. This means every user that wants to use a TOTP auth app will need to log in and run the helper app to get their own key.
After you run the command, you’ll be asked a few questions. The first one asks if authentication tokens should be time-based.
This PAM allows for time-based or sequential-based tokens. Using sequential-based tokens mean the code starts at a certain point and then increments the code after every use. Using time-based tokens mean the code changes randomly after a certain time elapses. We’ll stick with time-based because that is what apps like Google Authenticator anticipate, so answer yes.
Do you want authentication tokens to be time-based (y/n) y
After answering this question, a lot of output will scroll past, including a large QR code. Make sure you record the secret key, verification code, the emergency scratch codes in a safe place, like a password manager.
At this point, use your authenticator app on your phone to scan the QR code or manually type in the secret key. If the QR code is too big to scan, you can use the URL above the QR code to get a smaller version. Once it’s added, you’ll see a six digit code that changes every 30 seconds in your app.
The remaining questions inform the PAM how to function. We’ll go through them one by one.
Do you want me to update your "~/.google_authenticator" file (y/n) y
This basically writes the key and options to the .google_authenticator
file. If you say no, the program quits and nothing is written, which means the authenticator won’t work.
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 answering yes here, you are preventing a replay attack by making each code expire immediately after use. This prevents an attacker from capturing a code you just used and logging in with it.
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
Answering yes here allows up to 8 valid codes in a moving four minute window. By answering no, we limit it to 3 valid codes in a 1:30 minute rolling window. Unless you find issues with the 1:30 minute window, no is the more secure choice.
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
Rate limiting means a remote attacker can only attempt a certain number of guesses before being blocked. If you haven’t previously configured rate limiting directly into SSH, doing so now is a great hardening technique.
The next step now is to configure SSH to use your TOTP key. We’ll need to tell SSH about the PAM and then configure SSH to use it.
First, open up the sshd configuration file for editing using nano
or your favorite text editor.
Add the following line to the bottom of the file.
. . .
# Standard Un*x password updating.
@include common-password
auth required pam_google_authenticator.so nullok
The “nullok” word on the end tells PAM that this authentication method is optional. This allows users without a OATH-TOTP key to still log in using their SSH key. Once all users have an OATH-TOTP key, you can delete “nullok” on this line to make it MFA mandatory.
Save and close the file.
Next, we’ll configure SSH to support this kind of authentication. Open the SSH configuration file for editing.
Look for ChallengeResponseAuthentication
and set its value to yes
.
. . .
# Change to yes to enable challenge-response passwords (beware issues with
# some PAM modules and threads)
ChallengeResponseAuthentication yes
. . .
Save and close the file, then restart SSH to reload the configuration files.
In this step, we’ll test if the SSH key works.
First, open another terminal and try SSHing into the server now. You’ll notice that you logged into this second session using your SSH key, without entering your verification code or password. This is because an SSH key overrides all other authentication options by default. We need to tell SSH to use the TOTP code and to use your SSH key in place of your password.
Now, open the sshd configuration file again.
Locate the PasswordAuthentication
line, uncomment it by deleting the #
character the head of the line, and update its value to no
. This tells SSH not to prompt for a password.
. . .
# Change to no to disable tunnelled clear text passwords
PasswordAuthentication no
. . .
Next, add the following line at the bottom of the file. This tells SSH which authentication methods are required.
. . .
UsePAM yes
AuthenticationMethods publickey,keyboard-interactive
Save and close the file.
Next, open the PAM sshd configuration file.
Find the line @include common-auth
and comment it out by adding a #
character as the first character on the line. This tells PAM not to prompt for a password; we previously told SSH not to in sshd_config
.
. . .
# Standard Un*x authentication.
#@include common-auth
. . .
Save and close the file, then restart SSH.
Now try logging into the server again. You should see that you authenticated partially with your SSH key and then got prompted for your verification code. It will look like this:
ssh sammy@your_server_ip
Authenticated with partial success.
Verification code:
Enter your verification code from your OAUTH-TOTP ap, and you’ll log into the server. You now have MFA enabled for SSH!
As with any system that you harden and secure, you become responsible for managing that security. In this case, that means not losing your SSH key or your TOTP secret key. However, sometimes things happen, and you can lose control of the keys to get you in.
Here are a few suggestions to regain access to your server:
If you lose, or don’t have access to, your TOTP app, use your recovery codes as a verification code. This happens if you get a new phone and forgot to export your keys out of the old one, or if your phone runs out of power.
If you lose your secret key and the backup, use the console via the DigitalOcean control panel to log in. Then either rename or delete the file ~/.google_authenticator
. This will make sure PAM is unaware of your configuration, and won’t prompt you for a code. Make sure that /etc/pam.d/sshd
still has “nullok” added, like in step 2; if you change this, make sure to restart SSH.
If you lose your SSH key, use the console again to log in and remove your old public from ~/.ssh/authorized_hosts
. Then, you can either replace it with a new key.
By having two factors (an SSH key + MFA token) across two channels (your computer + your phone), you’ve made it nearly impossible for an outside agent to brute force their way into your machine via SSH and greatly increased the security of your machine.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
(y)
This comment has been deleted
@karlhaworth without seeing your config files here are a couple of things to check that may cause the issue.
If none of these help, post your /etc/pam.d/sshd and /etc/ssh/sshd_config files in a paste bin and post the links.
@michaelholley I have already deleted my comment, but instead I should have corrected it with the mistake I made. I said I followed the instructions, but I did not generate the token using google-authenticator for the right user… Apparently I can’t skim these articles ;) I needed to generate the token under the user I am logging in over ssh with.
When you want to allow a second user to use authenticator, how can I display the QR code again, after the initial setup? I don’t see any hints in the options for googleauthenticator.
Ah… I didn’t notice the URL that appears above the QR code. This time I copied the URL together with the verification and scratch codes. Very nice.
@albioner If you want a second user to be able to authenticate into the same user account as the first user, you found the solution. The URL can be used again to display the QR code, or you can manually enter the secret key into your MFA application.
If you want a second user to have their own token for their own account, a different account than the first, they just need to login to their own account as they would normally do, then run the ‘google-authenticator’ app and generate their own token.
This comment has been deleted
Does this MFA setup also work with scp?
Sure does. Any system that uses SSH will need to enter a verification code to work, and scp transfer’s files via SSH. Even a X2Go setup will require a verification code since the data is sent over a SSH connection, lucky for us the x2go client prompts for the verification code so it still works.
If you are using a tool to do scp other than a terminal application, it may be possible the client doesn’t prompt for it and the copy never goes through. For scp to work with MFA on it will expect a verification code just like for SSH.
@michaelholley For the file edits I see that a
\
has been prepended to comment lines.I’m pretty sure this is not what you intended considering this would nullify the comment itself and cause those particular lines of code to be executed.
For instance:
Should be changed to:
You are totally right @mheadroom. Those slashes were there because the Markdown program I used to write this article wanted to turn those into headers; so I commented them out so the Markdown would view them as actual ‘#’. I thought the DigitalOcean system the converts Markdown into these articles would have stripped them out. It looks like they didn’t. I’ll see what I can to fix it. Thanks for pointing that out.
You’re welcome. I just want to make sure someone doesn’t inadvertently gunk up their setup.
I’ve gone through and fixed those. Thanks for pointing that out!
Hi @michaelholley , your tutorial is really detailed, I set up and run successfully in my droplet. I love it, but I have a dump question, could you help me?
I made a mistake, answered “yes” in this question:
How can I turn it into “no”?
Thank you.
In your home folder is a file called ‘.google_authenticator’. The answers to all questions the setup asks you are recorded here. However I’ve looked at mine and a sample conf file that I marked no on the same question you did and I can’t easily discern the answer. So really the easiest thing to do is delete the file ~/.google_authenticator and re-generate it.
Make sure you open a new shell to test the new code so you don’t get locked out while testing. Sorry I wish I it was the config file was simpler and easier to modify after the fact.
Thank you for really fast reply :D I will try to research more about this before doing the delete-and-regenerate way.
Will the multi factor auth work for non networked ubuntu servers? I have a few servers that cannot connect to internet where I want multi factor auth to work.
Hi @nolim073. When you say non-networked do you mean no network period, or there is an internal LAN they are connected to, but that LAN isn’t connected to the general Internet?
If they are not connected to a network at all then this article pretty much doesn’t apply, since this article is for SSH authentication, which requires a network connection. Along those lines, I’m pretty sure you can set up Google Authenticator as a dual factor for a console login, in addition to your username and password, but that is beyond the scope of this article.
Now if these systems are connected to an internal LAN and you will be using SSH to remotely connect, then yes this article can still apply. SSH keys nor the Google auth token require the Internet. The token is time based, as long as your computer’s time is in sync then it’ll work without an Internet connection.
Hopefully that answers your question.
Will FileZilla still work over STFP? Will it prompt me for a verification key when I connect?
I just downloaded FileZilla to test (I use Cyberduck on my Mac and it works) and it doesn’t seem to work. Maybe a FileZilla guru can point out how to configure it. Sorry.
I set up Wordpress after setup this multi factor auth, and seem like I can’t let WP update/upload via ssh2. How can I open an exception for wp-user only?
Well as long as you have the “nullok” in the PAM file and wordpress is using it’s own user and not the user you set up MFA on, then it should work just fine.
The nullok option means don’t enforce and if there isn’t a .google_authenticator file in the root of the user’s home directory, normal SSH auth is available. I’d recommend using a passwordless SSH key for that Wordpress user, however, since if you followed this article exactly then you disable password based authentication in step three. Hopefully that helps.
I’ve followed your guide and it’s great and I was able to install and configure Google 2FA successfully. However, I’m required at work to supply a 2FA authentication method for our online servers and I’m also required to deploy it automatically to the environment. I’ve wrote the following script, which is supposed to configure the following:
The script can be found here: http://pastebin.com/VCNaYebx The command I’m using to configure google-authenticator is: /usr/bin/google-authenticator -t -d -f -r 3 -R 30 -W
When the script completes, and I run: “ssh localhost” I get the following error: ssh: connect to host localhost port 22: Connection refused
Your help is much appreciated!
Sorry it has taken so long to respond, busy times. I’m glad you were able to find a method for deploying 2FA to your servers in an automated fashion. As for why your test didn’t work could be a lot of different reasons.
When troubleshooting SSH add “-vv” after “ssh” and before anything else. Example: “ssh -vv localhost”. The double ‘v’ tells SSH to be verbose and it may point to what is blocking SSH. If you haven’t solved the problem hopefully that helps.
its working for me as documented in this post but i have one issue which i’m seeking for a solution. I have installed libpam-google-authenticator on my unbuntu system and configured sshd_config and pam.d/sshd files as given here! i am able to login fine without being challenged for token code with a message stating that “Authenticated with partial success.” Now if i run google-authenticator at next time login then it does prompt me for the verification code and it works fine again as expected. Now the issue here is - i am not really forcing user to login with 2FA and even if they dont install google-authenticator at the next login they can still login fine into linux system and continue to work without worrying about google-authenticator. What is the way to force them to login with google-authenticator and at the same time do not block there login so that can register with google-authenticator? Is there a way i can have some users, that i want to enforce 2FA, have go through google-authenticator registration during first time login? Really appreciate if you can help me resolve this. Thanks!
I wrote out a really indepth reply, and before posting I re-read your question and realized I answered the wrong question. :-D So let me see if I can answer the the right one.
In the PAM module (/etc/pam.d/sshd) there is the line you added to enable Google Auth. The last word on that line is ‘nullok’. This keyword tells PAM that this method of authentication is optional. Having said that, once a user enables Google Auth they have to use it to login (as long as the .google_authenticator files exists in the root of their home directory). If you removed the ‘nullok’ word and restarted sshd then every login would require authentication with Google Auth, and if their account wasn’t set up for it they can’t login.
So I’m going to make an assumption for the rest of the answer. I’m going to assume this system has new users added from time to time and even if you were to get everyone to enable Google Auth today, there will be a new user so enforcing Google Auth won’t work. But you want everyone to use Google Auth, and just creating a “policy” that 2FA is required isn’t enough. With those constraints this is what I would do.
find /home/* -name ".google_authenticator"
).Hopefully that answers your question, if not let me know what you actually meant.
Thanks @michaelholley for the speedy response. Well what you have replied is what i was looking for. I also thought of having some kinds of bash script to auto register the google authenticator for the users that i want to enforce 2FA but i was wondering if there was module that i can simply plug in which can do this for me. Anyways let me try this with bash as you suggested and see how it works! Many Thanks, Deepak
Hi I followed the steps above and now I am not able to log into my VM. I am getting Permission denied (publickey), I am using Secure Shell to log in. I am not even getting prompted to enter google authentication code. Please help!
Below is the message I am getting.
There is no right to privacy on this device. Permission denied (publickey). NaCl plugin exited with status code 255. ®econnect, ©hoose another connection, or E(x)it?
Below is the full output with ssh -v
OpenSSH_6.4, OpenSSL 1.0.1e-fips 11 Feb 2013 debug1: Reading configuration data /etc/ssh/ssh_config debug1: /etc/ssh/ssh_config line 51: Applying options for * debug1: Connecting to MYIP [MYIP] port 22. debug1: Connection established. debug1: identity file /home/device_admin/.ssh/id_rsa type -1 debug1: identity file /home/device_admin/.ssh/id_rsa-cert type -1 debug1: identity file /home/device_admin/.ssh/id_dsa type -1 debug1: identity file /home/device_admin/.ssh/id_dsa-cert type -1 debug1: identity file /home/device_admin/.ssh/id_ecdsa type -1 debug1: identity file /home/device_admin/.ssh/id_ecdsa-cert type -1 debug1: Enabling compatibility mode for protocol 2.0 debug1: Local version string SSH-2.0-OpenSSH_6.4 debug1: Remote protocol version 2.0, remote software version OpenSSH_6.7p1 Debian-5 debug1: match: OpenSSH_6.7p1 Debian-5 pat OpenSSH* debug1: SSH2_MSG_KEXINIT sent debug1: SSH2_MSG_KEXINIT received debug1: kex: server->client aes128-ctr hmac-sha1-etm@openssh.com none debug1: kex: client->server aes128-ctr hmac-sha1-etm@openssh.com none debug1: sending SSH2_MSG_KEX_ECDH_INIT debug1: expecting SSH2_MSG_KEX_ECDH_REPLY debug1: Server host key: ECDSA debug1: Host ‘MYIP’ is known and matches the ECDSA host key. debug1: Found key in /home/device_admin/.ssh/known_hosts:28 debug1: ssh_ecdsa_verify: signature correct debug1: SSH2_MSG_NEWKEYS sent debug1: expecting SSH2_MSG_NEWKEYS debug1: SSH2_MSG_NEWKEYS received debug1: Roaming not allowed by server debug1: SSH2_MSG_SERVICE_REQUEST sent debug1: SSH2_MSG_SERVICE_ACCEPT received Unauthorized access to this machine is prohibited. Disconnect IMMEDIATELY if you aren’t an authorized user with a valid account. All activities performed on this device may be logged, and violations of this policy may result in disciplinary action, and may be reported to law enforcement authorities.
There is no right to privacy on this device. debug1: Authentications that can continue: publickey debug1: Next authentication method: publickey debug1: Trying private key: /home/device_admin/.ssh/id_rsa debug1: Trying private key: /home/device_admin/.ssh/id_dsa debug1: Trying private key: /home/device_admin/.ssh/id_ecdsa debug1: No more authentication methods to try. Permission denied (publickey).
From what I can tell from your debug the server is waiting for a SSH key but the client isn’t providing one or isn’t providing the correct one. Did you checkout this article, https://www.digitalocean.com/community/tutorials/initial-server-setup-with-ubuntu-14-04, that details building an SSH key? If creating/installing the SSH key doesn’t work then I would recommend undoing everything you’ve done from this article with PAM and SSH and first make sure your can get in with just your SSH key.
Once that is working then you can start over and start enabling things again and test in new shell till everything works. The few people I’ve run into that had run into issues this solved it because they accidently missed a step which caused everything to break. Hopefully that gets you on the right track.
Thank you …I made a mistake not having a second terminal logged in. Lesson learned I was able to get in using the console and reverted the changed back to original state.
Is it possible to apply 2FA to only selected users? I have a service I’m trying to use that can’t support the 2FA so I need to disable it for a specific user. Thanks,
Yes. If you leave the ‘nullok’ line in the PAM.d module and then run the Google Authenticator app on the users you want then they will have 2fa but the users you don’t run it on will auth via the key as per normal. The nullok keyword tells PAM to use that auth method if possible but if not, then just skip it.
This is really cool! Is there any way to allow login with either a password and
libpam-google-authenticator
or SSH key?The way this article sets things up is to have multiple authentications used for each login, but if you don’t set it up for MFA then I think you can do what you are wanting. When doing the initial testing for this article the SSH key would skip password and google-authenticator authentication, I had to add a config option to require both.
Having said that, I don’t know off the top of my head what in my article needs to be modified to get your desired setup. You may want to look for a standard Google Authenticator login tutorial get that working and see if by adding an SSH key if it will skip Google authentication & password.
One thing that might present a problem is when using a CI/CD environment.
Local Environment/Build → Push to Master Branch on Github → CI/CD Notices Change, Runs Build → Success will SSH into server using key and update web content
I’m curious to see if anyone has any thoughts?
Sorry for the delay, shortly after you asked your question I was asked to update this article with the questions/answers in the replies, and so I was hoping to get that out soon and point to that article for your answer. But that is looking like it won’t happen quickly, so instead here is the answer.
With the guide I provided you can either use an SSH key and MFA or just an SSH key. To allow a CI/CD connection you’d need to create a service account with a password-less SSH key for that user. Then as long as you don’t generate a Google Auth token then you can login via SSH key only.
What enables MFA is running ‘google-authenticator’ as the user who needs it. As long as you don’t run that application for a user, MFA won’t be enabled. This is all assuming that in the pam.d/sshd you leave ‘nullok’ on the relevant auth line. That key word makes MFA optional, and it the only way to allow some users to have it and some user not. Hopefully that helps.
thanks for the helpful tutorial. is there a good practice to automate this for each user using
cloud-config
?I wish I had a good answer for this, and I’m sure it’s possible, but I don’t have any experience with cloud-config so I don’t know. Sorry.
This comment has been deleted
I’m getting
Permission denied (keyboard-interactive)
after Verification code enteredI had ssh key set, after google auth do I need another one?
Now I can’t enter into my server and that’s pretty huge problem.
How do I fix this?
This comment has been deleted
I was thinking about doing the same thing and moving the google_authenticator file to another host. How secure is this approach? If a hacker steals the contents of google_authenticator file is he going to be able to map it on his phone and generate tokens?
If someone were to steal the google authenticator settings file that lives at the root of a home directory, they would be able to generate the same code as the person who originally set it up. However, there is really only a few ways for them to obtain it:
In scenario one, it doesn’t matter if they get the file you’r hosed anyway. In scenario two, just use something like SSH to move the file from Server A to Server B and no one will siphon the key. Even if you pull it down to your machine, via SSH, and then push it up to the second server, via SSH. As for scenario three, treat this file as if it were a plain text file holding your password. Don’t store it in a git repo or a public place for ease of use. If you want to make it a part of a configuration management system so all systems use the same token as the second factor (not the most secure idea) then store the config, minus the secret, in the repo and put the secret in a secure secrets vault for your configuration management tool of choice.