From 1ae038a0a86348074b422ea87c03836b0962af67 Mon Sep 17 00:00:00 2001 From: azahi Date: Tue, 17 Dec 2024 02:04:27 +0300 Subject: 2024-12-17 --- modules/wireguard-ng.nix | 255 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 255 insertions(+) create mode 100644 modules/wireguard-ng.nix (limited to 'modules/wireguard-ng.nix') 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 + ); + }; + }) + ]; +} -- cgit 1.4.1