OpenVPN Control Plane
Overview
This is a production control plane for OpenVPN, built in TypeScript on Node. Instead of compiling a C plugin into the daemon, it speaks OpenVPN's management protocol over a unix socket and answers every connection with an explicit decision: allow, deny, evict, and exactly which IP and routes the client gets. The daemon runs as a child process under a supervisor, so start order, restart policy, and socket lifecycle all live in one place — no race with a separate systemd unit, no orphaned sockets after a crash.
The control plane sits between the VPN daemon and an upstream HTTPS API. It caches that API, makes authorization decisions inside the connection path, and pushes per-client network configuration back to the daemon the moment a client connects.
The problem
OpenVPN terminates tunnels well and decides almost nothing else. The moment a deployment needs sticky IPs that survive reconnects, group isolation, bandwidth quotas, geo-based anomaly detection, or an audit log a regulator will accept, that logic has to live somewhere. A compiled plugin is rigid and needs a recompile to change; a pile of client-config-dir files can't express anything dynamic. Both also force a daemon restart to change policy — which drops every connected user. This project moves every decision out of the daemon and into a process that changes policy as data, while clients stay connected.
What it does
- Per-connection authorization — allow / deny / kill per client over the management socket, against a cached upstream API and a live session map.
- IP address management — sticky IPs and certificate-CN leases that survive a reconnect, plus a virtual-subnet registry for site-to-site clients.
- Per-client routing — iroutes, a MAC-to-router map, and a reconciliation loop that converges kernel routes to desired state instead of assuming a one-time push held.
- Firewall as data — group-based and full-isolation policies stored as rows and compiled into nftables, so reachability is auditable.
- Anomaly detection — GeoVelocity impossible-travel detection (MaxMind GeoIP + minimum-distance gate), TLS fingerprinting, a per-client risk score, and Suricata IDS ingestion.
- Traffic — byte counters turned into quotas and a bandwidth baseline, enforced on the wire with Linux
tc. - Forensics & compliance — packet captures streamed to S3, and tamper-evident, hash-chained session archives with an access trail for KVKK/GDPR retention.
- Resilience & ops — a pre-auth DDoS lane, soft failover,
/healthz+ Prometheus metrics, and an OpenAPI spec.
Architecture
OpenVPN ──unix:mgmt.sock──► Node Control Plane ──HTTPS──► Upstream API
▲ │
│ client-auth / kill │ Redis (hot cache)
└──── ifconfig-push, route ◄───┘ SQLite (durable lease + fallback cache)
A stateful parser turns the daemon's line stream into typed events; every command is serialized so a multi-line client-auth block never interleaves with a kill. State is two-tier: Redis as the hot tier, an embedded SQLite database as the durable tier that still knows the answer after a restart. A thin decision module is the glue — event in, upstream call, push config out — with policies and handlers kept separate so the orchestration is testable without a live daemon.
Tech stack
TypeScript (strict) · Node 20+ · OpenVPN management interface · Express + EJS · Prisma + better-sqlite3 · Redis · Zod · nftables / Linux tc / Linux bridge · MaxMind GeoIP · AWS S3 · Prometheus · Docker + PM2 + systemd · OpenAPI 3.1.
Outcome
A VPN deployment that enforces business rules — quotas, isolation, risk-based access, retention — rather than merely terminating tunnels. Policy ships as data while clients stay connected, every connection is weighed against geo, identity, and intrusion signals, and the audit trail is built to satisfy a regulator. Authorization, accounting, and compliance stopped being scattered scripts and became one observable, single-process system.
Newsletter
Stay updated! Get all the latest and greatest posts delivered straight to your inbox