Using Google Authenticator MFA with OpenVPN on Ubuntu 16.04

Recently I was asked to setup a VPN service where we could authenticate users by using Google's multi factor authentication (MFA). In this setup we will have an user accessing a VPN service, OpenVPN was my choice, and authenticating himself with a TLS certificate, an username, a password and a token generated each minute by the Google Authenticator app that the users can freely install themselves in their phone. Sounds a little overwhelming, right? Well, security can be like that sometimes, but a VPN service exposed in the internet to provide our staff a mean of access to our private infrastructure is critical, so we need to bear with these steps.

First, a disclaimer, I will not go over the whole process of installing OpenVPN on a Ubuntu server and configuring it. I will assume you know how to do this. I will only focus on the bits to enable MFA. I am sure you can find information on how to install and configure OpenVPN in the internet. But just because I try to be a nice guy, here is a cool article on how to achieve that.

OpenVPN Server Configuration

First and foremost you will need to install a couple of packages to your operating system:

apt install libqrencode3 libpam-google-authenticator

Further, create the user and groups that will be used to run the Google Authenticator binary to generate the tokens and finally create the directory where they will be stored:

addgroup gauth
useradd -g gauth gauth
mkdir /etc/openvpn/google-authenticator
chown gauth:gauth /etc/openvpn/google-authenticator
chmod 0700 /etc/openvpn/google-authenticator

Ubuntu will install the configuration file for the OpenVPN service usually in /etc/openvpn/vpn.conf unless you have changed that for some reason. Add the following line to that file to make OpenVPN use the authentication of users through PAM:

plugin /usr/lib/openvpn/ openvpn

PAM Configuration

We will create a simple PAM configuration file for OpenVPN in /etc/pam.d/openvpn containing the following:

auth required /lib/security/ secret=/etc/openvpn/google-authenticator/${USER} user=gauth forward_pass

OpenVPN Client Configuration

Usually what we send to the user so he can configure its VPN client is a file, and in the case of OpenVPN, is a text file that will contain information on how to contact and communicate with the OpenVPN server, the user certificates, routes, DNS servers, and all sorts of information that will be pushed locally to the user's computer once the VPN connection is established.

Again, I will not go over each of the configuration you may need in this file to properly setup the client because that will heavily depend on your environment and what you want out of the OpenVPN server. However, what you need to make the MFA work is these two lines here:

ns-cert-type server

System admins normally create a script that automatically generate this file for each of their users, I have mine here. But the part you need to pay attention to in the generate_mfa() function. There we will create a system user with no no login permissions, prompt a password request to it and generate the Google Authenticator token by running:

su -c “google-authenticator -t -d -r3 -R30 -f -l \”${MFA_LABEL}\” -s $MFA_DIR/${user_id}” — $MFA_USER

This command there will use the previously created user to run the command to generate the token for the user we created and store it inside the folder mentioned earlier. Note that it will prompt the following question which you can safely say "no".

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 you are using my script it will also request the user e-mail to send the final configuration directly to him (this will require a working SMTP relay in your server).

That's it folks! I wish you can make this work and if not, just let me know and I can clarify any steps further.