224 lines
8.9 KiB
Nix
224 lines
8.9 KiB
Nix
inputs: final: prev: let
|
|
inherit
|
|
(inputs.nixpkgs.lib)
|
|
assertMsg
|
|
attrNames
|
|
attrValues
|
|
concatLists
|
|
concatMap
|
|
concatStringsSep
|
|
escapeShellArg
|
|
filter
|
|
flatten
|
|
flip
|
|
genAttrs
|
|
mapAttrs'
|
|
nameValuePair
|
|
partition
|
|
removeSuffix
|
|
;
|
|
|
|
inherit
|
|
(final.lib)
|
|
net
|
|
concatAttrs
|
|
types
|
|
;
|
|
|
|
inherit
|
|
(final.lib.secrets)
|
|
rageDecryptArgs
|
|
;
|
|
in {
|
|
lib =
|
|
prev.lib
|
|
// {
|
|
wireguard = userInputs: wgName: let
|
|
inherit (userInputs.self) nodes;
|
|
# Returns the given node's wireguard configuration of this network
|
|
wgCfgOf = node: nodes.${node}.config.wireguard.${wgName};
|
|
|
|
sortedPeers = peerA: peerB:
|
|
if peerA < peerB
|
|
then {
|
|
peer1 = peerA;
|
|
peer2 = peerB;
|
|
}
|
|
else {
|
|
peer1 = peerB;
|
|
peer2 = peerA;
|
|
};
|
|
|
|
peerPublicKeyFile = peerName: "/secrets/wireguard/${wgName}/keys/${peerName}.pub";
|
|
peerPublicKeyPath = peerName: userInputs.self.outPath + peerPublicKeyFile peerName;
|
|
|
|
peerPrivateKeyFile = peerName: "/secrets/wireguard/${wgName}/keys/${peerName}.age";
|
|
peerPrivateKeyPath = peerName: userInputs.self.outPath + peerPrivateKeyFile peerName;
|
|
peerPrivateKeySecret = peerName: "wireguard-${wgName}-priv-${peerName}";
|
|
|
|
peerPresharedKeyFile = peerA: peerB: let
|
|
inherit (sortedPeers peerA peerB) peer1 peer2;
|
|
in "/secrets/wireguard/${wgName}/psks/${peer1}+${peer2}.age";
|
|
peerPresharedKeyPath = peerA: peerB: userInputs.self.outPath + peerPresharedKeyFile peerA peerB;
|
|
peerPresharedKeySecret = peerA: peerB: let
|
|
inherit (sortedPeers peerA peerB) peer1 peer2;
|
|
in "wireguard-${wgName}-psks-${peer1}+${peer2}";
|
|
|
|
# All nodes that are part of this network
|
|
participatingNodes =
|
|
filter
|
|
(n: builtins.hasAttr wgName nodes.${n}.config.wireguard)
|
|
(attrNames nodes);
|
|
|
|
# Partition nodes by whether they are servers
|
|
_participatingNodes_isServerPartition =
|
|
partition
|
|
(n: (wgCfgOf n).server.host != null)
|
|
participatingNodes;
|
|
|
|
participatingServerNodes = _participatingNodes_isServerPartition.right;
|
|
participatingClientNodes = _participatingNodes_isServerPartition.wrong;
|
|
|
|
# Maps all nodes that are part of this network to their addresses
|
|
nodePeers = genAttrs participatingNodes (n: (wgCfgOf n).addresses);
|
|
|
|
externalPeerName = p: "external-${p}";
|
|
|
|
# Only peers that are defined as externalPeers on the given node.
|
|
# Prepends "external-" to their name.
|
|
externalPeersForNode = node:
|
|
mapAttrs' (p: nameValuePair (externalPeerName p)) (wgCfgOf node).server.externalPeers;
|
|
|
|
# All peers that are defined as externalPeers on any node.
|
|
# Prepends "external-" to their name.
|
|
allExternalPeers = concatAttrs (map externalPeersForNode participatingNodes);
|
|
|
|
# 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 (wgCfgOf n).server.externalPeers) participatingNodes;
|
|
|
|
# A list of all occurring addresses.
|
|
usedAddresses =
|
|
concatMap (n: (wgCfgOf n).addresses) participatingNodes
|
|
++ flatten (concatMap (n: attrValues (wgCfgOf n).server.externalPeers) participatingNodes);
|
|
|
|
# A list of all occurring addresses, but only includes addresses that
|
|
# are not assigned automatically.
|
|
explicitlyUsedAddresses =
|
|
flip concatMap participatingNodes
|
|
(n:
|
|
filter (x: !types.isLazyValue x)
|
|
(concatLists
|
|
(nodes.${n}.options.wireguard.type.functor.wrapped.getSubOptions (wgCfgOf n)).addresses.definitions))
|
|
++ flatten (concatMap (n: attrValues (wgCfgOf n).server.externalPeers) participatingNodes);
|
|
|
|
# The cidrv4 and cidrv6 of the network spanned by all participating peer addresses.
|
|
# This also takes into account any reserved address ranges that should be part of the network.
|
|
networkAddresses =
|
|
net.cidr.merge (usedAddresses
|
|
++ concatMap (n: (wgCfgOf n).server.reservedAddresses) participatingServerNodes);
|
|
|
|
# The network spanning cidr addresses. The respective cidrv4 and cirdv6 are only
|
|
# included if they exist.
|
|
networkCidrs = filter (x: x != null) (attrValues networkAddresses);
|
|
|
|
# The cidrv4 and cidrv6 of the network spanned by all reserved addresses only.
|
|
# Used to determine automatically assigned addresses first.
|
|
spannedReservedNetwork =
|
|
net.cidr.merge (concatMap (n: (wgCfgOf n).server.reservedAddresses) participatingServerNodes);
|
|
|
|
# Assigns an ipv4 address from spannedReservedNetwork.cidrv4
|
|
# to each participant that has not explicitly specified an ipv4 address.
|
|
assignedIpv4Addresses = assert assertMsg
|
|
(spannedReservedNetwork.cidrv4 != null)
|
|
"Wireguard network '${wgName}': At least one participating node must reserve a cidrv4 address via `reservedAddresses` so that ipv4 addresses can be assigned automatically from that network.";
|
|
net.cidr.assignIps
|
|
spannedReservedNetwork.cidrv4
|
|
# Don't assign any addresses that are explicitly configured on other hosts
|
|
(filter (x: net.cidr.contains x spannedReservedNetwork.cidrv4) (filter net.ip.isv4 explicitlyUsedAddresses))
|
|
participatingNodes;
|
|
|
|
# Assigns an ipv6 address from spannedReservedNetwork.cidrv6
|
|
# to each participant that has not explicitly specified an ipv6 address.
|
|
assignedIpv6Addresses = assert assertMsg
|
|
(spannedReservedNetwork.cidrv6 != null)
|
|
"Wireguard network '${wgName}': At least one participating node must reserve a cidrv6 address via `reservedAddresses` so that ipv6 addresses can be assigned automatically from that network.";
|
|
net.cidr.assignIps
|
|
spannedReservedNetwork.cidrv6
|
|
# Don't assign any addresses that are explicitly configured on other hosts
|
|
(filter (x: net.cidr.contains x spannedReservedNetwork.cidrv6) (filter net.ip.isv6 explicitlyUsedAddresses))
|
|
participatingNodes;
|
|
|
|
# Appends / replaces the correct cidr length to the argument,
|
|
# so that the resulting address is in the cidr.
|
|
toNetworkAddr = addr: let
|
|
relevantNetworkAddr =
|
|
if net.ip.isv6 addr
|
|
then networkAddresses.cidrv6
|
|
else networkAddresses.cidrv4;
|
|
in "${net.cidr.ip addr}/${toString (net.cidr.length relevantNetworkAddr)}";
|
|
|
|
# Creates a script that when executed outputs a wg-quick compatible configuration
|
|
# file for use with external peers. This is a script so we can access secrets without
|
|
# storing them in the nix-store.
|
|
wgQuickConfigScript = system: serverNode: extPeer: let
|
|
pkgs = userInputs.self.pkgs.${system};
|
|
snCfg = wgCfgOf serverNode;
|
|
peerName = externalPeerName extPeer;
|
|
addresses = map toNetworkAddr snCfg.server.externalPeers.${extPeer};
|
|
in
|
|
pkgs.writeShellScript "create-wg-conf-${wgName}-${serverNode}-${extPeer}" ''
|
|
privKey=$(${pkgs.rage}/bin/rage -d ${rageDecryptArgs} ${escapeShellArg (peerPrivateKeyPath peerName)}) \
|
|
|| { echo "[1;31merror:[m Failed to decrypt!" >&2; exit 1; }
|
|
serverPsk=$(${pkgs.rage}/bin/rage -d ${rageDecryptArgs} ${escapeShellArg (peerPresharedKeyPath serverNode peerName)}) \
|
|
|| { echo "[1;31merror:[m Failed to decrypt!" >&2; exit 1; }
|
|
|
|
cat <<EOF
|
|
[Interface]
|
|
Address = ${concatStringsSep ", " addresses}
|
|
PrivateKey = $privKey
|
|
|
|
[Peer]
|
|
PublicKey = ${removeSuffix "\n" (builtins.readFile (peerPublicKeyPath serverNode))}
|
|
PresharedKey = $serverPsk
|
|
AllowedIPs = ${concatStringsSep ", " networkCidrs}
|
|
Endpoint = ${snCfg.server.host}:${toString snCfg.server.port}
|
|
PersistentKeepalive = 25
|
|
EOF
|
|
'';
|
|
in {
|
|
inherit
|
|
allExternalPeers
|
|
allPeers
|
|
assignedIpv4Addresses
|
|
assignedIpv6Addresses
|
|
explicitlyUsedAddresses
|
|
externalPeerName
|
|
externalPeerNamesRaw
|
|
externalPeersForNode
|
|
networkAddresses
|
|
networkCidrs
|
|
nodePeers
|
|
participatingClientNodes
|
|
participatingNodes
|
|
participatingServerNodes
|
|
peerPresharedKeyFile
|
|
peerPresharedKeyPath
|
|
peerPresharedKeySecret
|
|
peerPrivateKeyFile
|
|
peerPrivateKeyPath
|
|
peerPrivateKeySecret
|
|
peerPublicKeyFile
|
|
peerPublicKeyPath
|
|
sortedPeers
|
|
spannedReservedNetwork
|
|
toNetworkAddr
|
|
usedAddresses
|
|
wgCfgOf
|
|
wgQuickConfigScript
|
|
;
|
|
};
|
|
};
|
|
}
|