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; 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 configForNetwork = wgName: wg: let
inherit inherit
(extraLib.wireguard wgName) (extraLib.wireguard wgName nodes)
allPeers allPeers
peerPresharedKeyPath peerPresharedKeyPath
peerPresharedKeySecret peerPresharedKeySecret
@ -46,7 +46,7 @@
peerPublicKeyPath peerPublicKeyPath
; ;
otherPeers = filterAttrs (n: _: n != nodeName) (allPeers nodes); otherPeers = filterAttrs (n: _: n != nodeName) allPeers;
in { in {
secrets = secrets =
concatAttrs (map (other: { concatAttrs (map (other: {
@ -130,8 +130,12 @@ in {
default = {}; default = {};
example = {my-android-phone = ["10.0.0.97/32"];}; example = {my-android-phone = ["10.0.0.97/32"];};
description = mdDoc '' description = mdDoc ''
Allows defining extra set of external peers that should be added to the configuration. Allows defining an extra set of peers that should be added to this wireguard network,
For each external peers you can define one or multiple allowed ips. 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 { in {
assertions = concatMap (wgName: let assertions = concatMap (wgName: let
inherit inherit
(extraLib.wireguard wgName) (extraLib.wireguard wgName nodes)
externalPeerNamesRaw externalPeerNamesRaw
usedAddresses usedAddresses
associatedNodes associatedNodes
; ;
duplicatePeers = duplicates (externalPeerNamesRaw nodes); duplicatePeers = duplicates externalPeerNamesRaw;
duplicateAddrs = duplicates (map (x: head (splitString "/" x)) (usedAddresses nodes)); duplicateAddrs = duplicates (map (x: head (splitString "/" x)) usedAddresses);
in [ 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."; message = "Wireguard network '${wgName}': At least one node must be a server.";
} }
{ {
@ -165,6 +169,10 @@ in {
assertion = duplicateAddrs == []; assertion = duplicateAddrs == [];
message = "Wireguard network '${wgName}': Addresses used multiple times: ${concatStringsSep ", " 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); ]) (attrNames cfg);
networking.firewall.allowedUDPPorts = mkIf (cfg.server.enable && cfg.server.openFirewall) [cfg.server.port]; networking.firewall.allowedUDPPorts = mkIf (cfg.server.enable && cfg.server.openFirewall) [cfg.server.port];

View file

@ -11,6 +11,7 @@
concatStringsSep concatStringsSep
escapeShellArg escapeShellArg
filter filter
optionalString
removeSuffix removeSuffix
substring substring
unique unique
@ -33,17 +34,19 @@
generateNetworkKeys = wgName: let generateNetworkKeys = wgName: let
inherit inherit
(extraLib.wireguard wgName) (extraLib.wireguard wgName self.nodes)
allPeers allPeers
associatedNodes associatedNodes
associatedServerNodes
associatedClientNodes
externalPeersForNode
peerPresharedKeyFile peerPresharedKeyFile
peerPrivateKeyFile peerPrivateKeyFile
peerPublicKeyFile peerPublicKeyFile
sortedPeers
; ;
nodesWithNet = associatedNodes self.nodes; # Every peer needs a private and public key.
peers = attrNames (allPeers self.nodes);
generatePeerKeys = peerName: let generatePeerKeys = peerName: let
keyBasename = escapeShellArg ("./" + removeSuffix ".pub" (peerPublicKeyFile peerName)); keyBasename = escapeShellArg ("./" + removeSuffix ".pub" (peerPublicKeyFile peerName));
pubkeyFile = escapeShellArg ("./" + peerPublicKeyFile peerName); pubkeyFile = escapeShellArg ("./" + peerPublicKeyFile peerName);
@ -59,22 +62,35 @@
fi fi
''; '';
generatePeerPsks = nodePeerName: # Generates the psk for peer1 and peer2.
map (peerName: let generatePeerPsk = {
pskFile = escapeShellArg ("./" + peerPresharedKeyFile nodePeerName peerName); peer1,
in '' peer2,
if [[ ! -e ${pskFile} ]]; then }: let
mkdir -p $(dirname ${pskFile}) pskFile = escapeShellArg ("./" + peerPresharedKeyFile peer1 peer2);
echo "Generating "${pskFile}"" in ''
psk=$(${pkgs.wireguard-tools}/bin/wg genpsk) if [[ ! -e ${pskFile} ]]; then
${pkgs.rage}/bin/rage -e ${masterIdentityArgs} ${extraEncryptionPubkeys} <<< "$psk" > ${pskFile} \ mkdir -p $(dirname ${pskFile})
|| { echo "error: Failed to encrypt wireguard psk for peers ${nodePeerName} and ${peerName} on network ${wgName}!" >&2; exit 1; } echo "Generating "${pskFile}""
fi psk=$(${pkgs.wireguard-tools}/bin/wg genpsk)
'') (filter (x: x != nodePeerName) peers); ${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 in
["echo ==== ${wgName} ===="] ["echo ==== ${wgName} ===="]
++ map generatePeerKeys peers ++ map generatePeerKeys (attrNames allPeers)
++ concatMap generatePeerPsks nodesWithNet; # 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 in
pkgs.writeShellScript "generate-wireguard-keys" '' pkgs.writeShellScript "generate-wireguard-keys" ''
set -euo pipefail set -euo pipefail

View file

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

View file

@ -11,6 +11,7 @@
mapAttrs' mapAttrs'
mergeAttrs mergeAttrs
nameValuePair nameValuePair
partition
unique unique
; ;
in rec { in rec {
@ -33,8 +34,8 @@ in rec {
concatAttrs = foldl' mergeAttrs {}; concatAttrs = foldl' mergeAttrs {};
# Wireguard related functions that are reused in several files of this flake # Wireguard related functions that are reused in several files of this flake
wireguard = wgName: rec { wireguard = wgName: nodes: rec {
_sortedPeers = peerA: peerB: sortedPeers = peerA: peerB:
if peerA < peerB if peerA < peerB
then { then {
peer1 = peerA; peer1 = peerA;
@ -53,32 +54,49 @@ in rec {
peerPrivateKeySecret = peerName: "wireguard-${wgName}-priv-${peerName}"; peerPrivateKeySecret = peerName: "wireguard-${wgName}-priv-${peerName}";
peerPresharedKeyFile = peerA: peerB: let peerPresharedKeyFile = peerA: peerB: let
inherit (_sortedPeers peerA peerB) peer1 peer2; inherit (sortedPeers peerA peerB) peer1 peer2;
in "secrets/wireguard/${wgName}/psks/${peer1}+${peer2}.age"; in "secrets/wireguard/${wgName}/psks/${peer1}+${peer2}.age";
peerPresharedKeyPath = peerA: peerB: "${../.}/" + peerPresharedKeyFile peerA peerB; peerPresharedKeyPath = peerA: peerB: "${../.}/" + peerPresharedKeyFile peerA peerB;
peerPresharedKeySecret = peerA: peerB: let peerPresharedKeySecret = peerA: peerB: let
inherit (_sortedPeers peerA peerB) peer1 peer2; inherit (sortedPeers peerA peerB) peer1 peer2;
in "wireguard-${wgName}-psks-${peer1}+${peer2}"; in "wireguard-${wgName}-psks-${peer1}+${peer2}";
# All nodes that are part of this network # All nodes that are part of this network
associatedNodes = nodes: filter (n: builtins.hasAttr wgName nodes.${n}.config.extra.wireguard) (attrNames nodes); associatedNodes =
nodePeers = nodes: genAttrs (associatedNodes nodes) (n: nodes.${n}.config.extra.wireguard.${wgName}.address); 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. # All peers that are defined as externalPeers on any node.
# Prepends "external-" to their name. # Prepends "external-" to their name.
externalPeers = nodes: allExternalPeers = concatAttrs (map externalPeersForNode associatedNodes);
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);
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);
}; };
} }