about summary refs log tree commit diff
diff options
context:
space:
mode:
authorGraham Christensen <graham@grahamc.com>2019-05-31 14:32:43 -0400
committerGraham Christensen <graham@grahamc.com>2019-05-31 14:51:25 -0400
commitdc44fc1760051d7d2983811b8fb0cce600bb9c32 (patch)
tree91aaf81d5e1c2a7959070cbb0e9299dcde2a4f4d
parent0c6fb79dd70d4e0f33a686ede8692af4399e24de (diff)
downloadnixlib-dc44fc1760051d7d2983811b8fb0cce600bb9c32.tar
nixlib-dc44fc1760051d7d2983811b8fb0cce600bb9c32.tar.gz
nixlib-dc44fc1760051d7d2983811b8fb0cce600bb9c32.tar.bz2
nixlib-dc44fc1760051d7d2983811b8fb0cce600bb9c32.tar.lz
nixlib-dc44fc1760051d7d2983811b8fb0cce600bb9c32.tar.xz
nixlib-dc44fc1760051d7d2983811b8fb0cce600bb9c32.tar.zst
nixlib-dc44fc1760051d7d2983811b8fb0cce600bb9c32.zip
wireguard: add each peer in a separate service
Before, changing any peers caused the entire WireGuard interface to
be torn down and rebuilt. By configuring each peer in a separate
service we're able to only restart the affected peers.

Adding each peer individually also means individual peer
configurations can fail, but the overall interface and all other peers
will still be added.

A WireGuard peer's internal identifier is its public key. This means
it is the only reliable identifier to use for the systemd service.
-rw-r--r--nixos/modules/services/networking/wireguard.nix95
1 files changed, 69 insertions, 26 deletions
diff --git a/nixos/modules/services/networking/wireguard.nix b/nixos/modules/services/networking/wireguard.nix
index 3a65f7ff32c7..d98446642de6 100644
--- a/nixos/modules/services/networking/wireguard.nix
+++ b/nixos/modules/services/networking/wireguard.nix
@@ -229,8 +229,60 @@ let
         '';
       };
 
+  generatePeerUnit = { interfaceName, interfaceCfg, peer }:
+    let
+      keyToUnitName = replaceChars
+        [ "/" "-"    " "     "+"     "="      ]
+        [ "-" "\\x2d" "\\x20" "\\x2b" "\\x3d" ];
+      unitName = keyToUnitName peer.publicKey;
+      psk =
+        if peer.presharedKey != null
+          then pkgs.writeText "wg-psk" peer.presharedKey
+          else peer.presharedKeyFile;
+    in nameValuePair "wireguard-${interfaceName}-peer-${unitName}"
+      {
+        description = "WireGuard Peer - ${interfaceName} - ${peer.publicKey}";
+        requires = [ "wireguard-${interfaceName}.service" ];
+        after = [ "wireguard-${interfaceName}.service" ];
+        wantedBy = [ "multi-user.target" ];
+        environment.DEVICE = interfaceName;
+        path = with pkgs; [ iproute wireguard-tools ];
+
+        serviceConfig = {
+          Type = "oneshot";
+          RemainAfterExit = true;
+        };
+
+        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}";
+          route_setup =
+            optionalString (interfaceCfg.allowedIPsAsRoutes != false)
+              (concatMapStringsSep "\n"
+                (allowedIP:
+                  "ip route replace ${allowedIP} dev ${interfaceName} table ${interfaceCfg.table}"
+                ) peer.allowedIPs);
+        in ''
+          ${wg_setup}
+          ${route_setup}
+        '';
 
-  generateSetupServiceUnit = name: values:
+        postStop = let
+          route_destroy = optionalString (interfaceCfg.allowedIPsAsRoutes != false)
+            (concatMapStringsSep "\n"
+              (allowedIP:
+                "ip route delete ${allowedIP} dev ${interfaceName} table ${interfaceCfg.table}"
+              ) peer.allowedIPs);
+        in ''
+          wg set ${interfaceName} peer ${peer.publicKey} remove
+          ${route_destroy}
+        '';
+      };
+
+  generateInterfaceUnit = name: values:
     # exactly one way to specify the private key must be set
     #assert (values.privateKey != null) != (values.privateKeyFile != null);
     let privKey = if values.privateKeyFile != null then values.privateKeyFile else pkgs.writeText "wg-key" values.privateKey;
@@ -245,9 +297,7 @@ let
         path = with pkgs; [ kmod iproute wireguard-tools ];
 
         serviceConfig = {
-          Type = "simple";
-          Restart = "on-failure";
-          RestartSec = "5s";
+          Type = "oneshot";
           RemainAfterExit = true;
         };
 
@@ -265,25 +315,8 @@ let
           wg set ${name} private-key ${privKey} ${
             optionalString (values.listenPort != null) " listen-port ${toString values.listenPort}"}
 
-          ${concatMapStringsSep "\n" (peer:
-            assert (peer.presharedKeyFile == null) || (peer.presharedKey == null); # at most one of the two must be set
-            let psk = if peer.presharedKey != null then pkgs.writeText "wg-psk" peer.presharedKey else peer.presharedKeyFile;
-            in
-              "wg set ${name} 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}"
-            ) values.peers}
-
           ip link set up dev ${name}
 
-          ${optionalString (values.allowedIPsAsRoutes != false) (concatStringsSep "\n" (concatMap (peer:
-              (map (allowedIP:
-                "ip route replace ${allowedIP} dev ${name} table ${values.table}"
-              ) peer.allowedIPs)
-            ) values.peers))}
-
           ${values.postSetup}
         '';
 
@@ -335,7 +368,12 @@ in
 
   ###### implementation
 
-  config = mkIf cfg.enable {
+  config = mkIf cfg.enable (let
+    all_peers = flatten
+      (mapAttrsToList (interfaceName: interfaceCfg:
+        map (peer: { inherit interfaceName interfaceCfg peer;}) interfaceCfg.peers
+      ) cfg.interfaces);
+  in {
 
     assertions = (attrValues (
         mapAttrs (name: value: {
@@ -346,19 +384,24 @@ in
         mapAttrs (name: value: {
           assertion = value.generatePrivateKeyFile -> (value.privateKey == null);
           message = "networking.wireguard.interfaces.${name}.generatePrivateKey must not be set if networking.wireguard.interfaces.${name}.privateKey is set.";
-        }) cfg.interfaces));
-
+        }) cfg.interfaces))
+        ++ map ({ interfaceName, peer, ... }: {
+          assertion = (peer.presharedKey == null) || (peer.presharedKeyFile == null);
+          message = "networking.wireguard.interfaces.${interfaceName} peer «${peer.publicKey}» has both presharedKey and presharedKeyFile set, but only one can be used.";
+        }) all_peers;
 
     boot.extraModulePackages = [ kernel.wireguard ];
     environment.systemPackages = [ pkgs.wireguard-tools ];
 
-    systemd.services = (mapAttrs' generateSetupServiceUnit cfg.interfaces)
+    systemd.services =
+      (mapAttrs' generateInterfaceUnit cfg.interfaces)
+      // (listToAttrs (map generatePeerUnit all_peers))
       // (mapAttrs' generateKeyServiceUnit
       (filterAttrs (name: value: value.generatePrivateKeyFile) cfg.interfaces));
 
     systemd.paths = mapAttrs' generatePathUnit
       (filterAttrs (name: value: value.privateKeyFile != null) cfg.interfaces);
 
-  };
+  });
 
 }