Applying two-factor authentication to SSH logins with Duo Security

Adding Two Factor Authentication to your server is a great way to increase security. After doing research on a number of different implemententations for getting two-factor authentication enabled on SSH login sessions, I came to the conclusion Duo Security is the easiest to setup and most mature of the available solutions. Other solutions are Google Authenticator and Authy. Google Authenticator is not an option if you want to add multiple SSH keys to a single user (i.e. root) - it requires seperate user accounts. I discarded Authy as an option because it breaks scp, required typing over a 7-digit code every time you log in, I could not login to the site using Safari and text messages were sent twice each time.

This guide aims to get you up and running with Duo Security as quickly as possible. The quickest way would be to run the ansible playbook at the bottom of this guide, but I suggest following the guide step by step to ensure that you know what you are doing (and what the playbook does). Note: It is also possible to configure Duo with PAM, but this is a little more complex. For now I will focus on getting the login_duo program to work, but we will compile and install the Duo Unix package with PAM support should you want to use it later.

Getting an Integration and Security key

To use Duo Security, you need two keys: an Integration and a security key. These will be used in the configuration files below. To get the keys, create an application in the Duo Security Admin dashboard.

Install Duo Security dependencies

The Duo Security login binary depends on the PAM and OpenSSL library development headers. On Debian and Ubuntu the packages are called libpam0g-dev and libssl-dev and can be installed by executing:

$ sudo apt-get install libpam0g-dev libssl-dev

On RedHat-based systems install the pam-devel and openssl-devel packages:

$ sudo yum install pam-devel openssl-devel

Download the Duo Security unix source

Download and unpack the latest version of the Duo Security unix source code:

$ wget https://dl.duosecurity.com/duo_unix-latest.tar.gz
$ tar xzvf duo_unix-latest.tar.gz

Install the Duo Security binaries

Configure, compile and install the Duo Security binaries:

$ cd duo_unix-1.9.18
$ ./configure --prefix=/usr --with-pam
$ make
$ sudo make install

Configure /etc/duo/login_duo.conf

Make sure the /etc/duo/login_duo.conf file consists of the following lines:

[duo]
ikey = {{ duo_integration_key }}
skey = {{ duo_secret_key }}
host = {{ duo_api_host }}
pushinfo = yes
failmode = secure
autopush = yes

Replace the three values in double curly brackets with your own information. Notice the three other settings: pushinfo = yes makes duo send additional information to the authenticator client, such as which IP is trying to SSH into the server. failmode = secure indicates we do not want you to be able to log in if Duo Security is unavailable or in maintenance. autopush = yes means we will automatically receive a push message to our phone, instead of having to choose an authentication method from a menu.

Configure /etc/duo/pam_duo.conf

Symlink the /etc/duo/login_duo.conf file to /etc/duo/pam_duo.conf. As stated previously, we will not be using PAM, but if you decide you want to use Duo with PAM, follow this guide.

$ sudo ln -s /etc/duo/login_duo.conf /etc/duo/pam_duo.conf

Configure SSH

Now that the Duo program is configured, we will configure SSH to use it. To do this, all you need to do is modify your authorized_keys file to invoke the login_duo binary for every public key you wish to enable it for:

command="/usr/sbin/login_duo" ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDlEJbk7t/XSl7EABuKHZgRuipHJ3bDKMurZxeMaJA5P7ZwmESqcsjccCT9Ep2HF21LhnWIfMWFKv+cyzmhhkRLOmFuGcHB7vZH5RGVjncChzwJx7uOVOkajk/ECJDZdh8gdUdUC8BXe3OkdiojgakiEMhx8/5m0u1WLGLf2jIun4efXORlTK3HRoNBwk5aLUTtehnssELXczeLtMdpeIjCl8iZww0v2zD9FqkUvwd+DkGw4ijtADjH0UA/p6uaS8wV9Tu1FwfiM3yrnQrRT2x/vWNNaqiWZbBYnL2Gr+jpeJeSuFsN2uB6HaTbAt2BP/kZvdszt91Q7ixpe3htdLpv7DaRcxfh7OfkpjJ8+9eZ9iqrqxDWhK1SQyFWKnncQAQgca4FksmmFM7K7RuXwTx09kaCOwQkFpnu+rAMKUVjKiJuy7GdszgZczpz/3xDFisgD/+myBx2ITkhtPTLeadVKyTyp/0Plb03UWOcGOhNdUna/QrT8ZSPQrVH3ejEk8uPcYmv/mEYVqLBlauB+E6CIkYRpraGZySfk+9sKcFnZPC++D61jCXqU3eofZG2QK69pdn25cZlihAdCy4FpHDJ8Fd5V1w8Pmy2SxpVehggnQe0S4N0cIcnpRr0JaE7k6al429LLMPBcWKzo4pnGp9ugvuicDckp7Hem2MgeUR+vw== rvdhof

