Using TOTPRadius for a VPN Protected by FIDO2
Preamble / possibly-useless banter
So I am a bit of a 2FA-nut and 2FA is obviously a bit of a pain with OVPN with its user-password based setup (certs are often not something you can have the user supply, but have to semi-permanently live on the device and changes to that cert are not always easy)
So I found a little something that can make radius credentials over a web portal, which granted is a business product but free for 5 users which is entirely fine when the users in this case can be summarized to Me, Myself and I.
To start off, TOTPRadius can be configured in several ways in terms of user backend and authentication.
As this is a tiny Homelab-like setup and there is already enough to deal with, I am going with local users as a backend, and for maximum Security I am going to solely use the FIDO2-Web Portal.
In Terms of the VPN I am going for a mostly simple enough using no client-side certificate (these always suck to replace), but with a server-certificate and tls-auth for some extra security on the user/password prompt as requests without the key will not even reach the Radius Server.
Disclaimer: while my employer works with Token2, the makers of TOTPRadius, this project was entirely done as a hobby and in the interest of having a secure, hopefully easy to use non-enterprise VPN.
Step 1: The Hardware and VM Basics
As this is a fairly tiny setup I am just using an ultra-small-formfactor PC (think like those thinkcenter PCs that slot into the screens) someone got rid of because of Windows 11 with an i3-7100T and 4GB RAM and a like 120GB SSD; IP (example): 192.0.2.200
On that machine I threw on a Proxmox Virtual Environment with a "full" VM for TOTPRadius and an LXC with a Debian that will act as reverse Proxy and OVPN server.
The Debian Container works fine for me with 512MB RAM, 8GB Size and 1 vCPU.
IP: 192.0.2.201
for TOTP Radius I imported the OVA onto the Proxmox (might need some partition-fiddling) and made it into a VM with 2GB RAM and 1 vCPU.
IP: 192.0.2.202
Step 2: Preparing TOTPRadius
on the Browser you can go to https://192.0.2.202 to go to the adminpanel where you can sign in with admin and totpradius.
there you should do the following:
1) change the backend User settings as you need (LDAP, Entra-OAuth, Local) - I will be going with Local
2) make yourself a new RADIUS-Secret
3) set the authentication as needed: in the case of a FIDO2-only setup, disable the LDAP enrollment portal, set FIDO2 settings to the following:

https://imgur.com/a/MxTfy4p
4) make yourself some internal certificates from a CA you can create yourself for the web portal, if you want to have a properly encrypted and verified connection from your reverse proxy.
5) make your first user if using local users
Step 3: Preparing the web-facing container
Step 3a: The Reverse Proxy
for a start you want to have a domain name that points to your IP, in home setups this usually involves Dynamic DNS, but this will not be part of this Manual, Additionally you will need to set a NAT Rule to this machine on Port 443 for HTTPS to properly work (which you WILL need for FIDO2)
on this machine we are going to install Caddy as an extremely simple webserver that can also do let's Encrypt automatically and also has an easy enough reverse Proxy option.
Due to Debian's Caddy being a bit old as of the time I set it up like 1-2 weeks ago, it's best to get caddy from their own repo:
https://caddyserver.com/docs/install#debian-ubuntu-raspbian
The config file is pretty short and simple:
https://ovpn.me.dyndnsprovider.example {
reverse_proxy https://192.0.2.202:9443 {
transport http {
tls_trust_pool file /etc/caddy/backend-ca.pem
tls_server_name ovpn.me.dyndnsprovider.example
}
header_up Host ovpn.me.dyndnsprovider.example
}
}
/etc/caddy/backend-ca.pem will be the CA signing the certificate you put into TOTPRadius.
Basically it creates a reverse proxy on the ovpn.me.dyndnsprovider.example domain, automatically sets up Let's Encrypt (best to prepare the NAT forwarding and IP allocation before you first start Caddy to not get timed out from getting a cert)
Step 3b: Testing that the revproxy and TOTPRadius' basic functions work
when this works, you can go to the address set up on the reverse Proxy to click to the FIDO2 / Passwordless Login, where after entering your username, you will be asked for the Password if there are no FIDO2-Devices yet and asked to enroll one.
after that you will get to the VPN Portal which will look like this:

