mirror of
https://github.com/oddlama/nix-config.git
synced 2025-10-11 07:10:39 +02:00
feat(wireguard): finish module and assertions
This commit is contained in:
parent
ea48c316cc
commit
786fb75920
6 changed files with 142 additions and 77 deletions
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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)";
|
||||||
|
|
|
@ -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";
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
23
nix/lib.nix
Normal 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);
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue