Keywords: Full guide to setting up OpenVPN LDAP and AD Authentication module, bypassing specific OS requirements.Setup OpenVPN LDAP/AD Authentication

How To - OpenVPN LDAP/AD Authentication Module in Python

As someone who values OpenVPN being one of the greatest opensource VPN solutions, in addition to having gone through series of challenges with AD Authentication some Linux distros, therefore I decided to code my own Python Module, that Handles AD/LDAP Flawlessly, without relying on the OS environment where OpenVPN is run.

Disclaimer: This should NOT be a go to solution as it introduces a form of security non-compliance concerns if not regulated. Although this script could be used just about anywhere, it should NOT be required for Centos 7 and Ubuntu 18+. 

Kerberos, PAM, and OpenVPN LDAP plugin are more than sufficient to get the job done. Yes, they’re a bit complex, but highly secured compared to this approach. This is a last resort – type of thing.

My auth module aims to completely eliminate the need for PAM, NSS, Kerberos and even OpenVPN LDAP Authentication module altogether

There are a number of challenges out there, like getting Centos 8 OpenVPN LDAP module (as of the time of writing this article Centos 8 did NOT have openvpn LDAP plugin namely openvpn-auth-ldap available).

Furthermore,  PAMd, NSS, OpenVPN could also become quite problematic on some Linux distros. Even when SSSd is properly configured and working, in a matter of days, the authentication may halt, while sssd is still running, and simply restarting the service fixes it.

As a matter of fact, running in a production environment, you try to have the least escalations possible, thus, I needed something solid, something that will work each time with 100% guaranteed success while being completely independent of the OS and it’s components, and that’s the birth of my authentication module.

Openvpn Offcial Site – Manual 

Step 1 - The Code

The Authentication module, plugs into OpenVPN Configuration, as an authentication script, that’s where the plugin receives two necessary pieces of information, the first being the username and the second being the password, In fact my current Auth module supports both AD/LDAP as well as OTP verification handler, but we’re only going to talk about LDAP/AD primarily.

That said, once the script receives the username/password combination, it then passes those to our python function that then attempts to create a bind auth. If the bind is successful it returns a zero, otherwise a non-zero code is returned.

Alrighty, let’s get to the Code Part:

First we capture both the username and password using these exact enviroment variables as that’s what OpenVPN passes down to us.
import sys
import datetime
from ldap3.core.exceptions import LDAPCursorError
import re
import logging

LogLevel = 'logging.DEBUG'
logging.basicConfig(filename='/var/log/openvpn-auth.log', level = eval(LogLevel))

username = os.getenv('username')
password = os.getenv('password')

server_name = '' #Windows Server or FreeIPA
domain_name = 'PETER.LOC'
It’s better if you add some form of username validation, some companies use naming conventions like firstinitial.lastname, other first.last and so on, so what you want to do is make sure we’re not just passing garbage over to our auth module. I added a generic check as an example
pattern = "^[a-zA-Z\_\.]+$"

if not re.match(pattern,username):
    print('Invalid username ' + username)

AD Bind Authentication function:

def validateADChecks(username,password):
    server = Server(server_name, get_info=ALL)
        conn = Connection(server, user='{}\\{}'.format(domain_name, username), password=password, authentication=NTLM, auto_bind=True)
        return True
        return False

You may also want to add an extra AD/LDAP connectivity however the above code should suffice to keep it kind of short :

    if conn:
        return True
        return False
Last we’ll add a mini logger just for the sole purpose of that use case, obviously you can just log everything and call it a day, but i added a bit of extra code, as a template, in case someone want to factor in other elements with different log levels

def logit(message):'%a %b %e %H:%M:%S %Y us=%f')
    untrusted_ip = os.getenv('untrusted_ip')
    untrusted_port = os.getenv('untrusted_port')
    if LogLevel == 'logging.DEBUG':
        logging.debug('{0} cmd={1} username={2} ip={3}:{4} {5}'.format(timestamp, processname, usrname, untrusted_ip, untrusted_port, message))
    else:'{0} cmd={1} username={2} {3}'.format(timestamp, processname, usrname, message))

Finally add validation that determines the auth status
if validateADChecks(username,userPass):
    logit('AD Authentication successful for user ' + username)
    logit('AD Authentication failed for user ' + username)

Step 2 - Deploy Script and Configure OpenVPN

So at this point, you should have a script to handle authentication lets say with the name get that script on to your server under
Make it executable:
chmod +x /usr/local/bin/
Add the Script to Openvpn Config.
vim /etc/openvpn/server/server.conf

auth-user-pass-verify /usr/local/bin/ via-env
script-security 3
Restart OpenVPN service:
systemctl restart openvpn-server@server

This approach is only meant to be last resort, when all others fail, and should not be a go to solution for OpenVPN AD/LDAP auth.

As a result, here are a few things to keep in mind, first, deploying a script like this in production must go through some kind of regulation, as this script can be used to exploit and capture passwords in plain text, thus the use of it must be supervised/monitored. there are multiple tools that can be used to achieve PCI DSS compliance even with a script like this, that entails how the script is handled, who reviews it, signs off, and deploys it. All things considered, Security must be prioritized over convenience at all times.

Keywords: Full guide to setting up OpenVPN LDAP and AD Authentication module, bypassing specific OS requirements.Setup OpenVPN LDAP/AD Authentication