Signs of Triviality

Opinions, mostly my own, on the importance of being and other things.
[homepage] [index] [jschauma@netmeister.org] [@jschauma] [RSS]

Integrating Duo 2FA with OpenVPN

Over the last couple of days, I've been working on integrating Duo Security's Two-Factor Authentication with OpenVPN. While the instructions on Duo's website are great, Here are a few notes that may be helpful if you're doing the same.

Duo Security Logo
OpenVPN Logo


Install from an RPM

The plugin provided by Duo installs into /opt/duo via a Makefile. I generally dislike software on my machines that is not installed via the package management system, and targeting a Linux based server, I wrote the necessary glue to build an RPM from the files, which installs into /usr.

If you're interested in those changes, you can fetch them via my fork on GitHub. (It contains a few more modifications, as discussed below.)


Add support for "composite" password

The plugin provided by Duo Security is intended to be combined with certificates to authenticate. However, we briefly considered combining it with regular password authentication via LDAP, but that solution is not currently supported by the plugin. However, there are ways around that. This blog entry explains in detail what needs to be done to make this happen.

After reading those instructions, I updated the duo_openvpn plugin to optionally support this as well. Those changes are also available in my fork on GitHub, but it should be noted that there are a few usability concerns: the two plugins are executed independently from each other, meaning that if one fails, the other is still attempted. This can lead to confusion when the user enters their password wrong, but still gets a Duo notification (which naturally takes a bit longer).

In the end, we decided that the user interface of these "composite passwords" is rather suboptimal: asking the user to enter their (hopefully complex) password (which we expect many users to have stored in 1Password and would have to copy from there), followed by a comma, followed by the Duo token seems prone to errors. Either way, the code is there, if you're interested.


reneg-sec bug in OpenVPN < 2.2

I found out after much banging my head against the wall that there is a bug in OpenVPN versions prior to 2.2 where setting reneg-sec 0 (as is recommended by the Duo OpenVPN instructions) causes the hand-window parameter to be reset to zero as well.

This manifested itself in the following way: the user initiates the authentication process and OpenVPN hands off authentication to the duo_openvpn plugin. Since the handshake window is set to zero, OpenVPN will wait for zero seconds before it determines that the plugin did not succeed, and the connection will be terminated. A few seconds later, the user will get the Duo push notification...

I tried to change the hand-window and various other settings all to no avail, until I found this note in the OpenVPN Changelog:

Fixed an issue where if reneg-sec was set to 0 on the client, so that the server-side value would take precedence, the auth_deferred_expire_window function would incorrectly return a window period of 0 seconds. In this case, the correct window period should be the handshake window period.

For OpenVPN versions below 2.2, the only viable solution I've found is to set the reneg-sec parameter on the server to a reasonable value. For Duo authentication, where you don't want a client to replay a one-time token, you can set it to a large value (say, 8 hours or so).


Things I've learned about OpenVPN

When trying to figure out the reneg-sec bug, I first thought that the problem was in the Duo plugin, and hastily added a wait(2) to prevent the plugin from immediately returning. Duo's akgood pointed out to me, however, that that does not make OpenVPN very happy. For you see, OpenVPN is single-threaded, and can't do a whole lot while it's busy waiting for an authenticating subprocess to return.

That is, while you're waiting for a user to get authenticated, no traffic can go over the VPN, not even for other sessions. This is by design, by the way. So instead of blocking, OpenVPN handles this by using "deferred authentication", where a plugin executes and returns immediately. But the plugin still has to communicate back the success or failure to the OpenVPN daemon, and what better method of IPC to use but... that's right, local files.

When a plugin like the duo_openvpn plugin executes, it is passed the name of a temporary file to which it writes the authentication status when it returns, and OpenVPN's hand-window dictates how long the daemon should maximally wait to check this file.

I'm not sure if using temporary files in the local filesystem give me a particularly warm and fuzzy feeling, considering OpenVPN may run as an unprivileged user, and to be able to create/update these files it might end up writing them in /tmp etc., where I could imagine the risk of a symlink attack by another local user. But anyway, it is what it is.


Example Configuration Files

Here are the configuration files (or at least the relevant bits) I've used to set up Duo Two-Factor Authentication with OpenVPN:

/etc/openvpn-duo/server.conf:

local <IP address>
port <port>
proto udp
dev tun0
ca /etc/openvpn-duo/easy-rsa/keys/ca.crt
cert /etc/openvpn-duo/easy-rsa/keys/server.crt
key /etc/openvpn-duo/easy-rsa/keys/server.key
dh /etc/openvpn-duo/easy-rsa/keys/dh1024.pem
crl-verify /etc/openvpn-duo/easy-rsa/keys/crl.pem
keepalive 10 120

user root
group wheel
persist-key
persist-tun

status /var/log/openvpn-duo-status.log
log-append /var/log/openvpn-duo.log
verb 2
tls-server
tls-auth /etc/openvpn-duo/easy-rsa/keys/ta.key 0

# In order to chroot, we'd need to build the duo-tool and its dependencies
# into /etc/openvpn-duo/usr/local ...
#chroot /etc/openvpn-duo

script-security 3
tmp-dir /tmp

# [SPF-443] - 2-factor authentication via duo security
plugin /usr/local/lib/duo_openvpn.so <IKEY> <SKEY> <HOST>
# In version <2.2, we can't set this to 0, as that will also set
# hand-window to 0 (no, not a feature).  Set it to 10 hours, which
# seems reasonable.
reneg-sec 36000

Tunnelblick client configuration:

client
dev tun
remote-random
remote <IP address> <port>
auth-user-pass
auth-retry interact
ca ca.crt
key user.key
cert user.crt
cipher BF-CBC
reneg-sec 0
tls-client
tls-auth ta.key 1
keepalive 10 120
nobind

(Yes, I'm aware of the local root exploit for Tunnelblick (and the one for Viscosity). :-)


Anyway, now I live in the future! Now, when I connect, my computers are calling me on my cell phone to say hi. I like that. Thanks, Duo Security!

August 15th, 2012


[From Company Closed to Open Source] [Index] [Updating Jira tickets via mail]