OpenVPN
Integrating OpenVPN with RADIUS
Below is the culmination of a month of playing with OpenVPN and learning about the how to integrate RADIUS authentication. The configuration below uses the following authentication strategies:
- Private CA created with EasyRSA, complete with CRL distribution endpoints. This is not detailed here.
- Server certificates generated by the above Private CA
- User certificates generated by the above Private CA
- The following factors are required for a successful login:
- A valid user certificate. The CN must match the username of the person logging into the OpenVPN server.
- Username and password supplied by the user is authentication against a RADIUS server.
- Using RADIUS allows any number of 2FA/MFA solutions to be leveraged for a third factor such as an MFA PUSH notification via Azure MFA or DUO.
Installing OpenVPN and the OpenVPN plugin in Ubuntu
apt -y install openvpn openvpn-auth-radius
mkdir -p /etc/openvpn/client /etc/openvpn/server /etc/openvpn/server-ccd
/etc/openvpn/server/server.conf
server 192.168.168.0 255.255.255.0
proto udp
port 1194
dev tun1
topology subnet
;user nobody
;group nogroup
cipher AES-256-GCM
auth SHA256
management 127.0.0.1 61194 /etc/openvpn/server/pw-file
duplicate-cn
;client-cert-not-required
verify-client-cert require
;username-as-common-name
;auth-user-pass-verify
ca /etc/openvpn/server/ca-chained.crt
crl-verify /etc/openvpn/server/crl.pem
cert /etc/openvpn/server/server.crt
key /etc/openvpn/server/server.key
dh none
ecdh-curve secp521r1
tls-auth /etc/openvpn/server/tls-auth.key 0
tls-version-min 1.2
verb 1
log /var/log/openvpn/openvpn.log
status /var/log/openvpn/openvpn-status.log
client-config-dir /etc/openvpn/server-ccd
script-security 2
tls-verify /etc/openvpn/server/script-tls-verify
auth-user-pass-verify /etc/openvpn/server/script-auth-user-pass-verify via-env
plugin /usr/lib/openvpn/radiusplugin.so /etc/openvpn/server/radiusplugin.cnf
client-connect /etc/openvpn/server/script-client-connect
ifconfig-pool-persist /var/log/openvpn/ipp.txt
push "dhcp-option DNS 8.8.8.8"
push "dhcp-option DNS 8.8.4.4"
;push "dhcp-option DNS 192.168.168.1"
push "dhcp-option DOMAIN ovpn.loc"
; Do you want the default gateway redirected? There's a few ways to do it:
push "redirect-gateway"
;push "redirect-gateway autolocal"
;push "redirect-gateway def1 bypass-dhcp"
; Pick other routes you want to push to the clients:
;push "route 10.0.0.0 255.0.0.0"
;push "route 172.16.0.0 255.240.0.0"
;push "route 192.168.0.0 255.255.0.0"
;push "route 100.64.0.0 255.192.0.0"
keepalive 10 120
persist-key
persist-tun
;mute 20
;explicit-exit-notify 1
/etc/openvpn/server/radiusplugin.cnf
# The NAS identifier which is sent to the RADIUS server
# The service type which is sent to the RADIUS server
# The framed protocol which is sent to the RADIUS server
# The NAS port type which is sent to the RADIUS server
# The NAS IP address which is sent to the RADIUS server
NAS-Identifier=something-that-is-meaningful
Service-Type=2
Framed-Protocol=1
NAS-Port-Type=5
NAS-IP-Address=192.168.1.1
# Path to the OpenVPN configfile. The plugin searches there for
# client-config-dir PATH (searches for the path)
# status FILE (searches for the file, version must be 1)
# client-cert-not-required (if the option is used or not)
# username-as-common-name (if the option is used or not)
OpenVPNConfig=/etc/openvpn/server/server.conf
# Support for topology option in OpenVPN 2.1
# If you don't specify anything, option "net30" (default in OpenVPN) is used.
# You can only use one of the options at the same time.
# If you use topology option "subnet", fill in the right netmask, e.g. from OpenVPN option "--server NETWORK NETMASK"
subnet=255.255.255.0
# If you use topology option "p2p", fill in the right network, e.g. from OpenVPN option "--server NETWORK NETMASK"
# p2p=10.8.0.1
# Allows the plugin to overwrite the client config in client config file directory,
# default is true
overwriteccfiles=true
# Allows the plugin to use auth control files if OpenVPN (>= 2.1 rc8) provides them.
# default is false
useauthcontrolfile=true
# Path to a script for vendor specific attributes.
# Leave it out if you don't use an own script.
#vsascript=/etc/openvpn/server/vsascript.pl
# Path to the pipe for communication with the vsascript.
# Leave it out if you don't use an own script.
#vsanamedpipe=/tmp/vsapipe
# A radius server definition, there could be more than one.
# The priority of the server depends on the order in this file. The first one has the highest priority.
server
{
acctport=1813
authport=1812
retry=0
wait=30
name=127.0.0.1
sharedsecret=ChangeMe
}
/etc/openvpn/server/script-auth-user-pass-verify
The script below is used currently to make sure that the username and the user certificate name match, otherwise, deny the login request.
Unfortunately, OpenVPN run the RADIUS plugin in a different thread, so if you're using RADIUS to do PUSH notifications to Azure MFA, DUO, Okta, etc., the user will most likely receive a PUSH notification if they have entered correct credentials, even though there may be a certificate mismatch since these two processes are running in parallel.
#!/bin/bash
DUMPFILE=/var/log/openvpn/dump.all
echo "script-auth-user-pass-verify" >> $DUMPFILE
date >> $DUMPFILE
echo "==========" >> $DUMPFILE
for arg in "$@"; do
echo "ARG: $arg" >> $DUMPFILE
done
echo "==========" >> $DUMPFILE
env >> $DUMPFILE
echo "==========" >> $DUMPFILE
echo "Pulled from environment:" >> $DUMPFILE
echo "common_name: $common_name" >> $DUMPFILE
echo "username: $username" >> $DUMPFILE
if [ "$common_name" = "$username" ]; then
echo "Names match!!! Allowing connection!"
echo "==========" >> $DUMPFILE
echo "" >> $DUMPFILE
echo "" >> $DUMPFILE
echo "" >> $DUMPFILE
else
echo "$untrusted_ip:$untrusted_port script-auth-user-pass-verify: common_name and username do not match... denying connection!!!"
echo "common_name and username do not match... denying connection!!!" >> $DUMPFILE
echo "==========" >> $DUMPFILE
echo "" >> $DUMPFILE
echo "" >> $DUMPFILE
echo "" >> $DUMPFILE
exit 1
fi
/etc/openvpn/server/script-client-connect
This script doesn't do anything currently but log available environment variables to the dump.all file. It can be used to push additional configuration details to the OpenVPN server for the user that is logging in.
#!/bin/bash
DUMPFILE=/var/log/openvpn/dump.all
echo "script-client-connect" >> $DUMPFILE
date >> $DUMPFILE
echo "==========" >> $DUMPFILE
for arg in "$@"; do
echo "ARG: $arg" >> $DUMPFILE
done
echo "==========" >> $DUMPFILE
env >> $DUMPFILE
echo "==========" >> $DUMPFILE
echo "" >> $DUMPFILE
echo "" >> $DUMPFILE
echo "" >> $DUMPFILE
OpenVPN Client Configuration
setenv FRIENDLY_NAME "Meaningful name"
setenv USERNAME "actual.username@some.domain.com"
setenv ALLOW_PASSWORD_SAVE 0
remote vpn.your.domain.here.com 1194
client
proto udp
dev tun1
connect-retry 300
connect-retry-max 0
ping 20
remote-cert-tls server
auth-user-pass
auth-retry interact
cipher AES-256-GCM
auth SHA256
nobind
persist-key
persist-tun
reneg-sec 0
<ca>
-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
</ca>
key-direction 1
<tls-auth>
-----BEGIN OpenVPN Static key V1-----
-----END OpenVPN Static key V1-----
</tls-auth>
<key>
-----BEGIN PRIVATE KEY-----
-----END PRIVATE KEY-----
</key>
<cert>
-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
</cert>