diff --git a/configurers/networkd.nix b/configurers/networkd.nix index 216d1b7..c7a42cb 100644 --- a/configurers/networkd.nix +++ b/configurers/networkd.nix @@ -19,8 +19,8 @@ let in with getKeyProviderFuncs keyProviders inputs intermediateConfig localPeerName; { - networking.extraHosts = concatStringsSep "\n" (concatLists ( concatLists (forEachAttrToList thisPeer.subnetConnections (subnetName: subnetConnection: - forEachAttrToList subnetConnection.peerConnections (remotePeerName: peerConnection: forEach peerConnection.ipAddresses (ip: "${cidr2ip ip} ${remotePeerName}.${subnetName}")) + 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-${shortName subnetName}" { @@ -30,14 +30,13 @@ with getKeyProviderFuncs keyProviders inputs intermediateConfig localPeerName; }; wireguardConfig = { ListenPort = subnetConnection.listenPort; - # *PLEASE* do not use getPrivKeyfor anything but testing PrivateKeyFile = getPrivKeyFile; }; wireguardPeers = forEachAttrToList subnetConnection.peerConnections (remotePeerName: peerConnection: { wireguardPeerConfig = { Endpoint = "${peerConnection.endpoint.ip}:${builtins.toString peerConnection.endpoint.port}"; PublicKey = getPeerPubKey remotePeerName; - AllowedIPs = map (ip: cidr2ip ip + (if match ".*:.*" ip != null then "/128" else "/32")) peerConnection.ipAddresses; + AllowedIPs = map (ip: asCidr ip) peerConnection.ipAddresses; PresharedKeyFile = getSubnetPSKFile subnetName; }; } @@ -48,7 +47,7 @@ with getKeyProviderFuncs keyProviders inputs intermediateConfig localPeerName; }); networks = forEachAttr' thisPeer.subnetConnections (subnetName: subnetConnection: nameValuePair "50-${shortName subnetName}" { matchConfig.Name = "${shortName subnetName}"; - address = subnetConnection.ipAddresses; + address = map (address: (asCidr' "64" "24" address)) subnetConnection.ipAddresses; }); }; } // getProviderConfig \ No newline at end of file diff --git a/configurers/static.nix b/configurers/static.nix index afc659a..af7797e 100644 --- a/configurers/static.nix +++ b/configurers/static.nix @@ -18,13 +18,13 @@ let in with getKeyProviderFuncs keyProviders inputs intermediateConfig localPeerName; { - networking.extraHosts = concatStringsSep "\n" (concatLists ( concatLists (forEachAttrToList thisPeer.subnetConnections (subnetName: subnetConnection: - forEachAttrToList subnetConnection.peerConnections (remotePeerName: peerConnection: forEach peerConnection.ipAddresses (ip: "${cidr2ip ip} ${remotePeerName}.${subnetName}")) + networking.hosts = foldl' (mergeAttrs) {} (concatLists ( concatLists (forEachAttrToList thisPeer.subnetConnections (subnetName: subnetConnection: + 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)}" { - ips = subnetConnection.ipAddresses; + ips = map (address: (asCidr' "64" "24" address)) subnetConnection.ipAddresses; listenPort = subnetConnection.listenPort; privateKeyFile = getPrivKeyFile; peers = forEachAttrToList subnetConnection.peerConnections (remotePeerName: peerConnection: @@ -32,7 +32,7 @@ with getKeyProviderFuncs keyProviders inputs intermediateConfig localPeerName; name = remotePeerName; publicKey = getPeerPubKey remotePeerName; presharedKeyFile = getSubnetPSKFile subnetName; - allowedIPs = map ( ip: cidr2ip ip + (if match ".*:.*" ip != null then "/128" else "/32")) peerConnection.ipAddresses; + allowedIPs = map ( ip: asCidr ip) peerConnection.ipAddresses; endpoint = "${peerConnection.endpoint.ip}:${builtins.toString peerConnection.endpoint.port}"; } // (mergeIf peerConnection.endpoint "persistentKeepalive") diff --git a/flake.nix b/flake.nix index 6ed6050..45f32f6 100644 --- a/flake.nix +++ b/flake.nix @@ -26,6 +26,9 @@ simple = import ./tests/simple.nix checkArgs; mesh = import ./tests/mesh.nix checkArgs; ring = import ./tests/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/lib.nix b/lib.nix index 5834391..24fd9d5 100644 --- a/lib.nix +++ b/lib.nix @@ -85,7 +85,10 @@ rec { generateIPv6Subnet = subnetName: (addColonsToIPv6 (generateIPv6Prefix subnetName)) + "::/64"; /** generates a full IPv6 address */ - generateIPv6Address = subnetName: peerName: (addColonsToIPv6 ((generateIPv6Prefix subnetName) + (generateIPv6Suffix peerName))) + "/64"; + generateIPv6Address = subnetName: peerName: (addColonsToIPv6 ((generateIPv6Prefix subnetName) + (generateIPv6Suffix peerName))); + + /** generates a full IPv6 address with cidr */ + generateIPv6Cidr = subnetName: peerName: (addColonsToIPv6 ((generateIPv6Prefix subnetName) + (generateIPv6Suffix peerName))) + "/64"; /** * makes the intermediate config non-recursive, so it can be pretty printed and @@ -127,5 +130,9 @@ rec { }; mergeIf = attr: key: if builtins.hasAttr key attr then {"${key}" = attr."${key}";} else {}; - cidr2ip = cidr: head (filter (item: item != []) (split "/" cidr)); + asIp = cidr: head (filter (item: item != []) (split "/" cidr)); + isIpv6 = ip: match ".*:.*" ip != null; + isCidr = cidr: 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/tests/acls/manual-ipv4.nix b/tests/acls/manual-ipv4.nix new file mode 100644 index 0000000..aaf602b --- /dev/null +++ b/tests/acls/manual-ipv4.nix @@ -0,0 +1,65 @@ +{ + version = "v1"; + subnets = [ + { + name = "manual"; + endpoints = [ + { + # No match mean match any + port = 51820; + } + ]; + } + ]; + groups = [ + # groups field is expected, but can be empty + ]; + peers = [ + { + name = "node1"; + subnets = { + manual = { + ipAddresses = [ + "10.0.0.1" + ]; + listenPort = 51820; + }; + }; + publicKey = "kdyzqV8cBQtDYeW6R1vUug0Oe+KaytHHDS7JoCp/kTE="; + privateKeyFile = "/etc/wg-key"; + #privateKey = "MIELhEc0I7BseAanhk/+LlY/+Yf7GK232vKWITExnEI="; # path is relative to the machine + endpoints = [ + { + # no match can be any + ip = "node1"; + } + ]; + } + { + name = "node2"; + subnets = { + manual = { + ipAddresses = [ + "10.0.0.2" + ]; + listenPort = 51820; + }; + }; + publicKey = "ztdAXTspQEZUNpxUbUdAhhRWbiL3YYWKSK0ZGdcsMHE="; + privateKeyFile = "/etc/wg-key"; + #privateKey = "yG4mJiduoAvzhUJMslRbZwOp1gowSfC+wgY8B/Mul1M="; + endpoints = [ + { + # no match can be any + ip = "node2"; + } + ]; + } + ]; + connections = [ + { + a = [{type= "subnet"; rule = "is"; value = "manual";}]; + b = [{type= "subnet"; rule = "is"; value = "manual";}]; + } + ]; +} \ No newline at end of file diff --git a/tests/acls/manual-ipv6-auto.nix b/tests/acls/manual-ipv6-auto.nix new file mode 100644 index 0000000..fac873b --- /dev/null +++ b/tests/acls/manual-ipv6-auto.nix @@ -0,0 +1,63 @@ +{ + version = "v1"; + subnets = [ + { + name = "manual"; + endpoints = [ + { + # No match mean match any + port = 51820; + } + ]; + } + ]; + groups = [ + # groups field is expected, but can be empty + ]; + peers = [ + { + name = "node1"; + subnets = { + manual = { + ipAddresses = [ + "auto" # "auto" explicitly generates an ipv6 address, opposed to implicitly via not having an `ipAddresses` property + ]; + listenPort = 51820; + }; + }; + publicKey = "kdyzqV8cBQtDYeW6R1vUug0Oe+KaytHHDS7JoCp/kTE="; + privateKeyFile = "/etc/wg-key"; + endpoints = [ + { + # no match can be any + ip = "node1"; + } + ]; + } + { + name = "node2"; + subnets = { + manual = { + ipAddresses = [ + "auto" + ]; + listenPort = 51820; + }; + }; + publicKey = "ztdAXTspQEZUNpxUbUdAhhRWbiL3YYWKSK0ZGdcsMHE="; + privateKeyFile = "/etc/wg-key"; + endpoints = [ + { + # no match can be any + ip = "node2"; + } + ]; + } + ]; + connections = [ + { + a = [{type= "subnet"; rule = "is"; value = "manual";}]; + b = [{type= "subnet"; rule = "is"; value = "manual";}]; + } + ]; +} \ No newline at end of file diff --git a/tests/acls/manual-ipv6.nix b/tests/acls/manual-ipv6.nix new file mode 100644 index 0000000..7c2e556 --- /dev/null +++ b/tests/acls/manual-ipv6.nix @@ -0,0 +1,63 @@ +{ + version = "v1"; + subnets = [ + { + name = "manual"; + endpoints = [ + { + # No match mean match any + port = 51820; + } + ]; + } + ]; + groups = [ + # groups field is expected, but can be empty + ]; + peers = [ + { + name = "node1"; + subnets = { + manual = { + ipAddresses = [ + "fc00::1" + ]; + listenPort = 51820; + }; + }; + publicKey = "kdyzqV8cBQtDYeW6R1vUug0Oe+KaytHHDS7JoCp/kTE="; + privateKeyFile = "/etc/wg-key"; + endpoints = [ + { + # no match can be any + ip = "node1"; + } + ]; + } + { + name = "node2"; + subnets = { + manual = { + ipAddresses = [ + "fc00::2" + ]; + listenPort = 51820; + }; + }; + publicKey = "ztdAXTspQEZUNpxUbUdAhhRWbiL3YYWKSK0ZGdcsMHE="; + privateKeyFile = "/etc/wg-key"; + endpoints = [ + { + # no match can be any + ip = "node2"; + } + ]; + } + ]; + connections = [ + { + a = [{type= "subnet"; rule = "is"; value = "manual";}]; + b = [{type= "subnet"; rule = "is"; value = "manual";}]; + } + ]; +} \ No newline at end of file diff --git a/tests/acls/simple.nix b/tests/acls/simple.nix index 0436bbf..90db8a6 100644 --- a/tests/acls/simple.nix +++ b/tests/acls/simple.nix @@ -25,7 +25,6 @@ }; publicKey = "kdyzqV8cBQtDYeW6R1vUug0Oe+KaytHHDS7JoCp/kTE="; privateKeyFile = "/etc/wg-key"; - #privateKey = "MIELhEc0I7BseAanhk/+LlY/+Yf7GK232vKWITExnEI="; # path is relative to the machine endpoints = [ { # no match can be any @@ -42,7 +41,6 @@ }; publicKey = "ztdAXTspQEZUNpxUbUdAhhRWbiL3YYWKSK0ZGdcsMHE="; privateKeyFile = "/etc/wg-key"; - #privateKey = "yG4mJiduoAvzhUJMslRbZwOp1gowSfC+wgY8B/Mul1M="; endpoints = [ { # no match can be any diff --git a/tests/manual-ipv4.nix b/tests/manual-ipv4.nix new file mode 100644 index 0000000..a2241bf --- /dev/null +++ b/tests/manual-ipv4.nix @@ -0,0 +1,53 @@ +/* + * 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) +{ + name = "manual ipv4 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/manual-ipv4.nix; + }; + 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/manual-ipv4.nix; + }; + environment.etc."wg-key" = { + text = "yG4mJiduoAvzhUJMslRbZwOp1gowSfC+wgY8B/Mul1M="; + }; + 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-manual-peer-node2") + node2.wait_for_unit("wireguard-manual-peer-node1") + node1.succeed("ping -c 1 node2 >&2") + node1.succeed("wg show >&2") + node2.succeed("ping -c 1 node1 >&2") + node2.succeed("wg show >&2") + node1.succeed("ping -c 1 node2.manual") + node2.succeed("ping -c 1 node1.manual") + ''; +} \ No newline at end of file diff --git a/tests/manual-ipv6-auto.nix b/tests/manual-ipv6-auto.nix new file mode 100644 index 0000000..896f945 --- /dev/null +++ b/tests/manual-ipv6-auto.nix @@ -0,0 +1,53 @@ +/* + * 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) +{ + name = "explicit auto ipv6 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/manual-ipv6-auto.nix; + }; + 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/manual-ipv6-auto.nix; + }; + environment.etc."wg-key" = { + text = "yG4mJiduoAvzhUJMslRbZwOp1gowSfC+wgY8B/Mul1M="; + }; + 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-manual-peer-node2") + node2.wait_for_unit("wireguard-manual-peer-node1") + node1.succeed("ping -c 1 node2 >&2") + node1.succeed("wg show >&2") + node2.succeed("ping -c 1 node1 >&2") + node2.succeed("wg show >&2") + node1.succeed("ping -c 1 node2.manual") + node2.succeed("ping -c 1 node1.manual") + ''; +} \ No newline at end of file diff --git a/tests/manual-ipv6.nix b/tests/manual-ipv6.nix new file mode 100644 index 0000000..45ea048 --- /dev/null +++ b/tests/manual-ipv6.nix @@ -0,0 +1,53 @@ +/* + * 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) +{ + name = "manual ipv6 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/manual-ipv6.nix; + }; + 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/manual-ipv6.nix; + }; + environment.etc."wg-key" = { + text = "yG4mJiduoAvzhUJMslRbZwOp1gowSfC+wgY8B/Mul1M="; + }; + 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-manual-peer-node2") + node2.wait_for_unit("wireguard-manual-peer-node1") + node1.succeed("ping -c 1 node2 >&2") + node1.succeed("wg show >&2") + node2.succeed("ping -c 1 node1 >&2") + node2.succeed("wg show >&2") + node1.succeed("ping -c 1 node2.manual") + node2.succeed("ping -c 1 node1.manual") + ''; +} \ No newline at end of file diff --git a/tests/mesh.nix b/tests/mesh.nix index 41fccb5..bf5e5ef 100644 --- a/tests/mesh.nix +++ b/tests/mesh.nix @@ -90,10 +90,13 @@ if local_name == "node1" or local_name == "node2": for remote_node in set(nodes.keys()) - set([local_name]): local_node.wait_for_unit(f"wireguard-mesh-peer-{remote_node}") + node1.wait_for_unit("wireguard-mesh.target") + 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 show >&2") + 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") diff --git a/tests/ring.nix b/tests/ring.nix index afaf4a2..6765342 100644 --- a/tests/ring.nix +++ b/tests/ring.nix @@ -83,8 +83,12 @@ for local_name, local_node in nodes.items(): for remote_name in connections[local_name]: local_node.wait_for_unit(f"wireguard-ring-peer-{remote_name}") + node1.wait_for_unit("wireguard-ring.target") + node2.wait_for_unit("wireguard-ring.target") + node3.wait_for_unit("wireguard-ring.target") + node4.wait_for_unit("wireguard-ring.target") for local_name, local_node in nodes.items(): - local_node.succeed("wg show >&2") + local_node.succeed("wg showconf ring >&2") for remote_name in set(nodes.keys()) - set([local_name]): local_node.succeed(f"ping -c 1 {remote_name} >&2") if remote_name in connections[local_name]: diff --git a/tests/simple.nix b/tests/simple.nix index 6ede5f3..687d909 100644 --- a/tests/simple.nix +++ b/tests/simple.nix @@ -17,6 +17,7 @@ peerName = "node1"; aclConfig = import ./acls/simple.nix; }; + # Don't do this! This is for testing only! environment.etc."wg-key" = { text = "MIELhEc0I7BseAanhk/+LlY/+Yf7GK232vKWITExnEI="; };