This will execute the login_duo binary when you use this public key to login to the server. If the username of the account you are logging in to does not match your Duo account name, specify it with the -f username option:

command="/usr/sbin/login_duo -f rvdhof" ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDlEJbk7t/XSl7EABuKHZgRuipHJ3bDKMurZxeMaJA5P7ZwmESqcsjccCT9Ep2HF21LhnWIfMWFKv+cyzmhhkRLOmFuGcHB7vZH5RGVjncChzwJx7uOVOkajk/ECJDZdh8gdUdUC8BXe3OkdiojgakiEMhx8/5m0u1WLGLf2jIun4efXORlTK3HRoNBwk5aLUTtehnssELXczeLtMdpeIjCl8iZww0v2zD9FqkUvwd+DkGw4ijtADjH0UA/p6uaS8wV9Tu1FwfiM3yrnQrRT2x/vWNNaqiWZbBYnL2Gr+jpeJeSuFsN2uB6HaTbAt2BP/kZvdszt91Q7ixpe3htdLpv7DaRcxfh7OfkpjJ8+9eZ9iqrqxDWhK1SQyFWKnncQAQgca4FksmmFM7K7RuXwTx09kaCOwQkFpnu+rAMKUVjKiJuy7GdszgZczpz/3xDFisgD/+myBx2ITkhtPTLeadVKyTyp/0Plb03UWOcGOhNdUna/QrT8ZSPQrVH3ejEk8uPcYmv/mEYVqLBlauB+E6CIkYRpraGZySfk+9sKcFnZPC++D61jCXqU3eofZG2QK69pdn25cZlihAdCy4FpHDJ8Fd5V1w8Pmy2SxpVehggnQe0S4N0cIcnpRr0JaE7k6al429LLMPBcWKzo4pnGp9ugvuicDckp7Hem2MgeUR+vw== rvdhof

Thats it! If you followed the steps in this guide, the next time you log in to your server with SSH, you will be asked to confirm the login:

Ricks-MacBook-Pro:~ rick$ ssh rick@vps01
Enter passphrase for key '/Users/rick/.ssh/id_rsa':
Autopushing login request to phone...

You will now get a Push Message on your phone:

Duo Mobile Screenshot

After tapping the big green button, you will be logged in:

Success. Logging you in...
rick@vps01:~$

Ansible Playbook

Note: the following will only work on Debian based servers.

To run all the above commands (except for configuring SSH) on one or more servers using Ansible, save the following as a .yml file:

---
- hosts: test

  vars:
    - duo_url: "https://dl.duosecurity.com/duo_unix-latest.tar.gz"
    - duo_version: "1.9.18"

  vars_prompt:
    - name: duo_integration_key
      prompt: Duo integration key
      private: false
    - name: duo_secret_key
      prompt: Duo secret key
      private: false
    - name: duo_api_host
      prompt: Duo API host
      private: false

  tasks:
    - name: install dependencies
      apt:
        name: "{{ item }}"
        update_cache: true
        cache_valid_time: 86400
      with_items:
        - libpam0g-dev
        - libssl-dev

    - name: download duosecurity
      get_url:
        url: "{{ duo_url }}"
        dest: /usr/src/duo_unix-latest.tar.gz

    - name: unpack it
      unarchive:
        src: /usr/src/duo_unix-latest.tar.gz
        dest: /usr/src/
        copy: false

    - name: configure duo
      command: ./configure --prefix=/usr --with-pam
      args:
        chdir: /usr/src/duo_unix-{{ duo_version }}
        creates: /usr/src/duo_unix-{{ duo_version }}/Makefile

    - name: compile duo
      command: make -j{{ ansible_processor_cores }}
      args:
        chdir: /usr/src/duo_unix-{{ duo_version }}
        creates: /usr/src/duo_unix-{{ duo_version }}/login_duo/login_duo

    - name: install duo
      command: make install
      args:
        chdir: /usr/src/duo_unix-{{ duo_version }}

    - name: configure login_duo.conf
      template:
        src: login_duo.conf.j2
        dest: /etc/duo/login_duo.conf

    - name: configure pam_duo.conf
      template:
        src: login_duo.conf.j2
        dest: /etc/duo/pam_duo.conf

Save the following file as templates/login_duo.conf.j2:

[duo]
ikey = {{ duo_integration_key }}
skey = {{ duo_secret_key }}
host = {{ duo_api_host }}
pushinfo = yes
failmode = secure
autopush = yes

You can now run the playbook:

ansible-playbook -i <your inventory file> playbook.yml

It will prompt you for the needed Duo variables and do everything else automatically. This makes it easy to deploy Duo on multiple servers simultaneously!