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.)
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.
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).
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.
Here are the configuration files (or at least the relevant bits) I've used to set up Duo Two-Factor Authentication with OpenVPN:
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
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
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