Squashed commit of the following:

commit 014fa13262
Author: Tilmann Meyer <me@atiltedtree.dev>
Date:   Sun Mar 24 23:09:00 2024 +0100

    Fixed persistent keepalive for networkd configurer

commit 37453982ab
Author: Matthew Salerno <m@salernosection.com>
Date:   Sun Mar 24 21:08:06 2024 -0400

    update flake

commit 68a0496bc7
Author: Matthew Salerno <m@salernosection.com>
Date:   Sun Mar 24 21:07:59 2024 -0400

    Update tests to use `subnets` in all connections

commit 8c7f741b7f
Author: Matthew Salerno <m@salernosection.com>
Date:   Sun Mar 24 21:07:31 2024 -0400

    Add error message to parser for incorrectly configured subnets in connections

commit 3e3a37fc0f
Author: Matthew Salerno <m@salernosection.com>
Date:   Sun Mar 24 21:05:27 2024 -0400

    Update disjoint test

commit 1236e4e8f2
Author: Adam Stephens <adam@valkor.net>
Date:   Sun Mar 24 20:59:26 2024 -0400

    Add disjointed meshes tests

commit dadd5bf720
Author: Matthew Salerno <m@salernosection.com>
Date:   Wed Dec 13 22:01:16 2023 -0500

    add tags to agenix-rekey provider

commit 1d3184639a
Author: Matthew Salerno <m@salernosection.com>
Date:   Mon Dec 11 22:09:45 2023 -0500

    sneaky systemd issues with dev only

commit c8fb5affe5
Author: Matthew Salerno <m@salernosection.com>
Date:   Tue Dec 5 20:51:30 2023 -0500

    wnlib is back

commit 1e697eb859
Author: Matthew Salerno <m@salernosection.com>
Date:   Sat Nov 11 22:09:26 2023 -0500

    allow multiple devs for same subnet

commit 45b70c9063
Author: Matthew Salerno <m@salernosection.com>
Date:   Mon Sep 18 16:04:49 2023 -0400

    added manual ip tests and resulting fixes

commit 3d49ebff29
Author: Matthew Salerno <m@salernosection.com>
Date:   Mon Sep 18 11:49:58 2023 -0400

    Added manual IP assignment tests

commit bd52d85d2d
Author: Matthew Salerno <m@salernosection.com>
Date:   Thu Sep 14 16:08:00 2023 -0400

    Generalized ip assignment to take cidr or IP

commit fd2b9ce77c
Author: Matthew Salerno <m@salernosection.com>
Date:   Thu Sep 14 13:50:11 2023 -0400

    Generalized ip assignment to take cidr or IP

commit 57f8e0e974
Author: Matthew Salerno <m@salernosection.com>
Date:   Wed Sep 13 18:38:42 2023 -0400

    Fixed manual ipv4 assignment issue

commit a24fffa753
Author: Matthew Salerno <m@salernosection.com>
Date:   Mon Sep 11 13:44:11 2023 -0400

    Update README.md with link to self

commit dd9de47a84
Author: Matthew Salerno <m@salernosection.com>
Date:   Sat Sep 2 19:55:17 2023 -0400

    fixed missing link

commit e761330e91
Merge: bb8636d b658653
Author: Matthew Salerno <m@salernosection.com>
Date:   Sat Sep 2 19:54:29 2023 -0400

    fixed missing link

commit bb8636dd8d
Author: Matthew Salerno <m@salernosection.com>
Date:   Thu Aug 31 20:45:14 2023 -0400

    Readme moved to wiki

commit 86e300428b
Author: Matthew Salerno <m@salernosection.com>
Date:   Tue Aug 22 21:07:34 2023 -0400

    Fixed a bug in allGroupEndpoints logic

commit 9a5c773355
Author: Matthew Salerno <m@salernosection.com>
Date:   Mon Aug 21 22:03:19 2023 -0400

    oops, delete net.nix

commit b97760e456
Author: Matthew Salerno <m@salernosection.com>
Date:   Mon Aug 21 21:59:30 2023 -0400

    fixup additional_ settings

commit 753c072663
Author: Matthew Salerno <m@salernosection.com>
Date:   Mon Aug 21 21:30:40 2023 -0400

    change null test to test enable = false

commit 690e13e902
Author: Matthew Salerno <m@salernosection.com>
Date:   Mon Aug 21 21:28:29 2023 -0400

    add mailing list to readme
release
Matthew Salerno 9 months ago
parent 0dea96cf37
commit c1e3bf1800

1
.gitignore vendored

@ -0,0 +1 @@
/result

