Skip to content

Commit

Permalink
wireguard: add creation and destination namespaces
Browse files Browse the repository at this point in the history
The two new options make it possible to create the interface in one namespace
and move it to a different one, as explained at https://www.wireguard.com/netns/.
  • Loading branch information
asymmetric committed Nov 9, 2019
1 parent b943338 commit 412f6a9
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 11 deletions.
66 changes: 55 additions & 11 deletions nixos/modules/services/networking/wireguard.nix
Expand Up @@ -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>.
'';
};
};

};
Expand Down Expand Up @@ -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}";
Expand All @@ -255,30 +285,30 @@ 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}
${route_setup}
'';

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}
'';
};
Expand All @@ -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}"
{
Expand All @@ -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

{
Expand Down
1 change: 1 addition & 0 deletions nixos/tests/all-tests.nix
Expand Up @@ -280,6 +280,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 {};
xdg-desktop-portal = handleTest ./xdg-desktop-portal.nix {};
Expand Down
80 changes: 80 additions & 0 deletions nixos/tests/wireguard/namespaces.nix
@@ -0,0 +1,80 @@
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");
'';
})

0 comments on commit 412f6a9

Please sign in to comment.