about summary refs log tree commit diff
path: root/modules/wireguard-ng.nix
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--modules/wireguard-ng.nix255
1 files changed, 255 insertions, 0 deletions
diff --git a/modules/wireguard-ng.nix b/modules/wireguard-ng.nix
new file mode 100644
index 0000000..5374a71
--- /dev/null
+++ b/modules/wireguard-ng.nix
@@ -0,0 +1,255 @@
+{
+  config,
+  inputs,
+  lib,
+  pkgs,
+  this,
+  ...
+}:
+with lib;
+let
+  cfg = config.nixfiles.modules.wireguard-ng;
+
+  DNSSetup = optionalString config.services.resolved.enable (
+    let
+      resolvectl = "${config.systemd.package}/bin/resolvectl";
+    in
+    ''
+      ${resolvectl} dns ${cfg.interface} ${cfg.server.ipv6.address} ${cfg.server.ipv4.address}
+      ${resolvectl} domain ${cfg.interface} local ${my.domain.shire}
+      ${resolvectl} dnssec ${cfg.interface} no
+      ${resolvectl} dnsovertls ${cfg.interface} no
+    ''
+  );
+in
+{
+  options.nixfiles.modules.wireguard-ng = {
+    client = {
+      enable = mkEnableOption "WireGuard client";
+
+      enableTrafficRouting = mkOption {
+        description = "Whether to enable traffic routing through the sever.";
+        type = with types; bool;
+        # default = !this.isHeadless;
+        default = false;
+      };
+    };
+
+    server = {
+      enable = mkEnableOption "WireGuard server";
+
+      ipv4.address = mkOption {
+        description = "IPv4 address to bind to.";
+        type = with types; str;
+        default = my.configurations.tulkas.wireguard-ng.ipv4.address;
+      };
+
+      ipv6.address = mkOption {
+        description = "IPv4 address to bind to.";
+        type = with types; str;
+        default = my.configurations.tulkas.wireguard-ng.ipv6.address;
+      };
+
+      address = mkOption {
+        description = "Endpoint address to use";
+        type = with types; str;
+        default = my.configurations.tulkas.ipv4.address;
+      };
+
+      port = mkOption {
+        description = "Endpoint port to use.";
+        type = with types; int;
+        default = 7070;
+      };
+
+      publicKey = mkOption {
+        description = "Server's public key.";
+        type = with types; str;
+        default = my.configurations.tulkas.wireguard.publicKey;
+      };
+
+      peers = mkOption {
+        description = "List of peers.";
+        type = with types; listOf attrs;
+        default =
+          mapAttrsToList
+            (
+              _: attr: with attr; {
+                inherit (wireguard-ng) publicKey;
+                allowedIPs = with wireguard-ng; [
+                  "${ipv6.address}/128"
+                  "${ipv4.address}/32"
+                ];
+              }
+            )
+            (
+              filterAttrs (
+                _: attr: attr.hostname != this.hostname && hasAttr "wireguard-ng" attr
+              ) my.configurations
+            );
+      };
+    };
+
+    interface = mkOption {
+      description = "Name of the interface to use WireGuard with.";
+      type = with types; str;
+      default = "wg70";
+    };
+
+    ipv4.subnet = mkOption {
+      description = "CIDR notation for the IPv4 subnet to use over WireGuard.";
+      type = with types; str;
+      default = "10.70.0.0/16";
+    };
+
+    ipv6.subnet = mkOption {
+      description = "CIDR notation for the IPv6 subnet to use over WireGuard.";
+      type = with types; str;
+      default = "fd70::/16";
+    };
+  };
+
+  config =
+    {
+      assertions = [
+        {
+          assertion = config.security.sudo.enable;
+          message = "Sudo is not enabled.";
+        }
+        {
+          assertion = any (x: x == "wheel") config.my.extraGroups;
+          message = ''User is not in the "wheel" group.'';
+        }
+      ];
+    }
+    // mkMerge [
+      (mkIf (cfg.client.enable || cfg.server.enable) {
+        secrets."wireguard-private-key-${this.hostname}".file =
+          "${inputs.self}/secrets/wireguard-private-key-${this.hostname}";
+
+        networking.firewall.trustedInterfaces = [ cfg.interface ];
+
+        topology = with cfg; {
+          nodes.${this.hostname}.interfaces.${interface} = {
+            network = interface;
+            icon = "interfaces.wireguard";
+          };
+        };
+      })
+      (mkIf cfg.client.enable {
+        networking.wg-quick.interfaces.${cfg.interface} = mkMerge [
+          (with this.wireguard-ng; {
+            privateKeyFile = config.secrets."wireguard-private-key-${this.hostname}".path;
+            address = [
+              "${ipv4.address}/16"
+              "${ipv6.address}/16"
+            ];
+          })
+          (with cfg.server; {
+            peers = [
+              {
+                inherit publicKey;
+                endpoint = "${address}:${toString port}";
+                allowedIPs =
+                  if cfg.client.enableTrafficRouting then
+                    [
+                      "::/0"
+                      "0.0.0.0/0"
+                    ]
+                  else
+                    [
+                      cfg.ipv6.subnet
+                      cfg.ipv4.subnet
+                    ];
+              }
+            ];
+            dns = [
+              ipv6.address
+              ipv4.address
+            ];
+            postUp = DNSSetup;
+          })
+        ];
+
+        environment.systemPackages = with pkgs; [
+          (writeShellApplication {
+            name = "wg-toggle-ng";
+            runtimeInputs = [
+              iproute2
+              jq
+            ];
+            text = ''
+              ip46() {
+                sudo ip -4 "$@"
+                sudo ip -6 "$@"
+              }
+
+              fwmark=$(sudo awg show ${cfg.interface} fwmark) || exit
+              if ip -j rule list lookup "$fwmark" | jq -e 'length > 0' >/dev/null; then
+                ip46 rule del lookup main suppress_prefixlength 0
+                ip46 rule del lookup "$fwmark"
+              else
+                ip46 rule add not fwmark "$fwmark" lookup "$fwmark"
+                ip46 rule add lookup main suppress_prefixlength 0
+              fi
+            '';
+          })
+        ];
+      })
+      (mkIf cfg.server.enable {
+        networking = {
+          wireguard = {
+            enable = true;
+            interfaces.${cfg.interface} = with cfg.server; {
+              privateKeyFile = config.secrets."wireguard-private-key-${this.hostname}".path;
+              ips = [
+                "${ipv6.address}/16"
+                "${ipv4.address}/16"
+              ];
+              listenPort = port;
+              inherit peers;
+              postSetup = DNSSetup;
+              allowedIPsAsRoutes = false;
+            };
+          };
+
+          nat = {
+            enable = true;
+            enableIPv6 = true;
+
+            externalInterface = mkDefault "eth0";
+
+            internalInterfaces = [ cfg.interface ];
+            internalIPs = [ cfg.ipv4.subnet ];
+            internalIPv6s = [ cfg.ipv6.subnet ];
+          };
+
+          firewall.allowedUDPPorts = [ cfg.server.port ];
+        };
+
+        services.prometheus.exporters.wireguard = {
+          enable = true;
+          listenAddress = mkDefault this.wireguard-ng.ipv4.address;
+          withRemoteIp = true;
+          port = 9586;
+        };
+
+        topology = with cfg; {
+          networks = {
+            ${interface} = {
+              name = interface;
+              cidrv4 = ipv4.subnet;
+              cidrv6 = ipv6.subnet;
+              icon = "interfaces.wireguard";
+            };
+          };
+
+          nodes.${this.hostname}.interfaces.${interface}.physicalConnections =
+            mapAttrsToList (name: _: config.lib.topology.mkConnection name interface)
+              (
+                filterAttrs (n: v: !v.isOther && n != this.hostname && hasAttr "wireguard-ng" v) my.configurations
+              );
+        };
+      })
+    ];
+}

Consider giving Nix/NixOS a try! <3