@ -0,0 +1,46 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
{lib, devNameMethod ? "short", ...}@inputs: keyProviders: intermediateConfig: localPeerName:
let wnlib = import ../lib.nix {inherit lib;}; in
with wnlib;
with lib;
let
thisPeer = intermediateConfig.peers."${localPeerName}";
# these aren't really important, I just wanted to reverse the argument order
forEachAttr' = flip mapAttrs';
forEachAttrToList = flip mapAttrsToList;
devName = getDevName devNameMethod localPeerName;
in
with getKeyProviderFuncs keyProviders inputs intermediateConfig localPeerName;
{
networking.hosts = foldl' (mergeAttrs) {} (concatLists ( concatLists (forEachAttrToList thisPeer.subnetConnections (subnetName: subnetConnection:
forEachAttrToList subnetConnection.peerConnections (remotePeerName: peerConnection: forEach peerConnection.ipAddresses (ip: {"${asIp ip}" = ["${remotePeerName}.${subnetName}"];}))
))));
systemd.network = {
netdevs = forEachAttr' thisPeer.subnetConnections (subnetName: subnetConnection: nameValuePair "50-${devName subnetName}" {
netdevConfig = {
Kind = "wireguard";
Name = "${devName subnetName}";
};
wireguardConfig = {
ListenPort = subnetConnection.listenPort;
PrivateKeyFile = getPrivKeyFile;
};
wireguardPeers = forEachAttrToList subnetConnection.peerConnections (remotePeerName: peerConnection: {
wireguardPeerConfig = {
Endpoint = "${peerConnection.endpoint.ip}:${builtins.toString peerConnection.endpoint.port}";
PublicKey = getPeerPubKey remotePeerName;
AllowedIPs = map (ip: asCidr ip) peerConnection.ipAddresses;
PresharedKeyFile = getSubnetPSKFile subnetName;
};
}
// (if peerConnection.endpoint ? persistentKeepalive then {PersistentKeepalive = peerConnection.endpoint.persistentKeepalive;} else {})
// (warnIf (peerConnection.endpoint ? dynamicEndpointRefreshSeconds) "dynamicEndpointRefreshSeconds not supported for networkd" {})
// (warnIf (peerConnection.endpoint ? dynamicEndpointRefreshRestartSeconds) "dynamicEndpointRefreshRestartSeconds not supported for networkd" {})
);
});
};
} // getProviderConfig

@ -3,19 +3,16 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
{lib, ...}@inputs: keyProviders: intermediateConfig: localPeerName:
with lib.trivial;
with lib.attrsets;
with lib.lists;
{lib, devNameMethod ? "short", ...}@inputs: keyProviders: intermediateConfig: localPeerName:
let wnlib = import ../lib.nix {inherit lib;}; in
with wnlib;
with lib;
with builtins;
with import ../lib.nix;
let
thisPeer = intermediateConfig.peers."${localPeerName}";
# these aren't really important, I just wanted to reverse the argument order
forEachAttr' = flip mapAttrs';
forEachAttrToList = flip mapAttrsToList;
shortName = fqdn: head (strings.splitString "." fqdn);
devName = getDevName devNameMethod localPeerName;
in
with getKeyProviderFuncs keyProviders inputs intermediateConfig localPeerName;
{
@ -23,10 +20,10 @@ with getKeyProviderFuncs keyProviders inputs intermediateConfig localPeerName;
forEachAttrToList subnetConnection.peerConnections (remotePeerName: peerConnection: forEach peerConnection.ipAddresses (ip: {"${asIp ip}" = ["${remotePeerName}.${subnetName}"];}))
))));
systemd.network = {
netdevs = forEachAttr' thisPeer.subnetConnections (subnetName: subnetConnection: nameValuePair "50-${shortName subnetName}" {
netdevs = forEachAttr' thisPeer.subnetConnections (subnetName: subnetConnection: nameValuePair "50-${devName subnetName}" {
netdevConfig = {
Kind = "wireguard";
Name = "${shortName subnetName}";
Name = "${devName subnetName}";
};
wireguardConfig = {
ListenPort = subnetConnection.listenPort;
@ -38,15 +35,14 @@ with getKeyProviderFuncs keyProviders inputs intermediateConfig localPeerName;
PublicKey = getPeerPubKey remotePeerName;
AllowedIPs = map (ip: asCidr ip) peerConnection.ipAddresses;
PresharedKeyFile = getSubnetPSKFile subnetName;
};
} // (if peerConnection.endpoint ? persistentKeepalive then {PersistentKeepalive = peerConnection.endpoint.persistentKeepalive;} else {});
}
// (if peerConnection.endpoint ? persistentKeepalive then {PersistentKeepalive = peerConnection.endpoint.persistentKeepalive;} else {})
// (warnIf (peerConnection.endpoint ? dynamicEndpointRefreshSeconds) "dynamicEndpointRefreshSeconds not supported for networkd" {})
// (warnIf (peerConnection.endpoint ? dynamicEndpointRefreshRestartSeconds) "dynamicEndpointRefreshRestartSeconds not supported for networkd" {})
);
});
networks = forEachAttr' thisPeer.subnetConnections (subnetName: subnetConnection: nameValuePair "50-${shortName subnetName}" {
matchConfig.Name = "${shortName subnetName}";
networks = forEachAttr' thisPeer.subnetConnections (subnetName: subnetConnection: nameValuePair "50-${devName subnetName}" {
matchConfig.Name = "${devName subnetName}";
address = map (address: (asCidr' "64" "24" address)) subnetConnection.ipAddresses;
});
};

