mirror of
https://github.com/oddlama/nixos-extra-modules.git
synced 2025-10-10 22:00:39 +02:00
chore: remove external peers from wireguard module
This commit is contained in:
parent
2502ff50ab
commit
0660c722cf
2 changed files with 278 additions and 361 deletions
|
@ -6,30 +6,18 @@ inputs: final: prev: let
|
||||||
attrValues
|
attrValues
|
||||||
concatLists
|
concatLists
|
||||||
concatMap
|
concatMap
|
||||||
concatStringsSep
|
|
||||||
escapeShellArg
|
|
||||||
filter
|
filter
|
||||||
flatten
|
|
||||||
flip
|
flip
|
||||||
genAttrs
|
genAttrs
|
||||||
mapAttrs'
|
|
||||||
nameValuePair
|
|
||||||
partition
|
partition
|
||||||
removeSuffix
|
|
||||||
warn
|
warn
|
||||||
;
|
;
|
||||||
|
|
||||||
inherit
|
inherit
|
||||||
(final.lib)
|
(final.lib)
|
||||||
net
|
net
|
||||||
concatAttrs
|
|
||||||
types
|
types
|
||||||
;
|
;
|
||||||
|
|
||||||
inherit
|
|
||||||
(final.lib.secrets)
|
|
||||||
rageDecryptArgs
|
|
||||||
;
|
|
||||||
in {
|
in {
|
||||||
lib =
|
lib =
|
||||||
prev.lib
|
prev.lib
|
||||||
|
@ -66,15 +54,15 @@ in {
|
||||||
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
|
||||||
participatingNodes =
|
participatingNodes = filter (n: builtins.hasAttr wgName nodes.${n}.config.wireguard) (
|
||||||
filter
|
attrNames nodes
|
||||||
(n: builtins.hasAttr wgName nodes.${n}.config.wireguard)
|
);
|
||||||
(attrNames nodes);
|
|
||||||
|
|
||||||
# Partition nodes by whether they are servers
|
# Partition nodes by whether they are servers
|
||||||
_participatingNodes_isServerPartition =
|
_participatingNodes_isServerPartition =
|
||||||
partition
|
partition (
|
||||||
(n: (wgCfgOf n).server.host != null)
|
n: (wgCfgOf n).server.host != null
|
||||||
|
)
|
||||||
participatingNodes;
|
participatingNodes;
|
||||||
|
|
||||||
participatingServerNodes = _participatingNodes_isServerPartition.right;
|
participatingServerNodes = _participatingNodes_isServerPartition.right;
|
||||||
|
@ -83,43 +71,26 @@ in {
|
||||||
# Maps all nodes that are part of this network to their addresses
|
# Maps all nodes that are part of this network to their addresses
|
||||||
nodePeers = genAttrs participatingNodes (n: (wgCfgOf n).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.
|
# A list of all occurring addresses.
|
||||||
usedAddresses =
|
usedAddresses = concatMap (n: (wgCfgOf n).addresses) participatingNodes;
|
||||||
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
|
# A list of all occurring addresses, but only includes addresses that
|
||||||
# are not assigned automatically.
|
# are not assigned automatically.
|
||||||
explicitlyUsedAddresses =
|
explicitlyUsedAddresses = flip concatMap participatingNodes (
|
||||||
flip concatMap participatingNodes
|
n:
|
||||||
(n:
|
filter (x: !types.isLazyValue x) (
|
||||||
filter (x: !types.isLazyValue x)
|
concatLists
|
||||||
(concatLists
|
(nodes.${n}.options.wireguard.type.nestedTypes.elemType.getSubOptions (wgCfgOf n))
|
||||||
(nodes.${n}.options.wireguard.type.nestedTypes.elemType.getSubOptions (wgCfgOf n)).addresses.definitions))
|
.addresses
|
||||||
++ flatten (concatMap (n: attrValues (wgCfgOf n).server.externalPeers) participatingNodes);
|
.definitions
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
# The cidrv4 and cidrv6 of the network spanned by all participating peer addresses.
|
# 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.
|
# This also takes into account any reserved address ranges that should be part of the network.
|
||||||
networkAddresses =
|
networkAddresses = net.cidr.merge (
|
||||||
net.cidr.merge (usedAddresses
|
usedAddresses ++ concatMap (n: (wgCfgOf n).server.reservedAddresses) participatingServerNodes
|
||||||
++ concatMap (n: (wgCfgOf n).server.reservedAddresses) participatingServerNodes);
|
);
|
||||||
|
|
||||||
# The network spanning cidr addresses. The respective cidrv4 and cirdv6 are only
|
# The network spanning cidr addresses. The respective cidrv4 and cirdv6 are only
|
||||||
# included if they exist.
|
# included if they exist.
|
||||||
|
@ -127,29 +98,30 @@ in {
|
||||||
|
|
||||||
# The cidrv4 and cidrv6 of the network spanned by all reserved addresses only.
|
# The cidrv4 and cidrv6 of the network spanned by all reserved addresses only.
|
||||||
# Used to determine automatically assigned addresses first.
|
# Used to determine automatically assigned addresses first.
|
||||||
spannedReservedNetwork =
|
spannedReservedNetwork = net.cidr.merge (
|
||||||
net.cidr.merge (concatMap (n: (wgCfgOf n).server.reservedAddresses) participatingServerNodes);
|
concatMap (n: (wgCfgOf n).server.reservedAddresses) participatingServerNodes
|
||||||
|
);
|
||||||
|
|
||||||
# Assigns an ipv4 address from spannedReservedNetwork.cidrv4
|
# Assigns an ipv4 address from spannedReservedNetwork.cidrv4
|
||||||
# to each participant that has not explicitly specified an ipv4 address.
|
# to each participant that has not explicitly specified an ipv4 address.
|
||||||
assignedIpv4Addresses = assert assertMsg
|
assignedIpv4Addresses = assert assertMsg (spannedReservedNetwork.cidrv4 != null)
|
||||||
(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.";
|
"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
|
net.cidr.assignIps spannedReservedNetwork.cidrv4
|
||||||
spannedReservedNetwork.cidrv4
|
|
||||||
# Don't assign any addresses that are explicitly configured on other hosts
|
# 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))
|
(filter (x: net.cidr.contains x spannedReservedNetwork.cidrv4) (
|
||||||
|
filter net.ip.isv4 explicitlyUsedAddresses
|
||||||
|
))
|
||||||
participatingNodes;
|
participatingNodes;
|
||||||
|
|
||||||
# Assigns an ipv6 address from spannedReservedNetwork.cidrv6
|
# Assigns an ipv6 address from spannedReservedNetwork.cidrv6
|
||||||
# to each participant that has not explicitly specified an ipv6 address.
|
# to each participant that has not explicitly specified an ipv6 address.
|
||||||
assignedIpv6Addresses = assert assertMsg
|
assignedIpv6Addresses = assert assertMsg (spannedReservedNetwork.cidrv6 != null)
|
||||||
(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.";
|
"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
|
net.cidr.assignIps spannedReservedNetwork.cidrv6
|
||||||
spannedReservedNetwork.cidrv6
|
|
||||||
# Don't assign any addresses that are explicitly configured on other hosts
|
# 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))
|
(filter (x: net.cidr.contains x spannedReservedNetwork.cidrv6) (
|
||||||
|
filter net.ip.isv6 explicitlyUsedAddresses
|
||||||
|
))
|
||||||
participatingNodes;
|
participatingNodes;
|
||||||
|
|
||||||
# Appends / replaces the correct cidr length to the argument,
|
# Appends / replaces the correct cidr length to the argument,
|
||||||
|
@ -160,45 +132,11 @@ in {
|
||||||
then networkAddresses.cidrv6
|
then networkAddresses.cidrv6
|
||||||
else networkAddresses.cidrv4;
|
else networkAddresses.cidrv4;
|
||||||
in "${net.cidr.ip addr}/${toString (net.cidr.length relevantNetworkAddr)}";
|
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 {
|
in {
|
||||||
inherit
|
inherit
|
||||||
allExternalPeers
|
|
||||||
allPeers
|
|
||||||
assignedIpv4Addresses
|
assignedIpv4Addresses
|
||||||
assignedIpv6Addresses
|
assignedIpv6Addresses
|
||||||
explicitlyUsedAddresses
|
explicitlyUsedAddresses
|
||||||
externalPeerName
|
|
||||||
externalPeerNamesRaw
|
|
||||||
externalPeersForNode
|
|
||||||
networkAddresses
|
networkAddresses
|
||||||
networkCidrs
|
networkCidrs
|
||||||
nodePeers
|
nodePeers
|
||||||
|
@ -218,27 +156,23 @@ in {
|
||||||
toNetworkAddr
|
toNetworkAddr
|
||||||
usedAddresses
|
usedAddresses
|
||||||
wgCfgOf
|
wgCfgOf
|
||||||
wgQuickConfigScript
|
|
||||||
;
|
;
|
||||||
};
|
};
|
||||||
|
|
||||||
wireguard.createEvalCache = userInputs: wgNames:
|
wireguard.createEvalCache = userInputs: wgNames: genAttrs wgNames (wireguard.evaluateNetwork userInputs);
|
||||||
genAttrs wgNames (wireguard.evaluateNetwork userInputs);
|
|
||||||
|
|
||||||
wireguard.getNetwork = userInputs: wgName:
|
wireguard.getNetwork = userInputs: wgName:
|
||||||
userInputs.self.wireguardEvalCache.${wgName}
|
userInputs.self.wireguardEvalCache.${wgName}
|
||||||
or (
|
or (warn ''
|
||||||
warn ''
|
The calculated information for the wireguard network "${wgName}" is not cached!
|
||||||
The calculated information for the wireguard network "${wgName}" is not cached!
|
This will siginificantly increase evaluation times. Please consider pre-evaluating
|
||||||
This will siginificantly increase evaluation times. Please consider pre-evaluating
|
this information by exposing it in your flake:
|
||||||
this information by exposing it in your flake:
|
|
||||||
|
|
||||||
wireguardEvalCache = lib.wireguard.createEvalCache inputs [
|
wireguardEvalCache = lib.wireguard.createEvalCache inputs [
|
||||||
"${wgName}"
|
"${wgName}"
|
||||||
# all other networks
|
# all other networks
|
||||||
];
|
];
|
||||||
|
|
||||||
'' (wireguard.evaluateNetwork userInputs wgName)
|
'' (wireguard.evaluateNetwork userInputs wgName));
|
||||||
);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,11 +7,8 @@
|
||||||
inherit
|
inherit
|
||||||
(lib)
|
(lib)
|
||||||
any
|
any
|
||||||
attrNames
|
|
||||||
attrValues
|
|
||||||
concatAttrs
|
concatAttrs
|
||||||
concatMap
|
concatMap
|
||||||
concatMapStrings
|
|
||||||
concatStringsSep
|
concatStringsSep
|
||||||
duplicates
|
duplicates
|
||||||
filter
|
filter
|
||||||
|
@ -36,8 +33,6 @@
|
||||||
configForNetwork = wgName: wgCfg: let
|
configForNetwork = wgName: wgCfg: let
|
||||||
inherit
|
inherit
|
||||||
(lib.wireguard.getNetwork inputs wgName)
|
(lib.wireguard.getNetwork inputs wgName)
|
||||||
externalPeerName
|
|
||||||
externalPeerNamesRaw
|
|
||||||
networkCidrs
|
networkCidrs
|
||||||
participatingClientNodes
|
participatingClientNodes
|
||||||
participatingNodes
|
participatingNodes
|
||||||
|
@ -57,9 +52,9 @@
|
||||||
filterSelf = filter (x: x != nodeName);
|
filterSelf = filter (x: x != nodeName);
|
||||||
|
|
||||||
# All nodes that use our node as the via into the wireguard network
|
# All nodes that use our node as the via into the wireguard network
|
||||||
ourClientNodes =
|
ourClientNodes = optionals isServer (
|
||||||
optionals isServer
|
filter (n: (wgCfgOf n).client.via == nodeName) participatingClientNodes
|
||||||
(filter (n: (wgCfgOf n).client.via == nodeName) participatingClientNodes);
|
);
|
||||||
|
|
||||||
# The list of peers for which we have to know the psk.
|
# The list of peers for which we have to know the psk.
|
||||||
neededPeers =
|
neededPeers =
|
||||||
|
@ -67,15 +62,11 @@
|
||||||
then
|
then
|
||||||
# Other servers in the same network
|
# Other servers in the same network
|
||||||
filterSelf participatingServerNodes
|
filterSelf participatingServerNodes
|
||||||
# Our external peers
|
|
||||||
++ map externalPeerName (attrNames wgCfg.server.externalPeers)
|
|
||||||
# Our clients
|
# Our clients
|
||||||
++ ourClientNodes
|
++ ourClientNodes
|
||||||
else [wgCfg.client.via];
|
else [wgCfg.client.via];
|
||||||
|
|
||||||
# Figure out if there are duplicate peers or addresses so we can
|
# Figure out if there are duplicate addresses so we can make an assertion later.
|
||||||
# make an assertion later.
|
|
||||||
duplicatePeers = duplicates externalPeerNamesRaw;
|
|
||||||
duplicateAddrs = duplicates usedAddresses;
|
duplicateAddrs = duplicates usedAddresses;
|
||||||
|
|
||||||
# Adds context information to the assertions for this network
|
# Adds context information to the assertions for this network
|
||||||
|
@ -93,10 +84,10 @@
|
||||||
map (net.cidr.make 128) (
|
map (net.cidr.make 128) (
|
||||||
# The server accepts traffic to it's own address
|
# The server accepts traffic to it's own address
|
||||||
snCfg.addresses
|
snCfg.addresses
|
||||||
# plus traffic for any of its external peers
|
|
||||||
++ attrValues snCfg.server.externalPeers
|
|
||||||
# plus traffic for any client that is connected via that server
|
# plus traffic for any client that is connected via that server
|
||||||
++ concatMap (n: (wgCfgOf n).addresses) (filter (n: (wgCfgOf n).client.via == serverNode) participatingClientNodes)
|
++ concatMap (n: (wgCfgOf n).addresses) (
|
||||||
|
filter (n: (wgCfgOf n).client.via == serverNode) participatingClientNodes
|
||||||
|
)
|
||||||
);
|
);
|
||||||
in {
|
in {
|
||||||
assertions = [
|
assertions = [
|
||||||
|
@ -104,10 +95,6 @@
|
||||||
assertion = any (n: (wgCfgOf n).server.host != null) participatingNodes;
|
assertion = any (n: (wgCfgOf n).server.host != null) participatingNodes;
|
||||||
message = "${assertionPrefix}: At least one node in a network must be a server.";
|
message = "${assertionPrefix}: At least one node in a network must be a server.";
|
||||||
}
|
}
|
||||||
{
|
|
||||||
assertion = duplicatePeers == [];
|
|
||||||
message = "${assertionPrefix}: Multiple definitions for external peer(s):${concatMapStrings (x: " '${x}'") duplicatePeers}";
|
|
||||||
}
|
|
||||||
{
|
{
|
||||||
assertion = duplicateAddrs == [];
|
assertion = duplicateAddrs == [];
|
||||||
message = "${assertionPrefix}: Addresses used multiple times: ${concatStringsSep ", " duplicateAddrs}";
|
message = "${assertionPrefix}: Addresses used multiple times: ${concatStringsSep ", " duplicateAddrs}";
|
||||||
|
@ -127,7 +114,9 @@
|
||||||
];
|
];
|
||||||
|
|
||||||
# Open the udp port for the wireguard endpoint in the firewall
|
# Open the udp port for the wireguard endpoint in the firewall
|
||||||
networking.firewall.allowedUDPPorts = mkIf (isServer && wgCfg.server.openFirewall) [wgCfg.server.port];
|
networking.firewall.allowedUDPPorts = mkIf (isServer && wgCfg.server.openFirewall) [
|
||||||
|
wgCfg.server.port
|
||||||
|
];
|
||||||
|
|
||||||
# If requested, create firewall rules for the network / specific participants and open ports.
|
# If requested, create firewall rules for the network / specific participants and open ports.
|
||||||
networking.nftables.firewall = let
|
networking.nftables.firewall = let
|
||||||
|
@ -138,17 +127,19 @@
|
||||||
# Parent zone for the whole interface
|
# Parent zone for the whole interface
|
||||||
"wg-${wgCfg.linkName}".interfaces = [wgCfg.linkName];
|
"wg-${wgCfg.linkName}".interfaces = [wgCfg.linkName];
|
||||||
}
|
}
|
||||||
// listToAttrs (flip map participatingNodes (
|
// listToAttrs (
|
||||||
peer: let
|
flip map participatingNodes (
|
||||||
peerCfg = wgCfgOf peer;
|
peer: let
|
||||||
in
|
peerCfg = wgCfgOf peer;
|
||||||
# Subzone to specifically target the peer
|
in
|
||||||
nameValuePair "wg-${wgCfg.linkName}-node-${peer}" {
|
# Subzone to specifically target the peer
|
||||||
parent = "wg-${wgCfg.linkName}";
|
nameValuePair "wg-${wgCfg.linkName}-node-${peer}" {
|
||||||
ipv4Addresses = [peerCfg.ipv4];
|
parent = "wg-${wgCfg.linkName}";
|
||||||
ipv6Addresses = [peerCfg.ipv6];
|
ipv4Addresses = [peerCfg.ipv4];
|
||||||
}
|
ipv6Addresses = [peerCfg.ipv6];
|
||||||
));
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
rules =
|
rules =
|
||||||
{
|
{
|
||||||
|
@ -166,34 +157,37 @@
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
# Open ports for specific nodes network
|
# Open ports for specific nodes network
|
||||||
// listToAttrs (flip map participatingNodes (
|
// listToAttrs (
|
||||||
peer:
|
flip map participatingNodes (
|
||||||
nameValuePair "wg-${wgCfg.linkName}-node-${peer}-to-${localZoneName}" (
|
peer:
|
||||||
mkIf (wgCfg.firewallRuleForNode ? ${peer}) {
|
nameValuePair "wg-${wgCfg.linkName}-node-${peer}-to-${localZoneName}" (
|
||||||
from = ["wg-${wgCfg.linkName}-node-${peer}"];
|
mkIf (wgCfg.firewallRuleForNode ? ${peer}) {
|
||||||
to = [localZoneName];
|
from = ["wg-${wgCfg.linkName}-node-${peer}"];
|
||||||
ignoreEmptyRule = true;
|
to = [localZoneName];
|
||||||
|
ignoreEmptyRule = true;
|
||||||
|
|
||||||
inherit
|
inherit
|
||||||
(wgCfg.firewallRuleForNode.${peer})
|
(wgCfg.firewallRuleForNode.${peer})
|
||||||
allowedTCPPorts
|
allowedTCPPorts
|
||||||
allowedUDPPorts
|
allowedUDPPorts
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
));
|
)
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
age.secrets =
|
age.secrets =
|
||||||
concatAttrs (map
|
concatAttrs (
|
||||||
(other: {
|
map (other: {
|
||||||
${peerPresharedKeySecret nodeName other} = {
|
${peerPresharedKeySecret nodeName other} = {
|
||||||
rekeyFile = peerPresharedKeyPath nodeName other;
|
rekeyFile = peerPresharedKeyPath nodeName other;
|
||||||
owner = "systemd-network";
|
owner = "systemd-network";
|
||||||
generator.script = {pkgs, ...}: "${pkgs.wireguard-tools}/bin/wg genpsk";
|
generator.script = {pkgs, ...}: "${pkgs.wireguard-tools}/bin/wg genpsk";
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
neededPeers)
|
neededPeers
|
||||||
|
)
|
||||||
// {
|
// {
|
||||||
${peerPrivateKeySecret nodeName} = {
|
${peerPrivateKeySecret nodeName} = {
|
||||||
rekeyFile = peerPrivateKeyPath nodeName;
|
rekeyFile = peerPrivateKeyPath nodeName;
|
||||||
|
@ -204,7 +198,9 @@
|
||||||
...
|
...
|
||||||
}: ''
|
}: ''
|
||||||
priv=$(${pkgs.wireguard-tools}/bin/wg genkey)
|
priv=$(${pkgs.wireguard-tools}/bin/wg genkey)
|
||||||
${pkgs.wireguard-tools}/bin/wg pubkey <<< "$priv" > ${lib.escapeShellArg (lib.removeSuffix ".age" file + ".pub")}
|
${pkgs.wireguard-tools}/bin/wg pubkey <<< "$priv" > ${
|
||||||
|
lib.escapeShellArg (lib.removeSuffix ".age" file + ".pub")
|
||||||
|
}
|
||||||
echo "$priv"
|
echo "$priv"
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
@ -227,34 +223,26 @@
|
||||||
if isServer
|
if isServer
|
||||||
then
|
then
|
||||||
# Always include all other server nodes.
|
# Always include all other server nodes.
|
||||||
map (serverNode: let
|
map (
|
||||||
snCfg = wgCfgOf serverNode;
|
serverNode: let
|
||||||
in {
|
snCfg = wgCfgOf serverNode;
|
||||||
PublicKey = builtins.readFile (peerPublicKeyPath serverNode);
|
in {
|
||||||
PresharedKeyFile = config.age.secrets.${peerPresharedKeySecret nodeName serverNode}.path;
|
PublicKey = builtins.readFile (peerPublicKeyPath serverNode);
|
||||||
AllowedIPs = serverAllowedIPs serverNode;
|
PresharedKeyFile = config.age.secrets.${peerPresharedKeySecret nodeName serverNode}.path;
|
||||||
Endpoint = "${snCfg.server.host}:${toString snCfg.server.port}";
|
AllowedIPs = serverAllowedIPs serverNode;
|
||||||
})
|
Endpoint = "${snCfg.server.host}:${toString snCfg.server.port}";
|
||||||
(filterSelf participatingServerNodes)
|
}
|
||||||
# All our external peers
|
) (filterSelf participatingServerNodes)
|
||||||
++ mapAttrsToList (extPeer: ips: let
|
|
||||||
peerName = externalPeerName extPeer;
|
|
||||||
in {
|
|
||||||
PublicKey = builtins.readFile (peerPublicKeyPath peerName);
|
|
||||||
PresharedKeyFile = config.age.secrets.${peerPresharedKeySecret nodeName peerName}.path;
|
|
||||||
AllowedIPs = map (net.cidr.make 128) ips;
|
|
||||||
# Connections to external peers should always be kept alive
|
|
||||||
PersistentKeepalive = 25;
|
|
||||||
})
|
|
||||||
wgCfg.server.externalPeers
|
|
||||||
# All client nodes that have their via set to us.
|
# All client nodes that have their via set to us.
|
||||||
++ map (clientNode: let
|
++ map (
|
||||||
clientCfg = wgCfgOf clientNode;
|
clientNode: let
|
||||||
in {
|
clientCfg = wgCfgOf clientNode;
|
||||||
PublicKey = builtins.readFile (peerPublicKeyPath clientNode);
|
in {
|
||||||
PresharedKeyFile = config.age.secrets.${peerPresharedKeySecret nodeName clientNode}.path;
|
PublicKey = builtins.readFile (peerPublicKeyPath clientNode);
|
||||||
AllowedIPs = map (net.cidr.make 128) clientCfg.addresses;
|
PresharedKeyFile = config.age.secrets.${peerPresharedKeySecret nodeName clientNode}.path;
|
||||||
})
|
AllowedIPs = map (net.cidr.make 128) clientCfg.addresses;
|
||||||
|
}
|
||||||
|
)
|
||||||
ourClientNodes
|
ourClientNodes
|
||||||
else
|
else
|
||||||
# We are a client node, so only include our via server.
|
# We are a client node, so only include our via server.
|
||||||
|
@ -286,189 +274,184 @@ in {
|
||||||
options.wireguard = mkOption {
|
options.wireguard = mkOption {
|
||||||
default = {};
|
default = {};
|
||||||
description = "Configures wireguard networks via systemd-networkd.";
|
description = "Configures wireguard networks via systemd-networkd.";
|
||||||
type = types.lazyAttrsOf (types.submodule ({
|
type = types.lazyAttrsOf (
|
||||||
config,
|
types.submodule (
|
||||||
name,
|
{
|
||||||
options,
|
config,
|
||||||
...
|
name,
|
||||||
}: {
|
options,
|
||||||
options = {
|
...
|
||||||
server = {
|
}: {
|
||||||
host = mkOption {
|
options = {
|
||||||
default = null;
|
server = {
|
||||||
type = types.nullOr types.str;
|
host = mkOption {
|
||||||
description = "The hostname or ip address which other peers can use to reach this host. No server functionality will be activated if set to null.";
|
default = null;
|
||||||
};
|
type = types.nullOr types.str;
|
||||||
|
description = "The hostname or ip address which other peers can use to reach this host. No server functionality will be activated if set to null.";
|
||||||
port = mkOption {
|
|
||||||
default = 51820;
|
|
||||||
type = types.port;
|
|
||||||
description = "The port to listen on.";
|
|
||||||
};
|
|
||||||
|
|
||||||
openFirewall = mkOption {
|
|
||||||
default = false;
|
|
||||||
type = types.bool;
|
|
||||||
description = "Whether to open the firewall for the specified {option}`port`.";
|
|
||||||
};
|
|
||||||
|
|
||||||
externalPeers = mkOption {
|
|
||||||
type = types.attrsOf (types.listOf (types.net.ip-in config.addresses));
|
|
||||||
default = {};
|
|
||||||
example = {my-android-phone = ["10.0.0.97"];};
|
|
||||||
description = ''
|
|
||||||
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.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
reservedAddresses = mkOption {
|
|
||||||
type = types.listOf types.net.cidr;
|
|
||||||
default = [];
|
|
||||||
example = ["10.0.0.0/24" "fd00:cafe::/64"];
|
|
||||||
description = ''
|
|
||||||
Allows defining extra CIDR network ranges that shall be reserved for this network.
|
|
||||||
Reservation means that those address spaces will be guaranteed to be included in
|
|
||||||
the spanned network, but no rules will be enforced as to who in the network may use them.
|
|
||||||
|
|
||||||
By default, this module will try to allocate the smallest address space that includes
|
|
||||||
all network peers. If you know that there might be additional external peers added later,
|
|
||||||
it may be beneficial to reserve a bigger address space from the start to avoid having
|
|
||||||
to update existing external peers when the generated address space expands.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
client = {
|
|
||||||
via = mkOption {
|
|
||||||
default = null;
|
|
||||||
type = types.nullOr types.str;
|
|
||||||
description = ''
|
|
||||||
The server node via which to connect to the network.
|
|
||||||
No client functionality will be activated if set to null.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
keepalive = mkOption {
|
|
||||||
default = true;
|
|
||||||
type = types.bool;
|
|
||||||
description = "Whether to keep this connection alive using PersistentKeepalive. Set to false only for networks where client and server IPs are stable.";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
priority = mkOption {
|
|
||||||
default = 40;
|
|
||||||
type = types.int;
|
|
||||||
description = "The order priority used when creating systemd netdev and network files.";
|
|
||||||
};
|
|
||||||
|
|
||||||
linkName = mkOption {
|
|
||||||
default = name;
|
|
||||||
type = types.str;
|
|
||||||
description = "The name for the created network interface.";
|
|
||||||
};
|
|
||||||
|
|
||||||
unitConfName = mkOption {
|
|
||||||
default = "${toString config.priority}-${config.linkName}";
|
|
||||||
readOnly = true;
|
|
||||||
type = types.str;
|
|
||||||
description = ''
|
|
||||||
The name used for unit configuration files. This is a read-only option.
|
|
||||||
Access this if you want to add additional settings to the generated systemd units.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
ipv4 = mkOption {
|
|
||||||
type = types.lazyOf types.net.ipv4;
|
|
||||||
default = types.lazyValue (lib.wireguard.getNetwork inputs name).assignedIpv4Addresses.${nodeName};
|
|
||||||
description = ''
|
|
||||||
The ipv4 address for this machine. If you do not set this explicitly,
|
|
||||||
a semi-stable ipv4 address will be derived automatically based on the
|
|
||||||
hostname of this machine. At least one participating server must reserve
|
|
||||||
a big-enough space of addresses by setting `reservedAddresses`.
|
|
||||||
See `net.cidr.assignIps` for more information on the algorithm.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
ipv6 = mkOption {
|
|
||||||
type = types.lazyOf types.net.ipv6;
|
|
||||||
default = types.lazyValue (lib.wireguard.getNetwork inputs name).assignedIpv6Addresses.${nodeName};
|
|
||||||
description = ''
|
|
||||||
The ipv6 address for this machine. If you do not set this explicitly,
|
|
||||||
a semi-stable ipv6 address will be derived automatically based on the
|
|
||||||
hostname of this machine. At least one participating server must reserve
|
|
||||||
a big-enough space of addresses by setting `reservedAddresses`.
|
|
||||||
See `net.cidr.assignIps` for more information on the algorithm.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
addresses = mkOption {
|
|
||||||
type = types.listOf (types.lazyOf types.net.ip);
|
|
||||||
default = [
|
|
||||||
(head options.ipv4.definitions)
|
|
||||||
(head options.ipv6.definitions)
|
|
||||||
];
|
|
||||||
description = ''
|
|
||||||
The ip addresses (v4 and/or v6) to use for this machine.
|
|
||||||
The actual network cidr will automatically be derived from all network participants.
|
|
||||||
By default this will just include {option}`ipv4` and {option}`ipv6` as configured.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
firewallRuleForAll = mkOption {
|
|
||||||
default = {};
|
|
||||||
description = ''
|
|
||||||
Allows you to set specific firewall rules for traffic originating from any participant in this
|
|
||||||
wireguard network. A corresponding rule `wg-<network-name>-to-<local-zone-name>` will be created to easily expose
|
|
||||||
services to the network.
|
|
||||||
'';
|
|
||||||
type = types.submodule {
|
|
||||||
options = {
|
|
||||||
allowedTCPPorts = mkOption {
|
|
||||||
type = types.listOf types.port;
|
|
||||||
default = [];
|
|
||||||
description = "Convenience option to open specific TCP ports for traffic from the network.";
|
|
||||||
};
|
};
|
||||||
allowedUDPPorts = mkOption {
|
|
||||||
type = types.listOf types.port;
|
port = mkOption {
|
||||||
|
default = 51820;
|
||||||
|
type = types.port;
|
||||||
|
description = "The port to listen on.";
|
||||||
|
};
|
||||||
|
|
||||||
|
openFirewall = mkOption {
|
||||||
|
default = false;
|
||||||
|
type = types.bool;
|
||||||
|
description = "Whether to open the firewall for the specified {option}`port`.";
|
||||||
|
};
|
||||||
|
|
||||||
|
reservedAddresses = mkOption {
|
||||||
|
type = types.listOf types.net.cidr;
|
||||||
default = [];
|
default = [];
|
||||||
description = "Convenience option to open specific UDP ports for traffic from the network.";
|
example = [
|
||||||
|
"10.0.0.0/24"
|
||||||
|
"fd00:cafe::/64"
|
||||||
|
];
|
||||||
|
description = ''
|
||||||
|
Allows defining extra CIDR network ranges that shall be reserved for this network.
|
||||||
|
Reservation means that those address spaces will be guaranteed to be included in
|
||||||
|
the spanned network, but no rules will be enforced as to who in the network may use them.
|
||||||
|
|
||||||
|
By default, this module will try to allocate the smallest address space that includes
|
||||||
|
all network peers.
|
||||||
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
firewallRuleForNode = mkOption {
|
client = {
|
||||||
default = {};
|
via = mkOption {
|
||||||
description = ''
|
default = null;
|
||||||
Allows you to set specific firewall rules just for traffic originating from another network node.
|
type = types.nullOr types.str;
|
||||||
A corresponding rule `wg-<network-name>-node-<node-name>-to-<local-zone-name>` will be created to easily expose
|
description = ''
|
||||||
services to that node.
|
The server node via which to connect to the network.
|
||||||
'';
|
No client functionality will be activated if set to null.
|
||||||
type = types.attrsOf (types.submodule {
|
'';
|
||||||
options = {
|
|
||||||
allowedTCPPorts = mkOption {
|
|
||||||
type = types.listOf types.port;
|
|
||||||
default = [];
|
|
||||||
description = "Convenience option to open specific TCP ports for traffic from another node.";
|
|
||||||
};
|
};
|
||||||
allowedUDPPorts = mkOption {
|
|
||||||
type = types.listOf types.port;
|
keepalive = mkOption {
|
||||||
default = [];
|
default = true;
|
||||||
description = "Convenience option to open specific UDP ports for traffic from another node.";
|
type = types.bool;
|
||||||
|
description = "Whether to keep this connection alive using PersistentKeepalive. Set to false only for networks where client and server IPs are stable.";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
});
|
|
||||||
};
|
priority = mkOption {
|
||||||
};
|
default = 40;
|
||||||
}));
|
type = types.int;
|
||||||
|
description = "The order priority used when creating systemd netdev and network files.";
|
||||||
|
};
|
||||||
|
|
||||||
|
linkName = mkOption {
|
||||||
|
default = name;
|
||||||
|
type = types.str;
|
||||||
|
description = "The name for the created network interface.";
|
||||||
|
};
|
||||||
|
|
||||||
|
unitConfName = mkOption {
|
||||||
|
default = "${toString config.priority}-${config.linkName}";
|
||||||
|
readOnly = true;
|
||||||
|
type = types.str;
|
||||||
|
description = ''
|
||||||
|
The name used for unit configuration files. This is a read-only option.
|
||||||
|
Access this if you want to add additional settings to the generated systemd units.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
ipv4 = mkOption {
|
||||||
|
type = types.lazyOf types.net.ipv4;
|
||||||
|
default = types.lazyValue (lib.wireguard.getNetwork inputs name).assignedIpv4Addresses.${nodeName};
|
||||||
|
description = ''
|
||||||
|
The ipv4 address for this machine. If you do not set this explicitly,
|
||||||
|
a semi-stable ipv4 address will be derived automatically based on the
|
||||||
|
hostname of this machine. At least one participating server must reserve
|
||||||
|
a big-enough space of addresses by setting `reservedAddresses`.
|
||||||
|
See `net.cidr.assignIps` for more information on the algorithm.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
ipv6 = mkOption {
|
||||||
|
type = types.lazyOf types.net.ipv6;
|
||||||
|
default = types.lazyValue (lib.wireguard.getNetwork inputs name).assignedIpv6Addresses.${nodeName};
|
||||||
|
description = ''
|
||||||
|
The ipv6 address for this machine. If you do not set this explicitly,
|
||||||
|
a semi-stable ipv6 address will be derived automatically based on the
|
||||||
|
hostname of this machine. At least one participating server must reserve
|
||||||
|
a big-enough space of addresses by setting `reservedAddresses`.
|
||||||
|
See `net.cidr.assignIps` for more information on the algorithm.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
addresses = mkOption {
|
||||||
|
type = types.listOf (types.lazyOf types.net.ip);
|
||||||
|
default = [
|
||||||
|
(head options.ipv4.definitions)
|
||||||
|
(head options.ipv6.definitions)
|
||||||
|
];
|
||||||
|
description = ''
|
||||||
|
The ip addresses (v4 and/or v6) to use for this machine.
|
||||||
|
The actual network cidr will automatically be derived from all network participants.
|
||||||
|
By default this will just include {option}`ipv4` and {option}`ipv6` as configured.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
firewallRuleForAll = mkOption {
|
||||||
|
default = {};
|
||||||
|
description = ''
|
||||||
|
Allows you to set specific firewall rules for traffic originating from any participant in this
|
||||||
|
wireguard network. A corresponding rule `wg-<network-name>-to-<local-zone-name>` will be created to easily expose
|
||||||
|
services to the network.
|
||||||
|
'';
|
||||||
|
type = types.submodule {
|
||||||
|
options = {
|
||||||
|
allowedTCPPorts = mkOption {
|
||||||
|
type = types.listOf types.port;
|
||||||
|
default = [];
|
||||||
|
description = "Convenience option to open specific TCP ports for traffic from the network.";
|
||||||
|
};
|
||||||
|
allowedUDPPorts = mkOption {
|
||||||
|
type = types.listOf types.port;
|
||||||
|
default = [];
|
||||||
|
description = "Convenience option to open specific UDP ports for traffic from the network.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
firewallRuleForNode = mkOption {
|
||||||
|
default = {};
|
||||||
|
description = ''
|
||||||
|
Allows you to set specific firewall rules just for traffic originating from another network node.
|
||||||
|
A corresponding rule `wg-<network-name>-node-<node-name>-to-<local-zone-name>` will be created to easily expose
|
||||||
|
services to that node.
|
||||||
|
'';
|
||||||
|
type = types.attrsOf (
|
||||||
|
types.submodule {
|
||||||
|
options = {
|
||||||
|
allowedTCPPorts = mkOption {
|
||||||
|
type = types.listOf types.port;
|
||||||
|
default = [];
|
||||||
|
description = "Convenience option to open specific TCP ports for traffic from another node.";
|
||||||
|
};
|
||||||
|
allowedUDPPorts = mkOption {
|
||||||
|
type = types.listOf types.port;
|
||||||
|
default = [];
|
||||||
|
description = "Convenience option to open specific UDP ports for traffic from another node.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
config = mkIf (cfg != {}) (mergeToplevelConfigs
|
config = mkIf (cfg != {}) (
|
||||||
["assertions" "age" "networking" "systemd"]
|
mergeToplevelConfigs ["assertions" "age" "networking" "systemd"] (
|
||||||
(mapAttrsToList configForNetwork cfg));
|
mapAttrsToList configForNetwork cfg
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue