From aaffc6447d4cd66be202fb34b31bacf947f0f709 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20Hamb=C3=BCchen?= Date: Fri, 30 Apr 2021 22:56:38 +0200 Subject: [PATCH 1/3] wireguard module: Quote all command line arguments correctly. Standard best-practice shell quoting, which can prevent the most horrible production accidents. Note that we cannot use `+ optionalString someBool '' someString''` because Nix's multi-line ''double-quoted'' strings remove leading whitespace. --- .../modules/services/networking/wireguard.nix | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/nixos/modules/services/networking/wireguard.nix b/nixos/modules/services/networking/wireguard.nix index 34c869345357..3e097063ae28 100644 --- a/nixos/modules/services/networking/wireguard.nix +++ b/nixos/modules/services/networking/wireguard.nix @@ -286,16 +286,18 @@ let }; script = let - wg_setup = "${wg} set ${interfaceName} peer ${peer.publicKey}" + - optionalString (psk != null) " preshared-key ${psk}" + - optionalString (peer.endpoint != null) " endpoint ${peer.endpoint}" + - optionalString (peer.persistentKeepalive != null) " persistent-keepalive ${toString peer.persistentKeepalive}" + - optionalString (peer.allowedIPs != []) " allowed-ips ${concatStringsSep "," peer.allowedIPs}"; + wg_setup = concatStringsSep " " ( + [ ''${wg} set ${interfaceName} peer "${peer.publicKey}"'' ] + ++ optional (psk != null) ''preshared-key "${psk}"'' + ++ optional (peer.endpoint != null) ''endpoint "${peer.endpoint}"'' + ++ optional (peer.persistentKeepalive != null) ''persistent-keepalive "${toString peer.persistentKeepalive}"'' + ++ optional (peer.allowedIPs != []) ''allowed-ips "${concatStringsSep "," peer.allowedIPs}"'' + ); route_setup = optionalString interfaceCfg.allowedIPsAsRoutes (concatMapStringsSep "\n" (allowedIP: - "${ip} route replace ${allowedIP} dev ${interfaceName} table ${interfaceCfg.table}" + ''${ip} route replace "${allowedIP}" dev "${interfaceName}" table "${interfaceCfg.table}"'' ) peer.allowedIPs); in '' ${wg_setup} @@ -306,10 +308,10 @@ let route_destroy = optionalString interfaceCfg.allowedIPsAsRoutes (concatMapStringsSep "\n" (allowedIP: - "${ip} route delete ${allowedIP} dev ${interfaceName} table ${interfaceCfg.table}" + ''${ip} route delete "${allowedIP}" dev "${interfaceName}" table "${interfaceCfg.table}"'' ) peer.allowedIPs); in '' - ${wg} set ${interfaceName} peer ${peer.publicKey} remove + ${wg} set "${interfaceName}" peer "${peer.publicKey}" remove ${route_destroy} ''; }; @@ -345,23 +347,25 @@ let ${values.preSetup} - ${ipPreMove} link add dev ${name} type wireguard - ${optionalString (values.interfaceNamespace != null && values.interfaceNamespace != values.socketNamespace) "${ipPreMove} link set ${name} netns ${ns}"} + ${ipPreMove} link add dev "${name}" type wireguard + ${optionalString (values.interfaceNamespace != null && values.interfaceNamespace != values.socketNamespace) ''${ipPreMove} link set "${name}" netns "${ns}"''} ${concatMapStringsSep "\n" (ip: - "${ipPostMove} address add ${ip} dev ${name}" + ''${ipPostMove} address add "${ip}" dev "${name}"'' ) values.ips} - ${wg} set ${name} private-key ${privKey} ${ - optionalString (values.listenPort != null) " listen-port ${toString values.listenPort}"} + ${concatStringsSep " " ( + [ ''${wg} set "${name}" private-key "${privKey}"'' ] + ++ optional (values.listenPort != null) ''listen-port "${toString values.listenPort}"'' + )} - ${ipPostMove} link set up dev ${name} + ${ipPostMove} link set up dev "${name}" ${values.postSetup} ''; postStop = '' - ${ipPostMove} link del dev ${name} + ${ipPostMove} link del dev "${name}" ${values.postShutdown} ''; }; @@ -371,7 +375,7 @@ let nsList = filter (ns: ns != null) [ src dst ]; ns = last nsList; in - if (length nsList > 0 && ns != "init") then "ip netns exec ${ns} ${cmd}" else cmd; + if (length nsList > 0 && ns != "init") then ''ip netns exec "${ns}" "${cmd}"'' else cmd; in { From 357cf46c8d753d08b5e9c837640aa43557b3675f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20Hamb=C3=BCchen?= Date: Fri, 30 Apr 2021 23:00:29 +0200 Subject: [PATCH 2/3] wireguard module: Add `dynamicEndpointRefreshSeconds` option. See for an intro: https://wiki.archlinux.org/index.php/WireGuard#Endpoint_with_changing_IP --- .../modules/services/networking/wireguard.nix | 73 +++++++++++++++++-- 1 file changed, 65 insertions(+), 8 deletions(-) diff --git a/nixos/modules/services/networking/wireguard.nix b/nixos/modules/services/networking/wireguard.nix index 3e097063ae28..6d4d8d617d25 100644 --- a/nixos/modules/services/networking/wireguard.nix +++ b/nixos/modules/services/networking/wireguard.nix @@ -198,7 +198,32 @@ let example = "demo.wireguard.io:12913"; type = with types; nullOr str; description = ''Endpoint IP or hostname of the peer, followed by a colon, - and then a port number of the peer.''; + and then a port number of the peer. + + Warning for endpoints with changing IPs: + The WireGuard kernel side cannot perform DNS resolution. + Thus DNS resolution is done once by the wg userspace + utility, when setting up WireGuard. Consequently, if the IP address + behind the name changes, WireGuard will not notice. + This is especially common for dynamic-DNS setups, but also applies to + any other DNS-based setup. + If you do not use IP endpoints, you likely want to set + + to refresh the IPs periodically. + ''; + }; + + dynamicEndpointRefreshSeconds = mkOption { + default = 0; + example = 5; + type = with types; int; + description = '' + Periodically re-execute the wg utility every + this many seconds in order to let WireGuard notice DNS / hostname + changes. + + Setting this to 0 disables periodic reexecution. + ''; }; persistentKeepalive = mkOption { @@ -256,12 +281,18 @@ let ''; }; - generatePeerUnit = { interfaceName, interfaceCfg, peer }: + peerUnitServiceName = interfaceName: publicKey: dynamicRefreshEnabled: let keyToUnitName = replaceChars [ "/" "-" " " "+" "=" ] [ "-" "\\x2d" "\\x20" "\\x2b" "\\x3d" ]; - unitName = keyToUnitName peer.publicKey; + unitName = keyToUnitName publicKey; + refreshSuffix = optionalString dynamicRefreshEnabled "-refresh"; + in + "wireguard-${interfaceName}-peer-${unitName}${refreshSuffix}"; + + generatePeerUnit = { interfaceName, interfaceCfg, peer }: + let psk = if peer.presharedKey != null then pkgs.writeText "wg-psk" peer.presharedKey @@ -270,7 +301,12 @@ let dst = interfaceCfg.interfaceNamespace; ip = nsWrap "ip" src dst; wg = nsWrap "wg" src dst; - in nameValuePair "wireguard-${interfaceName}-peer-${unitName}" + dynamicRefreshEnabled = peer.dynamicEndpointRefreshSeconds != 0; + # We generate a different name (a `-refresh` suffix) when `dynamicEndpointRefreshSeconds` + # to avoid that the same service switches `Type` (`oneshot` vs `simple`), + # with the intent to make scripting more obvious. + serviceName = peerUnitServiceName interfaceName peer.publicKey dynamicRefreshEnabled; + in nameValuePair serviceName { description = "WireGuard Peer - ${interfaceName} - ${peer.publicKey}"; requires = [ "wireguard-${interfaceName}.service" ]; @@ -280,10 +316,21 @@ let environment.WG_ENDPOINT_RESOLUTION_RETRIES = "infinity"; path = with pkgs; [ iproute2 wireguard-tools ]; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - }; + serviceConfig = + if !dynamicRefreshEnabled + then + { + Type = "oneshot"; + RemainAfterExit = true; + } + else + { + Type = "simple"; # re-executes 'wg' indefinitely + # Note that `Type = "oneshot"` services with `RemainAfterExit = true` + # cannot be used with systemd timers (see `man systemd.timer`), + # which is why `simple` with a loop is the best choice here. + # It also makes starting and stopping easiest. + }; script = let wg_setup = concatStringsSep " " ( @@ -302,6 +349,16 @@ let in '' ${wg_setup} ${route_setup} + + ${optionalString (peer.dynamicEndpointRefreshSeconds != 0) '' + # Re-execute 'wg' periodically to notice DNS / hostname changes. + # Note this will not time out on transient DNS failures such as DNS names + # because we have set 'WG_ENDPOINT_RESOLUTION_RETRIES=infinity'. + # Also note that 'wg' limits its maximum retry delay to 20 seconds as of writing. + while ${wg_setup}; do + sleep "${toString peer.dynamicEndpointRefreshSeconds}"; + done + ''} ''; postStop = let From 0da74875c087768badedc694ca1ea44a3a290a8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20Hamb=C3=BCchen?= Date: Fri, 30 Apr 2021 23:25:18 +0200 Subject: [PATCH 3/3] release notes: Mention WireGuard `dynamicEndpointRefreshSeconds` --- nixos/doc/manual/release-notes/rl-2105.xml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/nixos/doc/manual/release-notes/rl-2105.xml b/nixos/doc/manual/release-notes/rl-2105.xml index 6e4a9e7114b0..99e0110fdcbf 100644 --- a/nixos/doc/manual/release-notes/rl-2105.xml +++ b/nixos/doc/manual/release-notes/rl-2105.xml @@ -348,6 +348,15 @@ which is the new stable release. OpenAFS 1.6 was removed. + + + The WireGuard module gained a new option + + that implements refreshing the IP of DNS-based endpoints periodically + (which WireGuard itself + cannot do). + + MariaDB has been updated to 10.5.