In a previous blog post, I described my experience with setting up VyOS under KVM with an accompanying configuration for an L2TP/IPSEC VPN for laptops, iDevices, etc.

For various reasons, including the ability to run over TCP/443 for better NAT traversal, I wanted to get OpenVPN working, which is what this article is about.

While one can run OpenVPN with username/password, the recommended way to run it is with both client and server certs. Indeed, the OpenVPN Inc client for iOS actually wants to be handed a single file with all of the certificates and keys embedded in it, known as a “unified format” file. Even if you decided you didn’t want to go to the trouble to run PKI you’re still on the hook for using easy-rsa to generate Diffie-Hellman parameters as well as a CA key and a server key (which is signed by the CA key). That’s most of the way to running PKI, all you have to do is gen up certificates for your users. So no, using passwords won’t save work. And running your own CA with easy-rsa is not difficult. Just do it. :-)

A copy of easy-rsa comes in your VyOS distribution, and you can run your CA there, albeit with the drawback that if your router gets popped, your CA is compromised. You can also snag it from Github. Keeping your CA on thumb drives (multiple backups) in a safe meets the spirit, though not the letter, of FIPS 140-2 Level 2. You probably want to do it this way if you’re doing this in anything resembling a production environment. For playing around in the lab… knock yourself out. It’s all already on your VyOS box.

Jason Schaefer has an excellent introduction to getting OpenVPN running on VyOS, which was invaluable in getting up and running my first time. Being more than two years old, it shows its age a little bit in the form of comments such as “Increasing KEY_SIZE to 2048 is recommended”, when at this writing in 2016 a more apt sentiment might be “All your friends will laugh and call you an amateur if you don’t crank your DH key size up to 2048 bits”. So do it. It’ll take a bit of time to run on an Intel cpu (2m25s on my 2.8 GHz I7-based 2015 MacBook Pro) and correspondingly much longer on some random embedded ARM or MIPS device (11m35s on an ODROID U3 1.7 GHz Exynos 4412 ARM Cortex-A9). Patience. Go have a cup of coffee or something.

Here’s a sample configuration is done on VyOS 1.1.7 (Helium) with a single virtualized interface (virtio). It implements OpenVPN for talking to an iOS device using the OpenVPN Technologies “OpenVPN Connect” app. It’s also been tested with Tunnelblick.

This configuration includes a “NAT Out” pool of addresses (192.168.89/24). To my disappointment I was unable to get it running with static addresses outside of the “vpn pool” as I was with L2TP. Tried setting up a second openvpn interface (vtun1), but that made VyOS unhappy, and the config for vtun0 was missing. It seems that the easy way out for this is to split the functionality for static IPv4 addresses is to stand up a second instance. Obviously this is a pretty annoying attribute and if I feel motivated I may see if I can write code and submit patches to fix this behavior since it seems to be enforced in the UI at commit time for “no good reason”.

Speaking of things that are a little disappointing, after a half-hearted attempt I didn’t have any success in configuring IPv6 on the VyOS end of the OpenVPN tunnel (though I’ve successfully configured IPv6 elsewhere on VyOS and it seems to work properly). Clues via the usual channels would be welcome.

Security considerations: To reiterate from the last article, we’re running sshd on an alternate port. We’re under no illusions about this providing any kind of security improvement but it sure keeps the amount of crap in the logs down. The NAT provides a modicum of protection or the addresses that are in the NAT pool, but if you manage to get static addresses running be aware they’re completely unfiltered and hanging wide out there in the breeze. Firewall policy, implementation, and testing are left as an exercise to the reader. A brief word about the source validation on the OpenVPN interface - you may wonder why this is important when everything is going to get NATted out anyway. TL;DR - this comes from a work in progress for OOB access where different people get different IP addresses and will be subsequently filtered so that they can only talk to certain BMC/IPMI/global zones inside the secure enclave.

