From 414952dfa341ce2a1c0b3d119e63fb2bb0ca2fe8 Mon Sep 17 00:00:00 2001 From: pinpox Date: Fri, 18 Apr 2025 13:09:48 +0200 Subject: [PATCH] Add wireguard service module --- checks/flake-module.nix | 1 + checks/wireguard/default.nix | 115 +++++ .../sops/machines/controller1/key.json | 6 + .../sops/machines/controller2/key.json | 6 + checks/wireguard/sops/machines/peer1/key.json | 6 + checks/wireguard/sops/machines/peer2/key.json | 6 + checks/wireguard/sops/machines/peer3/key.json | 6 + .../sops/secrets/controller1-age.key/secret | 15 + .../secrets/controller1-age.key/users/admin | 1 + .../sops/secrets/controller2-age.key/secret | 15 + .../secrets/controller2-age.key/users/admin | 1 + .../sops/secrets/peer1-age.key/secret | 15 + .../sops/secrets/peer1-age.key/users/admin | 1 + .../sops/secrets/peer2-age.key/secret | 15 + .../sops/secrets/peer2-age.key/users/admin | 1 + .../sops/secrets/peer3-age.key/secret | 15 + .../sops/secrets/peer3-age.key/users/admin | 1 + checks/wireguard/sops/users/admin/key.json | 4 + .../privatekey/machines/controller1 | 1 + .../privatekey/secret | 19 + .../privatekey/users/admin | 1 + .../publickey/value | 1 + .../.validation-hash | 1 + .../prefix/value | 1 + .../privatekey/machines/controller2 | 1 + .../privatekey/secret | 19 + .../privatekey/users/admin | 1 + .../publickey/value | 1 + .../.validation-hash | 1 + .../prefix/value | 1 + .../privatekey/machines/peer1 | 1 + .../privatekey/secret | 19 + .../privatekey/users/admin | 1 + .../publickey/value | 1 + .../.validation-hash | 1 + .../suffix/value | 1 + .../privatekey/machines/peer2 | 1 + .../privatekey/secret | 19 + .../privatekey/users/admin | 1 + .../publickey/value | 1 + .../.validation-hash | 1 + .../suffix/value | 1 + .../privatekey/machines/peer3 | 1 + .../privatekey/secret | 19 + .../privatekey/users/admin | 1 + .../publickey/value | 1 + .../.validation-hash | 1 + .../suffix/value | 1 + clanServices/wireguard/README.md | 217 +++++++++ clanServices/wireguard/default.nix | 456 ++++++++++++++++++ clanServices/wireguard/flake-module.nix | 7 + clanServices/wireguard/ipv6_allocator.py | 135 ++++++ docs/mkdocs.yml | 2 +- 53 files changed, 1166 insertions(+), 1 deletion(-) create mode 100644 checks/wireguard/default.nix create mode 100755 checks/wireguard/sops/machines/controller1/key.json create mode 100755 checks/wireguard/sops/machines/controller2/key.json create mode 100755 checks/wireguard/sops/machines/peer1/key.json create mode 100755 checks/wireguard/sops/machines/peer2/key.json create mode 100755 checks/wireguard/sops/machines/peer3/key.json create mode 100644 checks/wireguard/sops/secrets/controller1-age.key/secret create mode 120000 checks/wireguard/sops/secrets/controller1-age.key/users/admin create mode 100644 checks/wireguard/sops/secrets/controller2-age.key/secret create mode 120000 checks/wireguard/sops/secrets/controller2-age.key/users/admin create mode 100644 checks/wireguard/sops/secrets/peer1-age.key/secret create mode 120000 checks/wireguard/sops/secrets/peer1-age.key/users/admin create mode 100644 checks/wireguard/sops/secrets/peer2-age.key/secret create mode 120000 checks/wireguard/sops/secrets/peer2-age.key/users/admin create mode 100644 checks/wireguard/sops/secrets/peer3-age.key/secret create mode 120000 checks/wireguard/sops/secrets/peer3-age.key/users/admin create mode 100644 checks/wireguard/sops/users/admin/key.json create mode 120000 checks/wireguard/vars/per-machine/controller1/wireguard-keys-wg-test-one/privatekey/machines/controller1 create mode 100644 checks/wireguard/vars/per-machine/controller1/wireguard-keys-wg-test-one/privatekey/secret create mode 120000 checks/wireguard/vars/per-machine/controller1/wireguard-keys-wg-test-one/privatekey/users/admin create mode 100644 checks/wireguard/vars/per-machine/controller1/wireguard-keys-wg-test-one/publickey/value create mode 100644 checks/wireguard/vars/per-machine/controller1/wireguard-network-wg-test-one/.validation-hash create mode 100644 checks/wireguard/vars/per-machine/controller1/wireguard-network-wg-test-one/prefix/value create mode 120000 checks/wireguard/vars/per-machine/controller2/wireguard-keys-wg-test-one/privatekey/machines/controller2 create mode 100644 checks/wireguard/vars/per-machine/controller2/wireguard-keys-wg-test-one/privatekey/secret create mode 120000 checks/wireguard/vars/per-machine/controller2/wireguard-keys-wg-test-one/privatekey/users/admin create mode 100644 checks/wireguard/vars/per-machine/controller2/wireguard-keys-wg-test-one/publickey/value create mode 100644 checks/wireguard/vars/per-machine/controller2/wireguard-network-wg-test-one/.validation-hash create mode 100644 checks/wireguard/vars/per-machine/controller2/wireguard-network-wg-test-one/prefix/value create mode 120000 checks/wireguard/vars/per-machine/peer1/wireguard-keys-wg-test-one/privatekey/machines/peer1 create mode 100644 checks/wireguard/vars/per-machine/peer1/wireguard-keys-wg-test-one/privatekey/secret create mode 120000 checks/wireguard/vars/per-machine/peer1/wireguard-keys-wg-test-one/privatekey/users/admin create mode 100644 checks/wireguard/vars/per-machine/peer1/wireguard-keys-wg-test-one/publickey/value create mode 100644 checks/wireguard/vars/per-machine/peer1/wireguard-network-wg-test-one/.validation-hash create mode 100644 checks/wireguard/vars/per-machine/peer1/wireguard-network-wg-test-one/suffix/value create mode 120000 checks/wireguard/vars/per-machine/peer2/wireguard-keys-wg-test-one/privatekey/machines/peer2 create mode 100644 checks/wireguard/vars/per-machine/peer2/wireguard-keys-wg-test-one/privatekey/secret create mode 120000 checks/wireguard/vars/per-machine/peer2/wireguard-keys-wg-test-one/privatekey/users/admin create mode 100644 checks/wireguard/vars/per-machine/peer2/wireguard-keys-wg-test-one/publickey/value create mode 100644 checks/wireguard/vars/per-machine/peer2/wireguard-network-wg-test-one/.validation-hash create mode 100644 checks/wireguard/vars/per-machine/peer2/wireguard-network-wg-test-one/suffix/value create mode 120000 checks/wireguard/vars/per-machine/peer3/wireguard-keys-wg-test-one/privatekey/machines/peer3 create mode 100644 checks/wireguard/vars/per-machine/peer3/wireguard-keys-wg-test-one/privatekey/secret create mode 120000 checks/wireguard/vars/per-machine/peer3/wireguard-keys-wg-test-one/privatekey/users/admin create mode 100644 checks/wireguard/vars/per-machine/peer3/wireguard-keys-wg-test-one/publickey/value create mode 100644 checks/wireguard/vars/per-machine/peer3/wireguard-network-wg-test-one/.validation-hash create mode 100644 checks/wireguard/vars/per-machine/peer3/wireguard-network-wg-test-one/suffix/value create mode 100644 clanServices/wireguard/README.md create mode 100644 clanServices/wireguard/default.nix create mode 100644 clanServices/wireguard/flake-module.nix create mode 100755 clanServices/wireguard/ipv6_allocator.py diff --git a/checks/flake-module.nix b/checks/flake-module.nix index 69c43aadd..64d6c9a92 100644 --- a/checks/flake-module.nix +++ b/checks/flake-module.nix @@ -104,6 +104,7 @@ in nixos-test-user-firewall-nftables = self.clanLib.test.containerTest ./user-firewall/nftables.nix nixosTestArgs; service-dummy-test = import ./service-dummy-test nixosTestArgs; + wireguard = import ./wireguard nixosTestArgs; service-dummy-test-from-flake = import ./service-dummy-test-from-flake nixosTestArgs; }; diff --git a/checks/wireguard/default.nix b/checks/wireguard/default.nix new file mode 100644 index 000000000..bc01cab62 --- /dev/null +++ b/checks/wireguard/default.nix @@ -0,0 +1,115 @@ +{ + pkgs, + nixosLib, + clan-core, + lib, + ... +}: +nixosLib.runTest ( + { ... }: + + let + machines = [ + "controller1" + "controller2" + "peer1" + "peer2" + "peer3" + ]; + in + { + imports = [ + clan-core.modules.nixosTest.clanTest + ]; + + hostPkgs = pkgs; + + name = "wireguard"; + + clan = { + directory = ./.; + modules."@clan/wireguard" = import ../../clanServices/wireguard/default.nix; + inventory = { + + machines = lib.genAttrs machines (_: { }); + + instances = { + + /* + wg-test-one + ┌───────────────────────────────┐ + │ ◄───────────── │ + │ controller2 controller1 + │ ▲ ─────────────► ▲ ▲ + │ │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ │ + │ │ │ │ └───────────────┐ │ │ │ │ + │ │ │ └──────────────┐ │ │ │ │ │ + │ ▼ │ ▼ ▼ ▼ + └─► peer2 │ peer1 peer3 + │ ▲ + └──────────┘ + */ + + wg-test-one = { + + module.name = "@clan/wireguard"; + module.input = "self"; + + roles.controller.machines."controller1".settings = { + endpoint = "192.168.1.1"; + }; + + roles.controller.machines."controller2".settings = { + endpoint = "192.168.1.2"; + }; + + roles.peer.machines = { + peer1.settings.controller = "controller1"; + peer2.settings.controller = "controller2"; + peer3.settings.controller = "controller1"; + }; + }; + + # TODO: Will this actually work with conflicting ports? Can we re-use interfaces? + #wg-test-two = { + # module.name = "@clan/wireguard"; + + # roles.controller.machines."controller1".settings = { + # endpoint = "192.168.1.1"; + # port = 51922; + # }; + + # roles.peer.machines = { + # peer1 = { }; + # }; + #}; + }; + }; + }; + + testScript = '' + start_all() + + # Show all addresses + machines = [peer1, peer2, peer3, controller1, controller2] + for m in machines: + m.systemctl("start network-online.target") + + for m in machines: + m.wait_for_unit("network-online.target") + m.wait_for_unit("systemd-networkd.service") + + print("\n\n" + "="*60) + print("STARTING PING TESTS") + print("="*60) + + for m1 in machines: + for m2 in machines: + if m1 != m2: + print(f"\n--- Pinging from {m1.name} to {m2.name}.wg-test-one ---") + m1.wait_until_succeeds(f"ping -c1 {m2.name}.wg-test-one >&2") + ''; + } +) diff --git a/checks/wireguard/sops/machines/controller1/key.json b/checks/wireguard/sops/machines/controller1/key.json new file mode 100755 index 000000000..6e5be0e5a --- /dev/null +++ b/checks/wireguard/sops/machines/controller1/key.json @@ -0,0 +1,6 @@ +[ + { + "publickey": "age1rnkc2vmrupy9234clyu7fpur5kephuqs3v7qauaw5zeg00jqjdasefn3cc", + "type": "age" + } +] diff --git a/checks/wireguard/sops/machines/controller2/key.json b/checks/wireguard/sops/machines/controller2/key.json new file mode 100755 index 000000000..d298a0dde --- /dev/null +++ b/checks/wireguard/sops/machines/controller2/key.json @@ -0,0 +1,6 @@ +[ + { + "publickey": "age1t2hhg99d4p2yymuhngcy5ccutp8mvu7qwvg5cdhck303h9e7ha9qnlt635", + "type": "age" + } +] diff --git a/checks/wireguard/sops/machines/peer1/key.json b/checks/wireguard/sops/machines/peer1/key.json new file mode 100755 index 000000000..836e0b426 --- /dev/null +++ b/checks/wireguard/sops/machines/peer1/key.json @@ -0,0 +1,6 @@ +[ + { + "publickey": "age1jts52rzlqcwjc36jkp56a7fmjn3czr7kl9ta2spkfzhvfama33sqacrzzd", + "type": "age" + } +] diff --git a/checks/wireguard/sops/machines/peer2/key.json b/checks/wireguard/sops/machines/peer2/key.json new file mode 100755 index 000000000..0f6f90866 --- /dev/null +++ b/checks/wireguard/sops/machines/peer2/key.json @@ -0,0 +1,6 @@ +[ + { + "publickey": "age12nqnp0zd435ckp5p0v2fv4p2x4cvur2mnxe8use2sx3fgy883vaq4ae75e", + "type": "age" + } +] diff --git a/checks/wireguard/sops/machines/peer3/key.json b/checks/wireguard/sops/machines/peer3/key.json new file mode 100755 index 000000000..1c6e06cc1 --- /dev/null +++ b/checks/wireguard/sops/machines/peer3/key.json @@ -0,0 +1,6 @@ +[ + { + "publickey": "age1sglr4zp34drjfydzeweq43fz3uwpul3hkh53lsfa9drhuzwmkqyqn5jegp", + "type": "age" + } +] diff --git a/checks/wireguard/sops/secrets/controller1-age.key/secret b/checks/wireguard/sops/secrets/controller1-age.key/secret new file mode 100644 index 000000000..0534759ea --- /dev/null +++ b/checks/wireguard/sops/secrets/controller1-age.key/secret @@ -0,0 +1,15 @@ +{ + "data": "ENC[AES256_GCM,data:zDF0RiBqaawpg+GaFkuLPomJ01Xu+lgY5JfUzaIk2j03XkCzIf8EMrmn6pRtBP3iUjPBm+gQSTQk6GHTONrixA5hRNyETV+UgQw=,iv:zUUCAGZ0cz4Tc2t/HOjVYNsdnrAOtid/Ns5ak7rnyCk=,tag:z43WtNSue4Ddf7AVu21IKA==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBlY1NEdjAzQm5RMFZWY3BJ\nclp6c01FdlZFK3dOSDB4cHc1NTdwMXErMFJFCnIrRVFNZEFYOG1rVUhFd2xsbTJ2\nVkJHNmdOWXlOcHJoQ0QzM1VyZmxmcGcKLS0tIFk1cEx4dFdvNGRwK1FWdDZsb1lR\nV2d1RFZtNzZqVFdtQ1FzNStEcEgyUUkKx8tkxqJz/Ko3xgvhvd6IYiV/lRGmrY13\nUZpYWR9tsQwZAR9dLjCyVU3JRuXeGB1unXC1CO0Ff3R0A/PuuRHh+g==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2025-08-13T09:19:37Z", + "mac": "ENC[AES256_GCM,data:8RGOUhZ2LGmC9ugULwHDgdMrtdo9vzBm3BJmL4XTuNJKm0NlKfgNLi1E4n9DMQ+kD4hKvcwbiUcwSGE8jZD6sm7Sh3bJi/HZCoiWm/O/OIzstli2NNDBGvQBgyWZA5H+kDjZ6aEi6icNWIlm5gsty7KduABnf5B3p0Bn5Uf5Bio=,iv:sGZp0XF+mgocVzAfHF8ATdlSE/5zyz5WUSRMJqNeDQs=,tag:ymYVBRwF5BOSAu5ONU2qKw==,type:str]", + "unencrypted_suffix": "_unencrypted", + "version": "3.10.2" + } +} diff --git a/checks/wireguard/sops/secrets/controller1-age.key/users/admin b/checks/wireguard/sops/secrets/controller1-age.key/users/admin new file mode 120000 index 000000000..9e21a9938 --- /dev/null +++ b/checks/wireguard/sops/secrets/controller1-age.key/users/admin @@ -0,0 +1 @@ +../../../users/admin \ No newline at end of file diff --git a/checks/wireguard/sops/secrets/controller2-age.key/secret b/checks/wireguard/sops/secrets/controller2-age.key/secret new file mode 100644 index 000000000..c86b14363 --- /dev/null +++ b/checks/wireguard/sops/secrets/controller2-age.key/secret @@ -0,0 +1,15 @@ +{ + "data": "ENC[AES256_GCM,data:dHM7zWzqnC1QLRKYpbI2t63kOFnSaQy6ur9zlkLQf17Q03CNrqUsZtdEbwMnLR3llu7eVMhtvVRkXjEkvn3leb9HsNFmtk/DP70=,iv:roEZsBFqRypM106O5sehTzo7SySOJUJgAR738rTtOo8=,tag:VDd9/6uU0SAM7pWRLIUhUQ==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBKTEVYUmVGbUtOcHZ4cnc3\nKzNETnlxaVRKYTI3eWVHdEoyc3l2SnhsZ1J3CnB2RnZrOXM5Uml6TThDUlZjY25J\nbkJ6eUZ2ckN1NWpNUU9IaE93UDJQdlEKLS0tIC95ZDhkU0R1VHhCdldxdW4zSmps\nN3NqL1cvd05hRTRPdDA3R2pzNUFFajgKS+DJH14fH9AvEAa3PoUC1jEqKAzTmExN\nl32FeHTHbGMo1PKeaFm+Eg0WSpAmFE7beBunc5B73SW30ok6x4FcQw==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2025-08-13T09:19:47Z", + "mac": "ENC[AES256_GCM,data:77EnuBQyguvkCtobUg8/6zoLHjmeGDrSBZuIXOZBMxdbJjzhRg++qxQjuu6t0FoWATtz7u4Y3/jzUMGffr/N5HegqSq0D2bhv7AqJwBiVaOwd80fRTtM+YiP/zXsCk52Pj/Gadapg208bDPQ1BBDOyz/DrqZ7w//j+ARJjAnugI=,iv:IuTDmJKZEuHXJXjxrBw0gP2t6vpxAYEqbtpnVbavVCY=,tag:4EnpX6rOamtg1O+AaEQahQ==,type:str]", + "unencrypted_suffix": "_unencrypted", + "version": "3.10.2" + } +} diff --git a/checks/wireguard/sops/secrets/controller2-age.key/users/admin b/checks/wireguard/sops/secrets/controller2-age.key/users/admin new file mode 120000 index 000000000..9e21a9938 --- /dev/null +++ b/checks/wireguard/sops/secrets/controller2-age.key/users/admin @@ -0,0 +1 @@ +../../../users/admin \ No newline at end of file diff --git a/checks/wireguard/sops/secrets/peer1-age.key/secret b/checks/wireguard/sops/secrets/peer1-age.key/secret new file mode 100644 index 000000000..e42782a7f --- /dev/null +++ b/checks/wireguard/sops/secrets/peer1-age.key/secret @@ -0,0 +1,15 @@ +{ + "data": "ENC[AES256_GCM,data:wcSsqxTKiMAnzPwxs5DNjcSdLyjVQ9UOrZxfSbOkVfniwx6F7xz6dLNhaDq7MHQ0vRWpg28yNs7NHrp52bYFnb/+eZsis46WiCw=,iv:B4t1lvS2gC601MtsmZfEiEulLWvSGei3/LSajwFS9Vs=,tag:hnRXlZyYEFfLJUrw1SqbSQ==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAybUgya2VEdzMvRG1hdkpu\nM2pGNmcyVmcvYVZ1ZjJlY3A1bXFUUUtkMTI0CmJoRFZmejZjN2UxUXNuc1k5WnE2\nNmxIcnpNQ1lJZ3ZKSmhtSlVURXJTSUUKLS0tIGU4Wi9yZ3VYekJkVW9pNWFHblFk\na0gzbTVKUWdSam1sVjRUaUlTdVd5YWMKntRc9yb9VPOTMibp8QM5m57DilP01N/X\nPTQaw8oI40znnHdctTZz7S+W/3Te6sRnkOhFyalWmsKY0CWg/FELlA==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2025-08-13T09:19:58Z", + "mac": "ENC[AES256_GCM,data:8nq+ugkUJxE24lUIySySs/cAF8vnfqr936L/5F0O1QFwNrbpPmKRXkuwa6u0V+187L2952Id20Fym4ke59f3fJJsF840NCKDwDDZhBZ20q9GfOqIKImEom/Nzw6D0WXQLUT3w8EMyJ/F+UaJxnBNPR6f6+Kx4YgStYzCcA6Ahzg=,iv:VBPktEz7qwWBBnXE+xOP/EUVy7/AmNCHPoK56Yt/ZNc=,tag:qXONwOLFAlopymBEf5p4Sw==,type:str]", + "unencrypted_suffix": "_unencrypted", + "version": "3.10.2" + } +} diff --git a/checks/wireguard/sops/secrets/peer1-age.key/users/admin b/checks/wireguard/sops/secrets/peer1-age.key/users/admin new file mode 120000 index 000000000..9e21a9938 --- /dev/null +++ b/checks/wireguard/sops/secrets/peer1-age.key/users/admin @@ -0,0 +1 @@ +../../../users/admin \ No newline at end of file diff --git a/checks/wireguard/sops/secrets/peer2-age.key/secret b/checks/wireguard/sops/secrets/peer2-age.key/secret new file mode 100644 index 000000000..c8b3a966f --- /dev/null +++ b/checks/wireguard/sops/secrets/peer2-age.key/secret @@ -0,0 +1,15 @@ +{ + "data": "ENC[AES256_GCM,data:4d3ri0EsDmWRtA8vzvpPRLMsSp4MIMKwvtn0n0pRY05uBPXs3KcjnweMPIeTE1nIhqnMR2o2MfLah5TCPpaFax9+wxIt74uacbg=,iv:0LBAldTC/hN4QLCxgXTl6d9UB8WmUTnj4sD2zHQuG2w=,tag:zr/RhG/AU4g9xj9l2BprKw==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBvV0JnZDhlU1piU1g2cng0\ncytKOEZ6WlZlNGRGUjV3MmVMd2Nzc0ZwelgwCjBGdThCUGlXbVFYdnNoZWpJZ3Vm\nc2xkRXhxS09vdzltSVoxLzhFSVduak0KLS0tIE5DRjJ6cGxiVlB1eElHWXhxN1pJ\nYWtIMDMvb0Z6akJjUzlqeEFsNHkxL2cKpghv/QegnXimeqd9OPFouGM//jYvoVmw\n2d4mLT2JSMkEhpfGcqb6vswhdJfCiKuqr2B4bqwAnPMaykhsm8DFRQ==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2025-08-13T09:20:08Z", + "mac": "ENC[AES256_GCM,data:BzlQVAJ7HzcxNPKB3JhabqRX/uU0EElj172YecjmOflHnzz/s9xgfdAfJK/c53hXlX4LtGPnubH7a8jOolRq98zmZeBYE27+WLs2aN7Ufld6mYk90/i7u4CqR+Fh2Kfht04SlUJCjnS5A9bTPwU9XGRHJ0BiOhzTuSMUJTRaPRM=,iv:L50K5zc1o99Ix9nP0pb9PRH+VIN2yvq7JqKeVHxVXmc=,tag:XFLkSCsdbTPxbasDYYxcFQ==,type:str]", + "unencrypted_suffix": "_unencrypted", + "version": "3.10.2" + } +} diff --git a/checks/wireguard/sops/secrets/peer2-age.key/users/admin b/checks/wireguard/sops/secrets/peer2-age.key/users/admin new file mode 120000 index 000000000..9e21a9938 --- /dev/null +++ b/checks/wireguard/sops/secrets/peer2-age.key/users/admin @@ -0,0 +1 @@ +../../../users/admin \ No newline at end of file diff --git a/checks/wireguard/sops/secrets/peer3-age.key/secret b/checks/wireguard/sops/secrets/peer3-age.key/secret new file mode 100644 index 000000000..7f6fc613e --- /dev/null +++ b/checks/wireguard/sops/secrets/peer3-age.key/secret @@ -0,0 +1,15 @@ +{ + "data": "ENC[AES256_GCM,data:qfLm6+g1vYnESCik9uyBeKsY6Ju2Gq3arnn2I8HHNO67Ri5BWbOQTvtz7WT8/q94RwVjv8SGeJ/fsJSpwLSrJSbqTZCPAnYwzzQ=,iv:PnA9Ao8RRELNhNQYbaorstc0KaIXRU7h3+lgDCXZFHk=,tag:VeLgYQYwqthYihIoQTwYiA==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBNWVVQaDJFd0N3WHptRC9Z\nZTgxTWh5bnU1SkpqRWRXZnhPaFhpSVJmVEhrCjFvdHFYenNWaFNrdXlha09iS2xj\nOTZDcUNkcHkvTDUwNjM4Z3gxUkxreUEKLS0tIE5oY3Q2bWhsb2FSQTVGTWVSclJw\nWllrelRwT3duYjJJbTV0d3FwU1VuNlkK2eN3fHFX/sVUWom8TeZC9fddqnSCsC1+\nJRCZsG46uHDxqLcKIfdFWh++2t16XupQYk3kn+NUR/aMc3fR32Uwjw==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2025-08-13T09:20:18Z", + "mac": "ENC[AES256_GCM,data:nUwsPcP1bsDjAHFjQ1NlVkTwyZY4B+BpzNkMx9gl0rE14j425HVLtlhlLndhRp+XMpnDldQppLAAtSdzMsrw8r5efNgTRl7cu4Fy/b9cHt84k7m0aou5lrGus9SV1bM7/fzC9Xm7CSXBcRzyDGVsKC6UBl1rx+ybh7HyAN05XSo=,iv:It57H+zUUNPkoN1D8sYwyZx5zIFIga7mydhGUHYBCGE=,tag:mBQdYqUpjPknbYa13qESyw==,type:str]", + "unencrypted_suffix": "_unencrypted", + "version": "3.10.2" + } +} diff --git a/checks/wireguard/sops/secrets/peer3-age.key/users/admin b/checks/wireguard/sops/secrets/peer3-age.key/users/admin new file mode 120000 index 000000000..9e21a9938 --- /dev/null +++ b/checks/wireguard/sops/secrets/peer3-age.key/users/admin @@ -0,0 +1 @@ +../../../users/admin \ No newline at end of file diff --git a/checks/wireguard/sops/users/admin/key.json b/checks/wireguard/sops/users/admin/key.json new file mode 100644 index 000000000..e408aa96b --- /dev/null +++ b/checks/wireguard/sops/users/admin/key.json @@ -0,0 +1,4 @@ +{ + "publickey": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg", + "type": "age" +} diff --git a/checks/wireguard/vars/per-machine/controller1/wireguard-keys-wg-test-one/privatekey/machines/controller1 b/checks/wireguard/vars/per-machine/controller1/wireguard-keys-wg-test-one/privatekey/machines/controller1 new file mode 120000 index 000000000..1ff577620 --- /dev/null +++ b/checks/wireguard/vars/per-machine/controller1/wireguard-keys-wg-test-one/privatekey/machines/controller1 @@ -0,0 +1 @@ +../../../../../../sops/machines/controller1 \ No newline at end of file diff --git a/checks/wireguard/vars/per-machine/controller1/wireguard-keys-wg-test-one/privatekey/secret b/checks/wireguard/vars/per-machine/controller1/wireguard-keys-wg-test-one/privatekey/secret new file mode 100644 index 000000000..30dba950b --- /dev/null +++ b/checks/wireguard/vars/per-machine/controller1/wireguard-keys-wg-test-one/privatekey/secret @@ -0,0 +1,19 @@ +{ + "data": "ENC[AES256_GCM,data:noe913+28JWkoDkGGMu++cc1+j5NPDoyIhWixdsowoiVO3cTWGkZ88SUGO5D,iv:ynYMljwqMcBdk8RpVcw/2Jflg2RCF28r4fKUgIAF8B4=,tag:+TsXDJgfUhKgg4iQVXKKlQ==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBhYVRReTZBQ05GYmVBVjhS\nNXM5aFlhVzZRaVl6UHl6S3JnMC9Sb1dwZ1ZjCmVuS2dEVExYZWROVklUZWFCSnM2\nZnlxbVNseTM2c0Q0TjhsT3NzYmtqREUKLS0tIHBRTFpvVGt6d1cxZ2lFclRsUVhZ\nZDlWaG9PcXVrNUZKaEgxWndjUDVpYjgKt0eOhAgcYdkg9JSEakx4FjChLTn3pis+\njOkuGd4JfXMKcwC7vJV5ygQBxzVJSBw+RucP7sYCBPK0m8Voj94ntw==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1rnkc2vmrupy9234clyu7fpur5kephuqs3v7qauaw5zeg00jqjdasefn3cc", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6MFJqNHNraG9DSnJZMFdz\ndU8zVXNTamxROFd1dWtuK2RiekhPdHhleVhFCi8zNWJDNXJMRUlDdjc4Q0UycTIz\nSGFGSmdnNU0wZWlDaTEwTzBqWjh6SFkKLS0tIEJOdjhOMDY2TUFLb3RPczNvMERx\nYkpSeW5VOXZvMlEvdm53MDE3aUFTNjgKyelSTjrTIR9I3rJd3krvzpsrKF1uGs4J\n4MtmQj0/3G+zPYZVBx7b3HF6B3f1Z7LYh05+z7nCnN/duXyPnDjNcg==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2025-08-13T09:19:37Z", + "mac": "ENC[AES256_GCM,data:+DmIkPG/H6tCtf8CvB98E1QFXv08QfTcCB3CRsi+XWnIRBkryRd/Au9JahViHMdK7MED8WNf84NWTjY2yH4y824/DjI8XXNMF1iVMo0CqY42xbVHtUuhXrYeT+c8CyEw+M6zfy1jC0+Bm3WQWgagz1G6A9SZk3D2ycu0N08+axA=,iv:kwBjTYebIy5i2hagAajSwwuKnSkrM9GyrnbeQXB2e/w=,tag:EgKJ5gVGYj1NGFUduxLGfg==,type:str]", + "unencrypted_suffix": "_unencrypted", + "version": "3.10.2" + } +} diff --git a/checks/wireguard/vars/per-machine/controller1/wireguard-keys-wg-test-one/privatekey/users/admin b/checks/wireguard/vars/per-machine/controller1/wireguard-keys-wg-test-one/privatekey/users/admin new file mode 120000 index 000000000..ca714e122 --- /dev/null +++ b/checks/wireguard/vars/per-machine/controller1/wireguard-keys-wg-test-one/privatekey/users/admin @@ -0,0 +1 @@ +../../../../../../sops/users/admin \ No newline at end of file diff --git a/checks/wireguard/vars/per-machine/controller1/wireguard-keys-wg-test-one/publickey/value b/checks/wireguard/vars/per-machine/controller1/wireguard-keys-wg-test-one/publickey/value new file mode 100644 index 000000000..fbab5a790 --- /dev/null +++ b/checks/wireguard/vars/per-machine/controller1/wireguard-keys-wg-test-one/publickey/value @@ -0,0 +1 @@ +lQfR7GhivN87XoXruTGOPjVPhNu1Brt//wyc3pdwE20= diff --git a/checks/wireguard/vars/per-machine/controller1/wireguard-network-wg-test-one/.validation-hash b/checks/wireguard/vars/per-machine/controller1/wireguard-network-wg-test-one/.validation-hash new file mode 100644 index 000000000..ece4cec0f --- /dev/null +++ b/checks/wireguard/vars/per-machine/controller1/wireguard-network-wg-test-one/.validation-hash @@ -0,0 +1 @@ +7470bb5c79df224a9b7f5a2259acd2e46db763c27e24cb3416c8b591cb328077 \ No newline at end of file diff --git a/checks/wireguard/vars/per-machine/controller1/wireguard-network-wg-test-one/prefix/value b/checks/wireguard/vars/per-machine/controller1/wireguard-network-wg-test-one/prefix/value new file mode 100644 index 000000000..339f39241 --- /dev/null +++ b/checks/wireguard/vars/per-machine/controller1/wireguard-network-wg-test-one/prefix/value @@ -0,0 +1 @@ +fd51:19c1:3b:f700 \ No newline at end of file diff --git a/checks/wireguard/vars/per-machine/controller2/wireguard-keys-wg-test-one/privatekey/machines/controller2 b/checks/wireguard/vars/per-machine/controller2/wireguard-keys-wg-test-one/privatekey/machines/controller2 new file mode 120000 index 000000000..0d2ebbf0b --- /dev/null +++ b/checks/wireguard/vars/per-machine/controller2/wireguard-keys-wg-test-one/privatekey/machines/controller2 @@ -0,0 +1 @@ +../../../../../../sops/machines/controller2 \ No newline at end of file diff --git a/checks/wireguard/vars/per-machine/controller2/wireguard-keys-wg-test-one/privatekey/secret b/checks/wireguard/vars/per-machine/controller2/wireguard-keys-wg-test-one/privatekey/secret new file mode 100644 index 000000000..3c476a10a --- /dev/null +++ b/checks/wireguard/vars/per-machine/controller2/wireguard-keys-wg-test-one/privatekey/secret @@ -0,0 +1,19 @@ +{ + "data": "ENC[AES256_GCM,data:2kehACgvNgoYGPwnW7p86BR0yUu689Chth6qZf9zoJtuTY9ATS68dxDyBc5S,iv:qb2iDUtExegTeN3jt6SA8RnU61W5GDDhn56QXiQT4gw=,tag:pSGPICX5p6qlZ1WMVoIEYQ==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBSTTR5TDY4RE9VYmlCK1dL\nWkVRcVZqVDlsbmQvUlJmdzF2b1Z1S0k3NngwCkFWNzRVaERtSmFsd0o2aFJOb0ZX\nSU9yUnVaNi9IUjJWeGRFcEpDUXo5WkEKLS0tIEczNkxiYnJsTWRoLzFhQVF1M21n\nWnZEdGV1N2N5d1FZQkJUQ1IrdGFLblkKPTpha2bxS8CCAMXWTDKX/WOcdvggaP3Y\nqewyahDNzb4ggP+LNKp55BtwFjdvoPoq4BpYOOgMRbQMMk+H1o9WFw==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1t2hhg99d4p2yymuhngcy5ccutp8mvu7qwvg5cdhck303h9e7ha9qnlt635", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBYcEZ6Tzk3M0pkV0tOdTBj\nenF2a0tHNnhBa0NrazMwV1VBbXBZR3pzSHpvCnBZOEU0VlFHS1FHcVpTTDdPczVV\nV0RFSlZ0VmIzWGoydEdKVXlIUE9OOEkKLS0tIFZ0cWVBR1loeVlWa2c4U3oweXE2\ncm1ja0JCS3U5Nk41dlAzV2NabDc2bDQKdgCDNnpRZlFPnEGlX6fo0SQX4yOB+E6r\ntnSwofR3xxZvkyme/6JJU5qBZXyCXEAhKMRkFyvJANXzMJAUo/Osow==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2025-08-13T09:19:48Z", + "mac": "ENC[AES256_GCM,data:e3EkL8vwRhLsec83Zi9DE3PKT+4RwgiffpN4QHcJKTgmDW6hzizWc5kAxbNWGJ9Qqe6sso2KY7tc+hg1lHEsmzjCbg153p8h+7lVI2XT6adi/CS8WZ2VpeL+0X9zDQCjqHmrESZAYFBdkLqO4jucdf0Pc3CKKD+N3BDDTwSUvHM=,iv:xvR7dJL8sdYen00ovrYT8PNxhB9XxSWDSRz1IK23I/o=,tag:OyhAvllBgfAp3eGeNpR/Nw==,type:str]", + "unencrypted_suffix": "_unencrypted", + "version": "3.10.2" + } +} diff --git a/checks/wireguard/vars/per-machine/controller2/wireguard-keys-wg-test-one/privatekey/users/admin b/checks/wireguard/vars/per-machine/controller2/wireguard-keys-wg-test-one/privatekey/users/admin new file mode 120000 index 000000000..ca714e122 --- /dev/null +++ b/checks/wireguard/vars/per-machine/controller2/wireguard-keys-wg-test-one/privatekey/users/admin @@ -0,0 +1 @@ +../../../../../../sops/users/admin \ No newline at end of file diff --git a/checks/wireguard/vars/per-machine/controller2/wireguard-keys-wg-test-one/publickey/value b/checks/wireguard/vars/per-machine/controller2/wireguard-keys-wg-test-one/publickey/value new file mode 100644 index 000000000..29cc6e7d1 --- /dev/null +++ b/checks/wireguard/vars/per-machine/controller2/wireguard-keys-wg-test-one/publickey/value @@ -0,0 +1 @@ +5Z7gbLFbXpEFfomW2pKyZBpZN5xvUtiqrIL0GVfNtQ8= diff --git a/checks/wireguard/vars/per-machine/controller2/wireguard-network-wg-test-one/.validation-hash b/checks/wireguard/vars/per-machine/controller2/wireguard-network-wg-test-one/.validation-hash new file mode 100644 index 000000000..7e9366e0e --- /dev/null +++ b/checks/wireguard/vars/per-machine/controller2/wireguard-network-wg-test-one/.validation-hash @@ -0,0 +1 @@ +c3672fdb9fb31ddaf6572fc813cf7a8fe50488ef4e9d534c62d4f29da60a1a99 \ No newline at end of file diff --git a/checks/wireguard/vars/per-machine/controller2/wireguard-network-wg-test-one/prefix/value b/checks/wireguard/vars/per-machine/controller2/wireguard-network-wg-test-one/prefix/value new file mode 100644 index 000000000..8c63fc5ac --- /dev/null +++ b/checks/wireguard/vars/per-machine/controller2/wireguard-network-wg-test-one/prefix/value @@ -0,0 +1 @@ +fd51:19c1:c1:aa00 \ No newline at end of file diff --git a/checks/wireguard/vars/per-machine/peer1/wireguard-keys-wg-test-one/privatekey/machines/peer1 b/checks/wireguard/vars/per-machine/peer1/wireguard-keys-wg-test-one/privatekey/machines/peer1 new file mode 120000 index 000000000..3e5f3fae3 --- /dev/null +++ b/checks/wireguard/vars/per-machine/peer1/wireguard-keys-wg-test-one/privatekey/machines/peer1 @@ -0,0 +1 @@ +../../../../../../sops/machines/peer1 \ No newline at end of file diff --git a/checks/wireguard/vars/per-machine/peer1/wireguard-keys-wg-test-one/privatekey/secret b/checks/wireguard/vars/per-machine/peer1/wireguard-keys-wg-test-one/privatekey/secret new file mode 100644 index 000000000..9a6185acd --- /dev/null +++ b/checks/wireguard/vars/per-machine/peer1/wireguard-keys-wg-test-one/privatekey/secret @@ -0,0 +1,19 @@ +{ + "data": "ENC[AES256_GCM,data:b+akw85T3D9xc75CPLHucR//k7inpxKDvgpR8tCNKwNDRVjVHjcABhfZNLXW,iv:g11fZE8UI0MVh9GKdjR6leBlxa4wN7ZubozXG/VlBbw=,tag:0YkzWCW3zJ3Mt3br/jmTYw==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1jts52rzlqcwjc36jkp56a7fmjn3czr7kl9ta2spkfzhvfama33sqacrzzd", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBXWkJUR0pIa2xOSEw2dThm\nYlNuOHZCVW93Wkc5LzE4YmpUTHRkZlk3ckc4CnN4M3ZRMWNFVitCT3FyWkxaR0di\nb0NmSXFhRHJmTWg0d05OcWx1LytscEEKLS0tIEtleTFqU3JrRjVsdHpJeTNuVUhF\nWEtnOVlXVXRFamFSak5ia2F2b0JiTzAKlhOBZvZ4AN+QqAYQXvd6YNmgVS4gtkWT\nbV3bLNTgwtrDtet9NDHM8vdF+cn5RZxwFfgmTbDEow6Zm8EXfpxj/g==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6YVYyQkZqMTJYQTlyRG5Y\nbnJ2UkE1TS9FZkpSa2tQbk1hQjViMi9OcGk0CjFaZUdjU3JtNzh0bDFXdTdUVW4x\nanFqZHZjZjdzKzA2MC8vTWh3Uy82UGcKLS0tIDhyOFl3UGs3czdoMlpza3UvMlB1\nSE90MnpGc05sSCtmVWg0UVNVdmRvN2MKHlCr4U+7bsoYb+2fgT4mEseZCEjxrtLu\n55sR/4YH0vqMnIBnLTSA0e+WMrs3tQfseeJM5jY/ZNnpec1LbxkGTg==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2025-08-13T09:19:58Z", + "mac": "ENC[AES256_GCM,data:gEoEC9D2Z7k5F8egaY1qPXT5/96FFVsyofSBivQ28Ir/9xHX2j40PAQrYRJUWsk/GAUMOyi52Wm7kPuacw+bBcdtQ0+MCDEmjkEnh1V83eZ/baey7iMmg05uO92MYY5o4e7ZkwzXoAeMCMcfO0GqjNvsYJHF1pSNa+UNDj+eflw=,iv:dnIYpvhAdvUDe9md53ll42krb0sxcHy/toqGc7JFxNA=,tag:0WkZU7GeKMD1DQTYaI+1dg==,type:str]", + "unencrypted_suffix": "_unencrypted", + "version": "3.10.2" + } +} diff --git a/checks/wireguard/vars/per-machine/peer1/wireguard-keys-wg-test-one/privatekey/users/admin b/checks/wireguard/vars/per-machine/peer1/wireguard-keys-wg-test-one/privatekey/users/admin new file mode 120000 index 000000000..ca714e122 --- /dev/null +++ b/checks/wireguard/vars/per-machine/peer1/wireguard-keys-wg-test-one/privatekey/users/admin @@ -0,0 +1 @@ +../../../../../../sops/users/admin \ No newline at end of file diff --git a/checks/wireguard/vars/per-machine/peer1/wireguard-keys-wg-test-one/publickey/value b/checks/wireguard/vars/per-machine/peer1/wireguard-keys-wg-test-one/publickey/value new file mode 100644 index 000000000..68081c5bf --- /dev/null +++ b/checks/wireguard/vars/per-machine/peer1/wireguard-keys-wg-test-one/publickey/value @@ -0,0 +1 @@ +juK7P/92N2t2t680aLIRobHc3ts49CsZBvfZOyIKpUc= diff --git a/checks/wireguard/vars/per-machine/peer1/wireguard-network-wg-test-one/.validation-hash b/checks/wireguard/vars/per-machine/peer1/wireguard-network-wg-test-one/.validation-hash new file mode 100644 index 000000000..da9a88035 --- /dev/null +++ b/checks/wireguard/vars/per-machine/peer1/wireguard-network-wg-test-one/.validation-hash @@ -0,0 +1 @@ +b36142569a74a0de0f9b229f2a040ae33a22d53bef5e62aa6939912d0cda05ba \ No newline at end of file diff --git a/checks/wireguard/vars/per-machine/peer1/wireguard-network-wg-test-one/suffix/value b/checks/wireguard/vars/per-machine/peer1/wireguard-network-wg-test-one/suffix/value new file mode 100644 index 000000000..fd09cf0ed --- /dev/null +++ b/checks/wireguard/vars/per-machine/peer1/wireguard-network-wg-test-one/suffix/value @@ -0,0 +1 @@ +6987:50a0:9b93:4337 \ No newline at end of file diff --git a/checks/wireguard/vars/per-machine/peer2/wireguard-keys-wg-test-one/privatekey/machines/peer2 b/checks/wireguard/vars/per-machine/peer2/wireguard-keys-wg-test-one/privatekey/machines/peer2 new file mode 120000 index 000000000..6370c90d4 --- /dev/null +++ b/checks/wireguard/vars/per-machine/peer2/wireguard-keys-wg-test-one/privatekey/machines/peer2 @@ -0,0 +1 @@ +../../../../../../sops/machines/peer2 \ No newline at end of file diff --git a/checks/wireguard/vars/per-machine/peer2/wireguard-keys-wg-test-one/privatekey/secret b/checks/wireguard/vars/per-machine/peer2/wireguard-keys-wg-test-one/privatekey/secret new file mode 100644 index 000000000..b6ccf727a --- /dev/null +++ b/checks/wireguard/vars/per-machine/peer2/wireguard-keys-wg-test-one/privatekey/secret @@ -0,0 +1,19 @@ +{ + "data": "ENC[AES256_GCM,data:apX2sLwtq6iQgLJslFwiRMNBUe0XLzLQbhKfmb2pKiJG7jGNHUgHJz3Ls4Ca,iv:HTDatm3iD5wACTkkd3LdRNvJfnfg75RMtn9G6Q7Fqd4=,tag:Mfehlljnes5CFD1NJdk27A==,type:str]", + "sops": { + "age": [ + { + "recipient": "age12nqnp0zd435ckp5p0v2fv4p2x4cvur2mnxe8use2sx3fgy883vaq4ae75e", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBVZzFyMUZsd2V2VWxOUmhP\nZE8yZTc4Q0RkZisxR25NemR1TzVDWmJZVjBVClA1MWhsU0xzSG16aUx3cWFWKzlG\nSkxrT09OTkVqLzlWejVESE1QWHVJaFkKLS0tIGxlaGVuWU43RXErNTB3c3FaUnM3\nT0N5M253anZkbnFkZWw2VHA0eWhxQW8Kd1PMtEX1h0Hd3fDLMi++gKJkzPi9FXUm\n+uYhx+pb+pJM+iLkPwP/q6AWC7T0T4bHfekkdzxrbsKMi73x/GrOiw==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqVzRIMWdlNjVwTURyMFkv\nSUhiajZkZVNuWklRYit6cno4UzNDa2szOFN3CkQ2TWhHb25pbmR1MlBsRXNLL2lx\ncVZ3c3BsWXN2aS9UUVYvN3I4S0xUSmMKLS0tIE5FV0U5aXVUZk9XL0U0Z2ZSNGd5\nbU9zY3IvMlpSNVFLYkRNQUpUYVZOWFUK7j4Otzb8CJTcT7aAj9/irxHEDXh1HkTg\nzz7Ho8/ZncNtaCVHlHxjTgVW9d5aIx8fSsV9LRCFwHMtNzvwj1Nshg==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2025-08-13T09:20:08Z", + "mac": "ENC[AES256_GCM,data:e7WNVEz78noHBiz6S3A6qNfop+yBXB3rYN0k4GvaQKz3b99naEHuqIF8Smzzt4XrbbiPKu2iLa5ddLBlqqsi32UQUB8JS9TY7hvW8ol+jpn0VxusGCXW9ThdDEsM/hXiPyr331C73zTvbOYI1hmcGMlJL9cunVRO9rkMtEqhEfo=,iv:6zt7wjIs1y5xDHNK+yLOwoOuUpY7/dOGJGT6UWAFeOg=,tag:gzFTgoxhoLzUV0lvzOhhfg==,type:str]", + "unencrypted_suffix": "_unencrypted", + "version": "3.10.2" + } +} diff --git a/checks/wireguard/vars/per-machine/peer2/wireguard-keys-wg-test-one/privatekey/users/admin b/checks/wireguard/vars/per-machine/peer2/wireguard-keys-wg-test-one/privatekey/users/admin new file mode 120000 index 000000000..ca714e122 --- /dev/null +++ b/checks/wireguard/vars/per-machine/peer2/wireguard-keys-wg-test-one/privatekey/users/admin @@ -0,0 +1 @@ +../../../../../../sops/users/admin \ No newline at end of file diff --git a/checks/wireguard/vars/per-machine/peer2/wireguard-keys-wg-test-one/publickey/value b/checks/wireguard/vars/per-machine/peer2/wireguard-keys-wg-test-one/publickey/value new file mode 100644 index 000000000..087ed4b7f --- /dev/null +++ b/checks/wireguard/vars/per-machine/peer2/wireguard-keys-wg-test-one/publickey/value @@ -0,0 +1 @@ +XI9uSaQRDBCb82cMnGzGJcbqRfDG/IXZobyeL+kV03k= diff --git a/checks/wireguard/vars/per-machine/peer2/wireguard-network-wg-test-one/.validation-hash b/checks/wireguard/vars/per-machine/peer2/wireguard-network-wg-test-one/.validation-hash new file mode 100644 index 000000000..38043bffa --- /dev/null +++ b/checks/wireguard/vars/per-machine/peer2/wireguard-network-wg-test-one/.validation-hash @@ -0,0 +1 @@ +360f9fce4a984eb87ce2a673eb5341ecb89c0f62126548d45ef25ff5243dd646 \ No newline at end of file diff --git a/checks/wireguard/vars/per-machine/peer2/wireguard-network-wg-test-one/suffix/value b/checks/wireguard/vars/per-machine/peer2/wireguard-network-wg-test-one/suffix/value new file mode 100644 index 000000000..41acb1910 --- /dev/null +++ b/checks/wireguard/vars/per-machine/peer2/wireguard-network-wg-test-one/suffix/value @@ -0,0 +1 @@ +3b21:3ced:003e:89b3 \ No newline at end of file diff --git a/checks/wireguard/vars/per-machine/peer3/wireguard-keys-wg-test-one/privatekey/machines/peer3 b/checks/wireguard/vars/per-machine/peer3/wireguard-keys-wg-test-one/privatekey/machines/peer3 new file mode 120000 index 000000000..356dfec23 --- /dev/null +++ b/checks/wireguard/vars/per-machine/peer3/wireguard-keys-wg-test-one/privatekey/machines/peer3 @@ -0,0 +1 @@ +../../../../../../sops/machines/peer3 \ No newline at end of file diff --git a/checks/wireguard/vars/per-machine/peer3/wireguard-keys-wg-test-one/privatekey/secret b/checks/wireguard/vars/per-machine/peer3/wireguard-keys-wg-test-one/privatekey/secret new file mode 100644 index 000000000..208bcea8c --- /dev/null +++ b/checks/wireguard/vars/per-machine/peer3/wireguard-keys-wg-test-one/privatekey/secret @@ -0,0 +1,19 @@ +{ + "data": "ENC[AES256_GCM,data:Gluvjes/3oH5YsDq00JDJyJgoEFcj56smioMArPSt309MDGExYX2QsCzeO1q,iv:oBBJRDdTj/1dWEvzhdFKQ2WfeCKyavKMLmnMbqnU5PM=,tag:2WNFxKz2dWyVcybpm5N4iw==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBtQWpjRmhZTFdPa2VSZkFN\nbUczMlY5bDBmMTdoMy8xcWxMaXpWVitMZGdjCnRWb2Y3eGpHU1hmNHRJVFBqbU5w\nVEZGdUIrQXk0U0dUUEZ6bE5EMFpTRHMKLS0tIGpYSmZmQThJUTlvTHpjc05ZVlM4\nQWhTOWxnUHZnYlJ3czE3ZUJ0L3ozWTQK3a7N0Zpzo4sUezYveqvKR49RUdJL23eD\n+cK5lk2xbtj+YHkeG+dg7UlHfDaicj0wnFH1KLuWmNd1ONa6eQp3BQ==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1sglr4zp34drjfydzeweq43fz3uwpul3hkh53lsfa9drhuzwmkqyqn5jegp", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA3a2FOWlVsSkdnendrYmUz\ndEpuL1hZSWNFTUtDYm14S3V1aW9KS3hsazJRCkp2SkFFbi9hbGJpNks1MlNTL0s5\nTk5pcUMxaEJobkcvWmRGeU9jMkdNdzAKLS0tIDR6M0Y5eE1ETHJJejAzVW1EYy9v\nZCtPWHJPUkhuWnRzSGhMUUtTa280UmMKXvtnxyop7PmRvTOFkV80LziDjhGh93Pf\nYwhD/ByD/vMmr21Fd6PVHOX70FFT30BdnMc1/wt7c/0iAw4w4GoQsA==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2025-08-13T09:20:18Z", + "mac": "ENC[AES256_GCM,data:3nXMTma0UYXCco+EM8UW45cth7DVMboFBKyesL86GmaG6OlTkA2/25AeDrtSVO13a5c2jC6yNFK5dE6pSe5R9f0BoDF7d41mgc85zyn+LGECNWKC6hy6gADNSDD6RRuV1S3FisFQl1F1LD8LiSWmg/XNMZzChNlHYsCS8M+I84g=,iv:pu5VVXAVPmVoXy0BJ+hq5Ar8R0pZttKSYa4YS+dhDNc=,tag:xp1S/4qExnxMTGwhfLJrkA==,type:str]", + "unencrypted_suffix": "_unencrypted", + "version": "3.10.2" + } +} diff --git a/checks/wireguard/vars/per-machine/peer3/wireguard-keys-wg-test-one/privatekey/users/admin b/checks/wireguard/vars/per-machine/peer3/wireguard-keys-wg-test-one/privatekey/users/admin new file mode 120000 index 000000000..ca714e122 --- /dev/null +++ b/checks/wireguard/vars/per-machine/peer3/wireguard-keys-wg-test-one/privatekey/users/admin @@ -0,0 +1 @@ +../../../../../../sops/users/admin \ No newline at end of file diff --git a/checks/wireguard/vars/per-machine/peer3/wireguard-keys-wg-test-one/publickey/value b/checks/wireguard/vars/per-machine/peer3/wireguard-keys-wg-test-one/publickey/value new file mode 100644 index 000000000..d16866845 --- /dev/null +++ b/checks/wireguard/vars/per-machine/peer3/wireguard-keys-wg-test-one/publickey/value @@ -0,0 +1 @@ +t6qN4VGLR+VMhrBDNKQEXZVyRsEXs1/nGFRs5DI82F8= diff --git a/checks/wireguard/vars/per-machine/peer3/wireguard-network-wg-test-one/.validation-hash b/checks/wireguard/vars/per-machine/peer3/wireguard-network-wg-test-one/.validation-hash new file mode 100644 index 000000000..6f65517da --- /dev/null +++ b/checks/wireguard/vars/per-machine/peer3/wireguard-network-wg-test-one/.validation-hash @@ -0,0 +1 @@ +e3facc99b73fe029d4c295f71829a83f421f38d82361cf412326398175da162a \ No newline at end of file diff --git a/checks/wireguard/vars/per-machine/peer3/wireguard-network-wg-test-one/suffix/value b/checks/wireguard/vars/per-machine/peer3/wireguard-network-wg-test-one/suffix/value new file mode 100644 index 000000000..7ad4d1b18 --- /dev/null +++ b/checks/wireguard/vars/per-machine/peer3/wireguard-network-wg-test-one/suffix/value @@ -0,0 +1 @@ +e42b:bf85:33f4:f0b1 \ No newline at end of file diff --git a/clanServices/wireguard/README.md b/clanServices/wireguard/README.md new file mode 100644 index 000000000..bbc22cc71 --- /dev/null +++ b/clanServices/wireguard/README.md @@ -0,0 +1,217 @@ +# Wireguard VPN Service + +This service provides a Wireguard-based VPN mesh network with automatic IPv6 address allocation and routing between clan machines. + +## Overview + +The wireguard service creates a secure mesh network between clan machines using two roles: +- **Controllers**: Machines with public endpoints that act as connection points and routers +- **Peers**: Machines that connect through controllers to access the network + +## Features + +- Automatic IPv6 address allocation using ULA (Unique Local Address) prefixes +- Full mesh connectivity between all machines +- Automatic key generation and distribution +- IPv6 forwarding on controllers for inter-peer communication +- Support for multiple controllers for redundancy + +## Network Architecture + +### IPv6 Address Allocation +- Base network: `/40` ULA prefix (deterministically generated from instance name) +- Controllers: Each gets a `/56` subnet from the base `/40` +- Peers: Each gets a unique 64-bit host suffix that is used in ALL controller subnets + +### Addressing Design +- Each peer generates a unique host suffix (e.g., `:8750:a09b:0:1`) +- This suffix is appended to each controller's `/56` prefix to create unique addresses +- Example: peer1 with suffix `:8750:a09b:0:1` gets: + - `fd51:19c1:3b:f700:8750:a09b:0:1` in controller1's subnet + - `fd51:19c1:c1:aa00:8750:a09b:0:1` in controller2's subnet +- Controllers allow each peer's `/96` subnet for routing flexibility + +### Connectivity +- Peers use a single WireGuard interface with multiple IPs (one per controller subnet) +- Controllers connect to ALL other controllers and ALL peers on a single interface +- Controllers have IPv6 forwarding enabled to route traffic between peers +- All traffic between peers flows through controllers +- Symmetric routing is maintained as each peer has consistent IPs across all controllers + +### Example Network Topology + +```mermaid +graph TB + subgraph Controllers + C1[controller1
endpoint: vpn1.example.com
fd51:19c1:3b:f700::/56] + C2[controller2
endpoint: vpn2.example.com
fd51:19c1:c1:aa00::/56] + end + + subgraph Peers + P1[peer1
designated: controller1] + P2[peer2
designated: controller2] + P3[peer3
designated: controller1] + end + + %% Controllers connect to each other + C1 <--> C2 + + %% All peers connect to all controllers + P1 <--> C1 + P1 <--> C2 + P2 <--> C1 + P2 <--> C2 + P3 <--> C1 + P3 <--> C2 + + %% Peer-to-peer traffic flows through controllers + P1 -.->|via controllers| P3 + P1 -.->|via controllers| P2 + P2 -.->|via controllers| P3 + + classDef controller fill:#f9f,stroke:#333,stroke-width:4px + classDef peer fill:#bbf,stroke:#333,stroke-width:2px + class C1,C2 controller + class P1,P2,P3 peer +``` + +## Configuration + +### Basic Setup with Single Controller + +```nix +# In your flake.nix or inventory +{ + services.wireguard.server1 = { + roles.controller = { + # Public endpoint where this controller can be reached + endpoint = "vpn.example.com"; + # Optional: Change the UDP port (default: 51820) + port = 51820; + }; + }; + + services.wireguard.laptop1 = { + roles.peer = { + # No configuration needed if only one controller exists + }; + }; +} +``` + +### Multiple Controllers Setup + +```nix +{ + services.wireguard.server1 = { + roles.controller = { + endpoint = "vpn1.example.com"; + }; + }; + + services.wireguard.server2 = { + roles.controller = { + endpoint = "vpn2.example.com"; + }; + }; + + services.wireguard.laptop1 = { + roles.peer = { + # Must specify which controller's subnet to use for IP allocation + controller = "server1"; + }; + }; +} +``` + +### Advanced Options + + +### Automatic Hostname Resolution + +The wireguard service automatically adds entries to `/etc/hosts` for all machines in the network. Each machine is accessible via its hostname in the format `.`. + +For example, with an instance named `vpn`: +- `server1.vpn` - resolves to server1's IPv6 address +- `laptop1.vpn` - resolves to laptop1's IPv6 address + +This allows machines to communicate using hostnames instead of IPv6 addresses: + +```bash +# Ping another machine by hostname +ping6 server1.vpn + +# SSH to another machine +ssh user@laptop1.vpn +``` + +## Troubleshooting + +### Check Wireguard Status +```bash +sudo wg show +``` + +### Verify IP Addresses +```bash +ip addr show dev +``` + +### Check Routing +```bash +ip -6 route show dev +``` + +### Interface Fails to Start: "Address already in use" + +If you see this error in your logs: +``` +wireguard: Could not bring up interface, ignoring: Address already in use +``` + +This means the configured port (default: 51820) is already in use by another service or wireguard instance. Solutions: + +1. **Check for conflicting wireguard instances:** + ```bash + sudo wg show + sudo ss -ulnp | grep 51820 + ``` + +2. **Use a different port:** + ```nix + services.wireguard.myinstance = { + roles.controller = { + endpoint = "vpn.example.com"; + port = 51821; # Use a different port + }; + }; + ``` + +3. **Ensure unique ports across multiple instances:** + If you have multiple wireguard instances on the same machine, each must use a different port. + +### Key Management + +Keys are automatically generated and stored in the clan vars system. To regenerate keys: + +```bash +# Regenerate keys for a specific machine and instance +clan vars generate --service wireguard-keys- --regenerate --machine + +# Apply the new keys +clan machines update +``` + +## Security Considerations + +- All traffic is encrypted using Wireguard's modern cryptography +- Private keys never leave the machines they're generated on +- Public keys are distributed through the clan vars system +- Controllers must have publicly accessible endpoints +- Firewall rules are automatically configured for the Wireguard ports + +## Requirements + +- Controllers must have a publicly accessible endpoint (domain name or static IP) +- IPv6 support in the kernel (standard in modern systems) +- UDP port access (default: 51820, configurable) diff --git a/clanServices/wireguard/default.nix b/clanServices/wireguard/default.nix new file mode 100644 index 000000000..74317a8c0 --- /dev/null +++ b/clanServices/wireguard/default.nix @@ -0,0 +1,456 @@ +/* + There are two roles: peers and controllers: + - Every controller has an endpoint set + - There can be multiple peers + - There has to be one or more controllers + - Peers connect to ALL controllers (full mesh) + - If only one controller exists, peers automatically use it for IP allocation + - If multiple controllers exist, peers must specify which controller's subnet to use + - Controllers have IPv6 forwarding enabled, every peer and controller can reach + everyone else, via extra controller hops if necessary + + Example: + ┌───────────────────────────────┐ + │ ◄───────────── │ + │ controller2 controller1 + │ ▲ ─────────────► ▲ ▲ + │ │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ │ + │ │ │ │ └───────────────┐ │ │ │ │ + │ │ │ └──────────────┐ │ │ │ │ │ + │ ▼ │ ▼ ▼ ▼ + └─► peer2 │ peer1 peer3 + │ ▲ + └──────────┘ + + Network Architecture: + + IPv6 Address Allocation: + - Base network: /40 ULA prefix (generated from instance name) + - Controllers: Each gets a /56 subnet from the base /40 + - Peers: Each gets a unique host suffix that is used in ALL controller subnets + + Address Assignment: + - Each peer generates a unique 64-bit host suffix (e.g., :8750:a09b:0:1) + - This suffix is appended to each controller's /56 prefix + - Example: peer1 with suffix :8750:a09b:0:1 gets: + - fd51:19c1:3b:f700:8750:a09b:0:1 in controller1's subnet + - fd51:19c1:c1:aa00:8750:a09b:0:1 in controller2's subnet + + Peers: Use a SINGLE interface that: + - Connects to ALL controllers + - Has multiple IPs, one in each controller's subnet (with /56 prefix) + - Routes to each controller's /56 subnet via that controller + - allowedIPs: Each controller's /56 subnet + - No routing conflicts due to unique IPs per subnet + + Controllers: Use a SINGLE interface that: + - Connects to ALL peers and ALL other controllers + - Gets a /56 subnet from the base /40 network + - Has IPv6 forwarding enabled for routing between peers + - allowedIPs: + - For peers: A /96 range containing the peer's address in this controller's subnet + - For other controllers: The controller's /56 subnet +*/ + +{ ... }: +let + # Shared module for extraHosts configuration + extraHostsModule = + { + instanceName, + settings, + roles, + config, + lib, + ... + }: + { + networking.extraHosts = + let + domain = if settings.domain == null then instanceName else settings.domain; + # Controllers use their subnet's ::1 address + controllerHosts = lib.mapAttrsToList ( + name: _value: + let + prefix = builtins.readFile ( + config.clan.core.settings.directory + + "/vars/per-machine/${name}/wireguard-network-${instanceName}/prefix/value" + ); + # Controller IP is always ::1 in their subnet + ip = prefix + "::1"; + in + "${ip} ${name}.${domain}" + ) roles.controller.machines; + + # Peers use their suffix in their designated controller's subnet only + peerHosts = lib.mapAttrsToList ( + peerName: peerValue: + let + peerSuffix = builtins.readFile ( + config.clan.core.settings.directory + + "/vars/per-machine/${peerName}/wireguard-network-${instanceName}/suffix/value" + ); + # Determine designated controller + designatedController = + if (builtins.length (builtins.attrNames roles.controller.machines) == 1) then + (builtins.head (builtins.attrNames roles.controller.machines)) + else + peerValue.settings.controller; + controllerPrefix = builtins.readFile ( + config.clan.core.settings.directory + + "/vars/per-machine/${designatedController}/wireguard-network-${instanceName}/prefix/value" + ); + peerIP = controllerPrefix + ":" + peerSuffix; + in + "${peerIP} ${peerName}.${domain}" + ) roles.peer.machines; + in + builtins.concatStringsSep "\n" (controllerHosts ++ peerHosts); + }; + + # Shared interface options + sharedInterface = + { lib, ... }: + { + options.port = lib.mkOption { + type = lib.types.int; + example = 51820; + default = 51820; + description = '' + Port for the wireguard interface + ''; + }; + + options.domain = lib.mkOption { + type = lib.types.nullOr lib.types.str; + defaultText = lib.literalExpression "instanceName"; + default = null; + description = '' + Domain suffix to use for hostnames in /etc/hosts. + Defaults to the instance name. + ''; + }; + }; +in +{ + _class = "clan.service"; + manifest.name = "clan-core/wireguard"; + manifest.description = "Wireguard-based VPN mesh network with automatic IPv6 address allocation"; + manifest.categories = [ + "System" + "Network" + ]; + manifest.readme = builtins.readFile ./README.md; + + # Peer options and configuration + roles.peer = { + interface = + { lib, ... }: + { + imports = [ sharedInterface ]; + + options.controller = lib.mkOption { + type = lib.types.str; + example = "controller1"; + description = '' + Machinename of the controller to attach to + ''; + }; + }; + + perInstance = + { + instanceName, + settings, + roles, + machine, + ... + }: + { + # Set default domain to instanceName + + # Peers connect to all controllers + nixosModule = + { + config, + pkgs, + lib, + ... + }: + { + imports = [ + (extraHostsModule { + inherit + instanceName + settings + roles + config + lib + ; + }) + ]; + # Network allocation generator for this peer - generates host suffix + clan.core.vars.generators."wireguard-network-${instanceName}" = { + files.suffix.secret = false; + + runtimeInputs = with pkgs; [ + python3 + ]; + + # Invalidate on hostname changes + validation.hostname = machine.name; + + script = '' + ${pkgs.python3}/bin/python3 ${./ipv6_allocator.py} "$out" "${instanceName}" peer "${machine.name}" + ''; + }; + + # Single wireguard interface with multiple IPs + networking.wireguard.interfaces."${instanceName}" = { + ips = + # Get this peer's suffix + let + peerSuffix = + config.clan.core.vars.generators."wireguard-network-${instanceName}".files.suffix.value; + in + # Create an IP in each controller's subnet + lib.mapAttrsToList ( + ctrlName: _: + let + controllerPrefix = builtins.readFile ( + config.clan.core.settings.directory + + "/vars/per-machine/${ctrlName}/wireguard-network-${instanceName}/prefix/value" + ); + peerIP = controllerPrefix + ":" + peerSuffix; + in + "${peerIP}/56" + ) roles.controller.machines; + + privateKeyFile = + config.clan.core.vars.generators."wireguard-keys-${instanceName}".files."privatekey".path; + + # Connect to all controllers + peers = lib.mapAttrsToList (name: value: { + publicKey = ( + builtins.readFile ( + config.clan.core.settings.directory + + "/vars/per-machine/${name}/wireguard-keys-${instanceName}/publickey/value" + ) + ); + + # Allow each controller's /56 subnet + allowedIPs = [ + "${ + builtins.readFile ( + config.clan.core.settings.directory + + "/vars/per-machine/${name}/wireguard-network-${instanceName}/prefix/value" + ) + }::/56" + ]; + + endpoint = "${value.settings.endpoint}:${toString value.settings.port}"; + + persistentKeepalive = 25; + }) roles.controller.machines; + }; + }; + }; + }; + + # Controller options and configuration + roles.controller = { + interface = + { lib, ... }: + { + imports = [ sharedInterface ]; + + options.endpoint = lib.mkOption { + type = lib.types.str; + example = "vpn.clan.lol"; + description = '' + Endpoint where the controller can be reached + ''; + }; + }; + perInstance = + { + settings, + instanceName, + roles, + machine, + ... + }: + { + + # Controllers connect to all peers and other controllers + nixosModule = + { + config, + pkgs, + lib, + ... + }: + let + allOtherControllers = lib.filterAttrs (name: _v: name != machine.name) roles.controller.machines; + allPeers = roles.peer.machines; + in + { + imports = [ + (extraHostsModule { + inherit + instanceName + settings + roles + config + lib + ; + }) + ]; + # Network allocation generator for this controller + clan.core.vars.generators."wireguard-network-${instanceName}" = { + files.prefix.secret = false; + + runtimeInputs = with pkgs; [ + python3 + ]; + + # Invalidate on network or hostname changes + validation.hostname = machine.name; + + script = '' + ${pkgs.python3}/bin/python3 ${./ipv6_allocator.py} "$out" "${instanceName}" controller "${machine.name}" + ''; + }; + + # Enable ip forwarding, so wireguard peers can reach eachother + boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = 1; + + networking.firewall.allowedUDPPorts = [ settings.port ]; + + # Single wireguard interface + networking.wireguard.interfaces."${instanceName}" = { + listenPort = settings.port; + + ips = [ + # Controller uses ::1 in its /56 subnet but with /40 prefix for proper routing + "${config.clan.core.vars.generators."wireguard-network-${instanceName}".files.prefix.value}::1/40" + ]; + + privateKeyFile = + config.clan.core.vars.generators."wireguard-keys-${instanceName}".files."privatekey".path; + + # Connect to all peers and other controllers + peers = lib.mapAttrsToList ( + name: value: + if allPeers ? ${name} then + # For peers: they now have our entire /56 subnet + { + publicKey = ( + builtins.readFile ( + config.clan.core.settings.directory + + "/vars/per-machine/${name}/wireguard-keys-${instanceName}/publickey/value" + ) + ); + + # Allow the peer's /96 range in ALL controller subnets + allowedIPs = lib.mapAttrsToList ( + ctrlName: _: + let + controllerPrefix = builtins.readFile ( + config.clan.core.settings.directory + + "/vars/per-machine/${ctrlName}/wireguard-network-${instanceName}/prefix/value" + ); + peerSuffix = builtins.readFile ( + config.clan.core.settings.directory + + "/vars/per-machine/${name}/wireguard-network-${instanceName}/suffix/value" + ); + in + "${controllerPrefix}:${peerSuffix}/96" + ) roles.controller.machines; + + persistentKeepalive = 25; + } + else + # For other controllers: use their /56 subnet + { + publicKey = ( + builtins.readFile ( + config.clan.core.settings.directory + + "/vars/per-machine/${name}/wireguard-keys-${instanceName}/publickey/value" + ) + ); + + allowedIPs = [ + "${ + builtins.readFile ( + config.clan.core.settings.directory + + "/vars/per-machine/${name}/wireguard-network-${instanceName}/prefix/value" + ) + }::/56" + ]; + + endpoint = "${value.settings.endpoint}:${toString value.settings.port}"; + persistentKeepalive = 25; + } + ) (allPeers // allOtherControllers); + }; + }; + }; + }; + + # Maps over all machines and produces one result per machine, regardless of role + perMachine = + { instances, machine, ... }: + { + nixosModule = + { pkgs, lib, ... }: + let + # Check if this machine has conflicting roles across all instances + machineRoleConflicts = lib.flatten ( + lib.mapAttrsToList ( + instanceName: instanceInfo: + let + isController = + instanceInfo.roles ? controller && instanceInfo.roles.controller.machines ? ${machine.name}; + isPeer = instanceInfo.roles ? peer && instanceInfo.roles.peer.machines ? ${machine.name}; + in + lib.optional (isController && isPeer) { + inherit instanceName; + machineName = machine.name; + } + ) instances + ); + in + { + # Add assertions for role conflicts + assertions = lib.forEach machineRoleConflicts (conflict: { + assertion = false; + message = '' + Machine '${conflict.machineName}' cannot have both 'controller' and 'peer' roles in the wireguard instance '${conflict.instanceName}'. + A machine must be either a controller or a peer, not both. + ''; + }); + + # Generate keys for each instance where this machine participates + clan.core.vars.generators = lib.mapAttrs' ( + name: _instanceInfo: + lib.nameValuePair "wireguard-keys-${name}" { + files.publickey.secret = false; + files.privatekey = { }; + + runtimeInputs = with pkgs; [ + wireguard-tools + ]; + + script = '' + wg genkey > $out/privatekey + wg pubkey < $out/privatekey > $out/publickey + ''; + } + ) instances; + + }; + }; +} diff --git a/clanServices/wireguard/flake-module.nix b/clanServices/wireguard/flake-module.nix new file mode 100644 index 000000000..66529b160 --- /dev/null +++ b/clanServices/wireguard/flake-module.nix @@ -0,0 +1,7 @@ +{ lib, ... }: +let + module = lib.modules.importApply ./default.nix { }; +in +{ + clan.modules.wireguard = module; +} diff --git a/clanServices/wireguard/ipv6_allocator.py b/clanServices/wireguard/ipv6_allocator.py new file mode 100755 index 000000000..16050efb2 --- /dev/null +++ b/clanServices/wireguard/ipv6_allocator.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 +""" +IPv6 address allocator for WireGuard networks. + +Network layout: +- Base network: /40 ULA prefix (fd00::/8 + 32 bits from hash) +- Controllers: Each gets a /56 subnet from the base /40 (256 controllers max) +- Peers: Each gets a /96 subnet from their controller's /56 +""" + +import hashlib +import ipaddress +import sys +from pathlib import Path + + +def hash_string(s: str) -> str: + """Generate SHA256 hash of string.""" + return hashlib.sha256(s.encode()).hexdigest() + + +def generate_ula_prefix(instance_name: str) -> ipaddress.IPv6Network: + """ + Generate a /40 ULA prefix from instance name. + + Format: fd{32-bit hash}/40 + This gives us fd00:0000:0000::/40 through fdff:ffff:ff00::/40 + """ + h = hash_string(instance_name) + + # For /40, we need 32 bits after 'fd' (8 hex chars) + # But only the first 32 bits count for the network prefix + # The last 8 bits of the 40-bit prefix must be 0 + prefix_bits = int(h[:8], 16) + + # Mask to ensure we only use the first 32 bits for /40 + # This gives us addresses like fd28:387a::/40 + prefix_bits = prefix_bits & 0xFFFFFF00 # Clear last 8 bits + + # Format as IPv6 address + prefix = f"fd{prefix_bits:08x}" + prefix_formatted = f"{prefix[:4]}:{prefix[4:8]}::/40" + + network = ipaddress.IPv6Network(prefix_formatted) + return network + + +def generate_controller_subnet( + base_network: ipaddress.IPv6Network, controller_name: str +) -> ipaddress.IPv6Network: + """ + Generate a /56 subnet for a controller from the base /40 network. + + We have 16 bits (40 to 56) to allocate controller subnets. + This allows for 65,536 possible controller subnets. + """ + h = hash_string(controller_name) + # Take 16 bits from hash for the controller subnet ID + controller_id = int(h[:4], 16) + + # Create the controller subnet by adding the controller ID to the base network + # The controller subnet is at base_prefix:controller_id::/56 + base_int = int(base_network.network_address) + controller_subnet_int = base_int | (controller_id << (128 - 56)) + controller_subnet = ipaddress.IPv6Network((controller_subnet_int, 56)) + + return controller_subnet + + +def generate_peer_suffix(peer_name: str) -> str: + """ + Generate a unique 64-bit host suffix for a peer. + + This suffix will be used in all controller subnets to create unique addresses. + Format: :xxxx:xxxx:xxxx:xxxx (64 bits) + """ + h = hash_string(peer_name) + # Take 64 bits (16 hex chars) from hash for the host suffix + suffix_bits = h[:16] + + # Format as IPv6 suffix without leading colon + suffix = f"{suffix_bits[0:4]}:{suffix_bits[4:8]}:{suffix_bits[8:12]}:{suffix_bits[12:16]}" + return suffix + + +def main() -> None: + if len(sys.argv) < 4: + print( + "Usage: ipv6_allocator.py " + ) + sys.exit(1) + + output_dir = Path(sys.argv[1]) + instance_name = sys.argv[2] + node_type = sys.argv[3] + + # Generate base /40 network + base_network = generate_ula_prefix(instance_name) + + if node_type == "controller": + if len(sys.argv) < 5: + print("Controller name required") + sys.exit(1) + + controller_name = sys.argv[4] + subnet = generate_controller_subnet(base_network, controller_name) + + # Extract clean prefix from subnet (e.g. "fd51:19c1:3b:f700::/56" -> "fd51:19c1:3b:f700") + prefix_str = str(subnet).split("/")[0].rstrip(":") + while prefix_str.endswith(":"): + prefix_str = prefix_str.rstrip(":") + + # Write file + (output_dir / "prefix").write_text(prefix_str) + + elif node_type == "peer": + if len(sys.argv) < 5: + print("Peer name required") + sys.exit(1) + + peer_name = sys.argv[4] + + # Generate the peer's host suffix + suffix = generate_peer_suffix(peer_name) + + # Write file + (output_dir / "suffix").write_text(suffix) + + else: + print(f"Unknown node type: {node_type}") + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 7baca7159..71bc37e43 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -92,7 +92,6 @@ nav: - Services: - Overview: - reference/clanServices/index.md - - reference/clanServices/admin.md - reference/clanServices/borgbackup.md - reference/clanServices/data-mesher.md @@ -109,6 +108,7 @@ nav: - reference/clanServices/trusted-nix-caches.md - reference/clanServices/users.md - reference/clanServices/wifi.md + - reference/clanServices/wireguard.md - reference/clanServices/zerotier.md - API: reference/clanServices/clan-service-author-interface.md