Unverified Commit e8484011 authored by Franz Pletz's avatar Franz Pletz Committed by GitHub
Browse files

Merge pull request #71510 from asymmetric/wg-ns

Add namespace support to Wireguard module
parents a1a0f45f 412f6a96
Loading
Loading
Loading
Loading
+55 −11
Original line number Diff line number Diff line
@@ -112,6 +112,32 @@ let
          Determines whether to add allowed IPs as routes or not.
        '';
      };

      socketNamespace = mkOption {
        default = null;
        type = with types; nullOr str;
        example = "container";
        description = ''The pre-existing network namespace in which the
        WireGuard interface is created, and which retains the socket even if the
        interface is moved via <option>interfaceNamespace</option>. When
        <literal>null</literal>, the interface is created in the init namespace.
        See <link
        xlink:href="https://www.wireguard.com/netns/">documentation</link>.
        '';
      };

      interfaceNamespace = mkOption {
        default = null;
        type = with types; nullOr str;
        example = "init";
        description = ''The pre-existing network namespace the WireGuard
        interface is moved to. The special value <literal>init</literal> means
        the init namespace. When <literal>null</literal>, the interface is not
        moved.
        See <link
        xlink:href="https://www.wireguard.com/netns/">documentation</link>.
        '';
      };
    };

  };
@@ -239,6 +265,10 @@ let
        if peer.presharedKey != null
          then pkgs.writeText "wg-psk" peer.presharedKey
          else peer.presharedKeyFile;
      src = interfaceCfg.socketNamespace;
      dst = interfaceCfg.interfaceNamespace;
      ip = nsWrap "ip" src dst;
      wg = nsWrap "wg" src dst;
    in nameValuePair "wireguard-${interfaceName}-peer-${unitName}"
      {
        description = "WireGuard Peer - ${interfaceName} - ${peer.publicKey}";
@@ -255,16 +285,16 @@ let
        };

        script = let
          wg_setup = "wg set ${interfaceName} peer ${peer.publicKey}" +
          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)
            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}
@@ -272,13 +302,13 @@ let
        '';

        postStop = let
          route_destroy = optionalString (interfaceCfg.allowedIPsAsRoutes != false)
          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}
        '';
      };
@@ -287,6 +317,13 @@ let
    # 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;
        src = values.socketNamespace;
        dst = values.interfaceNamespace;
        ipPreMove  = nsWrap "ip" src null;
        ipPostMove = nsWrap "ip" src dst;
        wg = nsWrap "wg" src dst;
        ns = if dst == "init" then "1" else dst;

    in
    nameValuePair "wireguard-${name}"
      {
@@ -307,26 +344,33 @@ let

          ${values.preSetup}

          ip link add dev ${name} type wireguard
          ${ipPreMove} link add dev ${name} type wireguard
          ${optionalString (values.interfaceNamespace != null && values.interfaceNamespace != values.socketNamespace) "${ipPreMove} link set ${name} netns ${ns}"}

          ${concatMapStringsSep "\n" (ip:
            "ip address add ${ip} dev ${name}"
            "${ipPostMove} address add ${ip} dev ${name}"
          ) values.ips}

          wg set ${name} private-key ${privKey} ${
          ${wg} set ${name} private-key ${privKey} ${
            optionalString (values.listenPort != null) " listen-port ${toString values.listenPort}"}

          ip link set up dev ${name}
          ${ipPostMove} link set up dev ${name}

          ${values.postSetup}
        '';

        postStop = ''
          ip link del dev ${name}
          ${ipPostMove} link del dev ${name}
          ${values.postShutdown}
        '';
      };

  nsWrap = cmd: src: dst:
    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;
in

{
+1 −0
Original line number Diff line number Diff line
@@ -279,6 +279,7 @@ in
  virtualbox = handleTestOn ["x86_64-linux"] ./virtualbox.nix {};
  wireguard = handleTest ./wireguard {};
  wireguard-generated = handleTest ./wireguard/generated.nix {};
  wireguard-namespaces = handleTest ./wireguard/namespaces.nix {};
  wordpress = handleTest ./wordpress.nix {};
  xautolock = handleTest ./xautolock.nix {};
  xfce = handleTest ./xfce.nix {};
+80 −0
Original line number Diff line number Diff line
let
  listenPort = 12345;
  socketNamespace = "foo";
  interfaceNamespace = "bar";
  node = {
    networking.wireguard.interfaces.wg0 = {
      listenPort = listenPort;
      ips = [ "10.10.10.1/24" ];
      privateKeyFile = "/etc/wireguard/private";
      generatePrivateKeyFile = true;
    };
  };

in

import ../make-test.nix ({ pkgs, ...} : {
  name = "wireguard-with-namespaces";
  meta = with pkgs.stdenv.lib.maintainers; {
    maintainers = [ asymmetric ];
  };

  nodes = {
    # interface should be created in the socketNamespace
    # and not moved from there
    peer0 = pkgs.lib.attrsets.recursiveUpdate node {
      networking.wireguard.interfaces.wg0 = {
        preSetup = ''
          ip netns add ${socketNamespace}
        '';
        inherit socketNamespace;
      };
    };
    # interface should be created in the init namespace
    # and moved to the interfaceNamespace
    peer1 = pkgs.lib.attrsets.recursiveUpdate node {
      networking.wireguard.interfaces.wg0 = {
        preSetup = ''
          ip netns add ${interfaceNamespace}
        '';
        inherit interfaceNamespace;
      };
    };
    # interface should be created in the socketNamespace
    # and moved to the interfaceNamespace
    peer2 = pkgs.lib.attrsets.recursiveUpdate node {
      networking.wireguard.interfaces.wg0 = {
        preSetup = ''
          ip netns add ${socketNamespace}
          ip netns add ${interfaceNamespace}
        '';
        inherit socketNamespace interfaceNamespace;
      };
    };
    # interface should be created in the socketNamespace
    # and moved to the init namespace
    peer3 = pkgs.lib.attrsets.recursiveUpdate node {
      networking.wireguard.interfaces.wg0 = {
        preSetup = ''
          ip netns add ${socketNamespace}
        '';
        inherit socketNamespace;
        interfaceNamespace = "init";
      };
    };
  };

  testScript = ''
    startAll();

    $peer0->waitForUnit("wireguard-wg0.service");
    $peer1->waitForUnit("wireguard-wg0.service");
    $peer2->waitForUnit("wireguard-wg0.service");
    $peer3->waitForUnit("wireguard-wg0.service");

    $peer0->succeed("ip -n ${socketNamespace} link show wg0");
    $peer1->succeed("ip -n ${interfaceNamespace} link show wg0");
    $peer2->succeed("ip -n ${interfaceNamespace} link show wg0");
    $peer3->succeed("ip link show wg0");
  '';
})