@ -3,18 +3,16 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
{lib, ...}@inputs: keyProviders: intermediateConfig: localPeerName:
with lib.trivial;
with lib.attrsets;
with lib.lists;
{lib, devNameMethod ? "short", ...}@inputs: keyProviders: intermediateConfig: localPeerName:
let wnlib = import ../lib.nix {inherit lib;}; in
with wnlib;
with lib;
with builtins;
with import ../lib.nix;
let
thisPeer = intermediateConfig.peers."${localPeerName}";
# these aren't really important, I just wanted to reverse the argument order
forEachAttr' = flip mapAttrs';
forEachAttrToList = flip mapAttrsToList;
devName = getDevName devNameMethod localPeerName;
in
with getKeyProviderFuncs keyProviders inputs intermediateConfig localPeerName;
{
@ -22,7 +20,7 @@ with getKeyProviderFuncs keyProviders inputs intermediateConfig localPeerName;
forEachAttrToList subnetConnection.peerConnections (remotePeerName: peerConnection: forEach peerConnection.ipAddresses (ip: {"${asIp ip}" = ["${remotePeerName}.${subnetName}"];}))
))));
networking.wireguard = {
interfaces = forEachAttr' thisPeer.subnetConnections (subnetName: subnetConnection: nameValuePair "${head (strings.splitString "." subnetName)}"
interfaces = forEachAttr' thisPeer.subnetConnections (subnetName: subnetConnection: nameValuePair (devName subnetName)
{
ips = map (address: (asCidr' "64" "24" address)) subnetConnection.ipAddresses;
listenPort = subnetConnection.listenPort;

@ -2,12 +2,10 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1692463654,
"narHash": "sha256-F8hZmsQINI+S6UROM4jyxAMbQLtzE44pI8Nk6NtMdao=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "ca3c9ac9f4cdd4bea19f592b32bb59b74ab7d783",
"type": "github"
"lastModified": 0,
"narHash": "sha256-g0bDwXFmTE7uGDOs9HcJsfLFhH7fOsASbAuOzDC+fhQ=",
"path": "/nix/store/k5l01g2zwhysjyl5zjvg5zxnj0lyxpp1-source",
"type": "path"
},
"original": {
"id": "nixpkgs",

@ -8,17 +8,18 @@
outputs = { self, nixpkgs, ... }:
let
forAllSystems = nixpkgs.lib.genAttrs [ "x86_64-linux" "aarch64-linux" ];
wnlib = import ./lib.nix { lib = nixpkgs.lib; };
in
{
wnlib = import ./lib.nix;
nixosModules.default = import ./wire.nix;
inherit wnlib;
checks = forAllSystems (system:
let
checkArgs = {
# reference to nixpkgs for the current system
pkgs = nixpkgs.legacyPackages.${system};
# this gives us a reference to our flake but also all flake inputs
inherit self;
inherit self wnlib;
};
in {
# import our test
@ -26,9 +27,11 @@
simple = import ./tests/simple.nix checkArgs;
mesh = import ./tests/mesh.nix checkArgs;
ring = import ./tests/ring.nix checkArgs;
double-dev-ring = import ./tests/double-dev-ring.nix checkArgs;
manual-ipv4 = import ./tests/manual-ipv4.nix checkArgs;
manual-ipv6 = import ./tests/manual-ipv6.nix checkArgs;
manual-ipv6-auto = import ./tests/manual-ipv6-auto.nix checkArgs;
disjointed-meshes = import ./tests/disjointed-meshes.nix checkArgs;
});
};
}

@ -4,9 +4,9 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
{lib, ...}: intermediateConfig: localPeerName:
with import ../lib.nix;
with lib.attrsets;
with builtins;
let wnlib = import ../lib.nix {inherit lib;}; in
with wnlib;
with lib;
{
getPeerPubKey = remotePeerName: attrByPath [remotePeerName "publicKey"] null intermediateConfig.peers;
getPrivKeyFile = attrByPath [localPeerName "privateKeyFile"] null intermediateConfig.peers;

@ -4,17 +4,21 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
{config, lib, ...}: intermediateConfig: localPeerName:
with (import ../lib.nix);
with lib.attrsets;
with builtins;
let wnlib = import ../lib.nix {inherit lib;}; in
with wnlib;
with lib;
{
config.age = {
secrets = {
"wirenix-peer-${localPeerName}" = {
owner = "root";
mode = "640";
group = if config.wirenix.configurer == "networkd" then "systemd-network" else "root";
group = if (builtins.match ".*networkd.*" config.wirenix.configurer != null) then "systemd-network" else "root";
rekeyFile = config.wirenix.secretsDir + /wirenix-peer- + localPeerName + ".age";
generator.tags = [
"wirenix"
"wirenix.peer"
];
generator.script = {pkgs, file, ...}: ''
priv=$(${pkgs.wireguard-tools}/bin/wg genkey)
${pkgs.wireguard-tools}/bin/wg pubkey <<< "$priv" > ${lib.escapeShellArg (lib.removeSuffix ".age" file + ".pub")}
@ -25,8 +29,12 @@ with builtins;
mapAttrs' (name: value: nameValuePair ("wirenix-subnet-${name}") {
owner = "root";
mode = "640";
group = if config.wirenix.configurer == "networkd" then "systemd-network" else "root";
group = if (builtins.match ".*networkd.*" config.wirenix.configurer != null) then "systemd-network" else "root";
rekeyFile = config.wirenix.secretsDir + /wirenix-subnet- + name + ".age";
generator.tags = [
"wirenix"
"wirenix.subnet"
];
generator.script = {pkgs, ...}: ''
psk=$(${pkgs.wireguard-tools}/bin/wg genpsk)
echo "$psk"

@ -3,21 +3,11 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
with builtins;
/**
ACL independent functions that can be used in parsers.
*/
let
# stubbornly not passing lib and reimplementing everything since 2023
findFirst = pred: default: list:
if length list == 0
then default
else
if pred (head list)
then head list
else findFirst pred default (tail list);
in
{ lib, ... }:
with lib;
rec {
/** Builtin Parsers */
defaultParsers = {
@ -27,6 +17,7 @@ rec {
defaultConfigurers = {
static = import ./configurers/static.nix;
networkd = import ./configurers/networkd.nix;
networkd-dev-only = import ./configurers/networkd-dev-only.nix;
};
/** Builtin key providers */
defaultKeyProviders = {
@ -76,10 +67,10 @@ rec {
funcs: list: map (pipe' funcs) list;
/** generate last 20 characters (80 bits) of the peer's IPv6 address */
generateIPv6Suffix = peerName: substring 0 16 (hashString "sha256" peerName);
generateIPv6Suffix = peerName: substring 0 16 (builtins.hashString "sha256" peerName);
/** generate the first 10 characters of the IPV6 address for the subnet name */
generateIPv6Prefix = subnetName: "fd" + (substring 0 14 (hashString "sha256" subnetName));
generateIPv6Prefix = subnetName: "fd" + (substring 0 14 (builtins.hashString "sha256" subnetName));
/** generates a full IPv6 subnet */
generateIPv6Subnet = subnetName: (addColonsToIPv6 (generateIPv6Prefix subnetName)) + "::/64";
@ -89,7 +80,25 @@ rec {
/** generates a full IPv6 address with cidr */
generateIPv6Cidr = subnetName: peerName: (addColonsToIPv6 ((generateIPv6Prefix subnetName) + (generateIPv6Suffix peerName))) + "/64";
getDevName' = devNameMethod: peerName: subnetName:
let
getDevNameLong = peerName: subnetName: subnetName;
getDevNameShort = peerName: subnetName: head (splitString "." subnetName);
getDevNameHash = peerName: subnetName: "wn." + (substring 0 12 (builtins.hashString "sha256" (peerName + "." + subnetName)));
in
if devNameMethod == "hash" then
getDevNameHash peerName subnetName
else if devNameMethod == "long" then
getDevNameLong peerName subnetName
else
getDevNameShort peerName subnetName;
getDevName = devNameMethod: peerName: subnetName:
let
name = getDevName' devNameMethod peerName subnetName;
in
throwIf (stringLength name > 15) "Wirenix: Dev name must be less than or equal to 15 characters. Try changing devNameMethod to \"hash\"" name;
# getDevName = subnetName: peerName: if stringLength (getDevNameLong subnetName peerName) > 12 then getDevNameShort subnetName peerName else getDevNameLong subnetName peerName;
/**
* makes the intermediate config non-recursive, so it can be pretty printed and
* inspected in the repl. Also helps with testing as it forces evaluation of the config.
@ -97,7 +106,7 @@ rec {
breakIntermediateRecursion = intermediateConfig:
let recurse = parentName:
mapAttrs (name: value:
if typeOf value == "set" then
if builtins.typeOf value == "set" then
if elem name [ "peer" "subnet" "group" "groups" ] then
"${name}s.${parentName}"
else if elem parentName ["peers"] then
@ -110,9 +119,9 @@ rec {
in
mapAttrs (name: value: recurse "" value) intermediateConfig;
nixosConfigForPeer = nixosConfigurations: peerName: builtins.head (builtins.attrValues (
lib.attrsets.filterAttrs (
name: value: (lib.attrsets.attrByPath ["config" "modules" "wirenix" "peerName"] null value) == peerName
nixosConfigForPeer = nixosConfigurations: peerName: head (attrValues (
filterAttrs (
name: value: (attrByPath ["config" "modules" "wirenix" "peerName"] null value) == peerName
) nixosConfigurations));
getKeyProviderFuncs = keyProvidersUninitialized: inputs: intermediateConfig: peerName:
@ -129,10 +138,10 @@ rec {
getProviderConfig = foldl' (x: y: x // y) {} (map (provider: if provider ? config then provider.config else {}) keyProviders);
};
mergeIf = attr: key: if builtins.hasAttr key attr then {"${key}" = attr."${key}";} else {};
asIp = cidr: head (filter (item: item != []) (split "/" cidr));
isIpv6 = ip: match ".*:.*" ip != null;
isCidr = cidr: match ".*/.*" cidr != null;
mergeIf = attr: key: if hasAttr key attr then {"${key}" = attr."${key}";} else {};
asIp = cidr: head (splitString "/" cidr);
isIpv6 = ip: builtins.match ".*:.*" ip != null;
isCidr = cidr: builtins.match ".*/.*" cidr != null;
asCidr' = ifv6: ifv4: ip: if (isCidr ip) then ip else if isIpv6 ip then ip+"/"+ifv6 else ip+"/"+ifv4;
asCidr = asCidr' "128" "32";
}

@ -4,11 +4,9 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
{lib, ...}: v1_acl:
with lib.attrsets;
with lib.lists;
with lib.trivial;
with (import ../lib.nix);
with builtins;
let wnlib = import ../lib.nix {inherit lib;}; in
with wnlib;
with lib;
let
/** parsePeer :: acl_peer -> ic_peer */
parsePeer = acl_peer: {
@ -45,11 +43,13 @@ let
/** getIpAddresses :: acl_peer -> acl_subnet -> [str] */
getIpAddresses = acl_subnet: acl_peer:
lib.throwIfNot (builtins.hasAttr acl_subnet.name acl_peer.subnets) "Tried getting the IP address for ${acl_peer.name} on subnet ${acl_subnet.name}. However, ${acl_peer.name} is not a member of ${acl_subnet.name}. It may be that you forgot to add ${acl_subnet.name} to `connections.*.subnets` in your ACL." (
if (acl_peer.subnets."${acl_subnet.name}" ? ipAddresses) then (
if (elem "auto" acl_peer.subnets."${acl_subnet.name}".ipAddresses) then (
(remove "auto" acl_peer.subnets."${acl_subnet.name}".ipAddresses) ++ (singleton (generateIPv6Address acl_subnet.name acl_peer.name))
) else acl_peer.subnets."${acl_subnet.name}".ipAddresses
) else (singleton (generateIPv6Address acl_subnet.name acl_peer.name));
) else (singleton (generateIPv6Address acl_subnet.name acl_peer.name))
);
/** getPeerConnections :: acl_peer -> acl_subnet -> str -> peerConnection */
getPeerConnections = acl_peerFrom: acl_subnet:
@ -129,8 +129,8 @@ let
subnetFromName = subnetName: findSingle
(subnet: subnet.name == subnetName)
(throw "No subnet " + subnetName)
(throw "Multiply defined subnet " + subnetName)
(throw "No subnet " + subnetName + " when processing peer " + acl_peer)
(throw "Multiply defined subnet " + subnetName + " when processing peer " + acl_peer)
v1_acl.subnets;

@ -0,0 +1,92 @@
{
version = "v1";
subnets = [
{
name = "disjoint1";
endpoints = [
{
# No match mean match any
port = 51820;
}
];
}
{
name = "disjoint2";
endpoints = [
{
# No match mean match any
port = 51821;
}
];
}
];
groups = [
# groups field is expected, but can be empty
];
peers = [
{
name = "node1";
subnets = {
disjoint1 = {
listenPort = 51820;
# empty ipAddresses will auto generate an IPv6 address
};
};
publicKey = "kdyzqV8cBQtDYeW6R1vUug0Oe+KaytHHDS7JoCp/kTE=";
privateKeyFile = "/etc/wg-key";
endpoints = [
{
# no match can be any
ip = "node1";
}
];
}
{
name = "node2";
subnets = {
disjoint1 = {
listenPort = 51820;
};
disjoint2 = {
listenPort = 51821;
};
};
publicKey = "ztdAXTspQEZUNpxUbUdAhhRWbiL3YYWKSK0ZGdcsMHE=";
privateKeyFile = "/etc/wg-key";
endpoints = [
{
# no match can be any
ip = "node2";
}
];
}
{
name = "node3";
subnets = {
disjoint2 = {
listenPort = 51821;
};
};
publicKey = "VR5SILc/2MkWSeGOVAJ/0Ru5H4DFheNvNUiT0fPtgiI=";
privateKeyFile = "/etc/wg-key";
endpoints = [
{
# no match can be any
ip = "node3";
}
];
}
];
connections = [
{
a = [{type= "subnet"; rule = "is"; value = "disjoint1";}];
b = [{type= "subnet"; rule = "is"; value = "disjoint1";}];
subnets = ["disjoint1"];
}
{
a = [{type= "subnet"; rule = "is"; value = "disjoint2";}];
b = [{type= "subnet"; rule = "is"; value = "disjoint2";}];
subnets = ["disjoint2"];
}
];
}

@ -0,0 +1,108 @@
{
version = "v1";
subnets = [
{
name = "ring";
endpoints = [
{}
];
}
];
groups = [
# groups field is expected, but can be empty
];
peers = [
{
name = "peer1";
subnets = {
ring = {
listenPort = 51820;
# empty ipAddresses will auto generate an IPv6 address
};
};
publicKey = "kdyzqV8cBQtDYeW6R1vUug0Oe+KaytHHDS7JoCp/kTE=";
privateKeyFile = "/etc/wg-key1";
endpoints = [
{
# no match can be any
ip = "node1";
port = 51820;
}
];
}
{
name = "peer2";
subnets = {
ring = {
listenPort = 51820;
};
};
publicKey = "ztdAXTspQEZUNpxUbUdAhhRWbiL3YYWKSK0ZGdcsMHE=";
privateKeyFile = "/etc/wg-key2";
endpoints = [
{
# no match can be any
ip = "node2";
port = 51820;
}
];
}
{
name = "peer3";
subnets = {
ring = {
listenPort = 51821;
# empty ipAddresses will auto generate an IPv6 address
};
};
publicKey = "43tP6JgckdTFrnbYuy8a42jdNt3+wwVcb4+ae5U4ez4=";
privateKeyFile = "/etc/wg-key3";
endpoints = [
{
# no match can be any
ip = "node1";
port = 51821;
}
];
}
{
name = "peer4";
subnets = {
ring = {
listenPort = 51821;
};
};
publicKey = "g6+Tq9aeVfm5CXPIwZDqoTxGmsQ/TlLtxcxVn2aSiVA=";
privateKeyFile = "/etc/wg-key4";
endpoints = [
{
# no match can be any
ip = "node2";
port = 51821;
}
];
}
];
connections = [
{
a = [{type= "peer"; rule = "is"; value = "peer1";}];
b = [{type= "peer"; rule = "is"; value = "peer2";}];
subnets = [ "ring" ];
}
{
a = [{type= "peer"; rule = "is"; value = "peer2";}];
b = [{type= "peer"; rule = "is"; value = "peer3";}];
subnets = [ "ring" ];
}
{
a = [{type= "peer"; rule = "is"; value = "peer3";}];
b = [{type= "peer"; rule = "is"; value = "peer4";}];
subnets = [ "ring" ];
}
{
a = [{type= "peer"; rule = "is"; value = "peer4";}];
b = [{type= "peer"; rule = "is"; value = "peer1";}];
subnets = [ "ring" ];
}
];
}

@ -60,6 +60,7 @@
{
a = [{type= "subnet"; rule = "is"; value = "manual";}];
b = [{type= "subnet"; rule = "is"; value = "manual";}];
subnets = [ "manual" ];
}
];
}

@ -58,6 +58,7 @@
{
a = [{type= "subnet"; rule = "is"; value = "manual";}];
b = [{type= "subnet"; rule = "is"; value = "manual";}];
subnets = [ "manual" ];
}
];
}

@ -58,6 +58,7 @@
{
a = [{type= "subnet"; rule = "is"; value = "manual";}];
b = [{type= "subnet"; rule = "is"; value = "manual";}];
subnets = [ "manual" ];
}
];
}

@ -86,6 +86,7 @@
{
a = [{type= "subnet"; rule = "is"; value = "mesh";}];
b = [{type= "subnet"; rule = "is"; value = "mesh";}];
subnets = [ "mesh" ];
}
];
}

@ -86,18 +86,22 @@
{
a = [{type= "peer"; rule = "is"; value = "node1";}];
b = [{type= "peer"; rule = "is"; value = "node2";}];
subnets = [ "ring" ];
}
{
a = [{type= "peer"; rule = "is"; value = "node2";}];
b = [{type= "peer"; rule = "is"; value = "node3";}];
subnets = [ "ring" ];
}
{
a = [{type= "peer"; rule = "is"; value = "node3";}];
b = [{type= "peer"; rule = "is"; value = "node4";}];
subnets = [ "ring" ];
}
{
a = [{type= "peer"; rule = "is"; value = "node4";}];
b = [{type= "peer"; rule = "is"; value = "node1";}];
subnets = [ "ring" ];
}
];
}

@ -53,6 +53,7 @@
{
a = [{type= "subnet"; rule = "is"; value = "simple";}];
b = [{type= "subnet"; rule = "is"; value = "simple";}];
subnets = [ "simple" ];
}
];
}

@ -0,0 +1,78 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
(import ./lib.nix) ({wnlib}:
{
name = "disjointed-meshes connection";
nodes = {
# `self` here is set by using specialArgs in `lib.nix`
node1 = { self, pkgs, ... }: {
virtualisation.vlans = [ 1 ];
imports = [ self.nixosModules.default ];
wirenix = {
enable = true;
keyProviders = ["acl"];
peerName = "node1";
aclConfig = import ./acls/disjointed-meshes.nix;
};
# Don't do this! This is for testing only!
environment.etc."wg-key" = {
text = "MIELhEc0I7BseAanhk/+LlY/+Yf7GK232vKWITExnEI=";
};
networking.firewall.enable = false;
};
node2 = { self, pkgs, ... }: {
virtualisation.vlans = [ 1 ];
imports = [ self.nixosModules.default ];
wirenix = {
enable = true;
keyProviders = ["acl"];
peerName = "node2";
aclConfig = import ./acls/disjointed-meshes.nix;
};
environment.etc."wg-key" = {
text = "yG4mJiduoAvzhUJMslRbZwOp1gowSfC+wgY8B/Mul1M=";
};
networking.firewall.enable = false;
};
node3 = { self, pkgs, ... }: {
virtualisation.vlans = [ 1 ];
imports = [ self.nixosModules.default ];
wirenix = {
enable = true;
keyProviders = ["acl"];
peerName = "node3";
aclConfig = import ./acls/disjointed-meshes.nix;
};
environment.etc."wg-key" = {
text = "MFsj7nmb2efBFNwON8RxZf+MHbopTY9P3+/xhiqJFlM=";
};
networking.firewall.enable = false;
};
};
# This is the test code that will check if our service is running correctly:
testScript = ''
start_all()
node1.wait_for_unit("wireguard-disjoint1-peer-node2")
node2.wait_for_unit("wireguard-disjoint1-peer-node1")
node2.wait_for_unit("wireguard-disjoint2-peer-node3")
node3.wait_for_unit("wireguard-disjoint2-peer-node2")
node1.succeed("wg show >&2")
node2.succeed("wg show >&2")
node3.succeed("wg show >&2")
node1.succeed("ping -c 1 node2.disjoint1")
node1.fail("ping -c 1 node3.disjoint2")
node2.succeed("ping -c 1 node1.disjoint1")
node2.succeed("ping -c 1 node3.disjoint2")
node3.fail("ping -c 1 node1.disjoint1")
node3.succeed("ping -c 1 node2.disjoint2")
'';
})

@ -0,0 +1,88 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
(import ./lib.nix) ({wnlib}:
{
name = "double dev ring connection";
nodes = {
# `self` here is set by using specialArgs in `lib.nix`
node1 = { self, pkgs, ... }: {
virtualisation.vlans = [ 1 ];
imports = [ self.nixosModules.default ];
systemd.network.enable = true;
networking.useDHCP = false;
wirenix = {
configurer = "networkd";
devNameMethod = "hash";
enable = true;
aclConfig = import ./acls/double-dev-ring.nix;
peerNames = ["peer1" "peer3"];
};
environment.etc."wg-key1" = {
text = "MIELhEc0I7BseAanhk/+LlY/+Yf7GK232vKWITExnEI=";
};
environment.etc."wg-key3" = {
text = "yPcTvQOK9eVXQjLNapOsv2iAkbOeSzCCxlrWPMe1o0g=";
};
environment.systemPackages = [pkgs.wireguard-tools];
networking.firewall.enable = false;
};
node2 = { self, pkgs, ... }: {
virtualisation.vlans = [ 1 ];
imports = [ self.nixosModules.default ];
systemd.network.enable = true;
networking.useDHCP = false;
wirenix = {
configurer = "networkd";
devNameMethod = "hash";
enable = true;
keyProviders = ["acl"];
aclConfig = import ./acls/double-dev-ring.nix;
peerNames = ["peer2" "peer4"];
};
environment.etc."wg-key2" = {
text = "yG4mJiduoAvzhUJMslRbZwOp1gowSfC+wgY8B/Mul1M=";
};
environment.etc."wg-key4" = {
text = "CLREBQ+oGXsGxhlQc3ufSoBd7MNFoM6KmMnNyuQ9S0E=";
};
environment.systemPackages = [pkgs.wireguard-tools];
networking.firewall.enable = false;
};
};
# This is the test code that will check if our service is running correctly:
testScript = ''
start_all()
nodes = {
"peer1": node1,
"peer2": node2,
"peer3": node1,
"peer4": node2
}
ifaces = {
"peer1": "${wnlib.getDevName "hash" "peer1" "ring"}",
"peer2": "${wnlib.getDevName "hash" "peer2" "ring"}",
"peer3": "${wnlib.getDevName "hash" "peer3" "ring"}",
"peer4": "${wnlib.getDevName "hash" "peer4" "ring"}"
}
connections = {
"peer1": ["peer2", "peer4"],
"peer2": ["peer3", "peer1"],
"peer3": ["peer4", "peer2"],
"peer4": ["peer1", "peer3"]
}
node1.wait_for_unit("systemd-networkd-wait-online")
node2.wait_for_unit("systemd-networkd-wait-online")
node1.succeed("ping -c 3 node2 >&2")
node2.succeed("ping -c 3 node1 >&2")
for local_name, local_node in nodes.items():
for remote_name in set(nodes.keys()) - set([local_name]):
if remote_name in connections[local_name]:
local_node.succeed(f"ping -c 3 -I {ifaces[local_name]} {remote_name}.ring >&2")
else:
local_node.fail(f"ping -c 3 -W 1 -I {ifaces[local_name]} {remote_name}.ring")
'';
})

@ -3,7 +3,7 @@
# The first argument to this function is the test module itself
test:
# These arguments are provided by `flake.nix` on import, see checkArgs
{ pkgs, self}:
{ pkgs, self, wnlib }:
let
inherit (pkgs) lib;
# this imports the nixos library that contains our testing framework
@ -17,5 +17,5 @@ in
# This is useful for referencing modules or packages from your own flake
# as well as importing from other flakes.
node.specialArgs = { inherit self; };
imports = [ test ];
imports = [ (test {inherit wnlib;}) ];
}).config.result

