summary refs log tree commit diff
path: root/modules/foundation/vpn.nix
diff options
context:
space:
mode:
authorMel <mel@rnrd.eu>2026-05-01 18:40:37 +0200
committerMel <mel@rnrd.eu>2026-05-01 18:40:37 +0200
commita4ab740d07706d86503b84d36addcbbbf7a4944b (patch)
tree24c52a29b9a3182149db37e332bee3af9b9f6014 /modules/foundation/vpn.nix
parentc9f2260dc0414d451d551f8ffdf1f573e7be41ff (diff)
downloadnetwork-a4ab740d07706d86503b84d36addcbbbf7a4944b.tar.zst
network-a4ab740d07706d86503b84d36addcbbbf7a4944b.zip
Re-add simple VPN and simplify module with shared users with tunnel
Signed-off-by: Mel <mel@rnrd.eu>
Diffstat (limited to 'modules/foundation/vpn.nix')
-rw-r--r--modules/foundation/vpn.nix90
1 files changed, 90 insertions, 0 deletions
diff --git a/modules/foundation/vpn.nix b/modules/foundation/vpn.nix
new file mode 100644
index 0000000..1a7524d
--- /dev/null
+++ b/modules/foundation/vpn.nix
@@ -0,0 +1,90 @@
+{
+  config,
+  lib,
+  ...
+}:
+
+let
+  inherit (lib)
+    mkEnableOption
+    mkOption
+    types
+    mkIf
+    attrValues
+    replaceString
+    ;
+
+  cfg = config.foundation.vpn;
+
+  inherit (import ../../assets/vpn.nix) users;
+
+  interface = "vpn0";
+  port = 51820;
+
+  # while the tunnel uses the lower subnets for the paths,
+  # the vpn always uses subnet number 10.
+  subnetIndex = 10;
+
+  addressFromTemplate =
+    template: prefix: "${replaceString "X" (toString subnetIndex) template}/${toString prefix}";
+in
+{
+  options.foundation.vpn = {
+    enable = mkEnableOption "WireGuard VPN server";
+
+    externalInterface = mkOption {
+      type = types.str;
+      description = "External network interface";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    age.secrets.wg-private-key = {
+      file = ../../secrets/wg-private-key.age;
+      owner = "systemd-network";
+    };
+
+    networking.firewall.allowedUDPPorts = [ port ];
+
+    systemd.network = {
+      netdevs."30-${interface}" = {
+        netdevConfig = {
+          Kind = "wireguard";
+          Name = interface;
+        };
+        wireguardConfig = {
+          PrivateKeyFile = config.age.secrets.wg-private-key.path;
+          ListenPort = port;
+        };
+        wireguardPeers = map (user: {
+          PublicKey = user.key;
+          AllowedIPs = [ (addressFromTemplate user.ip 32) ];
+        }) (attrValues users);
+      };
+
+      networks."30-${interface}" = {
+        name = interface;
+        address = [ (addressFromTemplate "10.123.X.1" 24) ];
+        linkConfig = {
+          RequiredForOnline = "no";
+        };
+      };
+    };
+
+    networking.nftables.tables.vpn-nat = {
+      family = "ip";
+      content = ''
+        chain postrouting {
+          type nat hook postrouting priority srcnat; policy accept;
+          iifname "${interface}" oifname "${cfg.externalInterface}" masquerade
+        }
+
+        chain forward {
+          type filter hook forward priority 0; policy accept;
+          iifname "${interface}" oifname "${cfg.externalInterface}" accept
+          iifname "${cfg.externalInterface}" oifname "${interface}" ct state established,related accept
+        }
+      '';
+    };
+  };
+}