wireguard: add support for external peers
This adds support for external peers via the instance option `roles.controller.<name>.settings.externalPeers = ["external1"]`. External peers are peers which are not associated wth a machine inside the clan, for example a mobile phone or a device that cannot be managed via clan for some reason.
This commit is contained in:
@@ -106,8 +106,30 @@ let
|
|||||||
in
|
in
|
||||||
"${peerIP} ${peerName}.${domain}"
|
"${peerIP} ${peerName}.${domain}"
|
||||||
) roles.peer.machines;
|
) roles.peer.machines;
|
||||||
|
|
||||||
|
# External peers
|
||||||
|
externalPeerHosts = lib.flatten (
|
||||||
|
lib.mapAttrsToList (
|
||||||
|
ctrlName: _ctrlValue:
|
||||||
|
lib.map (
|
||||||
|
peer:
|
||||||
|
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
|
in
|
||||||
builtins.concatStringsSep "\n" (controllerHosts ++ peerHosts);
|
"${peerIP} ${peer}.${domain}"
|
||||||
|
) (roles.controller.machines.${ctrlName}.settings.externalPeers)
|
||||||
|
) roles.controller.machines
|
||||||
|
);
|
||||||
|
in
|
||||||
|
builtins.concatStringsSep "\n" (controllerHosts ++ peerHosts ++ externalPeerHosts);
|
||||||
};
|
};
|
||||||
|
|
||||||
# Shared interface options
|
# Shared interface options
|
||||||
@@ -268,13 +290,34 @@ in
|
|||||||
{
|
{
|
||||||
imports = [ sharedInterface ];
|
imports = [ sharedInterface ];
|
||||||
|
|
||||||
options.endpoint = lib.mkOption {
|
options = {
|
||||||
|
endpoint = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
example = "vpn.clan.lol";
|
example = "vpn.clan.lol";
|
||||||
description = ''
|
description = ''
|
||||||
Endpoint where the controller can be reached
|
Endpoint where the controller can be reached
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
externalPeers = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
default = [ ];
|
||||||
|
example = [
|
||||||
|
"moms-phone"
|
||||||
|
"daddies-laptop"
|
||||||
|
];
|
||||||
|
description = ''
|
||||||
|
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.
|
||||||
|
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 =
|
perInstance =
|
||||||
{
|
{
|
||||||
@@ -310,8 +353,9 @@ in
|
|||||||
;
|
;
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
# Network allocation generator for this controller
|
# Network prefix allocation generator for this controller
|
||||||
clan.core.vars.generators."wireguard-network-${instanceName}" = {
|
clan.core.vars.generators = {
|
||||||
|
"wireguard-network-${instanceName}" = {
|
||||||
files.prefix.secret = false;
|
files.prefix.secret = false;
|
||||||
|
|
||||||
runtimeInputs = with pkgs; [
|
runtimeInputs = with pkgs; [
|
||||||
@@ -325,6 +369,36 @@ in
|
|||||||
${pkgs.python3}/bin/python3 ${./ipv6_allocator.py} "$out" "${instanceName}" controller "${machine.name}"
|
${pkgs.python3}/bin/python3 ${./ipv6_allocator.py} "$out" "${instanceName}" controller "${machine.name}"
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
# For external peers, generate: suffix, public key, private key
|
||||||
|
// lib.genAttrs' 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
|
# Enable ip forwarding, so wireguard peers can reach each other
|
||||||
boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = 1;
|
boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = 1;
|
||||||
@@ -344,11 +418,9 @@ in
|
|||||||
config.clan.core.vars.generators."wireguard-keys-${instanceName}".files."privatekey".path;
|
config.clan.core.vars.generators."wireguard-keys-${instanceName}".files."privatekey".path;
|
||||||
|
|
||||||
# Connect to all peers and other controllers
|
# Connect to all peers and other controllers
|
||||||
peers = lib.mapAttrsToList (
|
peers =
|
||||||
name: value:
|
# Peers configuration
|
||||||
if allPeers ? ${name} then
|
(lib.mapAttrsToList (name: _value: {
|
||||||
# For peers: they now have our entire /56 subnet
|
|
||||||
{
|
|
||||||
publicKey = (
|
publicKey = (
|
||||||
builtins.readFile (
|
builtins.readFile (
|
||||||
config.clan.core.settings.directory
|
config.clan.core.settings.directory
|
||||||
@@ -373,10 +445,39 @@ in
|
|||||||
) roles.controller.machines;
|
) roles.controller.machines;
|
||||||
|
|
||||||
persistentKeepalive = 25;
|
persistentKeepalive = 25;
|
||||||
}
|
}) allPeers)
|
||||||
else
|
++
|
||||||
# For other controllers: use their /56 subnet
|
# External peers configuration
|
||||||
{
|
(map (peer: {
|
||||||
|
publicKey = (
|
||||||
|
builtins.readFile (
|
||||||
|
config.clan.core.settings.directory
|
||||||
|
+ "/vars/shared/wireguard-network-${instanceName}-external-peer-${peer}/publickey/value"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
# Allow the external 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/shared/wireguard-network-${instanceName}-external-peer-${peer}/suffix/value"
|
||||||
|
);
|
||||||
|
in
|
||||||
|
"${controllerPrefix}:${peerSuffix}/96"
|
||||||
|
) roles.controller.machines;
|
||||||
|
|
||||||
|
# No endpoint for external peers, they initiate the connection
|
||||||
|
persistentKeepalive = 25;
|
||||||
|
}) settings.externalPeers)
|
||||||
|
++
|
||||||
|
# Other controllers configuration
|
||||||
|
(lib.mapAttrsToList (name: value: {
|
||||||
publicKey = (
|
publicKey = (
|
||||||
builtins.readFile (
|
builtins.readFile (
|
||||||
config.clan.core.settings.directory
|
config.clan.core.settings.directory
|
||||||
@@ -395,8 +496,7 @@ in
|
|||||||
|
|
||||||
endpoint = "${value.settings.endpoint}:${toString value.settings.port}";
|
endpoint = "${value.settings.endpoint}:${toString value.settings.port}";
|
||||||
persistentKeepalive = 25;
|
persistentKeepalive = 25;
|
||||||
}
|
}) allOtherControllers);
|
||||||
) (allPeers // allOtherControllers);
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
lib,
|
lib,
|
||||||
|
config,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
|
|
||||||
@@ -10,7 +11,23 @@ let
|
|||||||
"peer1"
|
"peer1"
|
||||||
"peer2"
|
"peer2"
|
||||||
"peer3"
|
"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"
|
||||||
|
);
|
||||||
|
# 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
|
in
|
||||||
{
|
{
|
||||||
name = "wireguard";
|
name = "wireguard";
|
||||||
@@ -47,6 +64,8 @@ in
|
|||||||
|
|
||||||
roles.controller.machines."controller1".settings = {
|
roles.controller.machines."controller1".settings = {
|
||||||
endpoint = "192.168.1.1";
|
endpoint = "192.168.1.1";
|
||||||
|
# add an external peer to controller1 only
|
||||||
|
externalPeers = [ "external1" ];
|
||||||
};
|
};
|
||||||
|
|
||||||
roles.controller.machines."controller2".settings = {
|
roles.controller.machines."controller2".settings = {
|
||||||
@@ -77,6 +96,48 @@ in
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
nodes.external1 =
|
||||||
|
let
|
||||||
|
controller1Prefix = controllerPrefix "controller1";
|
||||||
|
external1Suffix = externalPeerSuffix "external1";
|
||||||
|
in
|
||||||
|
{
|
||||||
|
networking.extraHosts = ''
|
||||||
|
${controller1Prefix}::1 controller1.wg-test-one
|
||||||
|
'';
|
||||||
|
networking.wireguard.interfaces."wg0" = {
|
||||||
|
|
||||||
|
ips = [ "${controller1Prefix + ":" + external1Suffix}/56" ];
|
||||||
|
|
||||||
|
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=";
|
||||||
|
|
||||||
|
peers = [
|
||||||
|
{
|
||||||
|
publicKey = (
|
||||||
|
builtins.readFile (
|
||||||
|
config.clan.directory + "/vars/per-machine/controller1/wireguard-keys-wg-test-one/publickey/value"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
# Allow each controller's /56 subnet
|
||||||
|
allowedIPs = [
|
||||||
|
# "${controller1Prefix}::/56"
|
||||||
|
"::/0"
|
||||||
|
];
|
||||||
|
|
||||||
|
endpoint = "controller1:51820";
|
||||||
|
|
||||||
|
persistentKeepalive = 25;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
testScript = ''
|
testScript = ''
|
||||||
start_all()
|
start_all()
|
||||||
|
|
||||||
@@ -94,9 +155,13 @@ in
|
|||||||
print("="*60)
|
print("="*60)
|
||||||
|
|
||||||
for m1 in machines:
|
for m1 in machines:
|
||||||
|
# ping all other machines
|
||||||
for m2 in machines:
|
for m2 in machines:
|
||||||
if m1 != m2:
|
if m1 != m2:
|
||||||
print(f"\n--- Pinging from {m1.name} to {m2.name}.wg-test-one ---")
|
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")
|
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")
|
||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user