OpenBSD: Two-Factor OpenVPN Authentication With Yubikey


This article explains how to authenticate OpenVPN clients that connect to an OpenVPN server that runs on OpenBSD using a Yubikey as the second factor, in addition to the regular authentication that employs X.509 certificates.

OpenBSD provides an excellent support for the Yubikey as a part of its implementation of the BSDAuth framework. Unfortunately, the openvpn_bsdauth program that is supposed to bridge between OpenVPN and BSDAuth suffers from a number of shortcomings that call for a better solution:

  1. openvpn_bsdauth is not an OpenVPN plugin but an authentication script, hence
  2. In the call to auth_userokay(3), openvpn_bsdauth unconditionally sets the arguments style and type to NULL thus making it impossible to use anything but the defaults specified in login.conf(5).
  3. openvpn_bsdauth, while checking for membership of the user that is being authenticated in the group of the accounts that are allowed to connect, matches the user name as is thus making it impossible for the user to specify a desired authentication style during the connection by appending the style identifier to the user name after a colon (as described in auth_userokay(3)). The name of the group that is checked is hard-coded.

Below I am offering a program which is an alternative to openvpn_bsdauth. This program is much more flexible and somewhat more secure.

Installation

Configure OpenBSD for Yubikey Authentication

There are several places that explain how to do that. Please do not proceed until you are familiar with the setup. I recommend you first getting OpenSSH to work with Yubikeys: as a by-product, you will have a system that uses a stronger authentication of remote shell sessions, which is a good thing.

Setup OpenBSD Ports

You will need to install OpenVPN from the ports tree. Please follow the instructions given in OpenBSD FAQ. The explanation below assume that you use the default location of the ports tree, /usr/ports.

Download the Source Code

First, download bsdauth-20160320.tar.gz, which is a Git checkout of my repository. Second, download bsdauth-20160320.tar.gz.sig, which is the signature of the package. Third, download vadim@penzin.net.pub, which is the public key. When you have all the three files in the same directory, please verify the signature:

signify -V -p vadim@penzin.net.pub -m bsdauth-20160320.tar.gz
Please do not proceed if you see anything different from Signature Verified in the output. Please let me know if verification of the signature fails.

If you do not trust this signify(1) public key, you can verify it using my public PGP key (I have only one PGP key which is published and is not revoked, its ID is 0x009E3949, you can find this key on the nearest public PGP key server) and the following signature:

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v2

iQEcBAABCAAGBQJY2ToTAAoJEHX3u/sAnjlJD60H/3+zgmP9mKdds9dcut70ZsuA
huRfeTu7Qu19R3k2eVEFv4qbEX1ePR/RPGDyAiSTTvJmeyRj5If59l/wo2MbSVjh
wv53Y0B2TkEL1WoyGjPRuzomaztTdL92QW9iF48L8F5rYceJthwINuBxoVe9Id4J
zuptIFU/NHdcQx6H0wxxgrGZJ2x1ptxVLMU8zOwWLZGiMMzzxFL5KiRxzPBix1qu
1GHmtrlU7f/tBIK4JKw5Fxk7I9iYciyYHwoyTEOyZGLWeBRBKOfGW9VZhKECILLC
Gni6nCERnA8BdWylWJSgvAchwIVlKDAeR6D0MFZLEiVmLqDvpbHAWn2LvTrTqHg=
=TAvt
-----END PGP SIGNATURE-----

Extract the Source Code

tar xzf bsdauth-20160320.tar.gz
cd bsdauth-20160320

Patch and Install OpenVPN

