Securing Your Server

If you have servers accessible from the Internet, either hosted on the cloud or perhaps a home server that you allow incoming connections to, then you've no doubt worried about how to keep your server secure.

In this post we'll demonstrate how you can quickly and easily implement two-factor authentication for SSH connections to your server.

What is Two Factor Authentication?

Two-Factor Authentication is a very secure way to protect your online accounts. It works by requiring you to identify yourself using two different things when you log-in to a site. The first is something you know (like a username/password combo) and the second is tied to something ‘you have’ (like a smartphone).

With Two-Factor Authentication you would use a username/password and a token. The token is a unique number that your smartphone generates and is constantly changing. Because only your smartphone can generate that number and only you own the smartphone, even if someone was able to guess or steal your password if you enable two-factor authentication they wouldn’t be able to hack your account without stealing your cellphone too.

What is Authy?

As you might guess, there's a number of different technologies available to generate tokens on a smartphone and not all websites use the same method. Authy makes it really easy to use Two-Factor Authentication for your online accounts by providing an app that supports a variety of two-factor technologies, letting you keep all your tokens in one place.

For the purposes of this post, we'll assume you're already set up with Authy. If not, go read this first - https://authy.com/learn-more/.

Getting Started

Sign up for an Authy API Key

  • Register an account at https://www.twilio.com/console/authy/getting-started
  • Click Setup Application
  • Click the '+' button to create a new application and call it something like 'homeserver'
  • Click on your new application
  • Choose Settings
  • Click to view your Production API Key and save it somewhere (you'll need it later)

Install authy-ssh

SSH in to your server as normal and download the authy-ssh installer:

[email protected]:~# curl -O 'https://raw.githubusercontent.com/authy/authy-ssh/master/authy-ssh'  
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 18676  100 18676    0     0  53539      0 --:--:-- --:--:-- --:--:-- 53666  

Run the auth-ssh installer. Don't worry, we'll take a closer look at what this actually does later. You'll be asked for the Authy API key that you got from the web console earlier. You'll also be asked some config question; thankfully, they're written in plain english...

If authy-ssh can't reach api.authy.com for some reason, do you want to block all access to the server? Personally, I've chosen to not to. It's not quite as secure, but makes it less likely I'm going to lock myself out due to some networking or DNS issue.

[email protected]:~# sudo bash authy-ssh install /usr/local/bin  
Copying authy-ssh to /usr/local/bin/authy-ssh...  
Setting up permissions...

Enter the Authy API key: G3xxxxxxxxxxxxxxxxxxxxxxxxxxxxCj

Default action when api.authy.com cannot be contacted:

  1. Disable two factor authentication until api.authy.com is back
  2. Don't allow logins until api.authy.com is back

type 1 or 2 to select the option: 1  
Generating initial config on /usr/local/bin/authy-ssh.conf...  
Adding 'ForceCommand /usr/local/bin/authy-ssh login' to /etc/ssh/sshd_config

Checking the validity of /etc/ssh/sshd_config file...  
Could not load host key: /etc/ssh/ssh_host_ed25519_key  
    MAKE SURE YOU DO NOT MOVE/REMOVE /usr/local/bin/authy-ssh BEFORE UNINSTALLING AUTHY SSH

Configure 2FA

Now we want to configure your user account to use Authy when you login. The command is of the format authy-ssh enable <username> <email address> <country-dialing-code> <your phone number>. This associates your linux account with the phone number you use with Authy. Once registered, a new application will appear the next time you load Authy.

[email protected]:~# sudo authy-ssh enable bryan [email protected] 44 7850111111

    Username:    bryan
    Cellphone:    (+44) 7850111111
    Email:    [email protected]

Do you want to enable this user? (y/n) y  
User was registered  

So far, so good. Now let's restart the sshd service so that our changes can take effect and give it a test.

[email protected]:~# sudo service sshd restart  
[email protected]:~#  

Demonstration

Rather than disconnect, we'll simply SSH to the local loopback interface. We'll get asked for our username/password as normal, and then we'll need to enter our Authy Token from our phone app.

[email protected]:~# ssh [email protected]  
[email protected]'s password:  
Authy Token (type 'sms' to request a SMS token): 1234567  
Good job! You've securely logged in with Authy.  
[email protected]:~#  

Well done, it worked! Before we congratulate ourselves too much, why don't we take a peek at how this all works.

How Does it Work?

auth-ssh adds a ForceCommand directive to the end of your /etc/ssh/sshd_config file. From the manpage...

ForceCommand forces the execution of the command specified, ignoring any command supplied by the client and ~/.ssh/rc if present. This applies to shell, command, or subsystem execution. It is most useful inside a Match block. The command originally supplied by the client is available in the SSH_ORIGINAL_COMMAND environment variable.

Let's take a look:

[email protected]:~# sudo tail -n1 /etc/ssh/sshd_config  
ForceCommand /usr/local/bin/authy-ssh login  

So, whenever someone tries to log in to your server, the SSH daemon will force execution of the /usr/local/bin/authy-ssh script that you installed. That script is what prompts the user for their Authy Token, and decides whether they should ever reach a console prompt.

The authy-ssh script has a config file too, which helps us understand what's going on. We can see the succesful authentication banner, along with the Authy API key used to connect to the api.authy.com web service. And, you'll remember I chose to disable 2FA if there's a problem reaching that web service. Finally, you can see that the local bryan user is now associated with an ID (used to identify your Authy account)

[email protected]:~# sudo cat /usr/local/bin/authy-ssh.conf  
banner=Good job! You've securely logged in with Authy.  
api_key=G3xxxxxxxxxxxxxxxxxxxxxxxxxxxxCj  
default_verify_action=disable  
user=bryan:2213814  

Improvements

Earlier, we demonstrated that authy-ssh was doing its thing by attempting to SSH into the local loopback interface and we were duly challenged for an Authy token. But is that really want we want, or would we like to have a little more control about which users should be subject to 2FA or perhaps exclude certain safe IP addresses?

For example, for a machine behind a strong firewall, we may choose to only apply 2FA to connections coming from the Internet, rather than our local network (or the loopback interface).

Enter Match blocks - which you may recall from the ForceCommand manpage! Well, from the manpage once more:

Match introduces a conditional block. If all of the criteria on the Match line are satisfied, the keywords on the following lines override those set in the global section of the config file, until either another Match line or the end of the file.

That sounds promising - let's look at a worked example:

[email protected]:~# tail -n2 /etc/ssh/sshd_config  
Match Address *,!127.0.0.1,!192.168.0.0/24  
    ForceCommand /usr/local/bin/authy-ssh login

So, if the incoming connection matches all of the criteria on the first line, then they will be forced to use 2FA. If one of the criteria is false, then the ForceCommand directive is ignored (and standard authentication will happen as normal).

In this case, if the incoming connection comes from any address and that address is not (!) the local loopback interface (127.0.0.1) and that address is not (!) part our private network (192.168.0.0/24), then the user should be forced through to authy-ssh for additional security measures. If the incoming address is the loopback or our network, then the criteria is false and thus normal authentication will apply.

Prove it!

Oh, you want to see that work? Okay...

Connection to the local loopback interface

[email protected]:~# ssh [email protected]  
[email protected]'s password:  
Welcome to Ubuntu 16.04.2 LTS (GNU/Linux 4.4.0-62-generic x86_64)

[email protected]:~#  

Connection from the local network

[email protected]:~# ssh [email protected]  
[email protected]'s password:  
Welcome to Ubuntu 16.04.2 LTS (GNU/Linux 4.4.0-62-generic x86_64)

[email protected]:~#  

Connection from anywhere else

[email protected]:~$ ssh [email protected]  
[email protected]'s password:  
Authy Token (type 'sms' to request a SMS token): 1234567  
Good job! You've securely logged in with Authy.  
[email protected]:~#