From d7287222747b89e0c246f0adf05f058219432c5c Mon Sep 17 00:00:00 2001 From: Dominik Polakovics Date: Thu, 11 Dec 2025 11:34:57 +0100 Subject: [PATCH] feat: nas add cyberghost --- hosts/nas/configuration.nix | 2 +- hosts/nas/modules/cyberghost.nix | 88 +++++++++++++++++++------------- 2 files changed, 54 insertions(+), 36 deletions(-) diff --git a/hosts/nas/configuration.nix b/hosts/nas/configuration.nix index 7ee98c9..d83e825 100644 --- a/hosts/nas/configuration.nix +++ b/hosts/nas/configuration.nix @@ -13,7 +13,7 @@ in { ./utils/modules/victoriametrics ./utils/modules/promtail - # ./modules/cyberghost.nix + ./modules/cyberghost.nix ./modules/pyload.nix ./modules/jellyfin.nix ./modules/power-management.nix diff --git a/hosts/nas/modules/cyberghost.nix b/hosts/nas/modules/cyberghost.nix index 33ca5ee..e2e7419 100644 --- a/hosts/nas/modules/cyberghost.nix +++ b/hosts/nas/modules/cyberghost.nix @@ -1,6 +1,7 @@ { config, pkgs, ... }: let localNetwork = "10.42.96.0/20"; + vpnServer = "87-1-hu.cg-dialup.net"; in { # SOPS secrets for CyberGhost credentials @@ -23,10 +24,16 @@ in environment.systemPackages = [ pkgs.openvpn ]; - # OpenVPN client service + # 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 = true; + updateResolvConf = false; config = '' client dev tun @@ -53,51 +60,62 @@ in # Connection ping 5 explicit-exit-notify 2 - route-delay 5 - # Split tunnel: Don't pull routes from server, we'll set our own + # Don't pull any routes - we manage routing ourselves route-nopull - # Route all traffic through VPN except local network - route 0.0.0.0 128.0.0.0 vpn_gateway - route 128.0.0.0 128.0.0.0 vpn_gateway - - # Keep local network route direct - route ${localNetwork} net_gateway - verb 4 ''; }; - # Kill switch: Block outgoing traffic if VPN is down - networking.firewall = { - extraCommands = '' - # Allow traffic to local network - iptables -A OUTPUT -d ${localNetwork} -j ACCEPT + # 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" ]; - # Allow traffic through VPN tunnel - iptables -A OUTPUT -o tun+ -j ACCEPT + 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 - # Allow loopback - iptables -A OUTPUT -o lo -j ACCEPT + # Add default route via VPN tunnel to table vpn + ${pkgs.iproute2}/bin/ip route add default dev tun0 table vpn 2>/dev/null || true - # Allow established connections (for responses) - iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT + # 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 - # Allow OpenVPN to establish connection (UDP 443) - iptables -A OUTPUT -p udp --dport 443 -j ACCEPT + # 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 - # Drop all other outgoing internet traffic (kill switch) - iptables -A OUTPUT ! -d ${localNetwork} -j DROP - ''; + # 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 - extraStopCommands = '' - iptables -D OUTPUT -d ${localNetwork} -j ACCEPT 2>/dev/null || true - iptables -D OUTPUT -o tun+ -j ACCEPT 2>/dev/null || true - iptables -D OUTPUT -o lo -j ACCEPT 2>/dev/null || true - iptables -D OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT 2>/dev/null || true - iptables -D OUTPUT -p udp --dport 443 -j ACCEPT 2>/dev/null || true - iptables -D OUTPUT ! -d ${localNetwork} -j DROP 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 + ''; + }; }; }