{ config, pkgs, ... }: let localNetwork = "10.42.96.0/20"; vpnServer = "87-1-hu.cg-dialup.net"; in { # SOPS secrets for CyberGhost credentials sops.secrets.cyberghost-auth = { mode = "0400"; owner = "root"; }; sops.secrets.cyberghost-ca = { mode = "0400"; owner = "root"; }; sops.secrets.cyberghost-cert = { mode = "0400"; owner = "root"; }; sops.secrets.cyberghost-key = { mode = "0400"; owner = "root"; }; environment.systemPackages = [ pkgs.openvpn ]; # Enable iproute2 for routing tables networking.iproute2.enable = true; networking.iproute2.rttablesExtraConfig = '' 100 vpn ''; # OpenVPN client service - only establishes tunnel, no routing services.openvpn.servers.cyberghost = { autoStart = true; updateResolvConf = false; config = '' client dev tun proto udp remote 87-1-hu.cg-dialup.net 443 resolv-retry infinite nobind persist-key persist-tun # Authentication auth-user-pass ${config.sops.secrets.cyberghost-auth.path} ca ${config.sops.secrets.cyberghost-ca.path} cert ${config.sops.secrets.cyberghost-cert.path} key ${config.sops.secrets.cyberghost-key.path} # Security data-ciphers AES-256-GCM:AES-128-GCM:AES-256-CBC data-ciphers-fallback AES-256-CBC auth SHA256 remote-cert-tls server script-security 2 # Connection ping 5 explicit-exit-notify 2 # Don't pull any routes - we manage routing ourselves route-nopull verb 4 ''; }; # Systemd service to set up VPN routing after tunnel is up systemd.services.cyberghost-routing = { description = "CyberGhost VPN routing rules"; after = [ "openvpn-cyberghost.service" ]; requires = [ "openvpn-cyberghost.service" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { Type = "oneshot"; RemainAfterExit = true; ExecStart = pkgs.writeShellScript "vpn-routing-up" '' # Wait for tun0 to be available for i in $(seq 1 30); do if ${pkgs.iproute2}/bin/ip link show tun0 &>/dev/null; then break fi sleep 1 done # Add default route via VPN tunnel to table vpn ${pkgs.iproute2}/bin/ip route add default dev tun0 table vpn 2>/dev/null || true # Policy rules (lower number = higher priority) # VPN server traffic must use main table to avoid routing loop # Resolve all IPs (hostname has multiple A records for load balancing) for VPN_SERVER_IP in $(${pkgs.dig}/bin/dig +short ${vpnServer} A); do ${pkgs.iproute2}/bin/ip rule add to $VPN_SERVER_IP table main priority 50 2>/dev/null || true done # Local network always uses main routing table ${pkgs.iproute2}/bin/ip rule add to ${localNetwork} table main priority 100 2>/dev/null || true ${pkgs.iproute2}/bin/ip rule add from ${localNetwork} table main priority 100 2>/dev/null || true # Everything else goes through VPN ${pkgs.iproute2}/bin/ip rule add table vpn priority 200 2>/dev/null || true ''; ExecStop = pkgs.writeShellScript "vpn-routing-down" '' ${pkgs.iproute2}/bin/ip rule del table vpn priority 200 2>/dev/null || true ${pkgs.iproute2}/bin/ip rule del from ${localNetwork} table main priority 100 2>/dev/null || true ${pkgs.iproute2}/bin/ip rule del to ${localNetwork} table main priority 100 2>/dev/null || true # Clean up all VPN server IP rules for VPN_SERVER_IP in $(${pkgs.dig}/bin/dig +short ${vpnServer} A); do ${pkgs.iproute2}/bin/ip rule del to $VPN_SERVER_IP table main priority 50 2>/dev/null || true done ${pkgs.iproute2}/bin/ip route del default table vpn 2>/dev/null || true ''; }; }; }