Compare commits
4 Commits
push-unvrq
...
wireguard
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a0b16e30b6 | ||
|
|
9e36b00b48 | ||
|
|
c48be6b34f | ||
|
|
6a3f5e077b |
@@ -133,6 +133,75 @@ graph TB
|
||||
|
||||
### Advanced Options
|
||||
|
||||
#### External Peers
|
||||
|
||||
External peers are devices outside of your clan (like phones, laptops, etc.) that can connect to the mesh network through controllers. Each external peer gets its own keypair and can be configured with specific options.
|
||||
|
||||
##### IPv6-only external peers
|
||||
|
||||
```nix
|
||||
{
|
||||
instances = {
|
||||
wireguard = {
|
||||
module.name = "wireguard";
|
||||
module.input = "clan-core";
|
||||
roles.controller.machines.server1.settings = {
|
||||
endpoint = "vpn.example.com";
|
||||
# Define external peers with configuration options
|
||||
externalPeers = {
|
||||
dave = {
|
||||
# No internet access - can only reach clan mesh
|
||||
allowInternetAccess = false;
|
||||
};
|
||||
moms-phone = {
|
||||
# Internet access enabled - IPv6 traffic routed through VPN
|
||||
allowInternetAccess = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
roles.peer.machines.laptop1 = {};
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### IPv4 support for external peers
|
||||
|
||||
If you need IPv4 internet access for external peers, you can enable IPv4 on the controller and assign IPv4 addresses to external peers:
|
||||
|
||||
```nix
|
||||
{
|
||||
instances = {
|
||||
wireguard = {
|
||||
module.name = "wireguard";
|
||||
module.input = "clan-core";
|
||||
roles.controller.machines.server1.settings = {
|
||||
endpoint = "vpn.example.com";
|
||||
# Enable IPv4 with controller's address
|
||||
ipv4.enable = true;
|
||||
ipv4.address = "10.42.1.1/24";
|
||||
externalPeers = {
|
||||
dave = {
|
||||
# No internet access - can only reach clan mesh
|
||||
allowInternetAccess = false;
|
||||
ipv4.address = "10.42.1.50/32";
|
||||
};
|
||||
moms-phone = {
|
||||
# Internet access enabled - IPv4 and IPv6 traffic routed through VPN
|
||||
allowInternetAccess = true;
|
||||
ipv4.address = "10.42.1.51/32";
|
||||
};
|
||||
};
|
||||
};
|
||||
roles.peer.machines.laptop1 = {};
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Note:** IPv4 addresses for external peers are only used for internet access through the controller, not for mesh communication (which uses IPv6).
|
||||
|
||||
External peers can connect to multiple controllers by adding the same peer name to multiple controllers' `externalPeers` configuration.
|
||||
|
||||
### Automatic Hostname Resolution
|
||||
|
||||
|
||||
@@ -105,9 +105,31 @@ let
|
||||
peerIP = controllerPrefix + ":" + peerSuffix;
|
||||
in
|
||||
"${peerIP} ${peerName}.${domain}"
|
||||
) roles.peer.machines;
|
||||
) roles.peer.machines or { };
|
||||
|
||||
# External peers
|
||||
externalPeerHosts = lib.flatten (
|
||||
lib.mapAttrsToList (
|
||||
ctrlName: _ctrlValue:
|
||||
lib.mapAttrsToList (
|
||||
peer: _peerSettings:
|
||||
let
|
||||
peerSuffix = builtins.readFile (
|
||||
config.clan.core.settings.directory
|
||||
+ "/vars/shared/wireguard-network-${instanceName}-external-peer-${peer}/suffix/value"
|
||||
);
|
||||
controllerPrefix = builtins.readFile (
|
||||
config.clan.core.settings.directory
|
||||
+ "/vars/per-machine/${ctrlName}/wireguard-network-${instanceName}/prefix/value"
|
||||
);
|
||||
peerIP = controllerPrefix + ":" + peerSuffix;
|
||||
in
|
||||
"${peerIP} ${peer}.${domain}"
|
||||
) (roles.controller.machines.${ctrlName}.settings.externalPeers)
|
||||
) roles.controller.machines
|
||||
);
|
||||
in
|
||||
builtins.concatStringsSep "\n" (controllerHosts ++ peerHosts);
|
||||
builtins.concatStringsSep "\n" (controllerHosts ++ peerHosts ++ externalPeerHosts);
|
||||
};
|
||||
|
||||
# Shared interface options
|
||||
@@ -268,12 +290,89 @@ in
|
||||
{
|
||||
imports = [ sharedInterface ];
|
||||
|
||||
options.endpoint = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
example = "vpn.clan.lol";
|
||||
description = ''
|
||||
Endpoint where the controller can be reached
|
||||
'';
|
||||
options = {
|
||||
endpoint = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
example = "vpn.clan.lol";
|
||||
description = ''
|
||||
Endpoint where the controller can be reached
|
||||
'';
|
||||
};
|
||||
ipv4 = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Enable IPv4 support for external peers on this controller.
|
||||
When enabled, the controller will have an IPv4 address and can route IPv4 traffic.
|
||||
|
||||
IPv4 is only used for internet access, not for mesh communication (which uses IPv6).
|
||||
'';
|
||||
};
|
||||
address = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
example = "10.42.1.1/24";
|
||||
description = ''
|
||||
IPv4 address for this controller in CIDR notation.
|
||||
External peers with IPv4 addresses must be within the same subnet.
|
||||
|
||||
IPv4 is only used for internet access, not for mesh communication (which uses IPv6).
|
||||
'';
|
||||
};
|
||||
};
|
||||
externalPeers = lib.mkOption {
|
||||
type = lib.types.attrsOf (
|
||||
lib.types.submodule {
|
||||
options = {
|
||||
allowInternetAccess = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to allow this external peer to access the internet through the controller.
|
||||
When enabled, the controller will route internet traffic for this peer.
|
||||
|
||||
IPv4 is only used for internet access, not for mesh communication (which uses IPv6).
|
||||
'';
|
||||
};
|
||||
ipv4.address = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "10.42.1.50/32";
|
||||
description = ''
|
||||
IPv4 address for this external peer in CIDR notation.
|
||||
The peer must be within the controller's IPv4 subnet.
|
||||
Only used when the controller has IPv4 enabled.
|
||||
|
||||
IPv4 is only used for internet access, not for mesh communication (which uses IPv6).
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
default = { };
|
||||
example = {
|
||||
dave = {
|
||||
allowInternetAccess = false;
|
||||
};
|
||||
"moms-phone" = {
|
||||
allowInternetAccess = true;
|
||||
ipv4.address = "10.42.1.51/32";
|
||||
};
|
||||
};
|
||||
description = ''
|
||||
External peers that are not part of the clan.
|
||||
|
||||
For every entry here, a key pair for an external device will be generated.
|
||||
This key pair can then be displayed via `clan vars get` and inserted into an external device, like a phone or laptop.
|
||||
|
||||
Each external peer can connect to the mesh through one or more controllers.
|
||||
To connect to multiple controllers, add the same peer name to multiple controllers' `externalPeers`, or simply set 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.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
perInstance =
|
||||
@@ -296,7 +395,37 @@ in
|
||||
}:
|
||||
let
|
||||
allOtherControllers = lib.filterAttrs (name: _v: name != machine.name) roles.controller.machines;
|
||||
allPeers = roles.peer.machines;
|
||||
allPeers = roles.peer.machines or { };
|
||||
# Collect all external peers from all controllers
|
||||
allExternalPeers = lib.unique (
|
||||
lib.flatten (
|
||||
lib.mapAttrsToList (_: ctrl: lib.attrNames ctrl.settings.externalPeers) roles.controller.machines
|
||||
)
|
||||
);
|
||||
|
||||
controllerPrefix =
|
||||
controllerName:
|
||||
builtins.readFile (
|
||||
config.clan.core.settings.directory
|
||||
+ "/vars/per-machine/${controllerName}/wireguard-network-${instanceName}/prefix/value"
|
||||
);
|
||||
|
||||
peerSuffix =
|
||||
peerName:
|
||||
builtins.readFile (
|
||||
config.clan.core.settings.directory
|
||||
+ "/vars/per-machine/${peerName}/wireguard-network-${instanceName}/suffix/value"
|
||||
);
|
||||
|
||||
externalPeerSuffix =
|
||||
externalName:
|
||||
builtins.readFile (
|
||||
config.clan.core.settings.directory
|
||||
+ "/vars/shared/wireguard-network-${instanceName}-external-peer-${externalName}/suffix/value"
|
||||
);
|
||||
|
||||
thisControllerPrefix =
|
||||
config.clan.core.vars.generators."wireguard-network-${instanceName}".files.prefix.value;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
@@ -310,93 +439,172 @@ in
|
||||
;
|
||||
})
|
||||
];
|
||||
# Network allocation generator for this controller
|
||||
clan.core.vars.generators."wireguard-network-${instanceName}" = {
|
||||
files.prefix.secret = false;
|
||||
# Network prefix allocation generator for this controller
|
||||
clan.core.vars.generators = {
|
||||
"wireguard-network-${instanceName}" = {
|
||||
files.prefix.secret = false;
|
||||
|
||||
runtimeInputs = with pkgs; [
|
||||
python3
|
||||
];
|
||||
runtimeInputs = with pkgs; [
|
||||
python3
|
||||
];
|
||||
|
||||
# Invalidate on network or hostname changes
|
||||
validation.hostname = machine.name;
|
||||
# Invalidate on network or hostname changes
|
||||
validation.hostname = machine.name;
|
||||
|
||||
script = ''
|
||||
${pkgs.python3}/bin/python3 ${./ipv6_allocator.py} "$out" "${instanceName}" controller "${machine.name}"
|
||||
'';
|
||||
script = ''
|
||||
${pkgs.python3}/bin/python3 ${./ipv6_allocator.py} "$out" "${instanceName}" controller "${machine.name}"
|
||||
'';
|
||||
};
|
||||
}
|
||||
# For external peers, generate: suffix, public key, private key
|
||||
// lib.genAttrs' (lib.attrNames settings.externalPeers) (peer: {
|
||||
name = "wireguard-network-${instanceName}-external-peer-${peer}";
|
||||
value = {
|
||||
files.suffix.secret = false;
|
||||
files.publickey.secret = false;
|
||||
files.privatekey.secret = true;
|
||||
files.privatekey.deploy = false;
|
||||
|
||||
# The external peers keys are not deployed and are globally unique.
|
||||
# Even if an external peer is connected to more than one controller,
|
||||
# its private keys will remain the same.
|
||||
share = true;
|
||||
|
||||
runtimeInputs = with pkgs; [
|
||||
python3
|
||||
wireguard-tools
|
||||
];
|
||||
|
||||
# Invalidate on hostname changes
|
||||
validation.hostname = peer;
|
||||
|
||||
script = ''
|
||||
${pkgs.python3}/bin/python3 ${./ipv6_allocator.py} "$out" "${instanceName}" peer "${peer}"
|
||||
wg genkey > $out/privatekey
|
||||
wg pubkey < $out/privatekey > $out/publickey
|
||||
'';
|
||||
};
|
||||
});
|
||||
|
||||
# Enable ip forwarding, so wireguard peers can reach each other
|
||||
boot.kernel.sysctl = {
|
||||
"net.ipv6.conf.all.forwarding" = 1;
|
||||
}
|
||||
// lib.optionalAttrs settings.ipv4.enable {
|
||||
"net.ipv4.conf.all.forwarding" = 1;
|
||||
};
|
||||
|
||||
# Enable ip forwarding, so wireguard peers can reach eachother
|
||||
boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = 1;
|
||||
|
||||
networking.firewall.allowedUDPPorts = [ settings.port ];
|
||||
|
||||
networking.firewall.extraCommands =
|
||||
let
|
||||
peersWithInternetAccess = lib.filterAttrs (
|
||||
_: peerConfig: peerConfig.allowInternetAccess
|
||||
) settings.externalPeers;
|
||||
|
||||
peerInfo = lib.mapAttrs (
|
||||
peer: peerConfig:
|
||||
let
|
||||
ipv6Address = "${thisControllerPrefix}:${externalPeerSuffix peer}";
|
||||
ipv4Address =
|
||||
if settings.ipv4.enable && peerConfig.ipv4.address != null then
|
||||
lib.head (lib.splitString "/" peerConfig.ipv4.address)
|
||||
else
|
||||
null;
|
||||
in
|
||||
{
|
||||
inherit ipv6Address ipv4Address;
|
||||
}
|
||||
) peersWithInternetAccess;
|
||||
|
||||
in
|
||||
lib.concatStringsSep "\n" (
|
||||
(lib.mapAttrsToList (_peer: info: ''
|
||||
ip6tables -t nat -A POSTROUTING -s ${info.ipv6Address}/128 ! -o '${instanceName}' -j MASQUERADE
|
||||
'') peerInfo)
|
||||
++ (lib.mapAttrsToList (
|
||||
_peer: info:
|
||||
lib.optionalString (info.ipv4Address != null) ''
|
||||
iptables -t nat -A POSTROUTING -s ${info.ipv4Address} ! -o '${instanceName}' -j MASQUERADE
|
||||
''
|
||||
) peerInfo)
|
||||
);
|
||||
|
||||
# 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"
|
||||
];
|
||||
"${thisControllerPrefix}::1/40"
|
||||
]
|
||||
++ lib.optional settings.ipv4.enable settings.ipv4.address;
|
||||
|
||||
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"
|
||||
)
|
||||
);
|
||||
peers =
|
||||
# Peers configuration
|
||||
(lib.mapAttrsToList (name: _value: {
|
||||
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;
|
||||
# Allow the peer's /96 range in ALL controller subnets
|
||||
allowedIPs = lib.mapAttrsToList (
|
||||
ctrlName: _: "${controllerPrefix ctrlName}:${peerSuffix name}/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"
|
||||
)
|
||||
);
|
||||
persistentKeepalive = 25;
|
||||
}) allPeers)
|
||||
++
|
||||
# External peers configuration - includes all external peers from all controllers
|
||||
(map (
|
||||
peer:
|
||||
let
|
||||
# IPv6 allowed IPs for mesh communication
|
||||
ipv6AllowedIPs = lib.mapAttrsToList (
|
||||
ctrlName: _: "${controllerPrefix ctrlName}:${externalPeerSuffix peer}/96"
|
||||
) roles.controller.machines;
|
||||
|
||||
allowedIPs = [
|
||||
"${
|
||||
# IPv4 allowed IP (only if this controller manages this peer and has IPv4 enabled)
|
||||
ipv4AllowedIPs = lib.optional (
|
||||
settings.ipv4.enable
|
||||
&& settings.externalPeers ? ${peer}
|
||||
&& settings.externalPeers.${peer}.ipv4.address != null
|
||||
) settings.externalPeers.${peer}.ipv4.address;
|
||||
in
|
||||
{
|
||||
publicKey = (
|
||||
builtins.readFile (
|
||||
config.clan.core.settings.directory
|
||||
+ "/vars/per-machine/${name}/wireguard-network-${instanceName}/prefix/value"
|
||||
+ "/vars/shared/wireguard-network-${instanceName}-external-peer-${peer}/publickey/value"
|
||||
)
|
||||
}::/56"
|
||||
];
|
||||
);
|
||||
|
||||
allowedIPs = ipv6AllowedIPs ++ ipv4AllowedIPs;
|
||||
|
||||
persistentKeepalive = 25;
|
||||
}
|
||||
) allExternalPeers)
|
||||
++
|
||||
# Other controllers configuration
|
||||
(lib.mapAttrsToList (name: value: {
|
||||
publicKey = (
|
||||
builtins.readFile (
|
||||
config.clan.core.settings.directory
|
||||
+ "/vars/per-machine/${name}/wireguard-keys-${instanceName}/publickey/value"
|
||||
)
|
||||
);
|
||||
|
||||
allowedIPs = [ "${controllerPrefix name}::/56" ];
|
||||
|
||||
endpoint = "${value.settings.endpoint}:${toString value.settings.port}";
|
||||
persistentKeepalive = 25;
|
||||
}
|
||||
) (allPeers // allOtherControllers);
|
||||
}) allOtherControllers);
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -416,7 +624,7 @@ in
|
||||
let
|
||||
isController =
|
||||
instanceInfo.roles ? controller && instanceInfo.roles.controller.machines ? ${machine.name};
|
||||
isPeer = instanceInfo.roles ? peer && instanceInfo.roles.peer.machines ? ${machine.name};
|
||||
isPeer = instanceInfo.roles ? peer && instanceInfo.roles.peer.machines or { } ? ${machine.name};
|
||||
in
|
||||
lib.optional (isController && isPeer) {
|
||||
inherit instanceName;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
|
||||
@@ -10,7 +11,28 @@ let
|
||||
"peer1"
|
||||
"peer2"
|
||||
"peer3"
|
||||
# external machine for external peer testing
|
||||
"external1"
|
||||
];
|
||||
|
||||
controllerPrefix =
|
||||
controllerName:
|
||||
builtins.readFile (
|
||||
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:
|
||||
builtins.readFile (
|
||||
config.clan.directory
|
||||
+ "/vars/shared/wireguard-network-wg-test-one-external-peer-${externalName}/suffix/value"
|
||||
);
|
||||
in
|
||||
{
|
||||
name = "wireguard";
|
||||
@@ -47,10 +69,19 @@ in
|
||||
|
||||
roles.controller.machines."controller1".settings = {
|
||||
endpoint = "192.168.1.1";
|
||||
# Enable IPv4 for external peers
|
||||
ipv4.enable = true;
|
||||
ipv4.address = "10.42.1.1/24";
|
||||
# add an external peer to controller1 with IPv4
|
||||
externalPeers.external1 = {
|
||||
ipv4.address = "10.42.1.50/32";
|
||||
};
|
||||
};
|
||||
|
||||
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 = {
|
||||
@@ -77,11 +108,77 @@ 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" = {
|
||||
|
||||
# Multiple IPs, one in each controller's subnet (IPv6) plus IPv4
|
||||
ips = [
|
||||
"${controller1Prefix + ":" + external1Suffix}/56"
|
||||
"${controller2Prefix + ":" + external1Suffix}/56"
|
||||
"10.42.1.50/32" # IPv4 address for controller1
|
||||
];
|
||||
|
||||
privateKeyFile =
|
||||
builtins.toFile "wg-priv-key"
|
||||
# This needs to be updated whenever update-vars was executed
|
||||
# Get the value from the generated vars via this command:
|
||||
# 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 (
|
||||
config.clan.directory + "/vars/per-machine/controller1/wireguard-keys-wg-test-one/publickey/value"
|
||||
)
|
||||
);
|
||||
|
||||
# Allow controller1's /56 subnet (IPv6) and IPv4 subnet
|
||||
allowedIPs = [
|
||||
"${controller1Prefix}::/56"
|
||||
"10.42.1.0/24" # IPv4 subnet for internet access
|
||||
];
|
||||
|
||||
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;
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
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")
|
||||
|
||||
@@ -93,10 +190,39 @@ in
|
||||
print("STARTING PING TESTS")
|
||||
print("="*60)
|
||||
|
||||
for m1 in 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")
|
||||
|
||||
# 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 IPv4 connectivity
|
||||
print("\n--- Testing external1 -> controller1 (IPv4) ---")
|
||||
external1.wait_until_succeeds("ping -c1 10.42.1.1 >&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")
|
||||
|
||||
'';
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
966fa6c74dbea65154816755934180edaf20fc031b15622c3e4433516c08fbbc
|
||||
@@ -0,0 +1 @@
|
||||
966fa6c74dbea65154816755934180edaf20fc031b15622c3e4433516c08fbbc
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"data": "ENC[AES256_GCM,data:dhEJ2dOGbK+oNj7YDzFD9455g5Cv7Ic8tZzPRI31ugzjHXOORbDq3+MyY/l/,iv:DP0S/taFONhNkVvvZQoTe2mnwRfJB7QgaILyZt9nT9E=,tag:TGFwbn6PTz4iGU7fnkSaWA==,type:str]",
|
||||
"sops": {
|
||||
"age": [
|
||||
{
|
||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBMQVRQNDNzUklFQnVIODZo\nbXNITlNhL1I2c1lyRjlUUGN2RTJLcFRJUjAwCmthT3JDRHdEa1RHM3pHT1pTQWd2\nMlkvMmVQRXk5VmZyTkU0RkpYVzlkNzgKLS0tIGVFY0ppc1dqQis5ay9HK2dydlht\neEI1eDlBekNtMTVoSk1hTFJUNUJiU2sK30cizM0xUDUDeTAQhtliL1KMNvnFcvxP\n8CFqOT36MBmIiC42lV0JTh/lsQbdirBNQLP9QUph8hGqbpehkleLlg==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
}
|
||||
],
|
||||
"lastmodified": "2025-10-15T04:08:18Z",
|
||||
"mac": "ENC[AES256_GCM,data:1BPlDmBFyNubxq2DRqsYUuh+WsbihR5D6OIA6Zdx1XPpOXxzxXXWHddtWxcaT14HLL3VKfINAI5MSIx7aVMuTgEJzaCkRSd11T/qwrV1mAiGW1bo3vXBVtbii271hYHCRaLuWnm4kIDyIV6onQARprTZgZ0Rlh0x5qwGnkk4uPI=,iv:Vda4Vxmm5j2nAIw2g1ydy9+bJHkHy7v7fKvS+4K1zds=,tag:qUpSRCa66D/LXDawXl9eOQ==,type:str]",
|
||||
"version": "3.11.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../../sops/users/admin
|
||||
@@ -0,0 +1 @@
|
||||
zrzikusuYoFNu7nF0HX6IdCYbCoZxOUutkVHArvSySc=
|
||||
@@ -0,0 +1 @@
|
||||
3f4b:fde7:06da:8b6a
|
||||
@@ -0,0 +1 @@
|
||||
25.11
|
||||
@@ -0,0 +1 @@
|
||||
966fa6c74dbea65154816755934180edaf20fc031b15622c3e4433516c08fbbc
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"data": "ENC[AES256_GCM,data:U5fkHGEM+/Xw9g1LGZckGWV225pnMZM28+oT4DscrMMt3XMFM+CRojd2JhC5,iv:ydAKRdJEKRI2wbcIsM/5YQQVkFVc3/JtCk3dwBJwJVw=,tag:ZiRsOzvFncUDZqok8fgh8A==,type:str]",
|
||||
"sops": {
|
||||
"age": [
|
||||
{
|
||||
"recipient": "age1qm0p4vf9jvcnn43s6l4prk8zn6cx0ep9gzvevxecv729xz540v8qa742eg",
|
||||
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB5OFFWRmp4R01WN0hZU2pu\nY0tCOWdrazZtbnVWczV2WnlPMUMvWUFmV1FBCjk1bnNtcmc5L0dRc1pTNUUwbzh2\nNUhhLzFycUFZd24yVEZVc3c4S2R0czAKLS0tIG8vY0lCRlhyYyszeEE0TzV4SWJj\nOFI0NFlvR25vNDB4KzJwdXQrTVJoS2MKHTgW/GlER4nP160Rcw9jZEraemouXrMz\nS9mLE/X3GBoAlgXmEwZC+7ZEZyNi/cd4FXX6edelD06S8uDjz0DKVg==\n-----END AGE ENCRYPTED FILE-----\n"
|
||||
}
|
||||
],
|
||||
"lastmodified": "2025-10-16T07:19:29Z",
|
||||
"mac": "ENC[AES256_GCM,data:DsQrWj8TwtgnAjDTFFu5ALjr3z3fx0k3grnPxk0zxo4RxvtNI6cv5de91YJ8GYvbElT+ylJul91XUSQ93PtqskrUDYvlKzU60zUDjR/G27g+BIHuKBYIDYNLFKjkoI2/KffbVexRFAqqvkjwSYbA2aJOLfyvUoZZhCuMmOW6jW4=,iv:gLe/wXzqix63d7Fv8vLDydU30ElkyVz6N2TlCueFInw=,tag:7QPD4kfyE4hUeGRC0cgsYg==,type:str]",
|
||||
"version": "3.11.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
../../../../../sops/users/admin
|
||||
@@ -0,0 +1 @@
|
||||
bb+pM+fpJzqc1A+dvTdsE4JliVvdMMXoQUPaElkrs0w=
|
||||
@@ -0,0 +1 @@
|
||||
3f4b:fde7:06da:8b6a
|
||||
Reference in New Issue
Block a user