https://imgur.com/a/tBSOhHf
There you can set up a radius credential with the duration of your choice, or just single use.
After that you will obtain a temporary Password as well as your username (the options below to download files for quick use with certain VPNs can be completely ignored, as the file dealing with the options is able to edit, you can remove that from the output site though)
the temporary passwords can be easily identified as they look like someone fell on the keyboard and then typed _t2f2_pass after that, such as wdpor5gvi3qc026lnuft4xby7jszhka9_t2f2_pass.
That password will only be shown once, after that it cannot be viewed on the Portal.
Step 3c: The OpenVPN
Now to the interesting part and a thing I had to struggle with a bit.
LXC Containers are a bit temparamental with getting the TUN interface to go, so best to refer to this:
https://pve.proxmox.com/wiki/OpenVPN_in_LXC
The main steps are:
1) in the contaner config (assuming container ID 123, use nano /etc/pve/lxc/123.conf), add
lxc.cgroup2.devices.allow: c 10:200 rwm
lxc.mount.entry: /dev/net dev/net none bind,create=dir
2) set the /dev/net/tun device owner with chown 100000:100000 /dev/net/tun
3) using APT we will need the following packages: openvpn, openvpn-auth-radius (well obviously) iptables-persistent (to get the firewall/NAT/Routing going), also net-tools isnt a bad idea to have at hand.
4) we'll copy ourselves a copy of easyrsa out of /usr into /etc/openvpn
cp -r /usr/share/easy-rsa /etc/openvpn
5) the variables of easyRSA can be adjusted as needed, aside from the Subject Data I set these:
set_var EASYRSA_ALGO ec
set_var EASYRSA_CURVE secp384r1
set_var EASYRSA_CA_EXPIRE 3650
set_var EASYRSA_CERT_EXPIRE 825
6) setup the PKI, Server CA, Server Cert and DH-Params using these commands from within the new easyrsa folder:
./easyrsa init-pki
./easyrsa build-ca
./easyrsa build-server-full vpn-server nopass
./easyrsa gen-dh
7) Set up the static TLS key within the OpenVPN folder
openvpn --genkey --secret statictls.key
8) Setup the IPTables and NAT (if you want to use NAT, routing will likely need something else)
iptables -t nat -F POSTROUTING
iptables -t nat -A POSTROUTING -o eth0 -s 192.0.2.0/24 -j MASQUERADE
iptables-save > /etc/iptables/rules.v4
save net.ipv4.ip_forward=1 as /etc/sysctl.d/80-ovpn.conf for IP-Forwarding
9) OpenVPN Client config
/etc/openvpn/vpn-server.conf
Note: 198.51.100.0 acts as the VPN-subnet, as this is a documentation-only IP as per RFC5737, CHANGE IT.
mode server
proto udp
port 1194
local 192.0.2.201
tls-server
key ./easyrsa/pki/private/vpn-server.key
cert ./easyrsa/pki/issued/vpn-server.crt
ca ./easyrsa/pki/ca.crt
dh ./easyrsa/pki/dh.pem
cipher AES-256-GCM
auth SHA512
verify-client-cert none
username-as-common-name
tls-auth statictls.key 0
plugin /usr/lib/openvpn/radiusplugin.so /etc/openvpn/radiusplugin.conf
dev tun
topology subnet
server 198.51.100.0 255.255.255.0
push "route 192.0.2.0 255.255.255.0"
ifconfig-pool-persist /var/log/openvpn/ipp.txt 15
log-append /var/log/openvpn/server.log
status /var/log/openvpn/server.status 15
verb 1
ping-timer-rem
keepalive 10 60
10) Radius Plugin Config
/etc/openvpn/radiusplugin.conf
NAS-Identifier=OpenVPN
NAS-IP-Address=192.0.2.201
OpenVPNConfig=/etc/openvpn/vpn-server.conf
#nonfatalaccounting=true
server
{
name=192.0.2.202
authport=1812
acctport=1813
sharedsecret=CHANGETHIS
}
11) VPN Client Config
client
proto udp
remote ovpn.me.dyndnsprovider.example 1194
cipher AES-256-GCM
auth SHA512
nobind
dev tun
auth-user-pass
setenv CLIENT_CERT 0
<ca>
-----BEGIN CERTIFICATE-----
MAKE YOUR OWN
-----END CERTIFICATE-----
</ca>
<tls-auth>
#
# 2048 bit OpenVPN static key
#
-----BEGIN OpenVPN Static key V1-----
HOW ABOUT NO
-----END OpenVPN Static key V1-----
</tls-auth>
key-direction 1
remote-cert-tls server
Step 4: Trying it all out
get a device on a different network, such as a phone to mobile internet, try going onto the portal and obtaining a RADIUS-password and try connecting the OpenVPN.
Afterword
I hope this thing helps, if anyone has suggestions to improve the thing such as using some options on the server or client I might not be aware of, feel free to add them, but be nice.
Also Major Thanks to @The Johan for helping with my dumb questions involving all this.