feat(wireguard): associate external peers to the specific defining node

This commit is contained in:
oddlama 2023-04-14 16:24:41 +02:00
parent d522a46f1d
commit 925d3856e0
No known key found for this signature in database
GPG key ID: 14EFE510775FE39A
6 changed files with 103 additions and 47 deletions

View file

@ -20,4 +20,6 @@
dhcpV6Config.RouteMetric = 40;
};
};
extra.wireguard.vms.address = ["10.0.0.10/32"];
}

View file

@ -17,4 +17,16 @@
};
};
};
extra.wireguard.vms = {
server = {
enable = true;
port = 51822;
openFirewall = true;
};
address = ["10.0.0.2/24"];
externalPeers = {
zack1 = ["10.0.0.90/32"];
};
};
}

View file

@ -37,7 +37,7 @@
configForNetwork = wgName: wg: let
inherit
(extraLib.wireguard wgName)
(extraLib.wireguard wgName nodes)
allPeers
peerPresharedKeyPath
peerPresharedKeySecret
@ -46,7 +46,7 @@
peerPublicKeyPath
;
otherPeers = filterAttrs (n: _: n != nodeName) (allPeers nodes);
otherPeers = filterAttrs (n: _: n != nodeName) allPeers;
in {
secrets =
concatAttrs (map (other: {
@ -130,8 +130,12 @@ in {
default = {};
example = {my-android-phone = ["10.0.0.97/32"];};
description = mdDoc ''
Allows defining extra set of external peers that should be added to the configuration.
For each external peers you can define one or multiple allowed ips.
Allows defining an extra set of peers that should be added to this wireguard network,
but will not be managed by this flake. (e.g. phones)
These external peers will only know this node as a peer, which will forward
their traffic to other members of the network if required. This requires
this node to act as a server.
'';
};
};
@ -144,17 +148,17 @@ in {
in {
assertions = concatMap (wgName: let
inherit
(extraLib.wireguard wgName)
(extraLib.wireguard wgName nodes)
externalPeerNamesRaw
usedAddresses
associatedNodes
;
duplicatePeers = duplicates (externalPeerNamesRaw nodes);
duplicateAddrs = duplicates (map (x: head (splitString "/" x)) (usedAddresses nodes));
duplicatePeers = duplicates externalPeerNamesRaw;
duplicateAddrs = duplicates (map (x: head (splitString "/" x)) usedAddresses);
in [
{
assertion = any (n: nodes.${n}.config.extra.wireguard.${wgName}.server.enable) (associatedNodes nodes);
assertion = any (n: nodes.${n}.config.extra.wireguard.${wgName}.server.enable) associatedNodes;
message = "Wireguard network '${wgName}': At least one node must be a server.";
}
{
@ -165,6 +169,10 @@ in {
assertion = duplicateAddrs == [];
message = "Wireguard network '${wgName}': Addresses used multiple times: ${concatStringsSep ", " duplicateAddrs}";
}
# TODO externalPeers != [] -> server.listen
# TODO externalPeers != [] -> ip forwarding
# TODO psks only between all nodes and each node-externalpeer pair
# TODO no overlapping allowed ip range? 0.0.0.0 would be ok to overlap though
]) (attrNames cfg);
networking.firewall.allowedUDPPorts = mkIf (cfg.server.enable && cfg.server.openFirewall) [cfg.server.port];

View file

@ -11,6 +11,7 @@
concatStringsSep
escapeShellArg
filter
optionalString
removeSuffix
substring
unique
@ -33,17 +34,19 @@
generateNetworkKeys = wgName: let
inherit
(extraLib.wireguard wgName)
(extraLib.wireguard wgName self.nodes)
allPeers
associatedNodes
associatedServerNodes
associatedClientNodes
externalPeersForNode
peerPresharedKeyFile
peerPrivateKeyFile
peerPublicKeyFile
sortedPeers
;
nodesWithNet = associatedNodes self.nodes;
peers = attrNames (allPeers self.nodes);
# Every peer needs a private and public key.
generatePeerKeys = peerName: let
keyBasename = escapeShellArg ("./" + removeSuffix ".pub" (peerPublicKeyFile peerName));
pubkeyFile = escapeShellArg ("./" + peerPublicKeyFile peerName);
@ -59,22 +62,35 @@
fi
'';
generatePeerPsks = nodePeerName:
map (peerName: let
pskFile = escapeShellArg ("./" + peerPresharedKeyFile nodePeerName peerName);
in ''
if [[ ! -e ${pskFile} ]]; then
mkdir -p $(dirname ${pskFile})
echo "Generating "${pskFile}""
psk=$(${pkgs.wireguard-tools}/bin/wg genpsk)
${pkgs.rage}/bin/rage -e ${masterIdentityArgs} ${extraEncryptionPubkeys} <<< "$psk" > ${pskFile} \
|| { echo "error: Failed to encrypt wireguard psk for peers ${nodePeerName} and ${peerName} on network ${wgName}!" >&2; exit 1; }
fi
'') (filter (x: x != nodePeerName) peers);
# Generates the psk for peer1 and peer2.
generatePeerPsk = {
peer1,
peer2,
}: let
pskFile = escapeShellArg ("./" + peerPresharedKeyFile peer1 peer2);
in ''
if [[ ! -e ${pskFile} ]]; then
mkdir -p $(dirname ${pskFile})
echo "Generating "${pskFile}""
psk=$(${pkgs.wireguard-tools}/bin/wg genpsk)
${pkgs.rage}/bin/rage -e ${masterIdentityArgs} ${extraEncryptionPubkeys} <<< "$psk" > ${pskFile} \
|| { echo "error: Failed to encrypt wireguard psk for peers ${peer1} and ${peer2} on network ${wgName}!" >&2; exit 1; }
fi
'';
# This generates all psks for each combination of peers given.
# xs is a list of peers and fys a function that generates a list of peers
# for any given x.
psksForPeerCombinations = xs: fys: map generatePeerPsk (unique (concatMap (x: map (sortedPeers x) (fys x)) xs));
in
["echo ==== ${wgName} ===="]
++ map generatePeerKeys peers
++ concatMap generatePeerPsks nodesWithNet;
++ map generatePeerKeys (attrNames allPeers)
# All server-nodes need a psk for each other, but not reflexive.
++ psksForPeerCombinations associatedServerNodes (n: filter (x: x != n) associatedServerNodes)
# Each server-node need a psk for all client nodes
++ psksForPeerCombinations associatedServerNodes (_: associatedClientNodes)
# Each server-node need a psk for all their external peers
++ psksForPeerCombinations associatedServerNodes (n: attrNames (externalPeersForNode n));
in
pkgs.writeShellScript "generate-wireguard-keys" ''
set -euo pipefail

View file

@ -20,7 +20,7 @@
externalPeersForNet = wgName:
map (peer: {inherit wgName peer;})
(attrNames ((extraLib.wireguard wgName).externalPeers self.nodes));
(attrNames (extraLib.wireguard wgName self.nodes).allExternalPeers);
allExternalPeers = concatMap externalPeersForNet wireguardNetworks;
in
# TODO generate "classic" config and run qrencode

View file

@ -11,6 +11,7 @@
mapAttrs'
mergeAttrs
nameValuePair
partition
unique
;
in rec {
@ -33,8 +34,8 @@ in rec {
concatAttrs = foldl' mergeAttrs {};
# Wireguard related functions that are reused in several files of this flake
wireguard = wgName: rec {
_sortedPeers = peerA: peerB:
wireguard = wgName: nodes: rec {
sortedPeers = peerA: peerB:
if peerA < peerB
then {
peer1 = peerA;
@ -53,32 +54,49 @@ in rec {
peerPrivateKeySecret = peerName: "wireguard-${wgName}-priv-${peerName}";
peerPresharedKeyFile = peerA: peerB: let
inherit (_sortedPeers peerA peerB) peer1 peer2;
inherit (sortedPeers peerA peerB) peer1 peer2;
in "secrets/wireguard/${wgName}/psks/${peer1}+${peer2}.age";
peerPresharedKeyPath = peerA: peerB: "${../.}/" + peerPresharedKeyFile peerA peerB;
peerPresharedKeySecret = peerA: peerB: let
inherit (_sortedPeers peerA peerB) peer1 peer2;
inherit (sortedPeers peerA peerB) peer1 peer2;
in "wireguard-${wgName}-psks-${peer1}+${peer2}";
# All nodes that are part of this network
associatedNodes = nodes: filter (n: builtins.hasAttr wgName nodes.${n}.config.extra.wireguard) (attrNames nodes);
nodePeers = nodes: genAttrs (associatedNodes nodes) (n: nodes.${n}.config.extra.wireguard.${wgName}.address);
associatedNodes =
filter
(n: builtins.hasAttr wgName nodes.${n}.config.extra.wireguard)
(attrNames nodes);
# Partition nodes by whether they are servers
_associatedNodes_isServerPartition =
partition
(n: nodes.${n}.config.extra.wireguard.${wgName}.server.enable)
associatedNodes;
associatedServerNodes = _associatedNodes_isServerPartition.right;
associatedClientNodes = _associatedNodes_isServerPartition.wrong;
# Maps all nodes that are part of this network to their addresses
nodePeers = genAttrs associatedNodes (n: nodes.${n}.config.extra.wireguard.${wgName}.address);
# Only peers that are defined as externalPeers on the given node.
# Prepends "external-" to their name.
externalPeersForNode = node:
mapAttrs' (p: nameValuePair "external-${p}") nodes.${node}.config.extra.wireguard.${wgName}.externalPeers;
# All peers that are defined as externalPeers on any node.
# Prepends "external-" to their name.
externalPeers = nodes:
concatAttrs (
map (n: mapAttrs' (extPeerName: nameValuePair "external-${extPeerName}") nodes.${n}.config.extra.wireguard.${wgName}.externalPeers)
(associatedNodes nodes)
);
# Concatenation of all external peer names names without any transformations.
externalPeerNamesRaw = nodes: concatMap (n: attrNames nodes.${n}.config.extra.wireguard.${wgName}.externalPeers) (associatedNodes nodes);
# A list of all occurring addresses.
usedAddresses = nodes: let
nodesWithNet = associatedNodes nodes;
in
concatMap (n: nodes.${n}.config.extra.wireguard.${wgName}.address) nodesWithNet
++ flatten (concatMap (n: attrValues nodes.${n}.config.extra.wireguard.${wgName}.externalPeers) nodesWithNet);
allExternalPeers = concatAttrs (map externalPeersForNode associatedNodes);
allPeers = nodes: nodePeers nodes // externalPeers nodes;
# All peers that are part of this network
allPeers = nodePeers // allExternalPeers;
# Concatenation of all external peer names names without any transformations.
externalPeerNamesRaw = concatMap (n: attrNames nodes.${n}.config.extra.wireguard.${wgName}.externalPeers) associatedNodes;
# A list of all occurring addresses.
usedAddresses =
concatMap (n: nodes.${n}.config.extra.wireguard.${wgName}.address) associatedNodes
++ flatten (concatMap (n: attrValues nodes.${n}.config.extra.wireguard.${wgName}.externalPeers) associatedNodes);
};
}