wireguard: make external peers connect to all controllers

This commit is contained in:
DavHau
2025-10-16 20:00:45 +07:00
parent c48be6b34f
commit 9e36b00b48
2 changed files with 72 additions and 18 deletions

View File

@@ -309,10 +309,12 @@ in
List of external peer names that are not part of the clan.
For ever entry here, a key pair for an external device will be generated.
Each external peer must be configured on exactly ONE controller.
This key pair can then then be displayed via `clan vars get` and inserted into an external device, like a phone or laptop.
The names in this list must not collide with machine names in the clan.
Each external peer can connect to the mesh through one or more controllers.
To connect to multiple controllers, set `roles.controller.settings.externalPeers`.
The external peer names must not collide with machine names in the clan.
The machines which are part of the clan will be able to resolve the external peers via their host names, but not vice versa.
External peers can still reach machines from within the clan via their IPv6 addresses.
'';
@@ -340,6 +342,10 @@ in
let
allOtherControllers = lib.filterAttrs (name: _v: name != machine.name) roles.controller.machines;
allPeers = roles.peer.machines;
# Collect all external peers from all controllers
allExternalPeers = lib.unique (
lib.flatten (lib.mapAttrsToList (_: ctrl: ctrl.settings.externalPeers) roles.controller.machines)
);
in
{
imports = [
@@ -447,7 +453,7 @@ in
persistentKeepalive = 25;
}) allPeers)
++
# External peers configuration
# External peers configuration - includes all external peers from all controllers
(map (peer: {
publicKey = (
builtins.readFile (
@@ -474,7 +480,7 @@ in
# No endpoint for external peers, they initiate the connection
persistentKeepalive = 25;
}) settings.externalPeers)
}) allExternalPeers)
++
# Other controllers configuration
(lib.mapAttrsToList (name: value: {

View File

@@ -21,6 +21,11 @@ let
config.clan.directory
+ "/vars/per-machine/${controllerName}/wireguard-network-wg-test-one/prefix/value"
);
peerSuffix =
peerName:
builtins.readFile (
config.clan.directory + "/vars/per-machine/${peerName}/wireguard-network-wg-test-one/suffix/value"
);
# external peer suffixes are stored via shared vars
externalPeerSuffix =
externalName:
@@ -70,6 +75,8 @@ in
roles.controller.machines."controller2".settings = {
endpoint = "192.168.1.2";
# add the same external peer to controller2 to test multi-controller connection
externalPeers = [ "external1" ];
};
roles.peer.machines = {
@@ -99,15 +106,21 @@ in
nodes.external1 =
let
controller1Prefix = controllerPrefix "controller1";
controller2Prefix = controllerPrefix "controller2";
external1Suffix = externalPeerSuffix "external1";
in
{
networking.extraHosts = ''
${controller1Prefix}::1 controller1.wg-test-one
${controller2Prefix}::1 controller2.wg-test-one
'';
networking.wireguard.interfaces."wg0" = {
ips = [ "${controller1Prefix + ":" + external1Suffix}/56" ];
# Multiple IPs, one in each controller's subnet
ips = [
"${controller1Prefix + ":" + external1Suffix}/56"
"${controller2Prefix + ":" + external1Suffix}/56"
];
privateKeyFile =
builtins.toFile "wg-priv-key"
@@ -116,7 +129,9 @@ in
# echo "AGE-SECRET-KEY-1PL0M9CWRCG3PZ9DXRTTLMCVD57U6JDFE8K7DNVQ35F4JENZ6G3MQ0RQLRV" | SOPS_AGE_KEY_FILE=/dev/stdin nix run nixpkgs#sops decrypt clanServices/wireguard/tests/vm/vars/shared/wireguard-network-wg-test-one-external-peer-external1/privatekey/secret
"wO8dl3JWgV5J+0D/2UDcLsxTD25IWTvd5ed6vv2Nikk=";
# Connect to both controllers
peers = [
# Controller 1
{
publicKey = (
builtins.readFile (
@@ -124,14 +139,26 @@ in
)
);
# Allow each controller's /56 subnet
allowedIPs = [
# "${controller1Prefix}::/56"
"::/0"
];
# Allow controller1's /56 subnet
allowedIPs = [ "${controller1Prefix}::/56" ];
endpoint = "controller1:51820";
persistentKeepalive = 25;
}
# Controller 2
{
publicKey = (
builtins.readFile (
config.clan.directory + "/vars/per-machine/controller2/wireguard-keys-wg-test-one/publickey/value"
)
);
# Allow controller2's /56 subnet
allowedIPs = [ "${controller2Prefix}::/56" ];
endpoint = "controller2:51820";
persistentKeepalive = 25;
}
];
@@ -141,8 +168,8 @@ in
testScript = ''
start_all()
# Show all addresses
machines = [peer1, peer2, peer3, controller1, controller2]
# Start network on all machines including external1
machines = [peer1, peer2, peer3, controller1, controller2, external1]
for m in machines:
m.systemctl("start network-online.target")
@@ -154,14 +181,35 @@ in
print("STARTING PING TESTS")
print("="*60)
for m1 in machines:
# ping all other machines
for m2 in machines:
# Test mesh connectivity between regular clan machines
clan_machines = [peer1, peer2, peer3, controller1, controller2]
for m1 in clan_machines:
for m2 in clan_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")
# ping external peer from all other peers and controllers
print(f"\n--- Pinging from {m1.name} to external1.wg-test-one ---")
m1.wait_until_succeeds("ping -c1 external1.wg-test-one >&2")
# Test that external peer can reach both controllers (multi-controller connection)
print("\n--- Testing external1 -> controller1 (direct connection) ---")
external1.wait_until_succeeds("ping -c1 controller1.wg-test-one >&2")
print("\n--- Testing external1 -> controller2 (direct connection) ---")
external1.wait_until_succeeds("ping -c1 controller2.wg-test-one >&2")
# Test that all clan machines can reach the external peer
for m in clan_machines:
print(f"\n--- Pinging from {m.name} to external1.wg-test-one ---")
m.wait_until_succeeds("ping -c1 external1.wg-test-one >&2")
# Test that external peer can reach a regular peer via controller1
print("\n--- Testing external1 -> peer1 (via controller1) ---")
external1.wait_until_succeeds("ping -c1 ${controllerPrefix "controller1"}:${peerSuffix "peer1"} >&2")
# Test controller failover
print("\n--- Shutting down controller1 ---")
controller1.shutdown()
print("\n--- Testing external1 -> peer1 (via controller2 after controller1 shutdown) ---")
external1.wait_until_succeeds("ping -c1 ${controllerPrefix "controller2"}:${peerSuffix "peer1"} >&2")
'';
}