The return of the Linux router... (from pfSense to Debian, part 7: OpenVPN without PKI, script+file auth setup)

The return of the Linux router... (from pfSense to Debian, part 7: OpenVPN without PKI, script+file auth setup)

Hello World ... back again... but it is more than three months since my last article!!!
2018 was very very hard to finish, with dad passing away :-( , being in the middle of my degree ending project and with lots of projects opened at job, the level of stress bring me to very limit.
Missing dad every single day, specially during Christmas time, time went on, life goes on... and projects get successfully closed, my degree finished, and now, I'm finally having time to come back.

...well, time to go for the matter!!!


Scenario and goals

Today I'm going to share how to have OpenVPN server (this time a typical tun/routed setup listening on UDP 1194 ) proxying authentication for incoming connections to an external, completelly transparent to it, authentication mechanism... just a script file!

The client, in turn, will also use a plain file to read a credentials pair an send them to the server.
So, although we still need the usual RSA bunch of files (the CA cert, the server cert and server key) on the server side, and the CA cert on the client side to perform tunnel encryption, we do not need a pair of different client cert and a client key files, for every single client anymore, since authentication isn't related to RSA.

In the example here, I'm going to use a shell script that has a user/password pair hardcoded in, but the key idea is that you could point your OpenVPN server to execute a Python script wich, in turn, would authenticate your users against a Database, a Directory Server, or wathever you want!!!


Server-side authentication script

So, what does OpenVPN server configured this way do upon receiving a new connection? and, what it does expect to happen?...

Very simple... It will execute a script that is declared in server configuration, passing username and password as arguments to it, and then, it will simply check what happens... if either the script fails to execute, reporting unsuccessful exit, or if it just executed normally and exited successfully.

Note that the OpenVPN server will not try to handle any kind of return or output from the executed script.
This is important, since any script that 'just executes', without encountering any problem, would end up reporting success exit.
And here's the key: if the script reports back having exited unsuccessfully, this means failed authentication, otherways, a normal successful execution, means authentication success.

So, what we have to do is a script that parses the input argument as a credentials pair, do some kind of execution logic (here you may query an API, DB, Directory... whatever), and ensure that if, and only if, authentication was successful you allow the script to exit successfully (0 code) , and otherwise, you force an unsuccessful (1 code) exit.

Here's a very basic shell script example,, to show it:


# Parse argument (two lines) as an array
readarray -t array < $1

# Here you would implement some true auth logic.
# Here we simply check for a given password.
# Note we leave the script with code 0 if 
# password do match.
if [[ "$PASS" == "verySecret" ]]; then
  exit 0

# The script ends up always exiting code 1 (failed)
exit 1

If you're wondering if this setup allows to have a per-client ccd file to configure client-specific static IP addresses and/or routes (as we use to have whith the usual RSA authentication), the answer is yes, it is.
This time, instead of using the Common Name field of the client certificate to select the ccd file, the server will use the first argument as the criteria to select the ccd file... that simple.


Client-side credentials file

Little to explain here, just a plain file with two lines, that will turn to be the two arguments that the server will pass to the script.
What to put there depend on the script logic, but tipically, a username in the first line, and a token, password or whatever on the second line.

In the example avobe, the first line doesn't even matter...since it uses just the second to get a password... so imagine a file auth.txt like this:


and now let's look at some configuration examples using this setup.


Server setup

This time I'm using the most typical, routed, tun-device, configuration using the default UDP 1194.
Just note how we indicate a path to the auth execute script with the auth-user-pass-verify keyword and via-file
Also note the username-as-common-name stanza

mode server
port 1194
proto udp
dev tun1
user nobody
group nogroup

ca /etc/openvpn/easy-rsa/keys/CA.crt
cert /etc/openvpn/easy-rsa/keys/server.pem
key /etc/openvpn/easy-rsa/keys/server.key
dh /etc/openvpn/server/dh1024.pem
cipher AES-128-CBC
auth SHA1

verify-client-cert none
auth-user-pass-verify /etc/openvpn/server/ via-file

keepalive 10 60

client-config-dir /etc/openvpn/server/ccd
ifconfig-pool-persist /etc/openvpn/server/ipp.txt
status /etc/openvpn/server/status.log
log /var/log/openvpn/myserver_openvpn.log
verb 3

push "topology subnet"
topology subnet

max-clients 100

comp-lzo adaptive


Client setup

And here a sample client-side configuration file.
Note that only the CA cert file to validate server cert is necessary, since no RSA auth is involved with this setup. Instead, note that auth-user-pass is used to set the authentication file.

dev tun1
proto udp
remote 1194
resolv-retry infinite

remote-cert-tls server
ca /etc/openvpn/easy-rsa/keys/CA.crt
cipher AES-128-CBC
auth SHA1


remote-cert-tls server
auth-user-pass /etc/openvpn/client/auth.txt

verb 3
reneg-sec 0

script-security 2

And that's it, no RSA/SSL/PKI stuff involved beyond the actual data ciphering to crate the tunneling... and you're free to authenticate against whatever you need.
Hoping this one maybe useful to anyone... best regards!