diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..82e0cdf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/result \ No newline at end of file diff --git a/configurers/networkd-dev-only.nix b/configurers/networkd-dev-only.nix new file mode 100644 index 0000000..750b0a6 --- /dev/null +++ b/configurers/networkd-dev-only.nix @@ -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 \ No newline at end of file diff --git a/configurers/networkd.nix b/configurers/networkd.nix index c7a42cb..93c5b0f 100644 --- a/configurers/networkd.nix +++ b/configurers/networkd.nix @@ -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; @@ -45,8 +42,8 @@ with getKeyProviderFuncs keyProviders inputs intermediateConfig localPeerName; // (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; }); }; diff --git a/configurers/static.nix b/configurers/static.nix index af7797e..7485071 100644 --- a/configurers/static.nix +++ b/configurers/static.nix @@ -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; diff --git a/flake.nix b/flake.nix index 45f32f6..86593ac 100644 --- a/flake.nix +++ b/flake.nix @@ -8,9 +8,9 @@ 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; checks = forAllSystems (system: let @@ -18,7 +18,7 @@ # 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,6 +26,7 @@ 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; diff --git a/key-providers/acl.nix b/key-providers/acl.nix index 6e16f38..f4a69e5 100644 --- a/key-providers/acl.nix +++ b/key-providers/acl.nix @@ -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; diff --git a/key-providers/agenix-rekey.nix b/key-providers/agenix-rekey.nix index 7acc383..27e8f13 100644 --- a/key-providers/agenix-rekey.nix +++ b/key-providers/agenix-rekey.nix @@ -4,9 +4,9 @@ * 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 = { diff --git a/lib.nix b/lib.nix index 24fd9d5..9c4d3d6 100644 --- a/lib.nix +++ b/lib.nix @@ -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"; } \ No newline at end of file diff --git a/parsers/v1.nix b/parsers/v1.nix index 776ba75..b9b94c0 100644 --- a/parsers/v1.nix +++ b/parsers/v1.nix @@ -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: { diff --git a/tests/acls/double-dev-ring.nix b/tests/acls/double-dev-ring.nix new file mode 100644 index 0000000..5c535a2 --- /dev/null +++ b/tests/acls/double-dev-ring.nix @@ -0,0 +1,104 @@ +{ + 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";}]; + } + { + a = [{type= "peer"; rule = "is"; value = "peer2";}]; + b = [{type= "peer"; rule = "is"; value = "peer3";}]; + } + { + a = [{type= "peer"; rule = "is"; value = "peer3";}]; + b = [{type= "peer"; rule = "is"; value = "peer4";}]; + } + { + a = [{type= "peer"; rule = "is"; value = "peer4";}]; + b = [{type= "peer"; rule = "is"; value = "peer1";}]; + } + ]; +} \ No newline at end of file diff --git a/tests/double-dev-ring.nix b/tests/double-dev-ring.nix new file mode 100644 index 0000000..596910e --- /dev/null +++ b/tests/double-dev-ring.nix @@ -0,0 +1,86 @@ +/* + * 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; + 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; + 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") + ''; +}) \ No newline at end of file diff --git a/tests/lib.nix b/tests/lib.nix index fd601e4..5e825a3 100644 --- a/tests/lib.nix +++ b/tests/lib.nix @@ -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 diff --git a/tests/manual-ipv4.nix b/tests/manual-ipv4.nix index a2241bf..d706a37 100644 --- a/tests/manual-ipv4.nix +++ b/tests/manual-ipv4.nix @@ -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") ''; -} \ No newline at end of file +}) \ No newline at end of file diff --git a/tests/manual-ipv6-auto.nix b/tests/manual-ipv6-auto.nix index 896f945..4b6af4a 100644 --- a/tests/manual-ipv6-auto.nix +++ b/tests/manual-ipv6-auto.nix @@ -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") ''; -} \ No newline at end of file +}) \ No newline at end of file diff --git a/tests/manual-ipv6.nix b/tests/manual-ipv6.nix index 45ea048..de688fe 100644 --- a/tests/manual-ipv6.nix +++ b/tests/manual-ipv6.nix @@ -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") ''; -} \ No newline at end of file +}) \ No newline at end of file diff --git a/tests/mesh.nix b/tests/mesh.nix index bf5e5ef..32535a0 100644 --- a/tests/mesh.nix +++ b/tests/mesh.nix @@ -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 = { @@ -94,11 +94,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") ''; -} \ No newline at end of file +}) \ No newline at end of file diff --git a/tests/null.nix b/tests/null.nix index cb75d7f..297d188 100644 --- a/tests/null.nix +++ b/tests/null.nix @@ -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'" ''; -} \ No newline at end of file +}) \ No newline at end of file diff --git a/tests/ring.nix b/tests/ring.nix index 6765342..826f3d5 100644 --- a/tests/ring.nix +++ b/tests/ring.nix @@ -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") ''; -} \ No newline at end of file +}) \ No newline at end of file diff --git a/tests/simple.nix b/tests/simple.nix index 687d909..1cd94b5 100644 --- a/tests/simple.nix +++ b/tests/simple.nix @@ -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") ''; -} \ No newline at end of file +}) \ No newline at end of file diff --git a/wire.nix b/wire.nix index 1143d74..8c0a58e 100644 --- a/wire.nix +++ b/wire.nix @@ -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) + )) + ) + )); } \ No newline at end of file