Skip to content

A dockerised OpenVPN server using LDAP for authentication and with optional MFA

License

Notifications You must be signed in to change notification settings

wheelybird/openvpn-server-ldap-otp

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

88 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

OpenVPN server with LDAP authentication and MFA

A Docker container providing OpenVPN with LDAP authentication and optional two-factor authentication (TOTP/MFA). Part of an integrated suite of tools for enterprise-grade VPN access with centralised user management.

Why use this?

The goal of this project is to build a Docker container that provides an OpenVPN server which authenticates users against an existing OpenLDAP directory, with optional two-factor authentication using TOTP (via liboath).

OpenVPN is a mature, free, and open-source VPN solution known for its strong security, flexibility, and active development since 2001. It supports a wide range of operating systems through well-maintained client applications.

OpenLDAP is a robust open-source implementation of the Lightweight Directory Access Protocol (LDAP), widely used for centralised authentication, authorisation, and user information management. It provides a flexible, standards-based system for managing directory data across diverse environments.

This project extends that functionality by allowing OpenVPN to verify users’ TOTP keys and related metadata directly from OpenLDAP. Managing these credentials within the same directory simplifies administration, ensures consistent access control, and improves security by keeping all authentication data in a single, authoritative source.

  • Centralised authentication: Use your existing LDAP directory for VPN access
  • Optional MFA/2FA: Add time-based one-time passwords (TOTP) for enhanced security
  • Enterprise ready: Supports fail2ban, custom routing, and advanced networking
  • Docker-native: Easy deployment with persistent configuration

Complete solution stack

This OpenVPN server works best as part of an integrated solution:

Component Purpose Repository
OpenVPN server (this project) VPN gateway with LDAP + MFA authentication openvpn-server-ldap-otp
Luminary A web UI for self-service MFA enrolment luminary

Benefits of the complete stack:

  • Users can scan QR codes and set up MFA themselves
  • Self-service MFA enrolment without admin intervention
  • Grace periods allow users time to enrol before VPN access is restricted
  • No need to SSH into servers to manage OTP secrets

Quick start

Basic setup (LDAP authentication only)

docker run \
  --name openvpn \
  --cap-add=NET_ADMIN \
  -p 1194:1194/udp \
  -v /path/to/data:/etc/openvpn \
  -e "OVPN_SERVER_CN=vpn.example.com" \
  -e "LDAP_URI=ldap://ldap.example.com" \
  -e "LDAP_BASE_DN=dc=example,dc=com" \
  -e "LDAP_BIND_USER_DN=cn=pam-totp-ldap-auth,ou=services,dc=example,dc=com" \
  -e "LDAP_BIND_USER_PASS=password" \
  -d \
  wheelybird/openvpn-ldap-otp:v2.1.0

NOTE: LDAP_BIND_USER should be the DN for an LDAP account that can access the user's LDAP attributes. If you don't have a specific service account set up for this then you can use the administrator base DN, but this isn't recommended. The example uses the example service account from the LDAP TOTP schema repository.

With MFA (recommended)

First, install the LDAP TOTP schema in your LDAP directory, then:

docker run \
  --name openvpn \
  --cap-add=NET_ADMIN \
  -p 1194:1194/udp \
  -v /path/to/data:/etc/openvpn \
  -e "OVPN_SERVER_CN=vpn.example.com" \
  -e "LDAP_URI=ldap://ldap.example.com" \
  -e "LDAP_BASE_DN=dc=example,dc=com" \
  -e "LDAP_BIND_USER_DN=cn=pam-totp-ldap-auth,ou=services,dc=example,dc=com" \
  -e "LDAP_BIND_USER_PASS=your_service_account_password" \
  -e "LDAP_FILTER=(memberOf=cn=vpn-users,ou=groups,dc=example,dc=com)" \
  -e "MFA_ENABLED=true" \
  -e "MFA_BACKEND=ldap" \
  -d \
  wheelybird/openvpn-ldap-otp:v2.1.0

Note: The LDAP_FILTER restricts VPN access to members of the vpn-users group. Omit this variable to allow all users in the base DN.

Deploy Luminary to give users a friendly web interface for MFA enrolment.

Get client configuration

docker exec -ti openvpn show-client-config > client.ovpn

Distribute this .ovpn file to your users. When connecting with MFA enabled, users enter: password123456 (password + 6-digit TOTP code concatenated).

Authentication modes

NOTE: This container uses the (pam_ldap_totp_auth) PAM module for LDAP and MFA authentication. The module can operate in three modes controlled by environment variables. MFA is implemented using TOTP (Time-based One-Time Password).

1. LDAP-backed MFA (recommended)

Store TOTP secrets in LDAP for centralised management.

Prerequisites:

  1. Install LDAP TOTP Schema in your LDAP directory
  2. Optionally deploy Luminary for self-service

Configuration:

-e "MFA_ENABLED=true"
-e "MFA_BACKEND=ldap"

Note: This implementation uses TOTP (Time-based One-Time Password) as the MFA method.

NOTE: you can configure which LDAP attributes store TOTP data, so if you're unable to install the suggested schema it'll still be possible to store the data in LDAP (but not recommended).

User experience:

  • Users enrol via web UI (scan QR code with authenticator app)
  • Users enter password123456 when connecting to VPN
  • Backup codes available for emergency access

Benefits:

  • Self-service enrolment via web interface
  • Admin oversight of MFA adoption
  • Grace periods for new users
  • Backup codes stored in LDAP
  • Standalone PAM module with direct LDAP integration
  • Configurable enforcement modes (strict, graceful, warn-only)

See AUTHENTICATION_MODES.md for detailed information.


2. File-based MFA (traditional)

Uses google-authenticator with file-based secret storage. The pam_ldap_totp_auth module handles LDAP password authentication (set totp_enabled=false).

Configuration:

-e "MFA_ENABLED=true"
# MFA_BACKEND defaults to 'file' if not set

Note: File-based mode uses google-authenticator TOTP implementation.

Setup: docker exec -ti openvpn add-otp-user username

User experience: Users enter password123456 (password + TOTP code).

How it works:

  • pam_google_authenticator validates the TOTP code from file
  • pam_ldap_totp_auth (with TOTP disabled) validates LDAP password
  • Both must succeed

3. LDAP password only (No MFA)

Simple LDAP password authentication without two-factor. Uses the pam_ldap_totp_auth module with totp_enabled=false.

Configuration:

-e "LDAP_URI=ldap://ldap.example.com"
-e "LDAP_BASE_DN=dc=example,dc=com"
# Do NOT set MFA_ENABLED=true (or leave it as default 'false')

Note: Not recommended for production. Enable MFA for security.


4. Client certificate authentication

Traditional X.509 certificate-based authentication (no LDAP required). Only one certificate is generated, so for more than one user to connect to the VPN you'd need to share the certificate. This isn't recommended.

Configuration:

-e "USE_CLIENT_CERTIFICATE=true"

User experience: User connect with certificate, no password needed.

Use case: Development, testing, or for a single-user VPN.


MFA enforcement

When MFA_ENABLED=true, MFA is required for all users authenticating to this OpenVPN server.

Enforcement modes

The MFA_ENFORCEMENT_MODE variable (default: graceful) controls how users without enrolled TOTP secrets are handled:

Mode Behaviour Use case
strict Users without TOTP secrets cannot authenticate Production - require MFA for all users
graceful Users without secrets can authenticate during grace period Migration - give users time to enrol
warn_only Users without secrets can authenticate (warning logged) Testing - MFA optional

Grace period: With MFA_ENFORCEMENT_MODE=graceful (the default), users have MFA_GRACE_PERIOD_DAYS (default: 7) from account creation to enrol in MFA before authentication is denied.

Restricting access by group

Use the LDAP_FILTER environment variable to restrict VPN access based on group membership or account attributes.

Example - Restrict to specific group:

docker run \
  ... other options ... \
  -e "LDAP_FILTER=(memberOf=cn=vpn-users,ou=groups,dc=example,dc=com)" \
  wheelybird/openvpn-ldap-otp:v2.1.0

How it works:

  • The PAM module combines your filter with the username search using AND logic
  • Final LDAP query: (&(uid=username)(memberOf=cn=vpn-users,ou=groups,dc=example,dc=com))
  • Only users matching BOTH criteria can authenticate

More examples:

# Allow multiple groups (OR)
-e "LDAP_FILTER=(|(memberOf=cn=vpn-users,ou=groups,dc=example,dc=com)(memberOf=cn=admins,ou=groups,dc=example,dc=com))"

# Require active account status
-e "LDAP_FILTER=(accountStatus=active)"

# Complex filter (active AND in group)
-e "LDAP_FILTER=(&(accountStatus=active)(memberOf=cn=vpn-users,ou=groups,dc=example,dc=com))"

Note: for memberOf filters the LDAP server must have memberOf overlay enabled

Configuration

Required settings

Variable Description Example
OVPN_SERVER_CN Server hostname (must be resolvable by clients) vpn.example.com

LDAP settings

