Skip to content

Fast & secure L2TP alternative: Unifi OpenVPN with Radius authentication

Development on mobile devices goes fast, but our network setups are staying behind. And that’s recently leading to L2TP unsecure warnings about missing IKEv2. Hence the requirement for a secure OpenVPN & Radius based connection into my network.

Initially I set the VPN up with the out of the box option you have in the user interface using L2TP. It worked, but it is slow. And now it’s even outdated. On Android 12 you cannot connect to the out of the box L2TP setup that Unifi provides.

OpenVPN

OpenVPN has now been around for more than 2 decades and as the moment of writing in its version 2.5.x release cycle. It uses the TCP or UDP protocol, making it more desirable over other VPN solutions which have alternative ways of communicating. This is important to me since I never want to be blocked by anyone, and that’s what OpenVPN can provide me.

On the Unifi USG-3P OpenVPN is being provided with version 2.3.2. Version 2.3 was the first version to deliver IPv6 support and has been publicly available since 2012.

Server setup

To be able to setup OpenVPN, we’ll first have to add logic to be to create keys to sign the communication. I’ve identified 2 possible strategies for this, either generate locally, or on the USG, with the latter to be way slower.

Local Easy-RSA

# install EasyRSA 3.x on a Mac
brew install easy-rsa
easyrsa init-pki

# Generate keys and give it a common-name like "OpenVPN CA"
easyrsa build-ca nopass

# Server key with common name "server"
easyrsa build-server-full server nopass

# Create one or more client certificates
easyrsa build-client-full client(1/2/3) nopass

# Build Diffie-Helman
easyrsa gen-dh

# Now your done, fetch the relevant info from /usr/local/etc/pki
# and secure copy ca.crt, dh.pem, server.crt and server.key
# to /config/auth/keys on the USG

Easy-RSA on the USG

sudo bash
curl -O http://ftp.us.debian.org/debian/pool/main/e/easy-rsa/easy-rsa_2.2.2-1_all.deb
sudo dpkg -i easy-rsa_2.2.2-1_all.deb

# Generate Keys
cd /usr/share/easy-rsa
. vars
./clean-all
# Give it a common-name like "OpenVPN CA"
./build-ca

# Set the common name to “server”
# Answer yes to signing the certificate and comitting it.
./build-key-server server

# Create one or more client certificates
./build-key client(1/2/3)

# Build Diffie-Helman (this will take a while)
./build-dh

