From 95501e7f7791ff3b4a7fee9009294cf68382cd50 Mon Sep 17 00:00:00 2001 From: Stefan Ellmauthaler Date: Sun, 31 Jul 2022 11:20:03 +0200 Subject: [PATCH] Add wireguard base config module provided by Maximilian Marx Co-authored-by: Maximilian Marx --- common/wireguard.nix | 39 ++++-- modules/wireguard.nix | 277 +++++++++++++++++++++++++++++++----------- 2 files changed, 235 insertions(+), 81 deletions(-) diff --git a/common/wireguard.nix b/common/wireguard.nix index 75f0f01..a0e37af 100644 --- a/common/wireguard.nix +++ b/common/wireguard.nix @@ -1,18 +1,33 @@ { config, pkgs, lib, ... }: with lib; { - config.elss.wireguard.interfaces = { - ellmaunet = { - # cough @ name - servers = { - metis = { - localIP = "1"; - publicKey = "bla"; - }; - }; - peers = { }; + config.elss.wireguard = { - prefix = { - ipv4 = "192.168.242."; + interfaces = { + stelnet = { + servers = { + metis = { + localIp = "1"; + extraIps = [ "142" ]; + publicKey = ""; #TODO + endpoint = "metis.ellmauthaler.net:51820"; #TODO + }; + }; + + peers = { # TODO + stel = { + localIp = "142"; + publicKey = "6ZwilfrS1J/dMYRnwIMcQ3cW0KtJdLRj5VnSOjwOpn8="; + }; + }; + + prefixes = { + ipv4 = [ ]; # TODO + ipv6 = { + ula = [ ]; # TODO + gua = [ ]; + }; + serial = "2022073100"; + }; }; }; }; diff --git a/modules/wireguard.nix b/modules/wireguard.nix index bbe1583..a64710e 100644 --- a/modules/wireguard.nix +++ b/modules/wireguard.nix @@ -1,86 +1,162 @@ -{ config, pkgs, lib, ... }: -with lib; { - options.elss.wireguard = { - enable = mkEnableOption "Setup wireguard"; +{ config, lib, pkgs, ... }: + +{ + options.elss.wireguard = with lib; { + enable = mkEnableOption "wireguard overlay network"; + interfaces = mkOption { default = { }; - type = types.attrsOf - (types.submodule { - options = { - servers = mkOption { - type = types.attrsOf (types.submodule { - options = { - localIP = mkOption { - type = types.str; - description = "local IP for the interface"; - }; - - publickey = mkOption { - type = types.str; - description = "Wireguard public key for the server"; - }; + type = types.attrsOf (types.submodule { + options = { + servers = mkOption { + type = types.attrsOf (types.submodule { + options = { + localIp = mkOption { + type = types.str; + description = "local IP part for the interfaces"; }; - }); + + extraIps = mkOption { + type = types.listOf types.str; + default = [ ]; + description = "extra IPs to add to allowedIPs"; + }; + + listenPort = mkOption { + type = types.port; + description = "Port to listen on"; + default = 51820; + }; + + publicKey = mkOption { + type = types.str; + description = "Wireguard public key for this peer"; + }; + + endpoint = mkOption { + type = types.str; + description = "Wireguard endpoint for this peer."; + }; + }; + }); + }; + + peers = mkOption { + type = types.attrsOf (types.submodule { + options = { + localIp = mkOption { + type = types.str; + description = "local IP part for the interfaces"; + }; + + listenPort = mkOption { + type = types.port; + description = "Port to listen on"; + default = 51820; + }; + + publicKey = mkOption { + type = types.str; + description = "Wireguard public key for this peer"; + }; + }; + }); + }; + + prefixes = { + ipv4 = mkOption { + type = types.listOf types.str; + description = "IPv4 prefixes to use for wireguard addressing"; }; - peers = mkOption { - type = types.attrsOf (types.submodule { - options = { - localIp = mkOption { - type = types.str; - description = "local IP for the peer"; - }; - publickey = mkOption { - type = types.str; - description = "Wireguard public key for the peer"; - }; - }; - }); - }; + ipv6 = { + ula = mkOption { + type = types.listOf types.str; + description = + "IPv6 prefixes to use for ULA wireguard addressing"; + }; - prefix = { - ipv4 = mkOption { - type = types.str; - description = "IPv4 prefix for wireguard address room"; + gua = mkOption { + type = types.listOf types.str; + description = + "IPv6 prefixes to use for GUA wireguard addressing"; }; }; - port = mkOption { - type = types.port; - description = "Port to use"; - default = 51820; + serial = mkOption { + type = types.str; + description = "serial for the generated DNS zone"; }; }; - }); + }; + }); }; }; + config = let cfg = config.elss; hostName = config.system.name; - secrets = ../machines + secretsFile = ../machines + builtins.toPath "/${hostName}/secrets/wireguard.yaml"; - mkRemoveEmpty = lib.filter (interface: interface != ""); - mkInterfaces = input: mkRemoveEmpty - ((expr: - lib.mapAttrsToList - (interface: value: if (expr interface value) then interface else "") - cfg.wireguard.interfaces) - input); - mkPeerInterface = mkInterfaces (interface: value: builtins.hasAttr hostName value.peers); - mkServInterface = mkInterfaces (interface: value: builtins.hasAttr hostName value.servers); - interfaces = mkServInterface ++ mkPeerInterface; + takeNonEmpty = lib.filter (interface: interface != ""); + testInterface = predicate: + lib.mapAttrsToList + (interface: value: if (predicate interface value) then interface else "") + cfg.wireguard.interfaces; + onlyInterfaces = predicate: takeNonEmpty (testInterface predicate); + peerInterfaces = + onlyInterfaces (interface: value: builtins.hasAttr hostName value.peers); + serverInterfaces = onlyInterfaces + (interface: value: builtins.hasAttr hostName value.servers); + interfaces = serverInterfaces ++ peerInterfaces; + + mkAddresses = prefixes: localIp: + (map (prefix: "${prefix}.${localIp}/32") prefixes.ipv4) + ++ (map (prefix: "${prefix}::${localIp}/128") prefixes.ipv6.ula) + ++ (map (prefix: "${prefix}::${localIp}/128") prefixes.ipv6.gua); + + mkServerAddresses = prefixes: serverIp: + (map (prefix: "${prefix}.${serverIp}") prefixes.ipv4) + ++ (map (prefix: "${prefix}::${serverIp}") prefixes.ipv6.ula) + ++ (map (prefix: "${prefix}::${serverIp}") prefixes.ipv6.gua); mkInterfaceName = interface: "wg-${interface}"; - mkInterfaceSops = interface: { - "wireguard-${interface}" = { sopsFile = secrets; }; + + mkServerPeer = prefixes: peer: { + allowedIPs = mkAddresses prefixes peer.localIp; + inherit (peer) publicKey; }; - mkConfig = hostName: interface: value: + mkPeerPeer = prefixes: peer: { + allowedIPs = (mkAddresses prefixes peer.localIp) + ++ (lib.concatMap (mkAddresses prefixes) peer.extraIps); + persistentKeepalive = 25; + inherit (peer) publicKey endpoint; + }; + + mkPostSetup = name: prefixes: servers: + let + ifName = mkInterfaceName name; + serverIps = name: server: mkServerAddresses prefixes server.localIp; + dnsServers = lib.concatLists (lib.mapAttrsToList serverIps servers); + in + lib.concatStrings ([ + '' + ${pkgs.systemd}/bin/resolvectl domain ${ifName} ${name}.${config.elss.dns.wgZone} + ${pkgs.systemd}/bin/resolvectl default-route ${ifName} true + '' + ] ++ (map + (ip: '' + ${pkgs.systemd}/bin/resolvectl dns ${ifName} ${ip} + '') + dnsServers)); + + mkInterfaceConfig = hostName: interface: value: let isServer = builtins.hasAttr hostName value.servers; isPeer = builtins.hasAttr hostName value.peers; - curConf = + myConfig = if isServer then value.servers."${hostName}" else @@ -89,17 +165,80 @@ with lib; { assert lib.asserts.assertMsg ((isServer || isPeer) && !(isServer && isPeer)) "host must be either server or peer"; - lib.nameValuepair (mkInterfaceName interface) ( - { - privateKeyFile = sops.secrets."wireguard-${interface}".path; - listenPort = value.listenPort; - } // (if isServer then { } else if isPeer then { - } - else - { }) - ); + lib.nameValuePair (mkInterfaceName interface) ({ + privateKeyFile = sops.secrets."wireguard-${interface}".path; + ips = mkAddresses value.prefixes myConfig.localIp; + inherit (myConfig) listenPort; + } // (if isServer then { + peers = lib.mapAttrsToList (_: mkServerPeer value.prefixes) value.peers; + } else if isPeer then { + peers = lib.mapAttrsToList (_: mkPeerPeer value.prefixes) value.servers; + postSetup = mkPostSetup interface value.prefixes value.servers; + } else + { })); + + mkInterfaceSecret = interface: { + "wireguard-${interface}" = { sopsFile = secretsFile; }; + }; + + mkListenPorts = hostName: interface: value: + if builtins.hasAttr hostName value.servers then + value.servers."${hostName}".listenPort + else if builtins.hasAttr hostName value.peers then + value.peers."${hostName}".listenPort + else + -1; + + mkSysctl = hostName: interface: [ + { + name = "net.ipv4.conf.${mkInterfaceName interface}.forwarding"; + value = "1"; + } + { + name = "net.ipv6.conf.${mkInterfaceName interface}.forwarding"; + value = "1"; + } + { + name = "net.ipv6.conf.all.forwarding"; + value = "1"; + } + ]; + in - mkIf cfg.wireguard.enable { - sops.secrets = lib.mkMerge (map mkInterfaceSops interfaces); + lib.mkIf cfg.wireguard.enable { + networking = { + wireguard.interfaces = + lib.mapAttrs' (mkInterfaceConfig hostName) cfg.wireguard.interfaces; + firewall = { + # allowedUDPPorts = lib.filter (port: port > 0) + # (lib.mapAttrsToList (mkListenPorts hostName) cfg.wireguard.interfaces); + allowedUDPPorts = lib.filter (port: port > 0) (map + (interface: + lib.attrByPath [ interface "servers" hostName "listenPort" ] (-1) + cfg.wireguard.interfaces) + serverInterfaces); + trustedInterfaces = map mkInterfaceName interfaces; + }; + interfaces = lib.listToAttrs (map + (interface: { + name = mkInterfaceName interface; + value = { mtu = 1300; }; + }) + interfaces); + }; + + services.unbound.settings.server.interface = map mkInterfaceName serverInterfaces; + systemd.services = lib.listToAttrs (map + (interface: { + name = "wireguard-${mkInterfaceName interface}"; + value = { serviceConfig.Restart = "on-failure"; }; + }) + interfaces); + + + boot.kernel.sysctl = + builtins.listToAttrs (lib.concatMap (mkSysctl hostName) serverInterfaces); + + sops.secrets = lib.mkMerge (map mkInterfaceSecret interfaces); }; }