vyos@openvpntest:~$ show configuration 
interfaces {
ethernet eth0 {
    duplex auto
    smp_affinity auto
    speed auto
loopback lo {
openvpn vtun0 {
    ip {
	source-validation strict
    local-port 443
    mode server
    protocol tcp-passive
    replace-default-route {
    server {
    tls {
	ca-cert-file /config/auth/ca.crt
	cert-file /config/auth/openvpntest-server.crt
	dh-file /config/auth/dh2048.pem
	key-file /config/auth/openvpntest-server.key
nat {
source {
    rule 5000 {
	outbound-interface eth0
	source {
	translation {
	    address masquerade
protocols {
static {
    route {
	next-hop {
service {
ssh {
    port 2200
system {
config-management {
    commit-revisions 20
console {
    device ttyS0 {
	speed 9600
login {
    user vyos {
	authentication {
	    encrypted-password ****************
	level admin
package {
    auto-sync 1
    repository community {
	components main
	distribution helium
	password ****************
	username ""
syslog {
    global {
	facility all {
	    level notice
	facility protocols {
	    level debug
time-zone UTC

Before I was able to have this configuration work, though, I had to do initial setup of the CA… in the case of this lab virtual router, it’s on the router itself (see above for caveats on doing this).

On VyOS, /config is preserved across upgrades, so if you put the CA there you won’t stomp on it when you upgrade the running image:

cp -pr /usr/share/doc/openvpn/examples/easy-rsa/2.0/ /config/easy-rsa2
vi /config/easy-rsa2/vars

In the bottom of /config/easy-rsa2/vars are a bunch of variables that you will want to localize. Set your KEY_SIZE to 2048 or risk your professional reputation.

By executing the vars file in a non-forked shell, we set all of or environment variables:

cd /config/easy-rsa2/
source ./vars

Run only once, to clean out any potential old cruft:


Build the CA:


Build the dh params file. Go have that cup of coffee you were thinking of.


Time to build the keys for this server (i.e. the VPN server). Note that the OpenVPN clients that I’ve used don’t seem to care whether the CN checks out to the hostname to which they think they’re talking, they do verify the extended key usage constraints, to wit, “X509v3 Extended Key Usage: TLS Web Server Authentication”. Why is this important? If the only constraint verified was “not revoked and signed by the proper CA”, then anyone who had a client certificate and could goof with routing would be able to spoof the VPN server. Not good. Client certificates have a different extended key usage constraint: “X509v3 Extended Key Usage: TLS Web Client Authentication”.

./build-key-server openvpntest

Now, we need to put the CA cert and all the other important stuff for the VPN server to do its job in /config/auth:

sudo cp /config/easy-rsa2/keys/ca.crt /config/auth/
sudo cp /config/easy-rsa2/keys/dh2048.pem /config/auth/
sudo cp /config/easy-rsa2/keys/openvpntest-server.key /config/auth/
sudo cp /config/easy-rsa2/keys/openvpntest-server.crt /config/auth/

Having set all that up, we can build a client certificate pair:

./build-key rs

and you’ll get some keys ready to go in /config/easy-rsa2/keys. At this point, you could build an rs.ovpn file that looked like this:

proto tcp
remote-cert-tls server
verb 2
dev tun0
cert rs.crt
key rs.key
ca ca.crt
remote 443

and copy all four files (rs.ovpn, rs.crt, rs.key, ca.crt) over to your favorite device. But the iDevices seem to want a file in “unified” format, which embeds the files inside one big file, so I wrote a quick and dirty shell script:

echo "client"
echo "proto tcp"
echo "remote-cert-tls server"
echo "verb 2"
echo "dev tun0"
echo "remote 443"
echo "<ca>"
) > $

cat ca.crt >> $

echo "</ca>"  >> $
echo "<cert>" >> $

cat $1.crt >> $

echo "</cert>"  >> $
echo "<key>"  >> $

cat $1.key >> $

echo "</key>" >> $

exit 0

Now, when you run this script like so:

./mkovpn rs

you’ll end up with, which is a unified format file with all the certs in it.