feat: implement cidr coersion to automatically determine wireguard network size from participants

This commit is contained in:
oddlama 2023-05-20 15:57:19 +02:00
parent 6d8f8ab2e3
commit 4057ee9051
No known key found for this signature in database
GPG key ID: 14EFE510775FE39A
14 changed files with 240 additions and 29 deletions

View file

@ -142,8 +142,8 @@
static = {
matchConfig.Name = vmCfg.networking.mainLinkName;
address = [
vmCfg.networking.static.ipv4
vmCfg.networking.static.ipv6
"${vmCfg.networking.static.ipv4}/${toString (net.cidr.length cfg.networking.static.baseCidrv4)}"
"${vmCfg.networking.static.ipv6}/${toString (net.cidr.length cfg.networking.static.baseCidrv6)}"
];
gateway = [
cfg.networking.host
@ -161,13 +161,14 @@
boot.initrd.systemd.enable = mkForce false;
# Create a firewall zone for the bridged traffic and secure vm traffic
# TODO mkForce nftables
networking.nftables.firewall = {
zones = lib.mkForce {
zones = mkForce {
"${vmCfg.networking.mainLinkName}".interfaces = [vmCfg.networking.mainLinkName];
"local-vms".interfaces = ["wg-local-vms"];
};
rules = lib.mkForce {
rules = mkForce {
"${vmCfg.networking.mainLinkName}-to-local" = {
from = [vmCfg.networking.mainLinkName];
to = ["local"];
@ -184,8 +185,8 @@
# We have a resolvable hostname / static ip, so all peers can directly communicate with us
server = optionalAttrs (cfg.networking.host != null) {
inherit (vmCfg.networking) host;
port = 51829;
openFirewallInRules = ["${vmCfg.networking.mainLinkName}-to-local"];
inherit (cfg.networking.wireguard) port;
openFirewallRules = ["${vmCfg.networking.mainLinkName}-to-local"];
};
# If We don't have such guarantees, so we must use a client-server architecture.
client = optionalAttrs (cfg.networking.host == null) {
@ -262,6 +263,18 @@ in {
description = mdDoc "The ipv6 network address range to use for internal vm traffic.";
default = "fddd::/64";
};
port = mkOption {
default = 51829;
type = types.port;
description = mdDoc "The port to listen on.";
};
openFirewallRules = mkOption {
default = [];
type = types.listOf types.str;
description = mdDoc "The {option}`port` will be opened for all of the given rules in the nftable-firewall.";
};
};
};
@ -387,8 +400,7 @@ in {
extra.wireguard."${nodeName}-local-vms" = {
server = {
inherit (cfg.networking) host;
port = 51829;
openFirewallInRules = ["lan-to-local"];
inherit (cfg.networking.wireguard) openFirewallRules port;
};
cidrv4 = net.cidr.hostCidr 1 cfg.networking.wireguard.cidrv4;
cidrv6 = net.cidr.hostCidr 1 cfg.networking.wireguard.cidrv6;

View file

@ -26,7 +26,6 @@
mkOption
optionalAttrs
optionals
splitString
types
;
@ -54,6 +53,7 @@
peerPrivateKeySecret
peerPublicKeyPath
usedAddresses
toNetworkAddr
;
isServer = wgCfg.server.host != null;
@ -82,7 +82,7 @@
# Figure out if there are duplicate peers or addresses so we can
# make an assertion later.
duplicatePeers = duplicates externalPeerNamesRaw;
duplicateAddrs = duplicates (map (x: head (splitString "/" x)) usedAddresses);
duplicateAddrs = duplicates (map net.cidr.ip usedAddresses);
# Adds context information to the assertions for this network
assertionPrefix = "Wireguard network '${wgName}' on '${nodeName}'";
@ -118,16 +118,27 @@
(isServer && wgCfg.server.openFirewall)
[wgCfg.server.port];
# TODO mkForce nftables
networking.nftables.firewall.rules =
mkIf
(isServer && wgCfg.server.openFirewallInRules != [])
(genAttrs wgCfg.server.openFirewallInRules (_: {allowedUDPPorts = [wgCfg.server.port];}));
(isServer && wgCfg.server.openFirewallRules != [])
(lib.mkForce (genAttrs wgCfg.server.openFirewallRules (_: {allowedUDPPorts = [wgCfg.server.port];})));
rekey.secrets =
concatAttrs (map
(other: {${peerPresharedKeySecret nodeName other}.file = peerPresharedKeyPath nodeName other;})
(other: {
${peerPresharedKeySecret nodeName other} = {
file = peerPresharedKeyPath nodeName other;
owner = "systemd-network";
};
})
neededPeers)
// {${peerPrivateKeySecret nodeName}.file = peerPrivateKeyPath nodeName;};
// {
${peerPrivateKeySecret nodeName} = {
file = peerPrivateKeyPath nodeName;
owner = "systemd-network";
};
};
systemd.network.netdevs."${toString wgCfg.priority}-${wgName}" = {
netdevConfig = {
@ -156,21 +167,18 @@
# plus each external peer's addresses,
# plus each client's addresses that is connected via that node.
AllowedIPs = snCfg.addresses;
# TODO this needed? or even wanted at all?
# ++ attrValues snCfg.server.externalPeers;
# ++ map (n: (wgCfgOf n).addresses) snCfg.ourClientNodes;
Endpoint = "${snCfg.server.host}:${toString snCfg.server.port}";
};
})
(filterSelf associatedServerNodes)
# All our external peers
++ mapAttrsToList (extPeer: allowedIPs: let
++ mapAttrsToList (extPeer: ips: let
peerName = externalPeerName extPeer;
in {
wireguardPeerConfig = {
PublicKey = builtins.readFile (peerPublicKeyPath peerName);
PresharedKeyFile = config.rekey.secrets.${peerPresharedKeySecret nodeName peerName}.path;
AllowedIPs = allowedIPs;
AllowedIPs = map (net.cidr.make 128) ips;
# Connections to external peers should always be kept alive
PersistentKeepalive = 25;
};
@ -207,7 +215,7 @@
systemd.network.networks."${toString wgCfg.priority}-${wgName}" = {
matchConfig.Name = wgName;
networkConfig.Address = wgCfg.addresses;
address = map toNetworkAddr wgCfg.addresses;
};
};
in {
@ -239,16 +247,16 @@ in {
description = mdDoc "Whether to open the firewall for the specified {option}`port`.";
};
openFirewallInRules = mkOption {
openFirewallRules = mkOption {
default = [];
type = types.listOf types.str;
description = mdDoc "The {option}`port` will be opened for all of the given rules in the nftable-firewall.";
};
externalPeers = mkOption {
type = types.attrsOf (types.listOf (net.types.cidr-in config.addresses));
type = types.attrsOf (types.listOf (net.types.ip-in config.addresses));
default = {};
example = {my-android-phone = ["10.0.0.97/32"];};
example = {my-android-phone = ["10.0.0.97"];};
description = mdDoc ''
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)
@ -329,6 +337,7 @@ in {
description = mdDoc ''
The addresses (with cidr mask) to configure for this interface.
The cidr mask determines this peers allowed address range as configured on other peers.
The actual network cidr will automatically be derived from all network participants.
By default this will just include {option}`cidrv4` and {option}`cidrv6` as configured.
'';
};