# OpenVPN road-warrior endpoint — RackNerd hub # Auth chain: AD user/pass -> PAM -> RADIUS # -> Duo Auth Proxy (127.0.0.1:1812) -> LDAPS to the DCs. # No individual client certs: identity is LDAP + Duo only. tls-crypt # (shared key in the profile) still gates who can attempt a handshake. port 1194 proto udp4 dev tun0 topology subnet server 172.31.8.0 255.255.255.0 # Identity = AD username (username-as-common-name), and one user WILL # connect from several devices (laptop + phone). duplicate-cn allows the # concurrent sessions and gives each its own pool IP. Sticky per-user IPs # (ifconfig-pool-persist) are incompatible with that — do not re-enable. duplicate-cn # Split tunnel: only the AD sites + transfer net ride the VPN. push "route 192.168.20.0 255.255.255.0" push "route 192.168.7.0 255.255.255.0" push "route 172.31.255.0 255.255.255.0" # ALL client DNS goes to Technitium. Deliberately NO "dhcp-option DOMAIN" # or DOMAIN-SEARCH here: in OpenVPN3 clients (Connect on macOS/iOS/ # Android/Windows) those options MEAN split-DNS — pushing them scopes the # VPN DNS to just those domains. With only DNS pushed, 172.31.8.1 becomes # the default resolver for every query. ADAPTER_DOMAIN_SUFFIX sets the # Windows connection-specific suffix (short names like "ahdc01" still # resolve) without triggering split-DNS. block-outside-dns additionally # stops Windows from leaking queries to the local LAN resolver. push "dhcp-option DNS 172.31.8.1" push "dhcp-option ADAPTER_DOMAIN_SUFFIX ad.atlashorizon.net" push "block-outside-dns" # PKI — server certificate only (offline CA, see pki/README.md); # clients present no certificate. ca /etc/openvpn/pki/ca.crt cert /etc/openvpn/pki/server.crt key /etc/openvpn/pki/server.key dh none tls-crypt /etc/openvpn/pki/ta.key tls-version-min 1.2 data-ciphers AES-256-GCM:CHACHA20-POLY1305 auth SHA256 # User auth via Duo (password only = push; "password,123456" = passcode) plugin /usr/lib/openvpn/openvpn-plugin-auth-pam.so openvpn verify-client-cert none username-as-common-name hand-window 120 # allow time for the Duo push round-trip # Hourly rekey + 12h auth token: renegotiations are satisfied silently by # the pushed token (no mid-session Duo prompts); a fresh password + push # is only required once the token expires. (auth-gen-token requires a # non-zero reneg-sec — do not set reneg-sec 0 here.) reneg-sec 3600 auth-gen-token 43200 keepalive 10 60 explicit-exit-notify 1 # wg0 MTU is 1420; keep the client tunnel small enough that client->site # packets never fragment inside the WireGuard leg. tun-mtu 1400 mssfix 1360 user nobody group nogroup persist-key persist-tun # Register each client as .$VPN_DNS_ZONE (A + PTR) in Technitium # on connect, clean up on disconnect. Invoked via sh so the mounted script # needs no exec bit. NOTE: openvpn runs hooks with a clean environment, so # the script sources /etc/openvpn/learn-address.env (written by the # entrypoint from the container env) rather than reading env vars directly. script-security 2 learn-address "/bin/sh /etc/openvpn/learn-address.sh" status /var/lib/openvpn/status.log 30 # Unix socket (root-only) instead of a passwordless TCP port: # docker compose exec openvpn sh -c "socat - UNIX:/var/lib/openvpn/mgmt.sock" management /var/lib/openvpn/mgmt.sock unix verb 3