@ -3,7 +3,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
(import ./lib.nix)
(import ./lib.nix) ({wnlib}:
{
name = "manual ipv4 connection";
nodes = {
@ -50,4 +50,4 @@
node1.succeed("ping -c 1 node2.manual")
node2.succeed("ping -c 1 node1.manual")
'';
}
})

@ -3,7 +3,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
(import ./lib.nix)
(import ./lib.nix) ({wnlib}:
{
name = "explicit auto ipv6 connection";
nodes = {
@ -50,4 +50,4 @@
node1.succeed("ping -c 1 node2.manual")
node2.succeed("ping -c 1 node1.manual")
'';
}
})

@ -3,7 +3,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
(import ./lib.nix)
(import ./lib.nix) ({wnlib}:
{
name = "manual ipv6 connection";
nodes = {
@ -50,4 +50,4 @@
node1.succeed("ping -c 1 node2.manual")
node2.succeed("ping -c 1 node1.manual")
'';
}
})

@ -3,7 +3,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
(import ./lib.nix)
(import ./lib.nix) ({wnlib}:
{
name = "mesh connection";
nodes = {
@ -39,6 +39,7 @@
virtualisation.vlans = [ 1 ];
imports = [ self.nixosModules.default ];
systemd.network.enable = true;
networking.useDHCP = false;
wirenix = {
enable = true;
configurer = "networkd";
@ -60,6 +61,7 @@
virtualisation.vlans = [ 1 ];
imports = [ self.nixosModules.default ];
systemd.network.enable = true;
networking.useDHCP = false;
wirenix = {
enable = true;
configurer = "networkd";
@ -94,11 +96,9 @@
node2.wait_for_unit("wireguard-mesh.target")
node3.wait_for_unit("systemd-networkd-wait-online")
node4.wait_for_unit("systemd-networkd-wait-online")
for local_name, local_node in nodes.items():
local_node.succeed("wg showconf mesh >&2")
for local_name, local_node in nodes.items():
for remote_name in set(nodes.keys()) - set([local_name]):
local_node.succeed(f"ping -c 1 {remote_name} >&2")
local_node.succeed(f"ping -c 1 {remote_name}.mesh >&2")
'';
}
})

@ -3,7 +3,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
(import ./lib.nix) {
(import ./lib.nix) ({wnlib}: {
name = "null test";
nodes = {
# `self` here is set by using specialArgs in `lib.nix`
@ -19,4 +19,4 @@
# Check if our webserver returns the expected result
assert "Hello world" in output, f"'{output}' does not contain 'Hello world'"
'';
}
})

@ -3,7 +3,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
(import ./lib.nix)
(import ./lib.nix) ({wnlib}:
{
name = "ring connection";
nodes = {
@ -96,4 +96,4 @@
else:
local_node.fail(f"ping -c 1 -W 1 {remote_name}.ring")
'';
}
})

@ -3,7 +3,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
(import ./lib.nix)
(import ./lib.nix) ({wnlib}:
{
name = "simple connection";
nodes = {
@ -51,4 +51,4 @@
node1.succeed("ping -c 1 node2.simple")
node2.succeed("ping -c 1 node1.simple")
'';
}
})

@ -4,9 +4,9 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
{ config, lib, ... }@inputs:
let wnlib = import ./lib.nix {inherit lib;}; in
with wnlib;
with lib;
with import ./lib.nix;
with builtins;
let
cfg = config.wirenix;
parsers = defaultParsers // cfg.additionalParsers;
@ -14,10 +14,10 @@ let
availableKeyProviders = defaultKeyProviders // cfg.additionalKeyProviders;
acl = cfg.aclConfig;
parser = parsers."${acl.version}" inputs;
configurer = (getAttr cfg.configurer configurers) inputs; #config.wirenix.configurer inputs;
configurer = (getAttr cfg.configurer configurers) (inputs//{devNameMethod = cfg.devNameMethod;}); #config.wirenix.configurer inputs;
keyProviders = map (providerName: getAttr providerName availableKeyProviders) cfg.keyProviders; # config.wirenix.keyProviders;
mkMergeTopLevel = names: attrs: attrsets.getAttrs names (
mapAttrs (k: v: mkMerge v) (attrsets.foldAttrs (n: a: [n] ++ a) [] attrs)
mkMergeTopLevel = names: attrs: getAttrs names (
mapAttrs (k: v: mkMerge v) (foldAttrs (n: a: [n] ++ a) [] attrs)
);
/**
* We can merge if we want to
@ -26,7 +26,7 @@ let
* Well they're, no friends of mine.
*/
safetyMerge = possibleTopLevelKeys: attrs:
(mkMergeTopLevel possibleTopLevelKeys ((lists.singleton (attrsets.genAttrs possibleTopLevelKeys (name: {})))++attrs));
(mkMergeTopLevel possibleTopLevelKeys ((singleton (genAttrs possibleTopLevelKeys (name: {})))++attrs));
in
{
options = {
@ -35,15 +35,24 @@ in
wirenix = {
enable = mkEnableOption "wirenix";
peerName = mkOption {
default = config.networking.hostName;
defaultText = literalExpression "hostName";
default = config.system.name;
defaultText = literalExpression "config.system.name";
example = "bernd";
type = types.str;
type = with types; str;
description = mdDoc ''
Name of the peer using this module, to match the name in
`wirenix.config.peers.*.name`
'';
};
peerNames = mkOption {
default = null;
example = [ "container1" "container2" ];
type = with types; nullOr (listOf str);
description = mdDoc ''
When one host needs multiple devs for the same subnet, specify
multiple names manually. Overrides peerName.
'';
};
configurer = mkOption {
default = "static";
type = types.str;
@ -102,15 +111,33 @@ in
the same on all peers that need to connect to eachother
'';
};
devNameMethod = mkOption {
default = "short";
type = with types; strMatching "hash|long|short";
description = mdDoc ''
The method used to derive device names. Device names are limited to 15 characters,
but often times subnet names will exceed that. "hash" is the most reliable, and
will always create a name unique to the subnet and peer combination. "long" will
return the entire subnet, and "short" will return the beginning of the subnet up
until the first "." character.
'';
};
};
};
# --------------------------------------------------------------- #
# Due to merge weirdness, I have to define what configuration keys
# we're touching upfront, and make sure they exist
config = mkIf cfg.enable (safetyMerge ["networking" "sops" "age" "systemd" "services" "environment"]
[
(configurer keyProviders (parser acl) cfg.peerName)
]
);
config =
mkIf cfg.enable (safetyMerge ["networking" "sops" "age" "systemd" "services" "environment"] (
if builtins.typeOf cfg.peerNames == "null" then (
[(configurer keyProviders (parser acl) cfg.peerName)]
)
else (
warnIf (cfg.devNameMethod != "hash") "Wirenix: Using multiple peerNames for devNameMethod = \"${cfg.devNameMethod}\" can (will) cause device name collisions. Please use devNameMethod = \"hash\" instead" (
warnIf (cfg.configurer == "static") "Wirenix: static configurer not supported with multiple peerNames. Please use networkd or networkd-dev-only instead." (
(map (name: (configurer keyProviders (parser acl) name)) cfg.peerNames)
))
)
));
}
Loading…
Cancel
Save