Variable Required Description Example
LDAP_URI Yes* LDAP server URI ldap://ldap.example.com or ldaps://ldap.example.com:636
LDAP_BASE_DN Yes* Base DN for user searches dc=example,dc=com
LDAP_BIND_USER_DN Recommended DN for bind user (if anonymous bind disabled) cn=readonly,dc=example,dc=com
LDAP_BIND_USER_PASS Recommended Password for bind user supersecret
LDAP_LOGIN_ATTRIBUTE No Attribute to match username (default: uid) sAMAccountName for AD
LDAP_FILTER No Additional LDAP filter for access control (memberOf=cn=vpn-users,ou=groups,dc=example,dc=com)
LDAP_ENCRYPT_CONNECTION No TLS mode: on, starttls, or off starttls
LDAP_TLS_VALIDATE_CERT No Validate TLS certificate (default: true) false for self-signed
LDAP_TLS_CA_CERT No CA certificate contents for TLS Contents of CA cert file

*Not required if USE_CLIENT_CERTIFICATE=true

Active Directory users: Set -e "ACTIVE_DIRECTORY_COMPAT_MODE=true" to automatically configure appropriate settings.

MFA settings

Note: MFA is implemented using TOTP (Time-based One-Time Password).

Variable Default Description
MFA_ENABLED false Enable multi-factor authentication (using TOTP)
ENABLE_OTP false Alias for MFA_ENABLED (backwards compatibility)
MFA_BACKEND file MFA storage backend: ldap or file
MFA_TOTP_ATTRIBUTE totpSecret LDAP attribute storing TOTP secret (LDAP backend only)
MFA_GRACE_PERIOD_DAYS 7 Grace period for new users (days)
MFA_ENFORCEMENT_MODE graceful Enforcement mode (see below)

Backwards compatibility: Both MFA_ENABLED and ENABLE_OTP work. If both are set, MFA_ENABLED takes precedence.

Enforcement modes:

  • graceful (default): Users without secrets can authenticate during grace period
  • strict: Users without TOTP secrets cannot authenticate
  • warn_only: Users without secrets can authenticate (warning logged)

See MFA enforcement section for details.

Network Settings

Variable Default Description
OVPN_PORT 1194 OpenVPN listen port (update Docker -p to match)
OVPN_PROTOCOL udp Protocol: udp or tcp
OVPN_NETWORK 10.50.50.0 255.255.255.0 VPN network address and netmask
OVPN_ROUTES All traffic Routes to push to clients (format: 192.168.1.0 255.255.255.0,10.0.0.0 255.255.0.0)
OVPN_NAT true Enable NAT/masquerading for client traffic
OVPN_DNS_SERVERS None DNS servers to push (comma-separated)
OVPN_DNS_SEARCH_DOMAIN None DNS search domains (comma-separated)

Security settings

Variable Default Description
KEY_LENGTH 2048 Certificate key length (higher = more secure, slower)
OVPN_TLS_CIPHERS Modern ciphers TLS 1.2 cipher list
OVPN_TLS_CIPHERSUITES Modern suites TLS 1.3 cipher suites
OVPN_IDLE_TIMEOUT None Disconnect idle connections (seconds)
REGENERATE_CERTS false Force certificate regeneration

Fail2ban protection

Variable Default Description
FAIL2BAN_ENABLED false Enable brute-force protection
FAIL2BAN_MAXRETRIES 3 Failed attempts before ban

Management interface

Variable Default Description
OVPN_MANAGEMENT_ENABLE false Enable TCP management interface on port 5555
OVPN_MANAGEMENT_NOAUTH false Allow access without authentication
OVPN_MANAGEMENT_PASSWORD None Management interface password

Advanced settings

Variable Default Description
OVPN_DEFAULT_SERVER true Auto-generate server network config
OVPN_EXTRA None Additional OpenVPN config directives (raw text)
OVPN_VERBOSITY 4 Log verbosity (0-11)
DEBUG false Enable debug logging
LOG_TO_STDOUT true Send OpenVPN logs to stdout

Note: You can add any extra server configuration for OpenVPN using OVPN_EXTRA. This should be a string with escaped newlines and quotes. For example: OVPN_EXTRA="sndbuf 393216\nrcvbuf 393216\ntxqueuelen 1000"

Data persistence

Mount /etc/openvpn as a volume to persist:

  • Generated certificates and keys
  • Server configuration
  • OTP secrets (if using file-based mode)
  • Client configuration
-v /path/on/host:/etc/openvpn

Important: The first run generates certificates (2048-bit key takes 2-10 minutes). Subsequent starts are much faster.

Security best practices

TLS/SSL for LDAP

-e "LDAP_ENCRYPT_CONNECTION=starttls"
-e "LDAP_TLS_VALIDATE_CERT=true"
-e "LDAP_TLS_CA_CERT=$(cat /path/to/ca.crt)"

Enable MFA

-e "MFA_ENABLED=true"
-e "MFA_BACKEND=ldap"

Note: MFA_ENABLED replaces the deprecated ENABLE_OTP variable. Both work, but MFA_ENABLED takes precedence if both are set. The MFA implementation uses TOTP (Time-based One-Time Password).

Deploy Luminary for self-service enrolment.

Restrict VPN access by group or attribute

# Use LDAP_FILTER to restrict by group membership
-e "LDAP_FILTER=(memberOf=cn=vpn-users,ou=groups,dc=example,dc=com)"

# Or use base DN to restrict by organisational unit
-e "LDAP_BASE_DN=ou=vpn-users,dc=example,dc=com"

See "Restricting access by group" section for more filter examples.

Enable Fail2ban

-e "FAIL2BAN_ENABLED=true"
-e "FAIL2BAN_MAXRETRIES=3"

Session timeouts

-e "OVPN_IDLE_TIMEOUT=3600"  # 1 hour

Common tasks

View client configuration

docker exec -ti openvpn show-client-config

Add file-based OTP user

docker exec -ti openvpn add-otp-user username

Check container logs

docker logs -f openvpn

Fail2ban administration

# Ban an IP
docker exec -ti openvpn fail2ban-client set openvpn banip 192.168.1.100

# Unban an IP
docker exec -ti openvpn fail2ban-client set openvpn unbanip 192.168.1.100

# View fail2ban logs
docker exec -ti openvpn tail -50 /var/log/fail2ban.log

Force certificate regeneration

docker run ... -e "REGENERATE_CERTS=true" ...

Note: After regenerating the certificates you'll need to provide users with the new client configuration.

Troubleshooting

Container hangs at "Generating DH parameters"

Cause: Low system entropy (common on VMs).

Solution:

# Install entropy daemon on Docker host
apt-get install haveged
systemctl enable haveged
systemctl start haveged

# Check available entropy (should be >1000)
cat /proc/sys/kernel/random/entropy_avail

Alternatively, wait longer (can take 30+ minutes on low-entropy systems) or use a lower key length (not recommended for production):

-e "KEY_LENGTH=2048"

LDAP authentication fails

Check LDAP connectivity:

docker exec -ti openvpn ldapsearch -x -H "${LDAP_URI}" -b "${LDAP_BASE_DN}" -D "${LDAP_BIND_USER_DN}" -w "${LDAP_BIND_USER_PASS}"

Common issues:

  • Bind user lacks search permissions
  • Base DN doesn't include the user
  • TLS certificate validation failing (try LDAP_TLS_VALIDATE_CERT=false temporarily)
  • Wrong base DN or bind credentials
  • Wrong LDAP_LOGIN_ATTRIBUTE (use sAMAccountName for Active Directory)

MFA/OTP not working

File-based TOTP:

  • Check OTP file exists: docker exec -ti openvpn ls /etc/openvpn/otp/
  • Ensure time synchronization with NTP

LDAP-backed TOTP:

  • Verify LDAP schema installed: ldap-totp-schema
  • Check user has totpSecret attribute populated
  • Verify PAM module config: docker exec -ti openvpn cat /etc/security/pam_ldap_totp_auth.conf
  • Enable debug: -e "DEBUG=true"

Time synchronisation critical for TOTP:

# Check time on the host
date

Install NTP/chrony on the host.

Clients can't reach internal networks

Issue: VPN connects but can't access internal resources.

Solutions:

  1. Enable NAT (easiest):

    -e "OVPN_NAT=true"
  2. Add return routes on internal gateways pointing the VPN network (e.g. 10.50.50.0/24) to the OpenVPN server IP

  3. Check routing:

    docker exec -ti openvpn ip route
    docker exec -ti openvpn iptables -t nat -L -n -v

Documentation

Support

Licence

See LICENCE file for details.

Credits

Built on top of:

Custom components: | Component | Purpose | Repository | | LDAP TOTP schema - An LDAP schema that allows the storage of MFA/TOTP keys and metadata in LDAP

  • LDAP TOTP PAM module - A Linux Pluggable-Authentication-Module that authenticates LDAP accounts and can authenticate One-Time-Passwords when LDAP uses the LDAP TOTP schema

Part of the wheelybird LDAP MFA suite.

About

A dockerised OpenVPN server using LDAP for authentication and with optional MFA

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 8