# Copy the generated keys
mkdir /config/auth/keys/
cp keys/* /config/auth/keys/

After this is finished we have the ability to start configuring our 2nd authentication measure on the Unifi Controller.

Radius

Before we configure the OpenVPN server on the USG, we need to enable the Radius server as a 2nd security measure. First enable the Radius Server via the Controller UI under Settings > Advanced features > Radius. Also create a secret and take note of that for later usage.

Radius configuration in the Unifi Controller
Enable Radius server and set a secret

After enabling the server we can also add users that later can be used to connect to the network. Important is to only set the username and password, there is no tunnel type & tunnel media type necessary.

Radius user setup

OpenVPN Radius connection

On the USG we have to connect the possibility of identifying users based on Radius configuration from the OpenVPN process. The necessary software exists on the USG, but configuration needs to be done on the CLI.

SSH into the USG and navigate to the /config/user-data directory. Create new directory there named openvpn_radius_config with 2 files inside it:

  • /config/user-data/openvpn_radius_config/pam_radius_auth.conf
# INTERNAL IP OF THE USG & RADIUS SECRET
USG-INTERNAL-LAN-IP RADIUSSECRET
  • /config/user-data/openvpn_radius_config/openvpn
auth sufficient pam_radius_auth.so debug
account sufficient pam_permit.so
session sufficient pam_permit.so

The only reason that these files are stored here is to have a place where we can read them from to facilitate the provisioning process. To be able to automate that we need to hook into the provisioning process. To do so, create another file called /config/scripts/postprovision.sh and make it executable by running chmod +x on it. Afterwards add the following contents to it:

#!/bin/vbash
readonly logFile="/var/log/postprovision.log"

source /opt/vyatta/etc/functions/script-template

echo "$(date) - Beginning post provision steps" >> ${logFile}

#restore the openvpn-radius configuration
cp -f /config/user-data/openvpn_radius_config/pam_radius_auth.conf /etc
cp -f /config/user-data/openvpn_radius_config/openvpn /etc/pam.d/openvpn

#the following lines remove the postprovision scheduled task
#do not modify below this line

configure >> ${logFile}
delete system task-scheduler task postprovision >> ${logFile}
commit >> ${logFile}
save >> ${logFile}
#exit

#end no edit

exit

echo "$(date) - Finished post provision steps" >> ${logFile}

Provisioning

Since above configuration was set up, we’ve actually not enabled anything on the machine yet. Now it’s time to connect the dots and have the setup become part of the provisioning process. The provisioning process is managed by the Unifi Controller, in my case running on a Unifi Cloud Key.

Provisioning this specific setup can only be managed via the CLI. After SSH’ing into the Cloud Key to make the setup repeatable we have to create a file config.gateway.json in the /usr/lib/unifi/data/sites/default directory.

{
 "firewall": {
  "name": {
   "WAN_LOCAL": {
    "rule": {
     "20": {
      "action": "accept",
      "description": "Allow OpenVPN clients in",
      "destination": {
       "port": "443"
      },
      "log": "disable",
      "protocol": "tcp"
     }
    }
   }
  }
 },
 "interfaces": {
  "openvpn": {
   "vtun0": {
    "encryption": "aes256",
    "mode": "server",
    "server": {
     "push-route": "192.168.1.0/24",
     "name-server": "192.168.1.1",
     "subnet": "192.168.10.0/28"
    },
    "openvpn-option": [
     "--keepalive 8 30",
     "--comp-lzo",
     "--user nobody --group nogroup",
     "--plugin /usr/lib/openvpn/openvpn-auth-pam.so openvpn",
     "--username-as-common-name",
     "--verb 1",
     "--proto tcp6",
     "--port 443",
     "--port-share 192.168.1.200 443",
     "--push dhcp-option DNS 192.168.1.1",
     "--push route 192.168.1.0/24"
    ],
    "tls": {
     "ca-cert-file": "/config/auth/keys/ca.crt",
     "cert-file": "/config/auth/keys/server.crt",
     "dh-file": "/config/auth/keys/dh2048.pem",
     "key-file": "/config/auth/keys/server.key"
    }
   }
  }
 },
 "service": {
  "gui": {
   "https-port": "8443",
   "older-ciphers": "disable"
  },
  "nat": {
   "rule": {
    "5010": {
     "description": "Masquerade for WAN",
     "outbound-interface": "eth0",
     "type": "masquerade"
    }
   }
  }
 },
 "system": {
  "task-scheduler": {
   "task": {
    "postprovision": {
     "executable": {
      "path": "/config/scripts/postprovision.sh"
     },
     "interval": "3m"
    }
   }
  }
 }
}

Be aware that I’m exposing some of my personal preferences here in this setup.

  • Connectivity on port 443 instead of the default 1194 to prevent company firewall blocks
  • Connectivity using the TCP protocol
  • Proxy port 443 sharing to sense non OpenVPN traffic and route that internally
  • Moving the USG GUI to port 8443 to make room for the OpenVPN connection
  • Subnet config should be unique for your situation
  • –username-as-common-name to make the connection identication be defined by the Radius user
  • Pushing the DNS config to be able to use SplitDNS

When you’ve added this file you should be able to manually provision the USG via the UI.

Verify the provision by checking the existing of the files:

  • /etc/pam_radius_auth.conf
  • /etc/pam.d/openvpn

And a running OpenVPN server by running : show openvpn status server

Rebooting

Unfortunately the server setup is still not resilient enough after this. Rebooting the USG will still rendering the OpenVPN setup useless. To prevent that, we need to restore the added files after reboot as well.

Again create another script on the USG on the path /config/scripts/post-config.d/restore-openvpn-radius-conf.sh and make the file executable by applying chmod +x on the file.

#!/bin/vbash
readonly logFile="/var/log/postreboot.log"

source /opt/vyatta/etc/functions/script-template

#restore the radius configuration
echo "Copying PAM and OpenVPN config files" >> ${logFile}
cp -f /config/user-data/openvpn_radius_config/pam_radius_auth.conf /etc
cp -f /config/user-data/openvpn_radius_config/openvpn /etc/pam.d/openvpn

This finalizes the server setup! Onwards to the client.

Client setup

The ovpn file format to setup a client is quite straight forward. Just plain text with references to the certificates. To force the combination of both the certificate validation and the additional user authentication the file looks like this:

remote vpn-ip-or-domain 443 tcp
dev tun
float
resolv-retry infinite
nobind
persist-key
persist-tun
auth-user-pass
pull
tls-client
remote-cert-tls server
cipher AES-256-CBC
comp-lzo
verb 5
ca ca.crt
cert client.crt
key client.key

Mind again the fact that I’m using port 443 to facilitate the communication. Also notice the auth-user-pass which triggers the user authentication after the certificate checks. If you put this content into a .ovpn file and store the certificates & key you generated earlier next to it you can import the setup into your favorite OpenVPN client.

OpenVPN END result

The end result is a fast & secure OpenVPN connection. Both authenticated via certificates & the additional Radius user authentication.

jrvandijk@ubnt:~$ show openvpn status server
OpenVPN server status on vtun0 []

Client CN       Remote IP       Tunnel IP       TX byte RX byte 
--------------- --------------- --------------- ------- ------- 
jrvandijk       192.168.10.3    192.168.10.3       1.7M    1.1M

Note: inspiration for this blog was found on the Unifi community forum. The described strategy works as well, but is less secure because it only relies on the Radius authentication.

Leave a Reply

Your email address will not be published. Required fields are marked *