mirror of
https://github.com/oddlama/nix-config.git
synced 2025-10-11 07:10:39 +02:00
refactor(wireguard): extract cross-host aggregation functions into extraLib
This commit is contained in:
parent
6cffccd75c
commit
d522a46f1d
6 changed files with 239 additions and 195 deletions
|
@ -11,88 +11,50 @@
|
|||
(lib)
|
||||
any
|
||||
attrNames
|
||||
attrValues
|
||||
concatMap
|
||||
concatMapStrings
|
||||
concatStringsSep
|
||||
filter
|
||||
flatten
|
||||
foldl'
|
||||
genAttrs
|
||||
filterAttrs
|
||||
head
|
||||
mapAttrs'
|
||||
mapAttrsToList
|
||||
mdDoc
|
||||
mergeAttrs
|
||||
mkIf
|
||||
mkOption
|
||||
nameValuePair
|
||||
optional
|
||||
recursiveUpdate
|
||||
mkEnableOption
|
||||
optionalAttrs
|
||||
splitString
|
||||
types
|
||||
;
|
||||
|
||||
inherit (extraLib) duplicates;
|
||||
inherit
|
||||
(extraLib)
|
||||
concatAttrs
|
||||
duplicates
|
||||
;
|
||||
|
||||
cfg = config.extra.wireguard;
|
||||
|
||||
sortedPeers = peerA: peerB:
|
||||
if peerA < peerB
|
||||
then {
|
||||
peer1 = peerA;
|
||||
peer2 = peerB;
|
||||
}
|
||||
else {
|
||||
peer1 = peerB;
|
||||
peer2 = peerA;
|
||||
};
|
||||
|
||||
configForNetwork = wgName: wg: let
|
||||
peerPublicKey = peerName: builtins.readFile (../secrets/wireguard + "/${wgName}/keys/${peerName}.pub");
|
||||
peerPrivateKeyFile = peerName: ../secrets/wireguard + "/${wgName}/keys/${peerName}.age";
|
||||
peerPrivateKeySecret = peerName: "wireguard-${wgName}-priv-${peerName}";
|
||||
inherit
|
||||
(extraLib.wireguard wgName)
|
||||
allPeers
|
||||
peerPresharedKeyPath
|
||||
peerPresharedKeySecret
|
||||
peerPrivateKeyPath
|
||||
peerPrivateKeySecret
|
||||
peerPublicKeyPath
|
||||
;
|
||||
|
||||
peerPresharedKeyFile = peerA: peerB: let
|
||||
inherit (sortedPeers peerA peerB) peer1 peer2;
|
||||
in
|
||||
../secrets/wireguard + "/${wgName}/psks/${peer1}-${peer2}.age";
|
||||
|
||||
peerPresharedKeySecret = peerA: peerB: let
|
||||
inherit (sortedPeers peerA peerB) peer1 peer2;
|
||||
in "wireguard-${wgName}-psks-${peer1}-${peer2}";
|
||||
|
||||
# All peers that are other nodes
|
||||
nodesWithThisNetwork = filter (n: builtins.hasAttr wgName nodes.${n}.config.extra.wireguard.networks) (attrNames nodes);
|
||||
nodePeers = genAttrs (filter (n: n != nodeName) nodesWithThisNetwork) (n: nodes.${n}.config.extra.wireguard.networks.${wgName}.address);
|
||||
# All peers that are defined as externalPeers on any node. Also prepends "external-" to their name.
|
||||
externalPeers = foldl' recursiveUpdate {} (
|
||||
map (n: mapAttrs' (extPeerName: nameValuePair "external-${extPeerName}") nodes.${n}.config.extra.wireguard.networks.${wgName}.externalPeers)
|
||||
nodesWithThisNetwork
|
||||
);
|
||||
|
||||
peers = nodePeers // externalPeers;
|
||||
|
||||
peerDefinition = peerName: peerAllowedIPs: {
|
||||
wireguardPeerConfig =
|
||||
{
|
||||
PublicKey = peerPublicKey wgName peerName;
|
||||
PresharedKeyFile = config.rekey.secrets.${peerPresharedKeySecret wgName nodeName peerName}.path;
|
||||
AllowedIPs = peerAllowedIPs;
|
||||
}
|
||||
// optional wg.listen {
|
||||
PersistentKeepalive = 25;
|
||||
};
|
||||
};
|
||||
otherPeers = filterAttrs (n: _: n != nodeName) (allPeers nodes);
|
||||
in {
|
||||
inherit nodesWithThisNetwork wgName;
|
||||
|
||||
secrets =
|
||||
foldl' mergeAttrs {
|
||||
${peerPrivateKeySecret nodeName}.file = peerPrivateKeyFile nodeName;
|
||||
} (map (peerName: {
|
||||
${peerPresharedKeySecret nodeName peerName}.file = peerPresharedKeyFile nodeName peerName;
|
||||
}) (attrNames peers));
|
||||
concatAttrs (map (other: {
|
||||
${peerPresharedKeySecret nodeName other}.file = peerPresharedKeyPath nodeName other;
|
||||
}) (attrNames otherPeers))
|
||||
// {
|
||||
${peerPrivateKeySecret nodeName}.file = peerPrivateKeyPath nodeName;
|
||||
};
|
||||
|
||||
netdevs."${wg.priority}-${wgName}" = {
|
||||
netdevConfig = {
|
||||
|
@ -100,11 +62,26 @@
|
|||
Name = "${wgName}";
|
||||
Description = "Wireguard network ${wgName}";
|
||||
};
|
||||
wireguardConfig = {
|
||||
PrivateKeyFile = config.rekey.secrets.${peerPrivateKeySecret nodeName}.path;
|
||||
ListenPort = wg.listenPort;
|
||||
};
|
||||
wireguardPeers = mapAttrsToList peerDefinition peers;
|
||||
wireguardConfig =
|
||||
{
|
||||
PrivateKeyFile = config.rekey.secrets.${peerPrivateKeySecret nodeName}.path;
|
||||
}
|
||||
// optionalAttrs wg.server.enable {
|
||||
ListenPort = wg.server.port;
|
||||
};
|
||||
wireguardPeers =
|
||||
mapAttrsToList (peerName: peerAllowedIPs: {
|
||||
wireguardPeerConfig =
|
||||
{
|
||||
PublicKey = builtins.readFile (peerPublicKeyPath peerName);
|
||||
PresharedKeyFile = config.rekey.secrets.${peerPresharedKeySecret nodeName peerName}.path;
|
||||
AllowedIPs = peerAllowedIPs;
|
||||
}
|
||||
// optionalAttrs wg.server.enable {
|
||||
PersistentKeepalive = 25;
|
||||
};
|
||||
})
|
||||
otherPeers;
|
||||
};
|
||||
|
||||
networks."${wg.priority}-${wgName}" = {
|
||||
|
@ -113,95 +90,88 @@
|
|||
};
|
||||
};
|
||||
in {
|
||||
options = {
|
||||
extra.wireguard.networks = mkOption {
|
||||
default = {};
|
||||
description = "Configures wireguard networks via systemd-networkd.";
|
||||
type = types.attrsOf (types.submodule {
|
||||
options = {
|
||||
address = mkOption {
|
||||
type = types.listOf types.str;
|
||||
description = mdDoc ''
|
||||
The addresses to configure for this interface. Will automatically be added
|
||||
as this peer's allowed addresses to all other peers.
|
||||
'';
|
||||
};
|
||||
options.extra.wireguard = mkOption {
|
||||
default = {};
|
||||
description = "Configures wireguard networks via systemd-networkd.";
|
||||
type = types.attrsOf (types.submodule {
|
||||
options = {
|
||||
server = {
|
||||
enable = mkEnableOption (mdDoc "wireguard server");
|
||||
|
||||
listen = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = mdDoc ''
|
||||
Enables listening for incoming wireguard connections.
|
||||
This also causes all other peers to include this as an endpoint in their configuration.
|
||||
'';
|
||||
};
|
||||
|
||||
listenPort = mkOption {
|
||||
port = mkOption {
|
||||
default = 51820;
|
||||
type = types.int;
|
||||
type = types.port;
|
||||
description = mdDoc "The port to listen on, if {option}`listen` is `true`.";
|
||||
};
|
||||
|
||||
priority = mkOption {
|
||||
default = "20";
|
||||
type = types.str;
|
||||
description = mdDoc "The order priority used when creating systemd netdev and network files.";
|
||||
};
|
||||
|
||||
openFirewall = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = mdDoc "Whether to open the firewall for the specified `listenPort`, if {option}`listen` is `true`.";
|
||||
};
|
||||
|
||||
externalPeers = mkOption {
|
||||
type = types.attrsOf (types.listOf types.str);
|
||||
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.
|
||||
'';
|
||||
};
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
priority = mkOption {
|
||||
default = "20";
|
||||
type = types.str;
|
||||
description = mdDoc "The order priority used when creating systemd netdev and network files.";
|
||||
};
|
||||
|
||||
address = mkOption {
|
||||
type = types.listOf types.str;
|
||||
description = mdDoc ''
|
||||
The addresses to configure for this interface. Will automatically be added
|
||||
as this peer's allowed addresses to all other peers.
|
||||
'';
|
||||
};
|
||||
|
||||
externalPeers = mkOption {
|
||||
type = types.attrsOf (types.listOf types.str);
|
||||
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.
|
||||
'';
|
||||
};
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
config = mkIf (cfg.networks != {}) (let
|
||||
networkCfgs = mapAttrsToList configForNetwork cfg.networks;
|
||||
collectAttrs = x: foldl' mergeAttrs {} (map (y: y.${x}) networkCfgs);
|
||||
config = mkIf (cfg != {}) (let
|
||||
networkCfgs = mapAttrsToList configForNetwork cfg;
|
||||
collectAllNetworkAttrs = x: concatAttrs (map (y: y.${x}) networkCfgs);
|
||||
in {
|
||||
assertions =
|
||||
concatMap (netCfg: let
|
||||
inherit (netCfg) wgName;
|
||||
externalPeers = concatMap (n: attrNames nodes.${n}.config.extra.wireguard.networks.${wgName}.externalPeers) netCfg.nodesWithThisNetwork;
|
||||
duplicatePeers = duplicates externalPeers;
|
||||
usedAddresses =
|
||||
concatMap (n: nodes.${n}.config.extra.wireguard.networks.${wgName}.address) netCfg.nodesWithThisNetwork
|
||||
++ flatten (concatMap (n: attrValues nodes.${n}.config.extra.wireguard.networks.${wgName}.externalPeers) netCfg.nodesWithThisNetwork);
|
||||
duplicateAddrs = duplicates (map (x: head (splitString "/" x)) usedAddresses);
|
||||
in [
|
||||
{
|
||||
assertion = any (n: nodes.${n}.config.extra.wireguard.networks.${wgName}.listen) netCfg.nodesWithThisNetwork;
|
||||
message = "Wireguard network '${wgName}': At least one node must be listening.";
|
||||
}
|
||||
{
|
||||
assertion = duplicatePeers == [];
|
||||
message = "Wireguard network '${wgName}': Multiple definitions for external peer(s):${concatMapStrings (x: " '${x}'") duplicatePeers}";
|
||||
}
|
||||
{
|
||||
assertion = duplicateAddrs == [];
|
||||
message = "Wireguard network '${wgName}': Addresses used multiple times: ${concatStringsSep ", " duplicateAddrs}";
|
||||
}
|
||||
])
|
||||
networkCfgs;
|
||||
assertions = concatMap (wgName: let
|
||||
inherit
|
||||
(extraLib.wireguard wgName)
|
||||
externalPeerNamesRaw
|
||||
usedAddresses
|
||||
associatedNodes
|
||||
;
|
||||
|
||||
networking.firewall.allowedUDPPorts = mkIf (cfg.listen && cfg.openFirewall) [cfg.listenPort];
|
||||
rekey.secrets = collectAttrs "secrets";
|
||||
duplicatePeers = duplicates (externalPeerNamesRaw nodes);
|
||||
duplicateAddrs = duplicates (map (x: head (splitString "/" x)) (usedAddresses nodes));
|
||||
in [
|
||||
{
|
||||
assertion = any (n: nodes.${n}.config.extra.wireguard.${wgName}.server.enable) (associatedNodes nodes);
|
||||
message = "Wireguard network '${wgName}': At least one node must be a server.";
|
||||
}
|
||||
{
|
||||
assertion = duplicatePeers == [];
|
||||
message = "Wireguard network '${wgName}': Multiple definitions for external peer(s):${concatMapStrings (x: " '${x}'") duplicatePeers}";
|
||||
}
|
||||
{
|
||||
assertion = duplicateAddrs == [];
|
||||
message = "Wireguard network '${wgName}': Addresses used multiple times: ${concatStringsSep ", " duplicateAddrs}";
|
||||
}
|
||||
]) (attrNames cfg);
|
||||
|
||||
networking.firewall.allowedUDPPorts = mkIf (cfg.server.enable && cfg.server.openFirewall) [cfg.server.port];
|
||||
rekey.secrets = collectAllNetworkAttrs "secrets";
|
||||
systemd.network = {
|
||||
netdevs = collectAttrs "netdevs";
|
||||
networks = collectAttrs "networks";
|
||||
netdevs = collectAllNetworkAttrs "netdevs";
|
||||
networks = collectAllNetworkAttrs "networks";
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue