Configure WireGuard VPN server to authenticate users against LDAP directory services like Active Directory. Automate client certificate management and implement centralized user access control for enterprise environments.
Prerequisites
- Root access to server
- LDAP/Active Directory server with service account
- Basic knowledge of Python and LDAP concepts
- Firewall configuration access
What this solves
WireGuard provides fast, secure VPN connections but lacks built-in user management beyond manual key distribution. This tutorial integrates WireGuard with LDAP authentication, allowing you to manage VPN access through your existing Active Directory or OpenLDAP infrastructure. You'll automate client certificate generation, implement user group-based access policies, and centralize VPN user management.
Step-by-step configuration
Update system packages
Start by updating your package manager and installing essential dependencies for WireGuard and LDAP integration.
sudo apt update && sudo apt upgrade -y
sudo apt install -y wireguard wireguard-tools python3-pip python3-venv ldap-utils libldap2-dev libsasl2-dev libssl-devsudo dnf update -y
sudo dnf install -y wireguard-tools python3-pip python3-devel openldap-devel cyrus-sasl-devel openssl-devel gccEnable IP forwarding
Configure the kernel to forward packets between network interfaces, which is required for VPN routing.
echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.conf
echo 'net.ipv6.conf.all.forwarding = 1' | sudo tee -a /etc/sysctl.conf
sudo sysctl -pGenerate WireGuard server keys
Create the cryptographic keys that WireGuard uses for secure communications.
sudo mkdir -p /etc/wireguard
sudo chmod 700 /etc/wireguard
cd /etc/wireguard
sudo wg genkey | sudo tee server_private.key
sudo chmod 600 server_private.key
sudo cat server_private.key | wg pubkey | sudo tee server_public.keyConfigure WireGuard server
Create the main WireGuard configuration file with network settings and firewall rules.
[Interface]
PrivateKey = $(sudo cat /etc/wireguard/server_private.key)
Address = 10.66.66.1/24
ListenPort = 51820
SaveConfig = false
# NAT rules for client traffic
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADEeth0 with your server's primary network interface name. Check with ip route show default.Configure firewall for WireGuard
Open the WireGuard port and ensure proper traffic forwarding through the system firewall.
sudo ufw allow 51820/udp
sudo ufw allow ssh
sudo ufw --force enablesudo firewall-cmd --permanent --add-port=51820/udp
sudo firewall-cmd --permanent --add-masquerade
sudo firewall-cmd --reloadCreate LDAP authentication script
Build a Python script that handles LDAP authentication and user group validation for VPN access.
sudo python3 -m venv /opt/wg-ldap
sudo /opt/wg-ldap/bin/pip install ldap3 configparser#!/opt/wg-ldap/bin/python3
import sys
import configparser
from ldap3 import Server, Connection, ALL, NTLM
from ldap3.core.exceptions import LDAPException
def authenticate_user(username, password):
config = configparser.ConfigParser()
config.read('/etc/wireguard/ldap.conf')
ldap_server = config.get('ldap', 'server')
ldap_port = config.getint('ldap', 'port', fallback=389)
use_ssl = config.getboolean('ldap', 'use_ssl', fallback=False)
bind_dn = config.get('ldap', 'bind_dn')
bind_password = config.get('ldap', 'bind_password')
user_base = config.get('ldap', 'user_base')
user_filter = config.get('ldap', 'user_filter')
vpn_group = config.get('ldap', 'vpn_group', fallback=None)
try:
server = Server(ldap_server, port=ldap_port, use_ssl=use_ssl, get_info=ALL)
# Bind with service account
bind_conn = Connection(server, bind_dn, bind_password, auto_bind=True)
# Search for user
search_filter = user_filter.format(username=username)
bind_conn.search(user_base, search_filter, attributes=['cn', 'memberOf'])
if not bind_conn.entries:
print(f"User {username} not found in LDAP")
return False
user_entry = bind_conn.entries[0]
user_dn = user_entry.entry_dn
# Check group membership if configured
if vpn_group:
member_of = [str(group).lower() for group in user_entry.memberOf]
if not any(vpn_group.lower() in group for group in member_of):
print(f"User {username} not in VPN group {vpn_group}")
return False
# Authenticate user with their credentials
user_conn = Connection(server, user_dn, password, auto_bind=True)
print(f"User {username} authenticated successfully")
return True
except LDAPException as e:
print(f"LDAP error: {e}")
return False
except Exception as e:
print(f"Authentication error: {e}")
return False
if __name__ == "__main__":
if len(sys.argv) != 3:
print("Usage: wg_ldap_auth.py sudo chmod +x /opt/wg-ldap/wg_ldap_auth.pyConfigure LDAP connection settings
Create the configuration file with your LDAP server details and authentication parameters.
[ldap]
server = ldap.example.com
port = 389
use_ssl = false
bind_dn = CN=wireguard-service,CN=Users,DC=example,DC=com
bind_password = your-service-account-password
user_base = CN=Users,DC=example,DC=com
user_filter = (&(objectClass=user)(sAMAccountName={username}))
vpn_group = CN=VPN Users,CN=Groups,DC=example,DC=comsudo chmod 600 /etc/wireguard/ldap.conf(&(objectClass=inetOrgPerson)(uid={username})) and adjust base DN format like ou=users,dc=example,dc=com.Create client management script
Build a comprehensive script for managing WireGuard client configurations with LDAP integration.
#!/opt/wg-ldap/bin/python3
import os
import sys
import subprocess
import argparse
import ipaddress
from pathlib import Path
WG_DIR = Path("/etc/wireguard")
CLIENT_DIR = WG_DIR / "clients"
CONFIG_FILE = WG_DIR / "wg0.conf"
AUTH_SCRIPT = "/opt/wg-ldap/wg_ldap_auth.py"
def get_next_ip():
"""Find the next available IP address in the VPN subnet"""
network = ipaddress.IPv4Network('10.66.66.0/24')
used_ips = set(['10.66.66.1']) # Server IP
# Parse existing client IPs from config
if CONFIG_FILE.exists():
with open(CONFIG_FILE, 'r') as f:
content = f.read()
for line in content.split('\n'):
if line.strip().startswith('AllowedIPs'):
ip = line.split('=')[1].strip().split('/')[0]
used_ips.add(ip)
for ip in network.hosts():
if str(ip) not in used_ips:
return str(ip)
raise Exception("No available IP addresses")
def generate_client_config(username, client_ip, server_public_key, client_private_key, client_public_key):
"""Generate client WireGuard configuration"""
config = f"""[Interface]
PrivateKey = {client_private_key}
Address = {client_ip}/32
DNS = 8.8.8.8, 1.1.1.1
[Peer]
PublicKey = {server_public_key}
AllowedIPs = 0.0.0.0/0
Endpoint = YOUR_SERVER_IP:51820
PersistentKeepalive = 25
"""
return config
def add_client(username, password):
"""Add a new VPN client after LDAP authentication"""
# Authenticate with LDAP
result = subprocess.run([AUTH_SCRIPT, username, password],
capture_output=True, text=True)
if result.returncode != 0:
print(f"LDAP authentication failed for {username}")
print(result.stdout)
return False
# Check if client already exists
client_key_file = CLIENT_DIR / f"{username}_private.key"
if client_key_file.exists():
print(f"Client {username} already exists")
return False
# Create client directory
CLIENT_DIR.mkdir(exist_ok=True)
# Generate client keys
client_private = subprocess.check_output(['wg', 'genkey'], text=True).strip()
client_public = subprocess.check_output(['wg', 'pubkey'],
input=client_private, text=True).strip()
# Get next available IP
client_ip = get_next_ip()
# Save client keys
with open(client_key_file, 'w') as f:
f.write(client_private)
os.chmod(client_key_file, 0o600)
with open(CLIENT_DIR / f"{username}_public.key", 'w') as f:
f.write(client_public)
# Get server public key
with open(WG_DIR / "server_public.key", 'r') as f:
server_public_key = f.read().strip()
# Generate client config
client_config = generate_client_config(username, client_ip, server_public_key,
client_private, client_public)
# Save client config
config_file = CLIENT_DIR / f"{username}.conf"
with open(config_file, 'w') as f:
f.write(client_config)
# Add peer to server config
peer_config = f"""\n# Client: {username}
[Peer]
PublicKey = {client_public}
AllowedIPs = {client_ip}/32\n"""
with open(CONFIG_FILE, 'a') as f:
f.write(peer_config)
# Reload WireGuard
subprocess.run(['systemctl', 'reload', 'wg-quick@wg0'], check=True)
print(f"Client {username} added successfully")
print(f"Config file: {config_file}")
print(f"Client IP: {client_ip}")
return True
def remove_client(username):
"""Remove a VPN client"""
client_key_file = CLIENT_DIR / f"{username}_private.key"
if not client_key_file.exists():
print(f"Client {username} does not exist")
return False
# Get client public key
with open(CLIENT_DIR / f"{username}_public.key", 'r') as f:
client_public = f.read().strip()
# Remove from server config
with open(CONFIG_FILE, 'r') as f:
lines = f.readlines()
new_lines = []
skip_peer = False
for line in lines:
if line.strip() == f"# Client: {username}":
skip_peer = True
continue
elif line.strip().startswith("[Peer]") and skip_peer:
continue
elif line.strip().startswith("PublicKey =") and skip_peer:
continue
elif line.strip().startswith("AllowedIPs =") and skip_peer:
skip_peer = False
continue
else:
new_lines.append(line)
with open(CONFIG_FILE, 'w') as f:
f.writelines(new_lines)
# Remove client files
for file_pattern in [f"{username}_private.key", f"{username}_public.key", f"{username}.conf"]:
file_path = CLIENT_DIR / file_pattern
if file_path.exists():
file_path.unlink()
# Reload WireGuard
subprocess.run(['systemctl', 'reload', 'wg-quick@wg0'], check=True)
print(f"Client {username} removed successfully")
return True
def list_clients():
"""List all VPN clients"""
if not CLIENT_DIR.exists():
print("No clients configured")
return
print("Configured VPN clients:")
for key_file in CLIENT_DIR.glob("*_private.key"):
username = key_file.stem.replace("_private", "")
print(f" - {username}")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='WireGuard LDAP Client Manager')
subparsers = parser.add_subparsers(dest='action', help='Available actions')
add_parser = subparsers.add_parser('add', help='Add a new client')
add_parser.add_argument('username', help='LDAP username')
add_parser.add_argument('password', help='LDAP password')
remove_parser = subparsers.add_parser('remove', help='Remove a client')
remove_parser.add_argument('username', help='Client username')
subparsers.add_parser('list', help='List all clients')
args = parser.parse_args()
if args.action == 'add':
add_client(args.username, args.password)
elif args.action == 'remove':
remove_client(args.username)
elif args.action == 'list':
list_clients()
else:
parser.print_help()sudo chmod +x /opt/wg-ldap/wg_client_manager.pyUpdate server IP in client template
Replace the placeholder with your actual server's public IP address in the client manager script.
SERVER_IP=$(curl -s ipv4.icanhazip.com)
sudo sed -i "s/YOUR_SERVER_IP/$SERVER_IP/g" /opt/wg-ldap/wg_client_manager.pyStart WireGuard service
Enable and start the WireGuard VPN service with the configuration you created.
sudo systemctl enable wg-quick@wg0
sudo systemctl start wg-quick@wg0
sudo systemctl status wg-quick@wg0Create user management aliases
Add convenient command aliases for managing VPN users with LDAP authentication.
sudo tee /usr/local/bin/wg-add-user << 'EOF'
#!/bin/bash
if [ $# -ne 2 ]; then
echo "Usage: wg-add-user Configure monitoring and logging
Set up connection logging
Configure WireGuard to log connection events for monitoring and auditing purposes.
# WireGuard logging
:msg, contains, "wireguard" /var/log/wireguard.log
& stopsudo systemctl restart rsyslog
sudo touch /var/log/wireguard.log
sudo chmod 644 /var/log/wireguard.logCreate monitoring script
Build a script to monitor VPN connections and generate usage reports.
#!/opt/wg-ldap/bin/python3
import subprocess
import json
import datetime
from pathlib import Path
def get_wg_status():
"""Get current WireGuard status and peer information"""
try:
result = subprocess.run(['wg', 'show', 'wg0', 'dump'],
capture_output=True, text=True, check=True)
lines = result.stdout.strip().split('\n')
if not lines or not lines[0]:
return {'server': {}, 'peers': []}
# First line is server info
server_line = lines[0].split('\t')
server_info = {
'private_key': server_line[0],
'public_key': server_line[1],
'listen_port': server_line[2] if len(server_line) > 2 else None,
'fwmark': server_line[3] if len(server_line) > 3 else None
}
# Remaining lines are peers
peers = []
for line in lines[1:]:
if not line.strip():
continue
parts = line.split('\t')
if len(parts) >= 3:
peer = {
'public_key': parts[0],
'preshared_key': parts[1] if parts[1] != '(none)' else None,
'endpoint': parts[2] if parts[2] != '(none)' else None,
'allowed_ips': parts[3] if len(parts) > 3 else None,
'latest_handshake': int(parts[4]) if len(parts) > 4 and parts[4] != '0' else None,
'transfer_rx': int(parts[5]) if len(parts) > 5 else 0,
'transfer_tx': int(parts[6]) if len(parts) > 6 else 0,
'persistent_keepalive': parts[7] if len(parts) > 7 and parts[7] != 'off' else None
}
peers.append(peer)
return {'server': server_info, 'peers': peers}
except subprocess.CalledProcessError:
return {'server': {}, 'peers': []}
def get_client_names():
"""Map public keys to client usernames"""
client_dir = Path('/etc/wireguard/clients')
client_map = {}
if not client_dir.exists():
return client_map
for pub_key_file in client_dir.glob('*_public.key'):
username = pub_key_file.stem.replace('_public', '')
try:
with open(pub_key_file, 'r') as f:
public_key = f.read().strip()
client_map[public_key] = username
except IOError:
continue
return client_map
def format_bytes(bytes_val):
"""Format bytes in human readable format"""
for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
if bytes_val < 1024.0:
return f"{bytes_val:.2f} {unit}"
bytes_val /= 1024.0
return f"{bytes_val:.2f} PB"
def format_timestamp(timestamp):
"""Format Unix timestamp to readable date"""
if timestamp:
return datetime.datetime.fromtimestamp(timestamp).strftime('%Y-%m-%d %H:%M:%S')
return 'Never'
def main():
status = get_wg_status()
clieAutomated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Configuration
WIREGUARD_PORT=${1:-51820}
VPN_NETWORK=${2:-10.66.66.0/24}
VPN_SERVER_IP=${3:-10.66.66.1}
usage() {
echo "Usage: $0 [wireguard_port] [vpn_network] [server_ip]"
echo "Example: $0 51820 10.66.66.0/24 10.66.66.1"
exit 1
}
log() {
echo -e "${GREEN}[INFO]${NC} $1"
}
warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
error() {
echo -e "${RED}[ERROR]${NC} $1"
exit 1
}
cleanup() {
warn "Installation failed. Cleaning up..."
systemctl stop wg-quick@wg0 2>/dev/null || true
rm -rf /etc/wireguard/wg0.conf /opt/wg-ldap 2>/dev/null || true
}
trap cleanup ERR
# Check if running as root
if [[ $EUID -ne 0 ]]; then
error "This script must be run as root"
fi
# Detect distribution
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update"
PKG_INSTALL="apt install -y"
FIREWALL_CMD="ufw"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
FIREWALL_CMD="firewalld"
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum update -y"
PKG_INSTALL="yum install -y"
FIREWALL_CMD="firewalld"
;;
*) error "Unsupported distribution: $ID" ;;
esac
else
error "Cannot detect distribution"
fi
# Get primary network interface
PRIMARY_IFACE=$(ip route show default | awk '/default/ { print $5 }' | head -n1)
if [[ -z "$PRIMARY_IFACE" ]]; then
error "Cannot detect primary network interface"
fi
log "Detected distribution: $ID"
log "Primary network interface: $PRIMARY_IFACE"
echo "[1/10] Updating system packages..."
$PKG_UPDATE
echo "[2/10] Installing WireGuard and dependencies..."
if [[ "$PKG_MGR" == "apt" ]]; then
$PKG_INSTALL wireguard wireguard-tools python3-pip python3-venv ldap-utils libldap2-dev libsasl2-dev libssl-dev
elif [[ "$PKG_MGR" == "dnf" ]]; then
$PKG_INSTALL wireguard-tools python3-pip python3-devel openldap-devel cyrus-sasl-devel openssl-devel gcc
elif [[ "$PKG_MGR" == "yum" ]]; then
$PKG_INSTALL python3-pip python3-devel openldap-devel cyrus-sasl-devel openssl-devel gcc
# Install WireGuard from EPEL for RHEL-based systems
amazon-linux-extras install -y epel 2>/dev/null || yum install -y epel-release 2>/dev/null || true
$PKG_INSTALL wireguard-tools
fi
echo "[3/10] Enabling IP forwarding..."
echo 'net.ipv4.ip_forward = 1' >> /etc/sysctl.conf
echo 'net.ipv6.conf.all.forwarding = 1' >> /etc/sysctl.conf
sysctl -p
echo "[4/10] Creating WireGuard directory and keys..."
mkdir -p /etc/wireguard
chmod 700 /etc/wireguard
cd /etc/wireguard
# Generate server keys
wg genkey > server_private.key
chmod 600 server_private.key
wg pubkey < server_private.key > server_public.key
chown root:root server_private.key server_public.key
SERVER_PRIVATE_KEY=$(cat server_private.key)
echo "[5/10] Creating WireGuard server configuration..."
cat > /etc/wireguard/wg0.conf << EOF
[Interface]
PrivateKey = $SERVER_PRIVATE_KEY
Address = $VPN_SERVER_IP/24
ListenPort = $WIREGUARD_PORT
SaveConfig = false
# NAT rules for client traffic
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o $PRIMARY_IFACE -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o $PRIMARY_IFACE -j MASQUERADE
EOF
chmod 644 /etc/wireguard/wg0.conf
echo "[6/10] Configuring firewall..."
if [[ "$FIREWALL_CMD" == "ufw" ]]; then
ufw allow $WIREGUARD_PORT/udp
ufw allow ssh
echo "y" | ufw enable
elif [[ "$FIREWALL_CMD" == "firewalld" ]]; then
systemctl enable firewalld
systemctl start firewalld
firewall-cmd --permanent --add-port=$WIREGUARD_PORT/udp
firewall-cmd --permanent --add-masquerade
firewall-cmd --reload
fi
echo "[7/10] Creating Python virtual environment for LDAP..."
python3 -m venv /opt/wg-ldap
/opt/wg-ldap/bin/pip install ldap3 configparser
echo "[8/10] Creating LDAP authentication script..."
cat > /opt/wg-ldap/wg-ldap-auth.py << 'EOF'
#!/opt/wg-ldap/bin/python3
import sys
import configparser
from ldap3 import Server, Connection, ALL
from ldap3.core.exceptions import LDAPException
def authenticate_user(username, password):
config = configparser.ConfigParser()
config.read('/etc/wireguard/ldap.conf')
ldap_server = config.get('ldap', 'server')
ldap_port = config.getint('ldap', 'port', fallback=389)
use_ssl = config.getboolean('ldap', 'use_ssl', fallback=False)
bind_dn = config.get('ldap', 'bind_dn')
bind_password = config.get('ldap', 'bind_password')
user_base = config.get('ldap', 'user_base')
user_filter = config.get('ldap', 'user_filter')
vpn_group = config.get('ldap', 'vpn_group', fallback=None)
try:
server = Server(ldap_server, port=ldap_port, use_ssl=use_ssl, get_info=ALL)
bind_conn = Connection(server, bind_dn, bind_password, auto_bind=True)
search_filter = user_filter.format(username=username)
bind_conn.search(user_base, search_filter, attributes=['cn', 'memberOf'])
if not bind_conn.entries:
return False
user_dn = bind_conn.entries[0].entry_dn
if vpn_group:
user_groups = bind_conn.entries[0].memberOf.values if bind_conn.entries[0].memberOf else []
if vpn_group not in [str(group) for group in user_groups]:
return False
user_conn = Connection(server, user_dn, password)
if user_conn.bind():
return True
except LDAPException:
return False
return False
if __name__ == "__main__":
if len(sys.argv) != 3:
sys.exit(1)
username = sys.argv[1]
password = sys.argv[2]
if authenticate_user(username, password):
sys.exit(0)
else:
sys.exit(1)
EOF
chmod 755 /opt/wg-ldap/wg-ldap-auth.py
chown root:root /opt/wg-ldap/wg-ldap-auth.py
echo "[9/10] Creating LDAP configuration template..."
cat > /etc/wireguard/ldap.conf.example << EOF
[ldap]
server = ldap://your-ldap-server.com
port = 389
use_ssl = false
bind_dn = CN=service-account,OU=Service Accounts,DC=company,DC=com
bind_password = your-service-account-password
user_base = OU=Users,DC=company,DC=com
user_filter = (&(objectClass=user)(sAMAccountName={username}))
vpn_group = CN=VPN-Users,OU=Groups,DC=company,DC=com
EOF
chmod 640 /etc/wireguard/ldap.conf.example
chown root:root /etc/wireguard/ldap.conf.example
echo "[10/10] Enabling WireGuard service..."
systemctl enable wg-quick@wg0
systemctl start wg-quick@wg0
log "WireGuard VPN server with LDAP authentication setup completed!"
log "Server public key: $(cat /etc/wireguard/server_public.key)"
log "Server endpoint: $(curl -s ifconfig.me 2>/dev/null || echo 'YOUR_PUBLIC_IP'):$WIREGUARD_PORT"
warn "Configure LDAP settings in /etc/wireguard/ldap.conf before using authentication"
warn "Example configuration available at /etc/wireguard/ldap.conf.example"
# Verification
if systemctl is-active --quiet wg-quick@wg0; then
log "WireGuard service is running"
else
error "WireGuard service failed to start"
fi
if wg show wg0 &>/dev/null; then
log "WireGuard interface is active"
else
error "WireGuard interface is not active"
fi
Review the script before running. Execute with: bash install.sh