2026-07-16 11:44:08 -07:00
2026-07-16 11:44:08 -07:00
2026-07-16 00:54:33 -07:00
2026-07-16 11:44:08 -07:00
2026-07-16 00:54:33 -07:00
2026-07-16 11:44:08 -07:00
2026-07-16 01:14:20 -07:00
2026-07-16 11:44:08 -07:00
2026-07-16 00:54:33 -07:00
2026-07-15 17:58:23 -07:00
2026-07-15 18:50:19 -07:00
2026-07-16 11:44:08 -07:00
2026-07-16 11:44:08 -07:00

RackNerd VPN Hub

Client VPN entry point + DNS forwarder for the AtlasHorizon AD environment. Roaming clients terminate here (OpenVPN + Duo MFA) and are routed over WireGuard to the two sites. Inter-site traffic stays on the existing direct Oswald↔Trochalakis tunnel — this box never carries it.

                          ┌────────────── RackNerd VPS ──────────────┐
 roaming client ══ovpn══▶ │ tun0 172.31.8.1                          │
 (172.31.8.x)             │   openvpn ─▶ duo-authproxy ─▶ Duo cloud  │
   DNS/routes pushed      │      (127.0.0.1:1812)   └──LDAPS 636──┐  │
                          │   technitium :53 / console :5380      │  │
                          │ wg0 172.31.255.1 ─────────────────────┼──┤
                          └──────────┬──────────────────┬─────────┘  │
                            wg   .255.2            wg .255.3     (via wg0)
                        Trochalakis 192.168.20/24   Oswald 192.168.7/24
                        AHDC01, AHDC03 ◀━━ direct s2s ━━▶ AH-OSWLDC

Address plan

Net Subnet Notes
Trochalakis LAN 192.168.20.0/24 AHDC01, AHDC03
Oswald LAN 192.168.7.0/24 AH-OSWLDC
WG transfer 172.31.255.0/24 hub .1, Troch .2, Oswald .3
OpenVPN clients 172.31.8.0/24 routed (not NATed) to both sites
WG travel routers 172.31.9.0/24 GL.iNet roaming peers, hub .1 (docs)

Public endpoint: vpn.atlashorizon.net — only 22/tcp (SSH), 51820/udp (WireGuard) and 1194/udp (OpenVPN) are open. RADIUS binds loopback only; Technitium's DNS (:53) and web console (:5380) are firewalled to the tunnels and additionally guarded by its recursion ACL.

Layout

  • docker-compose.yml — openvpn, duo-authproxy, technitium (all network_mode: host)
  • openvpn/ — server.conf, PAM→RADIUS glue, offline-CA workflow in pki/README.md
  • duo-authproxy/ — built from Duo's source tarball; cfg rendered from .env
  • technitium/ — Technitium DNS: AD conditional-forwarder zones created by an idempotent API init job (configure.sh), everything else public recursion; web console on :5380 (VPN/tunnel only)
  • host/ — setup.sh (sysctl, iptables, wg keygen), wg0.conf.example, firewall template
  • client/ — .ovpn template for end users
  • ansible/ — one idempotent playbook for setup and updates, deploys to /opt/<service> (see ansible/README.md)
  • docs/ — SITE-SIDE.md (DC/firewall work at the sites), TESTPLAN.md

Deploy: Ansible (preferred)

ansible/site.yml does everything in the manual list below — Docker install, sysctl, firewall, WireGuard keygen, and the compose stack under /opt — and re-running it is the update mechanism. See ansible/README.md. Site-side work (docs/SITE-SIDE.md) and the offline CA stay manual either way.

Deploy: manual

  1. Host: sudo host/setup.sh — installs wireguard + iptables rules, generates the WG key, prints the public key for the sites.
  2. Sites: do docs/SITE-SIDE.md (WG peers, return routes, DC firewall, Duo app, LDAPS CA export). Paste site pubkeys into /etc/wireguard/wg0.conf, then systemctl enable --now wg-quick@wg0.
  3. Secrets: cp .env.example .env && chmod 600 .env, fill in DC IPs, Duo keys, service account, RADIUS_SECRET.
  4. PKI: offline CA per openvpn/pki/README.md; copy server cert/key + ta.key into openvpn/pki/. Drop the AD CA chain in duo-authproxy/certs/ad-ldaps-ca.pem.
  5. Stack: docker compose up -d --build.
  6. Verify: docs/TESTPLAN.md, stages 0→6.

Design notes / gotchas

  • WireGuard is host-level (wg-quick@wg0), not containerized — tun0 and wg0 share the host netns so the kernel routes between them with no Docker bridge/NAT tricks, and the tunnel survives Docker restarts.
  • Routed, not NATed: DCs see real 172.31.8.x client IPs. Requires the site-side return routes in SITE-SIDE.md. Fallback masquerade snippet is commented at the bottom of host/rules.v4.template.
  • No hairpin: RackNerd's peer AllowedIPs only carry each site's own LAN; the sites' RackNerd peer only carries 172.31.8.0/24 + the hub IP.
  • MTU: OpenVPN tun-mtu 1400 / mssfix 1360 keeps client packets inside wg0's 1420 so nothing fragments on the middle leg.
  • Technitium is stateful: DNS_SERVER_* env vars seed config on the first start only; after that its state lives in /opt/technitium/config (persists down/up, backed up like data). The init job only creates missing forwarder zones — console edits are never overwritten. Changed a DC IP in .env? Delete the zone in the console and rerun docker compose up technitium-init.
  • Port 53 on the host: systemd-resolved's stub listener is disabled (setup.sh/Ansible) — Technitium binds wildcard :53 and would collide; /etc/resolv.conf is repointed at resolved's upstream file.
  • Duo flow: password → push; password,passcode → offline fallback (allow_concat=true). reneg-sec 0 + auth-gen-token prevent surprise re-prompts mid-session. failmode=safe fails open if Duo cloud is down — flip to secure in the template if you'd rather fail closed.
  • TCP/443 fallback: if client networks block 1194/udp, add a second server instance (proto tcp4, port 443, separate dev tun1 + firewall rule). Not enabled by default.
  • Firewall reloads: netfilter-persistent reload flushes Docker's chains — restart docker afterwards (setup.sh/Ansible handler do this).
  • IPv6 is locked down too: host/rules.v6.template default-drops the public v6 address (SSH/WG/OpenVPN only, ICMPv6 allowed) since Technitium and the console bind dual-stack wildcard.
S
Description
No description provided
Readme
127 KiB
0 Stars 1 Watchers 0 Forks
Languages
Shell 76.7%
Dockerfile 16.6%
Jinja 6.7%