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, auth.sh, 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
# The script ends up always exiting code 1 (failed)
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.
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
auth-user-pass-verify /etc/openvpn/server/auth.sh via-file
keepalive 10 60
ifconfig 10.1.194.1 255.255.255.0
ifconfig-pool 10.1.194.2 10.1.194.254
push "topology subnet"
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.
remote vpn.example.com 1194
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!