mirror of
https://github.com/oddlama/nix-config.git
synced 2025-10-10 23:00:39 +02:00
feat(wireguard): add ability to automatically assign addresses
This commit is contained in:
parent
4e8103af47
commit
c789e2de36
8 changed files with 136 additions and 47 deletions
10
flake.lock
generated
10
flake.lock
generated
|
@ -210,11 +210,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1685019994,
|
||||
"narHash": "sha256-81o6SKZPALvib21hIOMx2lIhFSs0mRy0PfPvg0zsfTk=",
|
||||
"lastModified": 1685189510,
|
||||
"narHash": "sha256-Hq5WF7zIixojPgvhgcd6MBvywwycVZ9wpK/8ogOyoaA=",
|
||||
"owner": "nix-community",
|
||||
"repo": "home-manager",
|
||||
"rev": "d1f04b0f365a34896a37d9015637796537ec88a3",
|
||||
"rev": "2d963854ae2499193c0c72fd67435fee34d3e4fd",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -260,8 +260,8 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1685048145,
|
||||
"narHash": "sha256-IGvX/JZReujsF8dLJUr0+DdjplR5qeSOWKQbpXc9j9E=",
|
||||
"lastModified": 1685202716,
|
||||
"narHash": "sha256-1+YC2fjzYI0XKmKIC0jswSYYWCPx2gOAf4/6FgcP+9Q=",
|
||||
"type": "git",
|
||||
"url": "file:///root/projects/microvm.nix"
|
||||
},
|
||||
|
|
|
@ -106,7 +106,7 @@
|
|||
# Collect all defined microvm nodes from each colmena node
|
||||
microvmNodes = nixpkgs.lib.concatMapAttrs (_: node:
|
||||
nixpkgs.lib.mapAttrs'
|
||||
(vm: def: nixpkgs.lib.nameValuePair def.nodeName node.config.microvm.vms.${vm})
|
||||
(vm: def: nixpkgs.lib.nameValuePair def.nodeName node.config.microvm.vms.${vm}.config)
|
||||
(node.config.extra.microvms.vms or {}))
|
||||
self.colmenaNodes;
|
||||
# Expose all nodes in a single attribute
|
||||
|
|
|
@ -231,7 +231,7 @@
|
|||
};
|
||||
in
|
||||
assert lib.assertMsg (cidrSize >= 2 && cidrSize <= 62)
|
||||
"assignIps: cidrSize=${cidrSize} is not in [2, 62].";
|
||||
"assignIps: cidrSize=${toString cidrSize} is not in [2, 62].";
|
||||
assert lib.assertMsg (nHosts <= capacity - nInit)
|
||||
"assignIps: number of hosts (${toString nHosts}) must be <= capacity (${toString capacity}) - reserved (${toString nInit})";
|
||||
# Assign an ip in the subnet to each element, in order
|
||||
|
@ -242,7 +242,9 @@
|
|||
sortedHosts)
|
||||
.assigned;
|
||||
};
|
||||
ip = {
|
||||
ip = rec {
|
||||
# Checks whether the given address (with or without cidr notation) is an ipv4 address.
|
||||
isv4 = x: !isv6 x;
|
||||
# Checks whether the given address (with or without cidr notation) is an ipv6 address.
|
||||
isv6 = lib.hasInfix ":";
|
||||
};
|
||||
|
|
|
@ -25,6 +25,13 @@ in {
|
|||
|
||||
boot.initrd.availableKernelModules = ["xhci_pci" "ahci" "nvme" "usbhid" "usb_storage" "sd_mod" "sdhci_pci" "r8169"];
|
||||
|
||||
extra.wireguard.proxy-sentinel.server = {
|
||||
host = "TODO REMOVE";
|
||||
port = 51443;
|
||||
reservedAddresses = ["10.0.43.0/24" "fd43::/120"];
|
||||
openFirewallRules = ["untrusted-to-local"];
|
||||
};
|
||||
|
||||
extra.microvms.vms = let
|
||||
defineVm = id: {
|
||||
inherit id;
|
||||
|
|
|
@ -36,18 +36,24 @@
|
|||
mergeToplevelConfigs
|
||||
;
|
||||
|
||||
inherit
|
||||
(extraLib.types)
|
||||
lazyOf
|
||||
lazyValue
|
||||
;
|
||||
|
||||
inherit (config.lib) net;
|
||||
cfg = config.extra.wireguard;
|
||||
|
||||
configForNetwork = wgName: wgCfg: let
|
||||
inherit
|
||||
(extraLib.wireguard wgName)
|
||||
associatedClientNodes
|
||||
associatedNodes
|
||||
associatedServerNodes
|
||||
externalPeerName
|
||||
externalPeerNamesRaw
|
||||
networkCidrs
|
||||
participatingClientNodes
|
||||
participatingNodes
|
||||
participatingServerNodes
|
||||
peerPresharedKeyPath
|
||||
peerPresharedKeySecret
|
||||
peerPrivateKeyPath
|
||||
|
@ -65,14 +71,14 @@
|
|||
# All nodes that use our node as the via into the wireguard network
|
||||
ourClientNodes =
|
||||
optionals isServer
|
||||
(filter (n: (wgCfgOf n).client.via == nodeName) associatedClientNodes);
|
||||
(filter (n: (wgCfgOf n).client.via == nodeName) participatingClientNodes);
|
||||
|
||||
# The list of peers for which we have to know the psk.
|
||||
neededPeers =
|
||||
if isServer
|
||||
then
|
||||
# Other servers in the same network
|
||||
filterSelf associatedServerNodes
|
||||
filterSelf participatingServerNodes
|
||||
# Our external peers
|
||||
++ map externalPeerName (attrNames wgCfg.server.externalPeers)
|
||||
# Our clients
|
||||
|
@ -102,12 +108,12 @@
|
|||
# plus traffic for any of its external peers
|
||||
++ attrValues snCfg.server.externalPeers
|
||||
# plus traffic for any client that is connected via that server
|
||||
++ map (n: (wgCfgOf n).addresses) (filter (n: (wgCfgOf n).client.via == serverNode) associatedClientNodes)
|
||||
++ map (n: (wgCfgOf n).addresses) (filter (n: (wgCfgOf n).client.via == serverNode) participatingClientNodes)
|
||||
);
|
||||
in {
|
||||
assertions = [
|
||||
{
|
||||
assertion = any (n: (wgCfgOf n).server.host != null) associatedNodes;
|
||||
assertion = any (n: (wgCfgOf n).server.host != null) participatingNodes;
|
||||
message = "${assertionPrefix}: At least one node in a network must be a server.";
|
||||
}
|
||||
{
|
||||
|
@ -184,7 +190,7 @@
|
|||
Endpoint = "${snCfg.server.host}:${toString snCfg.server.port}";
|
||||
};
|
||||
})
|
||||
(filterSelf associatedServerNodes)
|
||||
(filterSelf participatingServerNodes)
|
||||
# All our external peers
|
||||
++ mapAttrsToList (extPeer: ips: let
|
||||
peerName = externalPeerName extPeer;
|
||||
|
@ -241,6 +247,7 @@ in {
|
|||
type = types.lazyAttrsOf (types.submodule ({
|
||||
config,
|
||||
name,
|
||||
options,
|
||||
...
|
||||
}: {
|
||||
options = {
|
||||
|
@ -340,8 +347,8 @@ in {
|
|||
};
|
||||
|
||||
ipv4 = mkOption {
|
||||
type = net.types.ipv4;
|
||||
default = spannedReservedNetwork.cidrv4;
|
||||
type = lazyOf net.types.ipv4;
|
||||
default = lazyValue (extraLib.wireguard name).assignedIpv4Addresses.${nodeName};
|
||||
description = mdDoc ''
|
||||
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
|
||||
|
@ -352,8 +359,8 @@ in {
|
|||
};
|
||||
|
||||
ipv6 = mkOption {
|
||||
type = net.types.ipv6;
|
||||
default = ;
|
||||
type = lazyOf net.types.ipv6;
|
||||
default = lazyValue (extraLib.wireguard name).assignedIpv6Addresses.${nodeName};
|
||||
description = mdDoc ''
|
||||
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
|
||||
|
@ -364,8 +371,11 @@ in {
|
|||
};
|
||||
|
||||
addresses = mkOption {
|
||||
type = types.listOf net.types.ip;
|
||||
default = [config.ipv4 config.ipv6];
|
||||
type = types.listOf (lazyOf net.types.ip);
|
||||
default = [
|
||||
(head options.ipv4.definitions)
|
||||
(head options.ipv6.definitions)
|
||||
];
|
||||
description = mdDoc ''
|
||||
The ip addresses (v4 and/or v6) to use for this machine.
|
||||
The actual network cidr will automatically be derived from all network participants.
|
||||
|
@ -373,7 +383,7 @@ in {
|
|||
'';
|
||||
};
|
||||
|
||||
# TODO this needs to be implemented.
|
||||
# TODO this is not yet implemented.
|
||||
# - is 0.0.0.0/0 also for valid for routing global ipv6?
|
||||
# - is 0.0.0.0/0 routing private spaces such as 192.168.1 ? that'd be baaad
|
||||
# - force nodes to opt-in or allow nodes to opt-out? sometimes a node want's
|
||||
|
|
|
@ -26,10 +26,10 @@
|
|||
inherit
|
||||
(self.extraLib.wireguard wgName)
|
||||
allPeers
|
||||
associatedNodes
|
||||
associatedServerNodes
|
||||
associatedClientNodes
|
||||
externalPeersForNode
|
||||
participatingClientNodes
|
||||
participatingNodes
|
||||
participatingServerNodes
|
||||
peerPresharedKeyFile
|
||||
peerPrivateKeyFile
|
||||
peerPublicKeyFile
|
||||
|
@ -76,11 +76,11 @@
|
|||
["echo ==== ${wgName} ===="]
|
||||
++ map generatePeerKeys (attrNames allPeers)
|
||||
# All server-nodes need a psk for each other, but not reflexive.
|
||||
++ psksForPeerCombinations associatedServerNodes (n: filter (x: x != n) associatedServerNodes)
|
||||
++ psksForPeerCombinations participatingServerNodes (n: filter (x: x != n) participatingServerNodes)
|
||||
# Each server-node need a psk for all client nodes
|
||||
++ psksForPeerCombinations associatedServerNodes (_: associatedClientNodes)
|
||||
++ psksForPeerCombinations participatingServerNodes (_: participatingClientNodes)
|
||||
# Each server-node need a psk for all their external peers
|
||||
++ psksForPeerCombinations associatedServerNodes (n: attrNames (externalPeersForNode n));
|
||||
++ psksForPeerCombinations participatingServerNodes (n: attrNames (externalPeersForNode n));
|
||||
in
|
||||
pkgs.writeShellScript "generate-wireguard-keys" ''
|
||||
set -euo pipefail
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
map
|
||||
(peer: {inherit wgName serverNode peer;})
|
||||
(attrNames self.nodes.${serverNode}.config.extra.wireguard.${wgName}.server.externalPeers))
|
||||
(self.extraLib.wireguard wgName).associatedServerNodes;
|
||||
(self.extraLib.wireguard wgName).participatingServerNodes;
|
||||
allExternalPeers = concatMap externalPeersForNet wireguardNetworks;
|
||||
in
|
||||
pkgs.writeShellScript "show-wireguard-qr" ''
|
||||
|
|
104
nix/lib.nix
104
nix/lib.nix
|
@ -5,9 +5,11 @@
|
|||
}: let
|
||||
inherit
|
||||
(nixpkgs.lib)
|
||||
all
|
||||
assertMsg
|
||||
attrNames
|
||||
attrValues
|
||||
concatLists
|
||||
concatMap
|
||||
concatMapStrings
|
||||
concatStringsSep
|
||||
|
@ -15,24 +17,59 @@
|
|||
escapeShellArg
|
||||
filter
|
||||
flatten
|
||||
flip
|
||||
foldAttrs
|
||||
foldl'
|
||||
genAttrs
|
||||
genList
|
||||
head
|
||||
isAttrs
|
||||
mapAttrs'
|
||||
mergeAttrs
|
||||
mkMerge
|
||||
mkOptionType
|
||||
nameValuePair
|
||||
optionalAttrs
|
||||
partition
|
||||
recursiveUpdate
|
||||
removeSuffix
|
||||
showOption
|
||||
stringToCharacters
|
||||
substring
|
||||
unique
|
||||
;
|
||||
in rec {
|
||||
types = rec {
|
||||
# Checks whether the value is a lazy value without causing
|
||||
# it's value to be evaluated
|
||||
isLazyValue = x: isAttrs x && x ? _lazyValue;
|
||||
# Constructs a lazy value holding the given value.
|
||||
lazyValue = value: {_lazyValue = value;};
|
||||
|
||||
# Represents a lazy value of the given type, which
|
||||
# holds the actual value as an attrset like { _lazyValue = <actual value>; }.
|
||||
# This allows the option to be defined and filtered from a defintion
|
||||
# list without evaluating the value.
|
||||
lazyValueOf = type:
|
||||
mkOptionType rec {
|
||||
name = "lazyValueOf ${type.name}";
|
||||
inherit (type) description descriptionClass emptyValue getSubOptions getSubModules;
|
||||
check = isLazyValue;
|
||||
merge = loc: defs:
|
||||
assert assertMsg
|
||||
(all (x: type.check x._lazyValue) defs)
|
||||
"The option `${showOption loc}` is defined with a lazy value holding an invalid type";
|
||||
nixpkgs.lib.types.mergeOneOption loc defs;
|
||||
substSubModules = m: nixpkgs.lib.types.uniq (type.substSubModules m);
|
||||
functor = (nixpkgs.lib.types.defaultFunctor name) // {wrapped = type;};
|
||||
nestedTypes.elemType = type;
|
||||
};
|
||||
|
||||
# Represents a value or lazy value of the given type that will
|
||||
# automatically be coerced to the given type when merged.
|
||||
lazyOf = type: nixpkgs.lib.types.coercedTo (lazyValueOf type) (x: x._lazyValue) type;
|
||||
};
|
||||
|
||||
# Counts how often each element occurrs in xs
|
||||
countOccurrences = let
|
||||
addOrUpdate = acc: x:
|
||||
|
@ -169,11 +206,12 @@ in rec {
|
|||
rageDecryptArgs = "${rageMasterIdentityArgs}";
|
||||
rageEncryptArgs = "${rageMasterIdentityArgs} ${rageExtraEncryptionPubkeys}";
|
||||
|
||||
# TODO merge this into a _meta readonly option in the wireguard module
|
||||
# Wireguard related functions that are reused in several files of this flake
|
||||
wireguard = wgName: rec {
|
||||
# Get access to the networking lib by referring to one of the associated nodes.
|
||||
# Get access to the networking lib by referring to one of the participating nodes.
|
||||
# Not ideal, but ok.
|
||||
inherit (self.nodes.${head associatedNodes}.config.lib) net;
|
||||
inherit (self.nodes.${head participatingNodes}.config.lib) net;
|
||||
|
||||
# Returns the given node's wireguard configuration of this network
|
||||
wgCfgOf = node: self.nodes.${node}.config.extra.wireguard.${wgName};
|
||||
|
@ -205,22 +243,22 @@ in rec {
|
|||
in "wireguard-${wgName}-psks-${peer1}+${peer2}";
|
||||
|
||||
# All nodes that are part of this network
|
||||
associatedNodes =
|
||||
participatingNodes =
|
||||
filter
|
||||
(n: builtins.hasAttr wgName self.nodes.${n}.config.extra.wireguard)
|
||||
(attrNames self.nodes);
|
||||
|
||||
# Partition nodes by whether they are servers
|
||||
_associatedNodes_isServerPartition =
|
||||
_participatingNodes_isServerPartition =
|
||||
partition
|
||||
(n: (wgCfgOf n).server.host != null)
|
||||
associatedNodes;
|
||||
participatingNodes;
|
||||
|
||||
associatedServerNodes = _associatedNodes_isServerPartition.right;
|
||||
associatedClientNodes = _associatedNodes_isServerPartition.wrong;
|
||||
participatingServerNodes = _participatingNodes_isServerPartition.right;
|
||||
participatingClientNodes = _participatingNodes_isServerPartition.wrong;
|
||||
|
||||
# Maps all nodes that are part of this network to their addresses
|
||||
nodePeers = genAttrs associatedNodes (n: (wgCfgOf n).addresses);
|
||||
nodePeers = genAttrs participatingNodes (n: (wgCfgOf n).addresses);
|
||||
|
||||
externalPeerName = p: "external-${p}";
|
||||
|
||||
|
@ -231,34 +269,66 @@ in rec {
|
|||
|
||||
# All peers that are defined as externalPeers on any node.
|
||||
# Prepends "external-" to their name.
|
||||
allExternalPeers = concatAttrs (map externalPeersForNode associatedNodes);
|
||||
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) associatedNodes;
|
||||
externalPeerNamesRaw = concatMap (n: attrNames (wgCfgOf n).server.externalPeers) participatingNodes;
|
||||
|
||||
# A list of all occurring addresses.
|
||||
usedAddresses =
|
||||
concatMap (n: (wgCfgOf n).addresses) associatedNodes
|
||||
++ flatten (concatMap (n: attrValues (wgCfgOf n).server.externalPeers) associatedNodes);
|
||||
concatMap (n: (wgCfgOf n).addresses) participatingNodes
|
||||
++ flatten (concatMap (n: attrValues (wgCfgOf n).server.externalPeers) participatingNodes);
|
||||
|
||||
# The cidrv4 and cidrv6 of the network spanned by all reserved addresses only.
|
||||
# Used to determine automatically assigned addresses first.
|
||||
spannedReservedNetwork =
|
||||
net.cidr.merge (concatMap (n: (wgCfgOf n).server.reservedAddresses) associatedServerNodes);
|
||||
# A list of all occurring addresses, but only includes addresses that
|
||||
# are not assigned automatically.
|
||||
explicitlyUsedAddresses =
|
||||
flip concatMap participatingNodes
|
||||
(n:
|
||||
filter (x: !types.isLazyValue x)
|
||||
(concatLists
|
||||
(self.nodes.${n}.options.extra.wireguard.type.functor.wrapped.getSubOptions (wgCfgOf n)).addresses.definitions))
|
||||
++ flatten (concatMap (n: attrValues (wgCfgOf n).server.externalPeers) participatingNodes);
|
||||
|
||||
# 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.
|
||||
networkAddresses =
|
||||
net.cidr.merge (usedAddresses
|
||||
++ concatMap (n: (wgCfgOf n).server.reservedAddresses) associatedServerNodes);
|
||||
++ concatMap (n: (wgCfgOf n).server.reservedAddresses) participatingServerNodes);
|
||||
|
||||
# The network spanning cidr addresses. The respective cidrv4 and cirdv6 are only
|
||||
# included if they exist.
|
||||
networkCidrs = filter (x: x != null) (attrValues networkAddresses);
|
||||
|
||||
# The cidrv4 and cidrv6 of the network spanned by all reserved addresses only.
|
||||
# Used to determine automatically assigned addresses first.
|
||||
spannedReservedNetwork =
|
||||
net.cidr.merge (concatMap (n: (wgCfgOf n).server.reservedAddresses) participatingServerNodes);
|
||||
|
||||
# Assigns an ipv4 address from spannedReservedNetwork.cidrv4
|
||||
# to each participant that has not explicitly specified an ipv4 address.
|
||||
assignedIpv4Addresses = assert assertMsg
|
||||
(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.";
|
||||
net.cidr.assignIps
|
||||
spannedReservedNetwork.cidrv4
|
||||
# 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))
|
||||
participatingNodes;
|
||||
|
||||
# Assigns an ipv4 address from spannedReservedNetwork.cidrv4
|
||||
# to each participant that has not explicitly specified an ipv4 address.
|
||||
assignedIpv6Addresses = assert assertMsg
|
||||
(spannedReservedNetwork.cidrv6 != null)
|
||||
"Wireguard network '${wgName}': At least one participating node must reserve a cidrv6 address via `reservedAddresses` so that ipv4 addresses can be assigned automatically from that network.";
|
||||
net.cidr.assignIps
|
||||
spannedReservedNetwork.cidrv6
|
||||
# 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))
|
||||
participatingNodes;
|
||||
|
||||
# Appends / replaces the correct cidr length to the argument,
|
||||
# so that the resulting address is in the cidr.
|
||||
toNetworkAddr = addr: let
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue