Skip to content

About

DataGateOpenVpnManager is a Dockerized OpenVPN server with a built-in .NET management API and Easy-RSA integration. It automates PKI initialization, certificate management, OpenVPN configuration, and VPN lifecycle operations while running everything inside a single container.

Resources

Stars

2 stars

Watchers

1 watching

Forks

Repository files navigation

DataGateOpenVpnManager + OpenVPN Docker Image

OpenVPN sidecar for DataGate Monitor. In the monorepo this is the openvpn/ submodule (DataGateMonitor). Standalone clone: DataGateOpenVpnManager.

Links

Resource Link
DataGate datagateapp.com
Download datagateapp.com/download
Telegram channel @datagateapp

This project builds a Docker image containing:

  • A .NET 9 application: DataGateOpenVpnManager
  • A fully functional OpenVPN server
  • Integrated Easy-RSA certificate management

It uses a custom entrypoint to initialize PKI, generate default configuration, and run both OpenVPN and the .NET app in the same container.


πŸ“₯ Clone the Repository

git clone https://github.com/IMKolganov/DataGateOpenVpnManager.git
cd DataGateOpenVpnManager

πŸ›  Build

docker build --build-arg TARGETARCH=$(uname -m) -t datagate-openvpn .

You must pass TARGETARCH. Common values:

  • x64 or amd64
  • arm64

Optional:

docker build --build-arg TARGETARCH=amd64 --build-arg BUILD_CONFIGURATION=Debug -t datagate-openvpn .

πŸš€ Run

docker run \
  --cap-add=NET_ADMIN \
  -e PORT=1194 \
  -e PROTO=udp \
  -e DATA_DIR=/mnt \
  -v /path/to/data:/mnt \
  -p 1194:1194/udp \
  datagate-openvpn

Environment variables:

Variable Description Default
PORT OpenVPN port 1194
PROTO Protocol (udp or tcp) udp
DATA_DIR Data directory for config/logs/pki /mnt
DNS1, DNS2 Pushed DNS servers 8.8.8.8, 8.8.4.4
CIPHER Data-channel cipher; rewritten into issued .ovpn and /api/info AES-128-GCM if DCO, else AES-256-CBC
DATA_CIPHERS OpenVPN data-ciphers list; rewritten into issued .ovpn / /api/info GCM/ChaCha list if DCO, else unset
DCO Enable OpenVPN DCO (true/1/yes); exposed on /api/info false
AUTH HMAC digest (auth); server.conf + issued .ovpn / /api/info SHA256
TLS_VERSION_MIN tls-version-min on server and issued .ovpn / /api/info 1.2
CLIENT_VERB Suggested client .ovpn verb (not server log verb) / /api/info 3
MSSFIX Optional push "mssfix N" to clients; exposed on /api/info (not duplicated in .ovpn) (unset)
TUN_DEV Fixed tun device name (stable ufw/NAT) _(unset β†’ dev tun)
TUN_IF Extra FORWARD by iface (optional) (unset)
WAN_IF WAN iface for MASQUERADE eth0 (auto if missing)
VPN_SUBNET, VPN_NETMASK VPN subnet config 10.51.28.0/24
OpenVpnManagement__Port OpenVPN management interface port 5092
OpenVpnProxy__ByteDebug Compare proxy vs management bytes (WSS debug) false
OpenVpnProxy__ByteDebugIntervalSeconds Periodic byte comparison while connected (0 = on disconnect only) 0
OpenVpnProxy__CloseZombieAfterMissingSeconds Close WSS when OpenVPN peer missing from management (0 = off) 0
OpenVpnProxy__ZombieCheckIntervalSeconds How often to check for zombie proxy sessions 30
OpenVpnProxy__TlsLogEnrichmentEnabled Tag tls-crypt errors as external probe vs app client (for Wazuh) true
PROXY_BYTE_DEBUG Legacy alias for OpenVpnProxy__ByteDebug (1 / true) (unset)
PiHole__Enabled Collect VPN DNS queries from Pi-hole API false
PiHole__BaseUrl Pi-hole FTL API base URL (same network namespace) http://127.0.0.1:8080
PiHole__AppPassword Pi-hole application password for /api/auth (empty)
PiHole__PollIntervalSeconds How often to poll Pi-hole query log 60
PiHole__BatchSize Max queries per poll 200
PIHOLE_ENABLED Legacy env alias for PiHole__Enabled (unset)
PIHOLE_BASE_URL Legacy env alias for PiHole__BaseUrl (unset)
PIHOLE_APP_PASSWORD Legacy env alias for PiHole__AppPassword (unset)
PIHOLE_POLL_INTERVAL_SEC Legacy env alias for PiHole__PollIntervalSeconds (unset)

Pi-hole config priority (highest wins): PIHOLE_* / PiHole__* env vars β†’ dashboard Save & apply ($DATA_DIR/pihole-runtime-config.json) β†’ appsettings defaults. Env overrides only the fields that are set.

Multi-stack host (fixed tun + UFW by subnet)

Do not rely on tunN names in UFW β€” they change on every OpenVPN recreate. Use:

  1. Image β‰₯ 1.2.5.94 with TUN_DEV=ovpn-udp (unique per stack) β€” entrypoint writes dev ovpn-udp / dev-type tun, deletes orphans that still hold this subnet’s .1, and installs FORWARD/NAT by CIDR.
  2. Host UFW once per subnet (survives reboot and tun renames):
sudo chmod +x scripts/host-ufw-vpn-subnet.sh
# UDP pool β†’ internet + DNS via Pi-hole on TCP tun (10.51.30.1):
sudo ./scripts/host-ufw-vpn-subnet.sh 10.51.32.0/24 10.51.30.1
  1. Compose env for UDP WSS:
TUN_DEV: ovpn-udp
VPN_SUBNET: "10.51.32.0"
DNS1: "10.51.30.1"
DNS2: "10.51.30.1"
DCO: "true"
CIPHER: "AES-128-GCM"

Keep DNS push on Pi-hole (10.51.30.1 or this stack’s own .1), not public 1.1.1.1.

TCP stack + shared Pi-hole (host netns)

Same host usually has one Pi-hole in network_mode: "container:openvpn-tcp-wss" (host netns). OpenVPN TCP uses dev tun unless TUN_DEV is set β€” after reboot/recreate the kernel picks tun0/tun1, while UFW still allows 53 on the old name. Symptom: dashboard Pi-hole pipeline green (HTTP :8080), VPN connected, no internet (block-outside-dns + DNS timeout). UDP often still works if its iface name is stable (TUN_DEV=ovpn-udp) or UFW allows that subnet β†’ Pi-hole .1.

Do this:

  1. Pin TCP: TUN_DEV: tun-tcp and TUN_IF: tun-tcp (do not turn DCO off if it already worked).
  2. Prefer UFW by destination, not iface name (survives tun0 β†’ tun-tcp):
sudo ufw allow in on tun-tcp proto udp to any port 53 comment 'Pi-hole DNS tun-tcp'
sudo ufw allow in on tun-tcp proto tcp to any port 53 comment 'Pi-hole DNS tun-tcp'
# even better, already used on some hosts:
sudo ufw allow from 10.51.15.0/24 to 10.51.15.1 port 53
sudo ufw allow from 10.51.16.0/24 to 10.51.16.1 port 53
  1. Pi-hole FTL v6: dns.interface is one name. BIND + tun-tcp,ovpn-udp,br-… (comma list) or a custom entrypoint wrapping start.sh can leave :53 open with no answers (Recv-Q growing, no listening on … port 53 in FTL log). Working pattern:
# pi-hole compose β€” no custom entrypoint
FTLCONF_dns_listeningMode: 'BIND'
FTLCONF_dns_interface: 'tun-tcp'   # TCP .1 only

Extra listen IPs (UDP .1, docker-bridge for Xray) via /etc/dnsmasq.d/ after FTL is healthy:

printf '%s\n' 'listen-address=10.51.16.1' 'listen-address=172.20.0.1' \
  > ~/pi-hole/etc-dnsmasq.d/99-extra-listen.conf