On OpenBSD, a process that uses BSDAuth must belong to the auth group (otherwise it will not be able to execute BSDAuth's helper programs like login_passwd(8), login_yubikey(8), and such). The openvpn_bsdauth program works around this requirement by installing itself as a SGID binary that belongs to that group.

On OpenBSD, OpenVPN usually runs as the user _openvpn and the group _openvpn. One way of making OpenVPN being capable of using BSDAuth would be running it as the group auth but, at very least, this means breaking the default setup of the package that OpenBSD users are accustomed to.

A much better way of solving this problem is making the user _openvpn a member of the group auth which is a trivial task. There is a problem, however. This may sound surprising but OpenVPN does not initialize secondary (supplementary) groups when it switches to the GID/UID specified in the server configuration file with --user and --group directives.

To address this issue, I developed a set of patches that introduce an optional parameter to OpenVPN, --secondary-groups which tells OpenVPN to initialise secondary groups that the user specified with --user belongs to, just before it switches the UID.

To apply the patchset, copy the contents of the directory patches to the appropriate location of the ports tree:

doas cp ports/* /usr/ports/net/openvpn/patches
and proceed to the regular, straightforward installation of the OpenVPN port:
cd /usr/ports/net/openvpn
doas make install
cd -

Compile and Install the Plugin

make && doas make install
There will be a number of warnings about unused parameters, you can ignore them safely.

Plugin Parameters

The plugin, /usr/local/libexec/bsdauth.so has the following parameters, all of them optional:

style style
Sets the authentication style style, that is, specifies a particular authentication method, which usually involves an execution of one of the /usr/libexec/auth/login_* helpers.
type type
Sets the authentication type type, that is, specifies a named combination of authentication styles that must be defined in login.conf(5).
group group
Sets the name of the group group that the user who is being authenticated must be a member of in order for the authentication to succeed.
cache yes | no
Specifies whether the plugin should cache the list of members of the group that was specified with the parameter group. If the value of the parameter cache is yes then the plugin will resolve and store the list of users who are members of the group group during the initialisation of OpenVPN. This is the default behaviour. If the value of the parameter cache is no then the plugin will read group(5) on every authentication.
Bear in mind that the values of the style and of the type parameters are passed directly to the function auth_userokay(3), please find answers to all your questions regarding possible interactions of these parameters on its manual page. bsd_auth(3) also contains information that you may find valuable.

Configure the OpenVPN Server

If you decided to have auth as a secondary group of the user _openvpn then the server configuration file must contain the following:

user _openvpn
group _openvpn
secondary-groups
Obviously, you must not forget to add the user _openvpn as a member of the group auth in group(5).

If you decided not to use secondary-groups then the server configuration file must contain the following:

user _openvpn
group auth
Again, I recommend against this approach.

The plugin must be enabled in the server configuration file using the plugin option of OpenVPN. For instance, the following enables the plugin and specifies the authentication style yubikey and checking for membership in the group _openvpnusers:

plugin /usr/local/libexec/bsdauth.so style yubikey group _openvpnusers

Configure the OpenVPN Client

In the client configuration file, enable the username and password authentication using the option auth-user-pass.

The Complete Example: The Server Side

/etc/login.conf:

At the top level:

auth-vpn-defaults:auth-vpn=yubikey:	    
At the end of the definition of the class default:
        :tc=auth-vpn-defaults:

/etc/group:

The user _openvpn is a member of the group auth:

auth:*:11:_openvpn
The user vadimp is a member of the group _openvpnusers:
_openvpnusers:*:596:vadimp

openvpn.conf:

server 192.168.5.0 255.255.255.0
dev tun0
persist-key
persist-tun
keepalive 10 60
user _openvpn
group _openvpn
secondary-groups
dh dh1024.pem
ca ca.pem
cert cert.pem
key key.pem
plugin /usr/local/libexec/bsdauth.so type auth-vpn group _openvpnusers
route 192.168.5.0 255.255.255.0

The Complete Example: The Client Side

openvpn.ovpn:

client
ca ca.pem
cert cert.pem
key key.pem
auth-user-pass
dev tun
remote aa.bb.cc.dd 1194 udp
persist-key
persist-tun
(aa.bb.cc.dd is the public IP address or a FQDN of the OpenVPN server.)

Answers to Expected Questions

How do I select a non-default authentication style during authentication on the client?
Let us suppose that you have :auth-vpn=yubikey,passwd: in login.conf and type auth-vpn in the plugin options of the OpenVPN's server configuration. When you fill the user name in the authentication dialog on the client, entering a bare user name, for example, vadimp, selects the default authentication style of the method auth-vpn, i.e. yubikey (the first entry of the comma-separated list that appears after the equals sign). To select a non-default style of the method auth-vpn, the user must enter a user name that specifies the desired authentication style after a colon, i.e. vadimp:passwd. BSDAuth works this way in all compliant software: console login, OpenSSH authentication, etc.

How do I control the authentication style on the per-user basis?
It is not possible at present. In the future, I plan to implement a plugin option that will ignore the user name entered by the user and treat the common name (or another attribute) of the client certificate as the user name for the purpose of authentication. Thus, the administrator will be able to force a particular authentication style by issuing a client certificate for a common name of the form username:authentication style.

Vadim Penzin, March 20th, 2016


I hereby place this article along with the accompanying source code into the public domain.
You are welcome to contact me by writing to howto at this domain.
I publish this information in the hope that it will be useful, but without ANY WARRANTY.
You are responsible for any and all consequences that may arise as the result of using this information.