Skip to main content

Python OpenVPN LDAP Auth

PyPI license PyPI status PyPI version shields.io PyPI pyversions main build status

An auth verify script for OpenVPN to authenticate via LDAP. Each VPN login is forwarded to this script and the script in turn attempts a simple bind against the specified LDAP server. When the bind is successful the script returns exit code 0 telling OpenVPN that the credentials are valid.

Although there already is the openvpn-auth-ldap plugin I felt the need to write this auth script. First the source code is more accessible due to it being written in Python. Second it offers more possibilities regarding OpenVPN's static-challenge parameter (see below).

The downsides of using a script instead of a C-plugin are less performance and slightly reduced security. If you are fine with that go ahead.

Quickstart

Install the package (Python 3.11+, python-ldap needs the OpenLDAP and SASL headers to build):

apt install libldap-dev libsasl2-dev   # Debian/Ubuntu
pipx install openvpn-ldap-auth         # or: uv tool install openvpn-ldap-auth

Then create /etc/openvpn/ldap.yaml:

ldap:
  url: 'ldaps://first.ldap.tld:636/ ldaps://second.ldap.tld:636/'
  bind_dn: 'uid=readonly,dc=example,dc=org'
  password: 'somesecurepassword'
  timeout: 5 # (optional) wait this many seconds for connection and response
authorization:
  base_dn: 'ou=people,dc=example,dc=org'
  search_filter: '(uid={})' # optional, {} will be replaced with the (escaped) username and is required
  static_challenge: 'ignore' # optional, other values are prepend, append 

The file contains the bind password, so make it readable for the OpenVPN user only. Unknown keys are rejected, so typos fail loudly instead of being silently ignored.

Find out where openvpn-ldap-auth lives:

which openvpn-ldap-auth

Add the following line to your OpenVPN server configuration:

script-security 2
auth-user-pass-verify /path/to/openvpn-ldap-auth via-file

Now you can start your OpenVPN server and try to connect with a client.

Installation

Single Executable

For those who wish to sacrifice a little more performance for not having to install or compile a Python interpreter or you just want to quickly try the script out this option might be interesting. Each release also has executables attached to it: openvpn-ldap-auth-<version>-ubuntu-<ubuntu-version>-<amd64|arm64>. They are created via PyInstaller on the respective Ubuntu version and architecture. They also work on other distros that use the same or a later glibc version.

Important: /tmp must not be read only.

From Source

Download or clone this repository, cd into it and run

uv build
pipx install dist/openvpn_ldap_auth-*.whl

Configuration

Static Challenge

If you want users to provide a normal password combined with a one-time-password OpenVPN's static-challenge parameter is what you are looking for.

In the client configuration you need to add a line like

static-challenge "Enter OTP" 1 # use 0 if the OTP should not be echoed

When connecting you will now be prompted for your password and your OTP. By setting authorization.static_challenge you can now influence how the OTP is used:

  • ignore (default): Just use the password for binding.
  • prepend: Prepend the OTP to your password and use that for binding.
  • append: Append the OTP to your password and use that for binding.

The last two options are useful if your LDAP server offers internal 2FA validation like oath-ldap.

Using via-env

In the server configuration the following alternative setting is also supported but discouraged:

auth-user-pass-verify /path/to/openvpn-ldap-auth via-env

OpenVPN's manpage about that topic:

If method is set to "via-env", OpenVPN will call script with the environmental variables username and password set to the username/password strings provided by the client. Be aware that this method is insecure on some platforms which make the environment of a process publicly visible to other unprivileged processes.

If you still want to use via-env make sure to set script-security to 3.

Development

Install uv, then:

uv run pytest            # fast unit tests
uv run ruff format       # format
uv run ruff check        # lint
uv run mypy              # type-check

The system tests start a real OpenVPN server and client plus OpenLDAP in Docker (with the compose plugin), test the script installed from the built wheel and as a PyInstaller binary, and benchmark both against the openvpn-auth-ldap C plugin:

tests/system/run.sh                  # Python 3.14, OpenVPN 2.6
tests/system/run.sh 3.11 2.7 -k env  # any Python/OpenVPN combination, extra args go to pytest

Benchmark results land in benchmark/.

Releasing

Bump version in pyproject.toml, merge, then push a matching tag (git tag v0.2.0 && git push --tags). The release workflow creates the GitHub release, publishes to PyPI via Trusted Publishing and attaches the binaries.

Metadata

Release files for openvpn-ldap-auth 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for openvpn-ldap-auth 0.2.0
File Size Uploaded
openvpn_ldap_auth-0.2.0.tar.gz 8.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for openvpn-ldap-auth 0.2.0
File Interpreter ABI Platform
openvpn_ldap_auth-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 16.8 kB

Release files / openvpn_ldap_auth-0.2.0.tar.gz

Download URL openvpn_ldap_auth-0.2.0.tar.gz
Size 8.5 kB
Tags Source
SHA-256 checksum
How to use checksums
44f9daa5632f593e7749b51921543ed1cb38d5b0f2b2877e445a2e2284276645
BLAKE2b-256 checksum
How to use checksums
e2b8db737c6ce47fa848356e7cc29c6aa57e22c4cf64450e73991d8b54a4d629
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.

Transparency log

Release files / openvpn_ldap_auth-0.2.0-py3-none-any.whl

Download URL openvpn_ldap_auth-0.2.0-py3-none-any.whl
Size 8.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
7777a7c52ffaa63ef1d9b076263b842cdc77f33aee1d023de9c1f16b9ee8247e
BLAKE2b-256 checksum
How to use checksums
e7f2bf0fff9f7ac290d697998b17cf9ea7c6fadc854df8b4c112b4b83198764c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 12, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.8

2 release files

0.1.7

2 release files

0.1.6

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page