From 6ed9c38368e2b6100ba191243d2f4aa12c762c8c Mon Sep 17 00:00:00 2001
From: Julien Moutinho <julm+sourcephile-nix@sourcephile.fr>
Date: Fri, 4 Feb 2022 20:00:51 +0100
Subject: [PATCH] wireguard: prepare jettison of #128014

---
 .../modules/services/networking/wireguard.nix | 256 ++++++++++++++++++
 1 file changed, 256 insertions(+)
 create mode 100644 nixos/modules/services/networking/wireguard.nix

diff --git a/nixos/modules/services/networking/wireguard.nix b/nixos/modules/services/networking/wireguard.nix
new file mode 100644
index 0000000..3049bf1
--- /dev/null
+++ b/nixos/modules/services/networking/wireguard.nix
@@ -0,0 +1,256 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.networking.wireguard;
+
+  interfaceOpts = { config, ... }: {
+    options.peers = mkOption {
+      type = with types; listOf (submodule peerOpts);
+    };
+    options.peersAnnouncing = {
+      enable = mkEnableOption ''
+        announcing of peers' endpoints.
+      '';
+      listenPort = mkOption {
+        type = types.port;
+        defaultText = "<option>networking.wireguard.listenPort</option> or 51820 if null";
+        description = ''
+          TCP port on which to listen for peers queries for other peers' endpoints.
+
+          <warning><para>
+          This exposes the public key and current (or last known) endpoint address of all the peers
+          configured in the WireGuard interface, to all the peers,
+          and to any host connected to the peers or announcing peer
+          able to query the WireGuard interface
+          by spoofing the allowedIPs of any peer.
+          </para></warning>
+        '';
+      };
+    };
+
+    config.peersAnnouncing = {
+      listenPort = mkDefault (if config.listenPort == null then 51820 else config.listenPort);
+    };
+  };
+
+  peerOpts = { config, ... }: {
+    options.endpointsUpdater = {
+      enable = mkEnableOption ''
+        receiving the enpoint of other peers from a periodic TCP connection.
+        That connection is meant to be to a peer using <option>networking.wireguard.peersAnnouncing.enable</option>.
+
+        This is especially useful to punch-through non-symmetric NATs
+        effectively establishing peer-to-peer connectivity:
+        the peers initiate a Wireguard connection to the announcing peer,
+        then use that Wireguard tunnel to query the endpoints of the other peers
+        to establish Wireguard tunnels to them,
+        using UDP hole punching when both peers are behind non-symmetric NATs.
+
+        Note that it can also work for two peers behind the same NAT
+        if that NAT can reflect (to its internal network) connections
+        from its internal network to its external IP address,
+        which may require to set persistentKeepalive as low as 1
+        and/or redirecting at least one host's WireGuard port, eg. with UPnP.
+      '';
+      addr = mkOption {
+        type = types.str;
+        defaultText = "address of the first item of the peer's <option>allowedIPs</option>";
+        description = ''
+          Address at which to contact the announcing peer,
+          configured on the announcing peer's <option>networking.wireguard.ips</option>.
+        '';
+      };
+      port = mkOption {
+        type = types.port;
+        defaultText = "port of the peer's endpoint";
+        description = ''
+          TCP port on which to contact the peer announcing other peers.
+          This is configured on the announcing peer's <option>networking.wireguard.peersAnnouncing.listenPort</option>.
+        '';
+      };
+      refreshSeconds = mkOption {
+        type = with types; int;
+        example = 120;
+        default = 60;
+        description = ''
+          Time to wait between two successive queries of the endpoints known by the peer.
+        '';
+      };
+    };
+
+    config.endpointsUpdater = {
+      addr = mkDefault (head (builtins.match "^\([^/]*\).*$" (head (config.allowedIPs))));
+      port = mkDefault (toInt (head (builtins.match "^.*:\([0-9]*\)$" config.endpoint)));
+    };
+  };
+
+  keyToUnitName = replaceChars
+    [ "/" "-"    " "     "+"     "="      ]
+    [ "-" "\\x2d" "\\x20" "\\x2b" "\\x3d" ];
+
+  peerUnitServiceName = interfaceName: publicKey: dynamicRefreshEnabled:
+    let
+      unitName = keyToUnitName publicKey;
+      refreshSuffix = optionalString dynamicRefreshEnabled "-refresh";
+    in
+      "wireguard-${interfaceName}-peer-${unitName}${refreshSuffix}";
+
+  # See:
+  # - systemd-analyze security wireguard-${iface}-peers-announcing@
+  # - systemd-analyze security wireguard-${iface}-endpoints-updater-${public_key}.service
+  # Note that PrivateUsers=true would be too restrictive wrt. capabilities.
+  peerUpdateSecurity = {
+    IPAddressDeny = "any";
+    # For running wg(1)
+    AmbientCapabilities = [ "CAP_NET_ADMIN" ];
+    CapabilityBoundingSet = [ "CAP_NET_ADMIN" ];
+    RestrictNamespaces = true;
+    DynamicUser = true;
+    PrivateDevices = true;
+    ProtectClock = true;
+    ProtectControlGroups = true;
+    ProtectHome = true;
+    ProtectKernelLogs = true;
+    ProtectKernelModules = true;
+    ProtectKernelTunables = true;
+    ProtectProc = "invisible";
+    SystemCallArchitectures = "native";
+    # Remove (likely) unused groups from the basic @system-service group
+    SystemCallFilter = [
+      "@system-service"
+      "~@aio" "~@chown" "~@keyring" "~@privileged"
+      "~@memlock" "~@resources" "~@setuid"
+    ];
+    RestrictRealtime = true;
+    LockPersonality = true;
+    MemoryDenyWriteExecute = true;
+    UMask = 0077;
+    ProtectHostname = true;
+    ProcSubset = "pid";
+  };
+
+  generatePeersAnnouncingSocket = name: values:
+    nameValuePair "wireguard-${name}-peers-announcing"
+      {
+        enable = values.peersAnnouncing.enable;
+        listenStreams = [(toString values.peersAnnouncing.listenPort)];
+        socketConfig.Accept = true;
+        # Basic firewalling restricting answers to peers
+        # querying an internal IP address of the announcing peer.
+        # Note that IPv4 addresses can be spoofed using other interfaces unless
+        # sysctl net.ipv4.conf.${name}.rp_filter=1
+        socketConfig.BindToDevice = name;
+        socketConfig.IPAddressAllow = map (peer: peer.allowedIPs) values.peers;
+        socketConfig.IPAddressDeny = "any";
+        socketConfig.MaxConnectionsPerSource = 1;
+        socketConfig.ReusePort = true;
+        wantedBy = [ "sockets.target" ];
+      };
+
+  generatePeersAnnouncingUnit = name: values:
+    nameValuePair "wireguard-${name}-peers-announcing@"
+      {
+        description = "WireGuard Peers Announcing - ${name}";
+        requires = [ "wireguard-${name}.service" ];
+        after = [ "wireguard-${name}.service" ];
+
+        serviceConfig = mkMerge [
+          peerUpdateSecurity
+          {
+            Type = "simple";
+            ExecStart = "${pkgs.wireguard-tools}/bin/wg show '${name}' endpoints";
+            StandardInput = "null";
+            StandardOutput = "socket";
+            RestrictAddressFamilies = "";
+          }
+          (mkIf (values.interfaceNamespace != null)
+            { NetworkNamespacePath = "/var/run/netns/${values.interfaceNamespace}"; })
+        ];
+      };
+
+  generateEndpointsUpdaterUnit = { interfaceName, interfaceCfg, peer }: let
+      dynamicRefreshEnabled = peer.dynamicEndpointRefreshSeconds != 0;
+      peerService = peerUnitServiceName interfaceName peer.publicKey dynamicRefreshEnabled;
+    in
+    nameValuePair "wireguard-${interfaceName}-endpoints-updater-${keyToUnitName peer.publicKey}"
+      {
+        description = "WireGuard ${interfaceName} Endpoints Updater - ${peer.publicKey}";
+        requires = [ "${peerService}.service" ];
+        after = [ "${peerService}.service" ];
+        wantedBy = [ "${peerService}.service" ];
+        path = with pkgs; [ wireguard-tools ];
+
+        unitConfig = {
+          StartLimitIntervalSec = 0;
+        };
+        serviceConfig = mkMerge [
+          peerUpdateSecurity
+          {
+            Type = "simple";
+            IPAddressAllow = [ peer.endpointsUpdater.addr ];
+            RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_NETLINK" ];
+            Restart = "on-failure";
+          }
+          (mkIf (interfaceCfg.interfaceNamespace != null)
+            { NetworkNamespacePath = "/var/run/netns/${interfaceCfg.interfaceNamespace}"; })
+        ];
+
+        # Query the peer announcing other peers
+        # for setting the current endpoint of all configured peers
+        # (but the announcing peer).
+        # Note that socat is used instead of libressl's netcat
+        # (which would require MemoryDenyWriteExecute=true to load libtls.so)
+        # or netcat-gnu (which does not work on ARM and has last been released in 2004).
+        script = ''
+          wait="${toString peer.endpointsUpdater.refreshSeconds}"
+          declare -A configured_keys
+          configured_keys=(${concatMapStringsSep " " (p:
+            optionalString (p.publicKey != peer.publicKey) "[${p.publicKey}]=set")
+            interfaceCfg.peers})
+
+          while true; do
+            # Set stdin to socat's stdout
+            exec < <(exec ${pkgs.socat}/bin/socat STDOUT \
+              "TCP:${with peer.endpointsUpdater; addr+":"+toString port}")
+
+            # Update the endpoint of each configured peer
+            while read -t "$wait" -n 128 -r public_key endpoint x; do
+              if [ "$endpoint" != "(none)" -a "''${configured_keys[$public_key]}" ]; then
+                wg set "${interfaceName}" peer "$public_key" endpoint "$endpoint"
+              fi;
+            done
+
+            sleep "$wait"
+          done
+        '';
+      };
+
+in
+
+{
+
+  options.networking.wireguard.interfaces = mkOption {
+      type = with types; attrsOf (submodule interfaceOpts);
+    };
+
+  config = mkIf cfg.enable (let
+    all_peers = flatten
+      (mapAttrsToList (interfaceName: interfaceCfg:
+        map (peer: { inherit interfaceName interfaceCfg peer;}) interfaceCfg.peers
+      ) cfg.interfaces);
+  in {
+
+    systemd.sockets =
+      mapAttrs' generatePeersAnnouncingSocket cfg.interfaces;
+
+    systemd.services =
+      mapAttrs' generatePeersAnnouncingUnit cfg.interfaces //
+      (listToAttrs (map generateEndpointsUpdaterUnit
+        (filter ({peer, ...}: peer.endpointsUpdater.enable) all_peers)));
+
+  });
+
+}
-- 
2.47.2