Building a Zero-Trust VPN: How I Wrapped OpenVPN and WireGuard with Modern Authentication
It started with my homelab.
I have a modest setup—a few Proxmox nodes, some Kubernetes clusters, network storage, the usual suspects. I wanted secure remote access without exposing everything to the internet. Simple ask, right?
The Search for a Solution
I tried the modern zero-trust solutions first:
Tailscale - Great product, but I wanted self-hosted. Their open-source Headscale works, but felt like I was fighting the architecture. Coordination servers, DERP relays, ACL policies in HuJSON—it's elegant if you buy into their model, but I wanted something closer to traditional VPN semantics.
NetBird - Promising concept, but the setup was painful. Multiple components to deploy, documentation gaps, and I spent more time debugging connectivity issues than actually using it. The mesh networking model also felt like overkill for my use case.
Firezone - Looked good on paper, but the WireGuard-only approach was limiting. Some of my older devices needed OpenVPN. And the complexity of the Elixir/Phoenix stack made me nervous about long-term maintenance.
Pritunl - Closer to what I wanted, but the enterprise licensing model and MongoDB dependency gave me pause. Also, the codebase felt dated.
Each solution had pieces I liked, but none hit the sweet spot: simple deployment, SSO integration, both OpenVPN and WireGuard, and actual zero-trust with per-user firewall rules.
So I built my own.
What I Built
A few simple commands. That's it.
GateKey is a zero-trust VPN solution that wraps OpenVPN and WireGuard to provide software-defined perimeter capabilities—while maintaining 100% compatibility with existing clients.
In this article, I'll walk through the architecture decisions, the technical challenges, and how you can deploy a zero-trust VPN for your organization or homelab.
The Problem with Traditional VPNs
If you've managed a corporate VPN, you know the pain:
- Certificate Management Hell - Generating, distributing, and rotating certificates manually
- Shared Secrets - One compromised credential = everyone's compromised
- Flat Network Access - Once you're in, you can reach everything
- No Identity Awareness - The VPN doesn't know who you are, just that you have a valid cert
- Manual Onboarding - New employee? Here's a 10-step guide to configure your VPN client
Modern identity providers solved authentication years ago. Why are VPNs still using 1990s-era certificate distribution?
The Zero-Trust Approach
GateKey flips the model:
Authenticate first, connect second.
Instead of:
User has certificate → User connects → Hope they're authorized
We do:
User authenticates via SSO → System generates short-lived cert → User connects → Per-user firewall rules applied
Key Principles
- Identity-First - Integrate with your existing IdP (Okta, Azure AD, Google Workspace)
- Short-Lived Credentials - Certificates expire in 24 hours (configurable), no manual rotation
- Per-User Firewall - Each user gets individualized network access based on their role
- Zero Standing Privileges - No access without active authentication
Here's what SSO login looks like in the web UI:
Architecture Deep-Dive
Here's how the pieces fit together:
Component Breakdown
Control Plane (Go + React)
- Handles SSO authentication (OIDC/SAML)
- Embedded Certificate Authority for on-demand cert generation
- Policy engine for access rules
- REST API for all operations
Gateway Agents (Go)
- Lightweight agents that run alongside OpenVPN/WireGuard
- Communicate with control plane via authenticated API
- Manage nftables firewall rules per-user
- Support both OpenVPN and WireGuard protocols
Embedded CA
- No external PKI infrastructure required
- Generates short-lived certificates on-demand
- Supports graceful CA rotation with zero downtime
Technical Decision: Why Wrap Instead of Reimplement?
I considered building a custom VPN protocol. Then I remembered:
- OpenVPN and WireGuard are battle-tested - Millions of deployments, extensively audited
- Client compatibility - Every platform already has OpenVPN/WireGuard clients
- Security audits are expensive - Why audit a new protocol when proven ones exist?
Instead, GateKey uses hook scripts to intercept OpenVPN events:
# OpenVPN calls these scripts on client events
client-connect /usr/local/bin/gatekey-connect
client-disconnect /usr/local/bin/gatekey-disconnect
auth-user-pass-verify /usr/local/bin/gatekey-verify via-env
When a client connects:
- OpenVPN triggers
gatekey-verifywith the client certificate - Gateway agent validates the cert against the control plane
- Control plane checks: Is this cert valid? Is the user still authenticated? What access rules apply?
- Gateway receives the user's access rules and configures nftables
- User gets access only to their authorized resources
The VPN protocol itself is unchanged. We're just adding an authentication and authorization layer.
Per-User Firewall Rules with nftables
This is where zero-trust gets real. Each connected user gets their own firewall rules:
// internal/firewall/nftables.go
func (f *NFTablesFirewall) ApplyUserRules(userID string, vpnIP net.IP, rules []AccessRule) error {
// Create a chain for this specific user
chainName := fmt.Sprintf("user_%s", sanitize(userID))
// Add rules for each allowed network
for _, rule := range rules {
nftCmd := fmt.Sprintf(
"nft add rule inet gatekey %s ip saddr %s ip daddr %s accept",
chainName, vpnIP, rule.CIDR,
)
if err := exec.Command("sh", "-c", nftCmd).Run(); err != nil {
return fmt.Errorf("failed to add rule: %w", err)
}
}
// Default deny for this user
nftCmd := fmt.Sprintf(
"nft add rule inet gatekey %s ip saddr %s drop",
chainName, vpnIP,
)
return exec.Command("sh", "-c", nftCmd).Run()
}
Access rules are managed through a clean admin interface:
When access rules change in the admin UI, gateways poll for updates every 10 seconds and immediately update firewall rules—no client reconnection required.
Short-Lived Certificates: The Key to Zero Trust
Traditional VPNs issue certificates valid for 1-5 years. If compromised, you're exposed for years.
GateKey certificates expire in 24 hours by default:
// internal/pki/ca.go
func (ca *CA) IssueCertificate(commonName string, validityHours int) (*x509.Certificate, error) {
template := &x509.Certificate{
SerialNumber: big.NewInt(time.Now().UnixNano()),
Subject: pkix.Name{
CommonName: commonName,
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(time.Duration(validityHours) * time.Hour),
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
BasicConstraintsValid: true,
}
// Sign with our CA
certDER, err := x509.CreateCertificate(rand.Reader, template, ca.cert, pubKey, ca.privateKey)
// ...
}
The CA management interface provides full visibility into certificate lifecycle:
The client CLI handles renewal automatically:
$ gatekey connect
# First time: Opens browser for SSO, downloads config, connects
# Subsequent times: Refreshes config if needed, connects immediately
Users never see certificate expiration. They just authenticate and connect.
Dual Protocol Support: OpenVPN + WireGuard
Some environments need OpenVPN (maximum compatibility). Others want WireGuard (performance, mobile). GateKey supports both with the same zero-trust model:
# Gateway types in the database
gateways:
- name: us-east-openvpn
type: openvpn
endpoint: vpn-east.company.com:1194
- name: us-west-wireguard
type: wireguard
endpoint: vpn-west.company.com:51820
Users can connect to either:
$ gatekey connect us-east-openvpn # Uses OpenVPN
$ gatekey connect us-west-wireguard # Uses WireGuard
$ gatekey status
Active connections:
us-east-openvpn connected 10.8.0.5 tun0
us-west-wireguard connected 10.9.0.12 wg0
Multi-gateway support means users can be connected to multiple VPNs simultaneously—each with their own firewall rules.
Mesh Networking: Site-to-Site Connectivity
Beyond traditional client-to-site VPN, GateKey supports hub-and-spoke mesh networking for site-to-site connectivity:
This enables:
- Branch office connectivity - Connect remote offices to central resources
- Multi-cloud networking - Link AWS, GCP, and on-prem networks
- Zero-trust between sites - Same per-identity firewall rules apply
Network Topology Visualization
Monitor your entire VPN infrastructure in real-time:
The topology view shows:
- All gateways, hubs, and spokes
- Active VPN sessions
- Connection health and bandwidth
- User attribution for every connection
Geo-Fencing: IP-Based Access Control
Add another layer of security with geo-fencing rules:
- Whitelist model - Only connections from explicitly allowed IP ranges
- Hierarchical rules - User rules override group rules, which override global rules
- Audit mode - Log violations without blocking (for testing)
Authentication Integration
GateKey integrates with your existing identity provider:
Supported providers:
- Okta
- Azure AD / Entra ID
- Google Workspace
- Keycloak
- Any OIDC or SAML 2.0 compliant provider
The Admin Dashboard
A unified view of your zero-trust VPN infrastructure:
The User Experience
For end users, it's three commands:
# One-time setup
gatekey config init --server https://vpn.company.com
# Daily usage
gatekey login # Opens browser for SSO
gatekey connect # Downloads config, connects
gatekey disconnect # When done
Or use the web UI to download a standard .ovpn file that works with any OpenVPN client.
Deployment Options
Kubernetes (Recommended)
helm repo add gatekey https://dye-tech.github.io/gatekey-helm-chart
helm install gatekey gatekey/gatekey -n gatekey --create-namespace
Docker Compose
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: gatekey
gatekey-server:
image: dyetech/gatekey-server:latest
environment:
DATABASE_URL: postgres://gatekey:pass@postgres/gatekey
gatekey-web:
image: dyetech/gatekey-web:latest
ports:
- "80:8080"
What I Learned
Building GateKey taught me several things:
- Don't reinvent crypto - Wrap proven protocols instead of creating new ones
- Hook scripts are powerful - OpenVPN's plugin system is underutilized
- nftables > iptables - Modern, atomic updates, better performance
- Short-lived credentials change everything - When certs expire in hours, compromise windows shrink dramatically
-
Developer experience matters -
gatekey connectshould just work
Try It Out
GateKey is open source under Apache 2.0:
- GitHub: github.com/dye-tech/GateKey
- Documentation: gatekey.net
- Docker Hub: hub.docker.com/u/dyetech
- Helm Chart: github.com/dye-tech/gatekey-helm-chart
- GitHub Android Client: github.com/dye-tech/gatekey-android-client
If you're tired of managing VPN certificates manually, or want to add zero-trust to your existing OpenVPN/WireGuard infrastructure, give it a try.
What's your experience with VPN infrastructure? Have you implemented zero-trust networking? I'd love to hear about your approach in the comments.




















Top comments (0)