wireguard: add creation and destination namespaces

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/.
This commit is contained in:
Lorenzo Manacorda 2019-09-19 22:54:38 +02:00
parent b943338ea5
commit 412f6a967d
3 changed files with 136 additions and 11 deletions

View file

@ -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
{

View file

@ -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 {};

View file

@ -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");
'';
})