feat(wireguard): finish module and assertions

This commit is contained in:
oddlama 2023-04-11 17:15:36 +02:00
parent ea48c316cc
commit 786fb75920
No known key found for this signature in database
GPG key ID: 14EFE510775FE39A
6 changed files with 142 additions and 77 deletions

View file

@ -20,6 +20,8 @@ in {
./ssh.nix ./ssh.nix
./tmux.nix ./tmux.nix
./xdg.nix ./xdg.nix
../../../modules/wireguard.nix
]; ];
# Setup secret rekeying parameters # Setup secret rekeying parameters

View file

@ -22,7 +22,6 @@
}; };
}; };
imports = [../../modules/wireguard.nix];
extra.wireguard.networks.vms = { extra.wireguard.networks.vms = {
address = ["10.0.0.1/24"]; address = ["10.0.0.1/24"];
listen = true; listen = true;

View file

@ -522,7 +522,6 @@ in {
wifi6 = { wifi6 = {
enable = mkOption { enable = mkOption {
# TODO Change this default once WiFi 6 is enabled in hostapd upstream
default = false; default = false;
type = types.bool; type = types.bool;
description = mdDoc "Enables support for IEEE 802.11ax (WiFi 6, HE)"; description = mdDoc "Enables support for IEEE 802.11ax (WiFi 6, HE)";

View file

@ -1,6 +1,7 @@
{ {
config, config,
lib, lib,
extraLib,
pkgs, pkgs,
nodes, nodes,
nodeName, nodeName,
@ -8,108 +9,118 @@
}: let }: let
inherit inherit
(lib) (lib)
any
attrNames attrNames
attrValues
concatMap
concatMapAttrs concatMapAttrs
concatMapStrings
concatStringsSep
filter filter
flatten
foldl' foldl'
genAttrs genAttrs
mapAttrsToList head
mapAttrs
mapAttrs' mapAttrs'
mapAttrsToList
mdDoc mdDoc
mergeAttrs
mkIf mkIf
mkOption mkOption
nameValuePair nameValuePair
optional
recursiveUpdate recursiveUpdate
splitString
subtractLists
types types
unique
; ;
inherit (extraLib) duplicates;
cfg = config.extra.wireguard; cfg = config.extra.wireguard;
peerPublicKey = wgName: peerName: builtins.readFile (../secrets/wireguard + "/${wgName}/${peerName}.pub"); sortedPeers = peerA: peerB:
peerPrivateKeyFile = wgName: peerName: ../secrets/wireguard + "/${wgName}/${peerName}.priv.age"; if peerA < peerB
peerPrivateKeySecret = wgName: peerName: "wireguard-${wgName}-${peerName}.priv"; then {
peer1 = peerA;
peerPresharedKeyFile = wgName: peerA: peerB: let peer2 = peerB;
sortedPeers = }
if peerA < peerB else {
then { peer1 = peerB;
peer1 = peerA; peer2 = peerA;
peer2 = peerB;
}
else {
peer1 = peerB;
peer2 = peerA;
};
inherit (sortedPeers) peer1 peer2;
in
../secrets/wireguard + "/${wgName}/${peer1}-${peer2}.psk.age";
peerPresharedKeySecret = wgName: peerA: peerB: let
sortedPeers =
if peerA < peerB
then {
peer1 = peerA;
peer2 = peerB;
}
else {
peer1 = peerB;
peer2 = peerA;
};
inherit (sortedPeers) peer1 peer2;
in "wireguard-${wgName}-${peer1}-${peer2}.psk";
peerDefinition = wgName: peerName: peerAllowedIPs: {
wireguardPeerConfig = {
PublicKey = peerPublicKey wgName peerName;
PresharedKeyFile = config.rekey.secrets.${peerPresharedKeySecret wgName nodeName peerName}.path;
AllowedIPs = peerAllowedIPs;
# TODO only if we are the ones listening
PersistentKeepalive = 25;
}; };
};
configForNetwork = wgName: wg: let configForNetwork = wgName: wg: let
peerPublicKey = peerName: builtins.readFile (../secrets/wireguard + "/${wgName}/${peerName}.pub");
peerPrivateKeyFile = peerName: ../secrets/wireguard + "/${wgName}/${peerName}.priv.age";
peerPrivateKeySecret = peerName: "wireguard-${wgName}-${peerName}.priv";
peerPresharedKeyFile = peerA: peerB: let
inherit (sortedPeers peerA peerB) peer1 peer2;
in
../secrets/wireguard + "/${wgName}/${peer1}-${peer2}.psk.age";
peerPresharedKeySecret = peerA: peerB: let
inherit (sortedPeers peerA peerB) peer1 peer2;
in "wireguard-${wgName}-${peer1}-${peer2}.psk";
# All peers that are other nodes # All peers that are other nodes
nodePeerNames = filter (n: n != nodeName && builtins.hasAttr wgName nodes.${n}.config.extra.wireguard.networks) (attrNames nodes); nodesWithThisNetwork = filter (n: builtins.hasAttr wgName nodes.${n}.config.extra.wireguard.networks) (attrNames nodes);
nodePeers = genAttrs nodePeerNames (n: nodes.${n}.config.extra.wireguard.networks.${wgName}.address); 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. # 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) (attrNames nodes)); externalPeers = foldl' recursiveUpdate {} (
map (n: mapAttrs' (extPeerName: nameValuePair "external-${extPeerName}") nodes.${n}.config.extra.wireguard.networks.${wgName}.externalPeers)
nodesWithThisNetwork
);
peers = nodePeers // externalPeers; 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;
};
};
in { in {
rekey.secrets = inherit nodesWithThisNetwork wgName;
foldl' recursiveUpdate {
${peerPrivateKeySecret wgName nodeName}.file = peerPrivateKeyFile wgName nodeName; secrets =
foldl' mergeAttrs {
${peerPrivateKeySecret nodeName}.file = peerPrivateKeyFile nodeName;
} (map (peerName: { } (map (peerName: {
${peerPresharedKeySecret wgName nodeName peerName}.file = peerPresharedKeyFile wgName nodeName peerName; ${peerPresharedKeySecret nodeName peerName}.file = peerPresharedKeyFile nodeName peerName;
}) (attrNames peers)); }) (attrNames peers));
systemd.network = { netdevs."${wg.priority}-${wgName}" = {
netdevs."${wg.priority}-${wgName}" = { netdevConfig = {
netdevConfig = { Kind = "wireguard";
Kind = "wireguard"; Name = "${wgName}";
Name = "${wgName}"; Description = "Wireguard network ${wgName}";
Description = "Wireguard network ${wgName}";
};
wireguardConfig = {
PrivateKeyFile = config.rekey.secrets.${peerPrivateKeySecret wgName nodeName}.path;
ListenPort = wg.listenPort;
};
wireguardPeers = mapAttrsToList (peerDefinition wgName) peers;
}; };
wireguardConfig = {
PrivateKeyFile = config.rekey.secrets.${peerPrivateKeySecret nodeName}.path;
ListenPort = wg.listenPort;
};
wireguardPeers = mapAttrsToList peerDefinition peers;
};
networks."${wg.priority}-${wgName}" = { networks."${wg.priority}-${wgName}" = {
matchConfig.Name = wgName; matchConfig.Name = wgName;
networkConfig.Address = wg.address; networkConfig.Address = wg.address;
};
}; };
}; };
in { in {
options = { options = {
networks = mkOption { extra.wireguard.networks = mkOption {
default = {}; default = {};
description = ""; description = "Configures wireguard networks via systemd-networkd.";
type = types.attrsOf (types.submodule { type = types.attrsOf (types.submodule {
options = { options = {
address = mkOption { address = mkOption {
@ -161,12 +172,40 @@ in {
}; };
}; };
config = mkIf (cfg.networks != {}) ({ config = mkIf (cfg.networks != {}) (let
# TODO assert that at least one peer has listen true networkCfgs = mapAttrsToList configForNetwork cfg.networks;
# TODO assert that no external peers are specified twice in different node configs collectAttrs = x: foldl' mergeAttrs {} (map (y: y.${x}) networkCfgs);
#assertions = []; 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;
networking.firewall.allowedUDPPorts = mkIf (cfg.listen && cfg.openFirewall) [cfg.listenPort]; networking.firewall.allowedUDPPorts = mkIf (cfg.listen && cfg.openFirewall) [cfg.listenPort];
} rekey.secrets = collectAttrs "secrets";
// foldl' recursiveUpdate {} (map configForNetwork cfg.networks)); systemd.network = {
netdevs = collectAttrs "netdevs";
networks = collectAttrs "networks";
};
});
} }

View file

@ -14,12 +14,15 @@
(nixpkgs.lib) (nixpkgs.lib)
optionals optionals
; ;
extraLib = import ./lib.nix inputs;
in in
nodeName: nodeMeta: { nodeName: nodeMeta: {
inherit (nodeMeta) system; inherit (nodeMeta) system;
pkgs = self.pkgs.${nodeMeta.system}; pkgs = self.pkgs.${nodeMeta.system};
specialArgs = { specialArgs = {
inherit (nixpkgs) lib; inherit (nixpkgs) lib;
inherit extraLib;
inherit inputs; inherit inputs;
inherit nodeName; inherit nodeName;
inherit nodeMeta; inherit nodeMeta;

23
nix/lib.nix Normal file
View file

@ -0,0 +1,23 @@
{nixpkgs, ...}: let
inherit
(nixpkgs.lib)
filter
foldl'
unique
;
in rec {
# Counts how often each element occurrs in xs
countOccurrences = xs: let
addOrUpdate = acc: x:
if builtins.hasAttr x acc
then acc // {${x} = acc.${x} + 1;}
else acc // {${x} = 1;};
in
foldl' addOrUpdate {} xs;
# Returns all elements in xs that occur at least once
duplicates = xs: let
occurrences = countOccurrences xs;
in
unique (filter (x: occurrences.${x} > 1) xs);
}