docker exec datagate-pihole pihole-FTL --config misc.etc_dnsmasq_d true
docker restart datagate-pihole
# log must show: listening on 10.51.16.1 and 172.20.0.1
  1. network_mode: container:openvpn-tcp-wss stores the container ID. Recreate OpenVPN β†’ Pi-hole stays Exited (No such container: <old id>). Recreate Pi-hole after TCP is Up. Optional systemd oneshot (After=docker.service) that waits for openvpn-tcp-wss + openvpn-udp-wss then docker compose up -d --force-recreate in ~/pi-hole.
  2. Host reboot does not fix a wedged FTL. Check docker logs datagate-pihole | grep 'listening on'. Dashboard step 4 (API :8080) β‰  DNS :53.

Compose env for TCP WSS:

TUN_DEV: tun-tcp
TUN_IF: tun-tcp
VPN_SUBNET: "10.51.15.0"   # example
DNS1: "10.51.15.1"
DNS2: "10.51.15.1"

πŸ“ Directory Layout Inside Container

Path Purpose
/etc/openvpn Final OpenVPN config/cert destination
/usr/share/easy-rsa EasyRSA installation
$DATA_DIR (default /mnt) Mounted volume for logs, config, PKI

πŸ“¦ What This Does

  1. Copies and initializes Easy-RSA in $DATA_DIR/easy-rsa
  2. Generates CA, server cert/key, and ta.key if missing
  3. Creates server.conf if missing
  4. Configures iptables to allow VPN traffic
  5. Runs OpenVPN and .NET app in parallel

πŸ“ Logs

Log files in $DATA_DIR:

  • openvpn.log
  • openvpn-status.log
  • pihole-query-cursor.txt β€” last Pi-hole query log cursor (pre-created by entrypoint, mode 644)
  • pihole-runtime-config.json β€” Pi-hole collector config from dashboard Save & apply (survives container restart; mode 600, re-applied on each container start)

Raw tls-crypt unwrapping failed lines from internet scanners are filtered out of Docker stdout; the .NET app re-emits them with an origin tag:

Log prefix Meaning Suggested Wazuh level
[OpenVpnTlsExternalProbe] Direct probe to OpenVPN port (not our WSS app) ignore / low
[OpenVpnTlsAppClient] WSS proxy path (127.0.0.1:localPort) with clientRef / User-Agent alert
[OpenVpnTlsLocalUnknown] Loopback without matched proxy session investigate

Wazuh rule 100913 should match [OpenVpnTlsAppClient] instead of the raw TLS Error: tls-crypt string.


⚠ Notes

  • Requires --cap-add=NET_ADMIN to allow iptables setup
  • Container exposes OpenVPN on configured port/protocol
  • Designed to be used with external volume (-v) for persistence

πŸ§ͺ Example docker-compose.yml

services:
  openvpn_udp:
    image: imkolganov/datagate-monitor-openvpn:latest
    pull_policy: always
    container_name: openvpn_udp
    restart: unless-stopped
    networks:
      - backend_network
    cap_add:
      - NET_ADMIN
    devices:
      - "/dev/net/tun:/dev/net/tun"
    volumes:
      - openvpn_data_udp:/openvpn-udp
    ports:
      - "1194:1194/udp"
      - "5010:5010"
    environment:
      EASY_RSA_PATH: "/openvpn-udp/easy-rsa"
      DATA_DIR: /openvpn-udp
      PORT: "1194"
      API_PORT: 5010
      PROTO: udp
      # OPENVPN_WSS_UDP_PROXY: rust   # optional: native UDP WSS datapath (default: dotnet)
      OpenVpnManagement__Port: "5092"
      OpenVpnManagement__Host: "localhost"
      BACKEND__BASEURL: "http://backend:5581/"

πŸ‘€ Maintainer

Created by Ivan Kolganov based on Kyle Manna’s OpenVPN concepts.

Contact Link
Repository IMKolganov/DataGateOpenVpnManager
LinkedIn linkedin.com/in/imkolganov
Telegram @KolganovIvan
Buy Me a Coffee buymeacoffee.com/imkolganov

πŸ“„ License

MIT License .

About

DataGateOpenVpnManager is a Dockerized OpenVPN server with a built-in .NET management API and Easy-RSA integration. It automates PKI initialization, certificate management, OpenVPN configuration, and VPN lifecycle operations while running everything inside a single container.

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages