feat: rework wireguard modules using globals
This commit is contained in:
parent
93b08971cf
commit
853c9e2a2d
6 changed files with 442 additions and 622 deletions
|
@ -17,6 +17,7 @@ in
|
||||||
These should not include any config only option declaration.
|
These should not include any config only option declaration.
|
||||||
Will be included in the exported nixos Modules from this flake to be included
|
Will be included in the exported nixos Modules from this flake to be included
|
||||||
into the host evaluation.
|
into the host evaluation.
|
||||||
|
Be aware that at most 1 of these modules can have a default
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
defModules = mkOption {
|
defModules = mkOption {
|
||||||
|
@ -38,37 +39,45 @@ in
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
config.flake = flakeSubmod: {
|
config = {
|
||||||
globals =
|
globals = {
|
||||||
let
|
optModules = [
|
||||||
globalsSystem = lib.evalModules {
|
../modules/wireguardGlobals.nix
|
||||||
prefix = [ "globals" ];
|
];
|
||||||
specialArgs = {
|
attrkeys = [ "wireguard" ];
|
||||||
inherit (inputs.self.pkgs.x86_64-linux) lib;
|
};
|
||||||
inherit inputs;
|
flake = flakeSubmod: {
|
||||||
inherit (flakeSubmod.config) nodes;
|
globals =
|
||||||
};
|
let
|
||||||
modules =
|
globalsSystem = lib.evalModules {
|
||||||
config.globals.optModules
|
prefix = [ "globals" ];
|
||||||
++ config.globals.defModules
|
specialArgs = {
|
||||||
++ [
|
inherit (inputs.self.pkgs.x86_64-linux) lib;
|
||||||
../modules/globals.nix
|
inherit inputs;
|
||||||
(
|
inherit (flakeSubmod.config) nodes;
|
||||||
{ lib, ... }:
|
};
|
||||||
{
|
modules =
|
||||||
globals = lib.mkMerge (
|
config.globals.optModules
|
||||||
lib.concatLists (
|
++ config.globals.defModules
|
||||||
lib.flip lib.mapAttrsToList flakeSubmod.config.nodes (
|
++ [
|
||||||
name: cfg:
|
../modules/globals.nix
|
||||||
builtins.addErrorContext "while aggregating globals from nixosConfigurations.${name} into flake-level globals:" cfg.config._globalsDefs
|
(
|
||||||
|
{ lib, ... }:
|
||||||
|
{
|
||||||
|
globals = lib.mkMerge (
|
||||||
|
lib.concatLists (
|
||||||
|
lib.flip lib.mapAttrsToList flakeSubmod.config.nodes (
|
||||||
|
name: cfg:
|
||||||
|
builtins.addErrorContext "while aggregating globals from nixosConfigurations.${name} into flake-level globals:" cfg.config._globalsDefs
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
);
|
||||||
);
|
}
|
||||||
}
|
)
|
||||||
)
|
];
|
||||||
];
|
};
|
||||||
};
|
in
|
||||||
in
|
lib.genAttrs config.globals.attrkeys (x: globalsSystem.config.globals.${x});
|
||||||
lib.genAttrs config.globals.attrkeys (x: globalsSystem.config.globals.${x});
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,178 +0,0 @@
|
||||||
inputs: final: prev: let
|
|
||||||
inherit
|
|
||||||
(inputs.nixpkgs.lib)
|
|
||||||
assertMsg
|
|
||||||
attrNames
|
|
||||||
attrValues
|
|
||||||
concatLists
|
|
||||||
concatMap
|
|
||||||
filter
|
|
||||||
flip
|
|
||||||
genAttrs
|
|
||||||
partition
|
|
||||||
warn
|
|
||||||
;
|
|
||||||
|
|
||||||
inherit
|
|
||||||
(final.lib)
|
|
||||||
net
|
|
||||||
types
|
|
||||||
;
|
|
||||||
in {
|
|
||||||
lib =
|
|
||||||
prev.lib
|
|
||||||
// rec {
|
|
||||||
wireguard.evaluateNetwork = userInputs: wgName: let
|
|
||||||
inherit (userInputs.self) nodes;
|
|
||||||
# Returns the given node's wireguard configuration of this network
|
|
||||||
wgCfgOf = node: nodes.${node}.config.wireguard.${wgName};
|
|
||||||
|
|
||||||
sortedPeers = peerA: peerB:
|
|
||||||
if peerA < peerB
|
|
||||||
then {
|
|
||||||
peer1 = peerA;
|
|
||||||
peer2 = peerB;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
peer1 = peerB;
|
|
||||||
peer2 = peerA;
|
|
||||||
};
|
|
||||||
|
|
||||||
peerPublicKeyFile = peerName: "/secrets/wireguard/${wgName}/keys/${peerName}.pub";
|
|
||||||
peerPublicKeyPath = peerName: userInputs.self.outPath + peerPublicKeyFile peerName;
|
|
||||||
|
|
||||||
peerPrivateKeyFile = peerName: "/secrets/wireguard/${wgName}/keys/${peerName}.age";
|
|
||||||
peerPrivateKeyPath = peerName: userInputs.self.outPath + peerPrivateKeyFile peerName;
|
|
||||||
peerPrivateKeySecret = peerName: "wireguard-${wgName}-priv-${peerName}";
|
|
||||||
|
|
||||||
peerPresharedKeyFile = peerA: peerB: let
|
|
||||||
inherit (sortedPeers peerA peerB) peer1 peer2;
|
|
||||||
in "/secrets/wireguard/${wgName}/psks/${peer1}+${peer2}.age";
|
|
||||||
peerPresharedKeyPath = peerA: peerB: userInputs.self.outPath + peerPresharedKeyFile peerA peerB;
|
|
||||||
peerPresharedKeySecret = peerA: peerB: let
|
|
||||||
inherit (sortedPeers peerA peerB) peer1 peer2;
|
|
||||||
in "wireguard-${wgName}-psks-${peer1}+${peer2}";
|
|
||||||
|
|
||||||
# All nodes that are part of this network
|
|
||||||
participatingNodes = filter (n: builtins.hasAttr wgName nodes.${n}.config.wireguard) (
|
|
||||||
attrNames nodes
|
|
||||||
);
|
|
||||||
|
|
||||||
# Partition nodes by whether they are servers
|
|
||||||
_participatingNodes_isServerPartition =
|
|
||||||
partition (
|
|
||||||
n: (wgCfgOf n).server.host != null
|
|
||||||
)
|
|
||||||
participatingNodes;
|
|
||||||
|
|
||||||
participatingServerNodes = _participatingNodes_isServerPartition.right;
|
|
||||||
participatingClientNodes = _participatingNodes_isServerPartition.wrong;
|
|
||||||
|
|
||||||
# Maps all nodes that are part of this network to their addresses
|
|
||||||
nodePeers = genAttrs participatingNodes (n: (wgCfgOf n).addresses);
|
|
||||||
|
|
||||||
# A list of all occurring addresses.
|
|
||||||
usedAddresses = concatMap (n: (wgCfgOf n).addresses) participatingNodes;
|
|
||||||
|
|
||||||
# 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
|
|
||||||
(nodes.${n}.options.wireguard.type.nestedTypes.elemType.getSubOptions (wgCfgOf n))
|
|
||||||
.addresses
|
|
||||||
.definitions
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
# 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) 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 ipv6 address from spannedReservedNetwork.cidrv6
|
|
||||||
# to each participant that has not explicitly specified an ipv6 address.
|
|
||||||
assignedIpv6Addresses = assert assertMsg (spannedReservedNetwork.cidrv6 != null)
|
|
||||||
"Wireguard network '${wgName}': At least one participating node must reserve a cidrv6 address via `reservedAddresses` so that ipv6 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
|
|
||||||
relevantNetworkAddr =
|
|
||||||
if net.ip.isv6 addr
|
|
||||||
then networkAddresses.cidrv6
|
|
||||||
else networkAddresses.cidrv4;
|
|
||||||
in "${net.cidr.ip addr}/${toString (net.cidr.length relevantNetworkAddr)}";
|
|
||||||
in {
|
|
||||||
inherit
|
|
||||||
assignedIpv4Addresses
|
|
||||||
assignedIpv6Addresses
|
|
||||||
explicitlyUsedAddresses
|
|
||||||
networkAddresses
|
|
||||||
networkCidrs
|
|
||||||
nodePeers
|
|
||||||
participatingClientNodes
|
|
||||||
participatingNodes
|
|
||||||
participatingServerNodes
|
|
||||||
peerPresharedKeyFile
|
|
||||||
peerPresharedKeyPath
|
|
||||||
peerPresharedKeySecret
|
|
||||||
peerPrivateKeyFile
|
|
||||||
peerPrivateKeyPath
|
|
||||||
peerPrivateKeySecret
|
|
||||||
peerPublicKeyFile
|
|
||||||
peerPublicKeyPath
|
|
||||||
sortedPeers
|
|
||||||
spannedReservedNetwork
|
|
||||||
toNetworkAddr
|
|
||||||
usedAddresses
|
|
||||||
wgCfgOf
|
|
||||||
;
|
|
||||||
};
|
|
||||||
|
|
||||||
wireguard.createEvalCache = userInputs: wgNames: genAttrs wgNames (wireguard.evaluateNetwork userInputs);
|
|
||||||
|
|
||||||
wireguard.getNetwork = userInputs: wgName:
|
|
||||||
userInputs.self.wireguardEvalCache.${wgName}
|
|
||||||
or (warn ''
|
|
||||||
The calculated information for the wireguard network "${wgName}" is not cached!
|
|
||||||
This will siginificantly increase evaluation times. Please consider pre-evaluating
|
|
||||||
this information by exposing it in your flake:
|
|
||||||
|
|
||||||
wireguardEvalCache = lib.wireguard.createEvalCache inputs [
|
|
||||||
"${wgName}"
|
|
||||||
# all other networks
|
|
||||||
];
|
|
||||||
|
|
||||||
'' (wireguard.evaluateNetwork userInputs wgName));
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -10,7 +10,7 @@
|
||||||
./nginx.nix
|
./nginx.nix
|
||||||
./node.nix
|
./node.nix
|
||||||
./restic.nix
|
./restic.nix
|
||||||
./topology-wireguard.nix
|
#./topology-wireguard.nix
|
||||||
./wireguard.nix
|
./wireguard.nix
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -2,456 +2,278 @@
|
||||||
config,
|
config,
|
||||||
inputs,
|
inputs,
|
||||||
lib,
|
lib,
|
||||||
|
globals,
|
||||||
...
|
...
|
||||||
}: let
|
}:
|
||||||
inherit
|
let
|
||||||
(lib)
|
inherit (lib)
|
||||||
any
|
any
|
||||||
concatAttrs
|
attrNames
|
||||||
concatMap
|
concatMapAttrs
|
||||||
concatStringsSep
|
count
|
||||||
duplicates
|
mkMerge
|
||||||
filter
|
filterAttrs
|
||||||
flip
|
flip
|
||||||
head
|
mapAttrs'
|
||||||
listToAttrs
|
|
||||||
mapAttrsToList
|
mapAttrsToList
|
||||||
mergeToplevelConfigs
|
|
||||||
mkIf
|
mkIf
|
||||||
mkOption
|
|
||||||
nameValuePair
|
nameValuePair
|
||||||
|
attrValues
|
||||||
net
|
net
|
||||||
|
optional
|
||||||
optionalAttrs
|
optionalAttrs
|
||||||
optionals
|
|
||||||
stringLength
|
stringLength
|
||||||
types
|
concatLists
|
||||||
;
|
;
|
||||||
|
|
||||||
cfg = config.wireguard;
|
memberWG = filterAttrs (
|
||||||
nodeName = config.node.name;
|
_: cfg: any (x: x == config.node.name) (attrNames cfg.hosts)
|
||||||
|
) globals.wireguard;
|
||||||
|
in
|
||||||
|
|
||||||
configForNetwork = wgName: wgCfg: let
|
{
|
||||||
inherit
|
assertions = concatLists (
|
||||||
(lib.wireguard.getNetwork inputs wgName)
|
flip mapAttrsToList memberWG (
|
||||||
networkCidrs
|
networkName: networkCfg:
|
||||||
participatingClientNodes
|
let
|
||||||
participatingNodes
|
assertionPrefix = "While evaluation the wireguard network ${networkName}:";
|
||||||
participatingServerNodes
|
hostCfg = networkCfg.hosts.${config.node.name};
|
||||||
peerPresharedKeyPath
|
in
|
||||||
peerPresharedKeySecret
|
[
|
||||||
peerPrivateKeyPath
|
|
||||||
peerPrivateKeySecret
|
|
||||||
peerPublicKeyPath
|
|
||||||
toNetworkAddr
|
|
||||||
usedAddresses
|
|
||||||
wgCfgOf
|
|
||||||
;
|
|
||||||
|
|
||||||
isServer = wgCfg.server.host != null;
|
|
||||||
isClient = wgCfg.client.via != null;
|
|
||||||
filterSelf = filter (x: x != nodeName);
|
|
||||||
|
|
||||||
# All nodes that use our node as the via into the wireguard network
|
|
||||||
ourClientNodes = optionals isServer (
|
|
||||||
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 participatingServerNodes
|
|
||||||
# Our clients
|
|
||||||
++ ourClientNodes
|
|
||||||
else [wgCfg.client.via];
|
|
||||||
|
|
||||||
# Figure out if there are duplicate addresses so we can make an assertion later.
|
|
||||||
duplicateAddrs = duplicates usedAddresses;
|
|
||||||
|
|
||||||
# Adds context information to the assertions for this network
|
|
||||||
assertionPrefix = "Wireguard network '${wgName}' on '${nodeName}'";
|
|
||||||
|
|
||||||
# Calculates the allowed ips for another server from our perspective.
|
|
||||||
# Usually we just want to allow other peers to route traffic
|
|
||||||
# for our "children" through us, additional to traffic to us of course.
|
|
||||||
# If a server exposes additional network access (global, lan, ...),
|
|
||||||
# these can be added aswell.
|
|
||||||
# TODO (do that)
|
|
||||||
serverAllowedIPs = serverNode: let
|
|
||||||
snCfg = wgCfgOf serverNode;
|
|
||||||
in
|
|
||||||
map (net.cidr.make 128) (
|
|
||||||
# The server accepts traffic to it's own address
|
|
||||||
snCfg.addresses
|
|
||||||
# plus traffic for any client that is connected via that server
|
|
||||||
++ concatMap (n: (wgCfgOf n).addresses) (
|
|
||||||
filter (n: (wgCfgOf n).client.via == serverNode) participatingClientNodes
|
|
||||||
)
|
|
||||||
);
|
|
||||||
in {
|
|
||||||
assertions = [
|
|
||||||
{
|
|
||||||
assertion = any (n: (wgCfgOf n).server.host != null) participatingNodes;
|
|
||||||
message = "${assertionPrefix}: At least one node in a network must be a server.";
|
|
||||||
}
|
|
||||||
{
|
|
||||||
assertion = duplicateAddrs == [];
|
|
||||||
message = "${assertionPrefix}: Addresses used multiple times: ${concatStringsSep ", " duplicateAddrs}";
|
|
||||||
}
|
|
||||||
{
|
|
||||||
assertion = isServer != isClient;
|
|
||||||
message = "${assertionPrefix}: A node must either be a server (define server.host) or a client (define client.via).";
|
|
||||||
}
|
|
||||||
{
|
|
||||||
assertion = isClient -> ((wgCfgOf wgCfg.client.via).server.host != null);
|
|
||||||
message = "${assertionPrefix}: The specified via node '${wgCfg.client.via}' must be a wireguard server.";
|
|
||||||
}
|
|
||||||
{
|
|
||||||
assertion = stringLength wgCfg.linkName < 16;
|
|
||||||
message = "${assertionPrefix}: The specified linkName '${wgCfg.linkName}' is too long (must be max 15 characters).";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
# Open the udp port for the wireguard endpoint in the firewall
|
|
||||||
networking.firewall.allowedUDPPorts = mkIf (isServer && wgCfg.server.openFirewall) [
|
|
||||||
wgCfg.server.port
|
|
||||||
];
|
|
||||||
|
|
||||||
# If requested, create firewall rules for the network / specific participants and open ports.
|
|
||||||
networking.nftables.firewall = let
|
|
||||||
inherit (config.networking.nftables.firewall) localZoneName;
|
|
||||||
in {
|
|
||||||
zones =
|
|
||||||
{
|
{
|
||||||
# Parent zone for the whole interface
|
assertion = networkCfg.cidrv4 != null || networkCfg.cidrv6 != null;
|
||||||
"wg-${wgCfg.linkName}".interfaces = [wgCfg.linkName];
|
message = "${assertionPrefix}: At least one of cidrv4 or cidrv6 has to be set.";
|
||||||
}
|
}
|
||||||
// listToAttrs (
|
|
||||||
flip map participatingNodes (
|
|
||||||
peer: let
|
|
||||||
peerCfg = wgCfgOf peer;
|
|
||||||
in
|
|
||||||
# Subzone to specifically target the peer
|
|
||||||
nameValuePair "wg-${wgCfg.linkName}-node-${peer}" {
|
|
||||||
parent = "wg-${wgCfg.linkName}";
|
|
||||||
ipv4Addresses = [peerCfg.ipv4];
|
|
||||||
ipv6Addresses = [peerCfg.ipv6];
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
rules =
|
|
||||||
{
|
{
|
||||||
# Open ports for whole network
|
assertion = (count (x: x.server) (attrValues networkCfg.hosts)) == 1;
|
||||||
"wg-${wgCfg.linkName}-to-${localZoneName}" = {
|
message = "${assertionPrefix}: You have to declare exactly 1 server node.";
|
||||||
from = ["wg-${wgCfg.linkName}"];
|
}
|
||||||
to = [localZoneName];
|
{
|
||||||
|
assertion = (count (x: x.id == hostCfg.id) (attrValues networkCfg.hosts)) == 1;
|
||||||
|
message = "${assertionPrefix}: More than one host with id ${toString hostCfg.id}";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
assertion = stringLength hostCfg.linkName < 16;
|
||||||
|
message = "${assertionPrefix}: The specified linkName '${hostCfg.linkName}' is too long (must be max 15 characters).";
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
networking.firewall.allowedUDPPorts = mkMerge (
|
||||||
|
flip mapAttrsToList memberWG (
|
||||||
|
_: networkCfg:
|
||||||
|
let
|
||||||
|
hostCfg = networkCfg.hosts.${config.node.name};
|
||||||
|
in
|
||||||
|
optional (hostCfg.server && networkCfg.openFirewall) networkCfg.port
|
||||||
|
)
|
||||||
|
);
|
||||||
|
networking.nftables.firewall.zones = mkMerge (
|
||||||
|
flip mapAttrsToList memberWG (
|
||||||
|
_: networkCfg:
|
||||||
|
let
|
||||||
|
hostCfg = networkCfg.hosts.${config.node.name};
|
||||||
|
peers = filterAttrs (name: _: name != config.node.name) networkCfg.hosts;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
# Parent zone for the whole network
|
||||||
|
"wg-${hostCfg.linkName}".interfaces = [ hostCfg.linkName ];
|
||||||
|
}
|
||||||
|
// (flip mapAttrs' peers (
|
||||||
|
name: cfg:
|
||||||
|
nameValuePair "wg-${hostCfg.linkName}-node-${name}" {
|
||||||
|
parent = "wg-${hostCfg.linkName}";
|
||||||
|
ipv4Addresses = optional (cfg.ipv4 != null) cfg.ipv4;
|
||||||
|
ipv6Addresses = optional (cfg.ipv6 != null) cfg.ipv6;
|
||||||
|
}
|
||||||
|
))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
networking.nftables.firewall.rules = mkMerge (
|
||||||
|
flip mapAttrsToList memberWG (
|
||||||
|
_: networkCfg:
|
||||||
|
let
|
||||||
|
inherit (config.networking.nftables.firewall) localZoneName;
|
||||||
|
hostCfg = networkCfg.hosts.${config.node.name};
|
||||||
|
peers = filterAttrs (name: _: name != config.node.name) networkCfg.hosts;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
"wg-${hostCfg.linkName}-to-${localZoneName}" = {
|
||||||
|
from = [ "wg-${hostCfg.linkName}" ];
|
||||||
|
to = [ localZoneName ];
|
||||||
|
ignoreEmptyRule = true;
|
||||||
|
|
||||||
|
inherit (hostCfg.firewallRuleForAll)
|
||||||
|
allowedTCPPorts
|
||||||
|
allowedUDPPorts
|
||||||
|
;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// (flip mapAttrs' peers (
|
||||||
|
name: _:
|
||||||
|
nameValuePair "wg-${hostCfg.linkName}-node-${name}-to-${localZoneName}" (
|
||||||
|
mkIf (hostCfg.firewallRuleForNode ? ${name}) {
|
||||||
|
from = [ "wg-${hostCfg.linkName}-node-${name}" ];
|
||||||
|
to = [ localZoneName ];
|
||||||
ignoreEmptyRule = true;
|
ignoreEmptyRule = true;
|
||||||
|
|
||||||
inherit
|
inherit (hostCfg.firewallRuleForNode.${name})
|
||||||
(wgCfg.firewallRuleForAll)
|
|
||||||
allowedTCPPorts
|
allowedTCPPorts
|
||||||
allowedUDPPorts
|
allowedUDPPorts
|
||||||
;
|
;
|
||||||
};
|
|
||||||
}
|
|
||||||
# Open ports for specific nodes network
|
|
||||||
// listToAttrs (
|
|
||||||
flip map participatingNodes (
|
|
||||||
peer:
|
|
||||||
nameValuePair "wg-${wgCfg.linkName}-node-${peer}-to-${localZoneName}" (
|
|
||||||
mkIf (wgCfg.firewallRuleForNode ? ${peer}) {
|
|
||||||
from = ["wg-${wgCfg.linkName}-node-${peer}"];
|
|
||||||
to = [localZoneName];
|
|
||||||
ignoreEmptyRule = true;
|
|
||||||
|
|
||||||
inherit
|
}
|
||||||
(wgCfg.firewallRuleForNode.${peer})
|
)
|
||||||
allowedTCPPorts
|
))
|
||||||
allowedUDPPorts
|
)
|
||||||
;
|
);
|
||||||
}
|
age.secrets = flip concatMapAttrs memberWG (
|
||||||
)
|
networkName: networkCfg:
|
||||||
)
|
let
|
||||||
);
|
serverNode = filterAttrs (_: cfg: cfg.server) networkCfg.hosts;
|
||||||
};
|
connectedPeers = if hostCfg.server then peers else serverNode;
|
||||||
|
hostCfg = networkCfg.hosts.${config.node.name};
|
||||||
age.secrets =
|
peers = filterAttrs (name: _: name != config.node.name) networkCfg.hosts;
|
||||||
concatAttrs (
|
sortedPeers =
|
||||||
map (other: {
|
peerA: peerB:
|
||||||
${peerPresharedKeySecret nodeName other} = {
|
if peerA < peerB then
|
||||||
rekeyFile = peerPresharedKeyPath nodeName other;
|
{
|
||||||
owner = "systemd-network";
|
peer1 = peerA;
|
||||||
generator.script = {pkgs, ...}: "${pkgs.wireguard-tools}/bin/wg genpsk";
|
peer2 = peerB;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
peer1 = peerB;
|
||||||
|
peer2 = peerA;
|
||||||
};
|
};
|
||||||
})
|
|
||||||
neededPeers
|
peerPrivateKeyFile = peerName: "/secrets/wireguard/${networkName}/keys/${peerName}.age";
|
||||||
)
|
peerPrivateKeyPath = peerName: inputs.self.outPath + peerPrivateKeyFile peerName;
|
||||||
// {
|
peerPrivateKeySecret = peerName: "wireguard-${networkName}-priv-${peerName}";
|
||||||
${peerPrivateKeySecret nodeName} = {
|
peerPresharedKeyFile =
|
||||||
rekeyFile = peerPrivateKeyPath nodeName;
|
peerA: peerB:
|
||||||
owner = "systemd-network";
|
let
|
||||||
generator.script = {
|
inherit (sortedPeers peerA peerB) peer1 peer2;
|
||||||
|
in
|
||||||
|
"/secrets/wireguard/${networkName}/psks/${peer1}+${peer2}.age";
|
||||||
|
peerPresharedKeyPath = peerA: peerB: inputs.self.outPath + peerPresharedKeyFile peerA peerB;
|
||||||
|
peerPresharedKeySecret =
|
||||||
|
peerA: peerB:
|
||||||
|
let
|
||||||
|
inherit (sortedPeers peerA peerB) peer1 peer2;
|
||||||
|
in
|
||||||
|
"wireguard-${networkName}-psks-${peer1}+${peer2}";
|
||||||
|
in
|
||||||
|
flip mapAttrs' connectedPeers (
|
||||||
|
name: _:
|
||||||
|
nameValuePair (peerPresharedKeySecret config.node.name name) {
|
||||||
|
rekeyFile = peerPresharedKeyPath config.node.name name;
|
||||||
|
owner = "systemd-network";
|
||||||
|
generator.script = { pkgs, ... }: "${pkgs.wireguard-tools}/bin/wg genpsk";
|
||||||
|
}
|
||||||
|
)
|
||||||
|
// {
|
||||||
|
${peerPrivateKeySecret config.node.name} = {
|
||||||
|
rekeyFile = peerPrivateKeyPath config.node.name;
|
||||||
|
owner = "systemd-network";
|
||||||
|
generator.script =
|
||||||
|
{
|
||||||
pkgs,
|
pkgs,
|
||||||
file,
|
file,
|
||||||
...
|
...
|
||||||
}: ''
|
}:
|
||||||
|
''
|
||||||
priv=$(${pkgs.wireguard-tools}/bin/wg genkey)
|
priv=$(${pkgs.wireguard-tools}/bin/wg genkey)
|
||||||
${pkgs.wireguard-tools}/bin/wg pubkey <<< "$priv" > ${
|
${pkgs.wireguard-tools}/bin/wg pubkey <<< "$priv" > ${
|
||||||
lib.escapeShellArg (lib.removeSuffix ".age" file + ".pub")
|
lib.escapeShellArg (lib.removeSuffix ".age" file + ".pub")
|
||||||
}
|
}
|
||||||
echo "$priv"
|
echo "$priv"
|
||||||
'';
|
'';
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
systemd.network.netdevs = flip mapAttrs' memberWG (
|
||||||
|
networkName: networkCfg:
|
||||||
|
let
|
||||||
|
serverNode = filterAttrs (_: cfg: cfg.server) networkCfg.hosts;
|
||||||
|
hostCfg = networkCfg.hosts.${config.node.name};
|
||||||
|
peers = filterAttrs (name: _: name != config.node.name) networkCfg.hosts;
|
||||||
|
sortedPeers =
|
||||||
|
peerA: peerB:
|
||||||
|
if peerA < peerB then
|
||||||
|
{
|
||||||
|
peer1 = peerA;
|
||||||
|
peer2 = peerB;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
peer1 = peerB;
|
||||||
|
peer2 = peerA;
|
||||||
|
};
|
||||||
|
|
||||||
systemd.network.netdevs."${wgCfg.unitConfName}" = {
|
peerPublicKeyFile = peerName: "/secrets/wireguard/${networkName}/keys/${peerName}.pub";
|
||||||
|
peerPublicKeyPath = peerName: inputs.self.outPath + peerPublicKeyFile peerName;
|
||||||
|
|
||||||
|
peerPrivateKeySecret = peerName: "wireguard-${networkName}-priv-${peerName}";
|
||||||
|
peerPresharedKeySecret =
|
||||||
|
peerA: peerB:
|
||||||
|
let
|
||||||
|
inherit (sortedPeers peerA peerB) peer1 peer2;
|
||||||
|
in
|
||||||
|
"wireguard-${networkName}-psks-${peer1}+${peer2}";
|
||||||
|
in
|
||||||
|
nameValuePair "${hostCfg.unitConfName}" {
|
||||||
netdevConfig = {
|
netdevConfig = {
|
||||||
Kind = "wireguard";
|
Kind = "wireguard";
|
||||||
Name = wgCfg.linkName;
|
Name = hostCfg.linkName;
|
||||||
Description = "Wireguard network ${wgName}";
|
Description = "Wireguard network ${networkName}";
|
||||||
};
|
};
|
||||||
wireguardConfig =
|
wireguardConfig =
|
||||||
{
|
{
|
||||||
PrivateKeyFile = config.age.secrets.${peerPrivateKeySecret nodeName}.path;
|
PrivateKeyFile = config.age.secrets.${peerPrivateKeySecret config.node.name}.path;
|
||||||
}
|
}
|
||||||
// optionalAttrs isServer {
|
// optionalAttrs hostCfg.server {
|
||||||
ListenPort = wgCfg.server.port;
|
ListenPort = networkCfg.port;
|
||||||
};
|
};
|
||||||
wireguardPeers =
|
wireguardPeers =
|
||||||
if isServer
|
if hostCfg.server then
|
||||||
then
|
|
||||||
# Always include all other server nodes.
|
|
||||||
map (
|
|
||||||
serverNode: let
|
|
||||||
snCfg = wgCfgOf serverNode;
|
|
||||||
in {
|
|
||||||
PublicKey = builtins.readFile (peerPublicKeyPath serverNode);
|
|
||||||
PresharedKeyFile = config.age.secrets.${peerPresharedKeySecret nodeName serverNode}.path;
|
|
||||||
AllowedIPs = serverAllowedIPs serverNode;
|
|
||||||
Endpoint = "${snCfg.server.host}:${toString snCfg.server.port}";
|
|
||||||
}
|
|
||||||
) (filterSelf participatingServerNodes)
|
|
||||||
# All client nodes that have their via set to us.
|
# All client nodes that have their via set to us.
|
||||||
++ map (
|
mapAttrsToList (clientName: clientCfg: {
|
||||||
clientNode: let
|
PublicKey = builtins.readFile (peerPublicKeyPath clientName);
|
||||||
clientCfg = wgCfgOf clientNode;
|
PresharedKeyFile = config.age.secrets.${peerPresharedKeySecret config.node.name clientName}.path;
|
||||||
in {
|
AllowedIPs =
|
||||||
PublicKey = builtins.readFile (peerPublicKeyPath clientNode);
|
(optional (clientCfg.ipv4 != null) (net.cidr.make 32 clientCfg.ipv4))
|
||||||
PresharedKeyFile = config.age.secrets.${peerPresharedKeySecret nodeName clientNode}.path;
|
++ (optional (clientCfg.ipv6 != null) (net.cidr.make 128 clientCfg.ipv6));
|
||||||
AllowedIPs = map (net.cidr.make 128) clientCfg.addresses;
|
}) peers
|
||||||
}
|
|
||||||
)
|
|
||||||
ourClientNodes
|
|
||||||
else
|
else
|
||||||
# We are a client node, so only include our via server.
|
# We are a client node, so only include our via server.
|
||||||
[
|
mapAttrsToList (
|
||||||
(
|
serverName: _:
|
||||||
let
|
{
|
||||||
snCfg = wgCfgOf wgCfg.client.via;
|
PublicKey = builtins.readFile (peerPublicKeyPath serverName);
|
||||||
in
|
PresharedKeyFile = config.age.secrets.${peerPresharedKeySecret config.node.name serverName}.path;
|
||||||
{
|
Endpoint = "${networkCfg.host}:${toString networkCfg.port}";
|
||||||
PublicKey = builtins.readFile (peerPublicKeyPath wgCfg.client.via);
|
# Access to the whole network is routed through our entry node.
|
||||||
PresharedKeyFile = config.age.secrets.${peerPresharedKeySecret nodeName wgCfg.client.via}.path;
|
AllowedIPs =
|
||||||
Endpoint = "${snCfg.server.host}:${toString snCfg.server.port}";
|
(optional (networkCfg.cidrv4 != null) networkCfg.cidrv4)
|
||||||
# Access to the whole network is routed through our entry node.
|
++ (optional (networkCfg.cidrv6 != null) networkCfg.cidrv6);
|
||||||
AllowedIPs = networkCidrs;
|
}
|
||||||
}
|
// optionalAttrs hostCfg.keepalive {
|
||||||
// optionalAttrs wgCfg.client.keepalive {
|
PersistentKeepalive = 25;
|
||||||
PersistentKeepalive = 25;
|
}
|
||||||
}
|
) serverNode;
|
||||||
)
|
}
|
||||||
];
|
);
|
||||||
};
|
systemd.network.networks = flip mapAttrs' memberWG (
|
||||||
|
_: networkCfg:
|
||||||
systemd.network.networks.${wgCfg.unitConfName} = {
|
let
|
||||||
matchConfig.Name = wgCfg.linkName;
|
hostCfg = networkCfg.hosts.${config.node.name};
|
||||||
address = map toNetworkAddr wgCfg.addresses;
|
in
|
||||||
};
|
nameValuePair hostCfg.unitConfName {
|
||||||
};
|
matchConfig.Name = hostCfg.linkName;
|
||||||
in {
|
address =
|
||||||
options.wireguard = mkOption {
|
(optional (networkCfg.cidrv4 != null) (net.cidr.hostCidr hostCfg.id networkCfg.cidrv4))
|
||||||
default = {};
|
++ (optional (networkCfg.cidrv6 != null) (net.cidr.hostCidr hostCfg.id networkCfg.cidrv6));
|
||||||
description = "Configures wireguard networks via systemd-networkd.";
|
}
|
||||||
type = types.lazyAttrsOf (
|
|
||||||
types.submodule (
|
|
||||||
{
|
|
||||||
config,
|
|
||||||
name,
|
|
||||||
options,
|
|
||||||
...
|
|
||||||
}: {
|
|
||||||
options = {
|
|
||||||
server = {
|
|
||||||
host = mkOption {
|
|
||||||
default = null;
|
|
||||||
type = types.nullOr types.str;
|
|
||||||
description = "The hostname or ip address which other peers can use to reach this host. No server functionality will be activated if set to null.";
|
|
||||||
};
|
|
||||||
|
|
||||||
port = mkOption {
|
|
||||||
default = 51820;
|
|
||||||
type = types.port;
|
|
||||||
description = "The port to listen on.";
|
|
||||||
};
|
|
||||||
|
|
||||||
openFirewall = mkOption {
|
|
||||||
default = false;
|
|
||||||
type = types.bool;
|
|
||||||
description = "Whether to open the firewall for the specified {option}`port`.";
|
|
||||||
};
|
|
||||||
|
|
||||||
reservedAddresses = mkOption {
|
|
||||||
type = types.listOf types.net.cidr;
|
|
||||||
default = [];
|
|
||||||
example = [
|
|
||||||
"10.0.0.0/24"
|
|
||||||
"fd00:cafe::/64"
|
|
||||||
];
|
|
||||||
description = ''
|
|
||||||
Allows defining extra CIDR network ranges that shall be reserved for this network.
|
|
||||||
Reservation means that those address spaces will be guaranteed to be included in
|
|
||||||
the spanned network, but no rules will be enforced as to who in the network may use them.
|
|
||||||
|
|
||||||
By default, this module will try to allocate the smallest address space that includes
|
|
||||||
all network peers.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
client = {
|
|
||||||
via = mkOption {
|
|
||||||
default = null;
|
|
||||||
type = types.nullOr types.str;
|
|
||||||
description = ''
|
|
||||||
The server node via which to connect to the network.
|
|
||||||
No client functionality will be activated if set to null.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
keepalive = mkOption {
|
|
||||||
default = true;
|
|
||||||
type = types.bool;
|
|
||||||
description = "Whether to keep this connection alive using PersistentKeepalive. Set to false only for networks where client and server IPs are stable.";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
priority = mkOption {
|
|
||||||
default = 40;
|
|
||||||
type = types.int;
|
|
||||||
description = "The order priority used when creating systemd netdev and network files.";
|
|
||||||
};
|
|
||||||
|
|
||||||
linkName = mkOption {
|
|
||||||
default = name;
|
|
||||||
type = types.str;
|
|
||||||
description = "The name for the created network interface.";
|
|
||||||
};
|
|
||||||
|
|
||||||
unitConfName = mkOption {
|
|
||||||
default = "${toString config.priority}-${config.linkName}";
|
|
||||||
readOnly = true;
|
|
||||||
type = types.str;
|
|
||||||
description = ''
|
|
||||||
The name used for unit configuration files. This is a read-only option.
|
|
||||||
Access this if you want to add additional settings to the generated systemd units.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
ipv4 = mkOption {
|
|
||||||
type = types.lazyOf types.net.ipv4;
|
|
||||||
default = types.lazyValue (lib.wireguard.getNetwork inputs name).assignedIpv4Addresses.${nodeName};
|
|
||||||
description = ''
|
|
||||||
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
|
|
||||||
hostname of this machine. At least one participating server must reserve
|
|
||||||
a big-enough space of addresses by setting `reservedAddresses`.
|
|
||||||
See `net.cidr.assignIps` for more information on the algorithm.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
ipv6 = mkOption {
|
|
||||||
type = types.lazyOf types.net.ipv6;
|
|
||||||
default = types.lazyValue (lib.wireguard.getNetwork inputs name).assignedIpv6Addresses.${nodeName};
|
|
||||||
description = ''
|
|
||||||
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
|
|
||||||
hostname of this machine. At least one participating server must reserve
|
|
||||||
a big-enough space of addresses by setting `reservedAddresses`.
|
|
||||||
See `net.cidr.assignIps` for more information on the algorithm.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
addresses = mkOption {
|
|
||||||
type = types.listOf (types.lazyOf types.net.ip);
|
|
||||||
default = [
|
|
||||||
(head options.ipv4.definitions)
|
|
||||||
(head options.ipv6.definitions)
|
|
||||||
];
|
|
||||||
description = ''
|
|
||||||
The ip addresses (v4 and/or v6) to use for this machine.
|
|
||||||
The actual network cidr will automatically be derived from all network participants.
|
|
||||||
By default this will just include {option}`ipv4` and {option}`ipv6` as configured.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
firewallRuleForAll = mkOption {
|
|
||||||
default = {};
|
|
||||||
description = ''
|
|
||||||
Allows you to set specific firewall rules for traffic originating from any participant in this
|
|
||||||
wireguard network. A corresponding rule `wg-<network-name>-to-<local-zone-name>` will be created to easily expose
|
|
||||||
services to the network.
|
|
||||||
'';
|
|
||||||
type = types.submodule {
|
|
||||||
options = {
|
|
||||||
allowedTCPPorts = mkOption {
|
|
||||||
type = types.listOf types.port;
|
|
||||||
default = [];
|
|
||||||
description = "Convenience option to open specific TCP ports for traffic from the network.";
|
|
||||||
};
|
|
||||||
allowedUDPPorts = mkOption {
|
|
||||||
type = types.listOf types.port;
|
|
||||||
default = [];
|
|
||||||
description = "Convenience option to open specific UDP ports for traffic from the network.";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
firewallRuleForNode = mkOption {
|
|
||||||
default = {};
|
|
||||||
description = ''
|
|
||||||
Allows you to set specific firewall rules just for traffic originating from another network node.
|
|
||||||
A corresponding rule `wg-<network-name>-node-<node-name>-to-<local-zone-name>` will be created to easily expose
|
|
||||||
services to that node.
|
|
||||||
'';
|
|
||||||
type = types.attrsOf (
|
|
||||||
types.submodule {
|
|
||||||
options = {
|
|
||||||
allowedTCPPorts = mkOption {
|
|
||||||
type = types.listOf types.port;
|
|
||||||
default = [];
|
|
||||||
description = "Convenience option to open specific TCP ports for traffic from another node.";
|
|
||||||
};
|
|
||||||
allowedUDPPorts = mkOption {
|
|
||||||
type = types.listOf types.port;
|
|
||||||
default = [];
|
|
||||||
description = "Convenience option to open specific UDP ports for traffic from another node.";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
config = mkIf (cfg != {}) (
|
|
||||||
mergeToplevelConfigs ["assertions" "age" "networking" "systemd"] (
|
|
||||||
mapAttrsToList configForNetwork cfg
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
171
modules/wireguardGlobals.nix
Normal file
171
modules/wireguardGlobals.nix
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
{ lib, ... }:
|
||||||
|
let
|
||||||
|
inherit (lib)
|
||||||
|
mkOption
|
||||||
|
types
|
||||||
|
importJSON
|
||||||
|
;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.globals = mkOption {
|
||||||
|
type = types.submodule {
|
||||||
|
options = {
|
||||||
|
wireguard = mkOption {
|
||||||
|
default = { };
|
||||||
|
type = types.attrsOf (
|
||||||
|
types.submodule (
|
||||||
|
{ name, config, ... }:
|
||||||
|
let
|
||||||
|
wgConf = config;
|
||||||
|
wgName = name;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
host = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = "The host name or IP addresse for reaching the server node.";
|
||||||
|
};
|
||||||
|
idFile = mkOption {
|
||||||
|
type = types.nullOr types.path;
|
||||||
|
default = null;
|
||||||
|
description = "A json file containing a mapping from hostname to id.";
|
||||||
|
};
|
||||||
|
cidrv4 = mkOption {
|
||||||
|
type = types.nullOr types.net.cidrv4;
|
||||||
|
default = null;
|
||||||
|
description = "The server host of this wireguard";
|
||||||
|
};
|
||||||
|
cidrv6 = mkOption {
|
||||||
|
type = types.nullOr types.net.cidrv6;
|
||||||
|
default = null;
|
||||||
|
description = "The server host of this wireguard";
|
||||||
|
};
|
||||||
|
port = mkOption {
|
||||||
|
default = 51820;
|
||||||
|
type = types.port;
|
||||||
|
description = "The port the server listens on";
|
||||||
|
};
|
||||||
|
openFirewall = mkOption {
|
||||||
|
default = false;
|
||||||
|
type = types.bool;
|
||||||
|
description = "Whether to open the servers firewall for the specified {option}`port`. Has no effect for client nodes.";
|
||||||
|
};
|
||||||
|
|
||||||
|
hosts = mkOption {
|
||||||
|
default = { };
|
||||||
|
description = "Attrset of hostName to host specific config";
|
||||||
|
type = types.attrsOf (
|
||||||
|
types.submodule (
|
||||||
|
{ config, name, ... }:
|
||||||
|
{
|
||||||
|
config.id =
|
||||||
|
let
|
||||||
|
inherit (wgConf) idFile;
|
||||||
|
in
|
||||||
|
if (idFile == null) then
|
||||||
|
null
|
||||||
|
else
|
||||||
|
(
|
||||||
|
let
|
||||||
|
conf = importJSON idFile;
|
||||||
|
in
|
||||||
|
conf.${name} or null
|
||||||
|
);
|
||||||
|
options = {
|
||||||
|
server = mkOption {
|
||||||
|
default = false;
|
||||||
|
type = types.bool;
|
||||||
|
description = "Whether this host acts as the server and relay for the network. Has to be set for exactly 1 host.";
|
||||||
|
};
|
||||||
|
linkName = mkOption {
|
||||||
|
default = wgName;
|
||||||
|
type = types.str;
|
||||||
|
description = "The name of the created network interface. Has to be less than 15 characters.";
|
||||||
|
};
|
||||||
|
unitConfName = mkOption {
|
||||||
|
default = "40-${config.linkName}";
|
||||||
|
type = types.str;
|
||||||
|
description = "The name of the generated systemd unit configuration files.";
|
||||||
|
readOnly = true;
|
||||||
|
};
|
||||||
|
id = mkOption {
|
||||||
|
type = types.int;
|
||||||
|
description = "The unique id of this host. Used to derive its IP addresses. Has to be smaller than the size of the Subnet.";
|
||||||
|
};
|
||||||
|
ipv4 = mkOption {
|
||||||
|
type = types.nullOr types.net.ipv4;
|
||||||
|
default = if (wgConf.cidrv4 == null) then null else lib.net.cidr.host config.id wgConf.cidrv4;
|
||||||
|
readOnly = true;
|
||||||
|
description = "The IPv4 of this host. Automatically computed from the {option}`id`";
|
||||||
|
};
|
||||||
|
ipv6 = mkOption {
|
||||||
|
type = types.nullOr types.net.ipv6;
|
||||||
|
default = if (wgConf.cidrv4 == null) then null else lib.net.cidr.host config.id wgConf.cidrv6;
|
||||||
|
readOnly = true;
|
||||||
|
description = "The IPv4 of this host. Automatically computed from the {option}`id`";
|
||||||
|
};
|
||||||
|
keepalive = mkOption {
|
||||||
|
default = true;
|
||||||
|
type = types.bool;
|
||||||
|
description = "Whether to keep the connection alive using PersistentKeepalive. Has no effect for server nodes.";
|
||||||
|
};
|
||||||
|
firewallRuleForAll = mkOption {
|
||||||
|
default = { };
|
||||||
|
description = ''
|
||||||
|
Allows you to set specific firewall rules for traffic originating from any participant in this
|
||||||
|
wireguard network. A corresponding rule `wg-<network-name>-to-<local-zone-name>` will be created to easily expose
|
||||||
|
services to the network.
|
||||||
|
'';
|
||||||
|
type = types.submodule {
|
||||||
|
options = {
|
||||||
|
allowedTCPPorts = mkOption {
|
||||||
|
type = types.listOf types.port;
|
||||||
|
default = [ ];
|
||||||
|
description = "Convenience option to open specific TCP ports for traffic from the network.";
|
||||||
|
};
|
||||||
|
allowedUDPPorts = mkOption {
|
||||||
|
type = types.listOf types.port;
|
||||||
|
default = [ ];
|
||||||
|
description = "Convenience option to open specific UDP ports for traffic from the network.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
firewallRuleForNode = mkOption {
|
||||||
|
default = { };
|
||||||
|
description = ''
|
||||||
|
Allows you to set specific firewall rules just for traffic originating from another network node.
|
||||||
|
A corresponding rule `wg-<network-name>-node-<node-name>-to-<local-zone-name>` will be created to easily expose
|
||||||
|
services to that node.
|
||||||
|
'';
|
||||||
|
type = types.attrsOf (
|
||||||
|
types.submodule {
|
||||||
|
options = {
|
||||||
|
allowedTCPPorts = mkOption {
|
||||||
|
type = types.listOf types.port;
|
||||||
|
default = [ ];
|
||||||
|
description = "Convenience option to open specific TCP ports for traffic from another node.";
|
||||||
|
};
|
||||||
|
allowedUDPPorts = mkOption {
|
||||||
|
type = types.listOf types.port;
|
||||||
|
default = [ ];
|
||||||
|
description = "Convenience option to open specific UDP ports for traffic from another node.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
|
@ -11,12 +11,8 @@ prev.lib.composeManyExtensions (
|
||||||
./lib/disko.nix
|
./lib/disko.nix
|
||||||
# Requires misc
|
# Requires misc
|
||||||
./lib/net.nix
|
./lib/net.nix
|
||||||
# Requires misc, types
|
|
||||||
./lib/wireguard.nix
|
|
||||||
])
|
])
|
||||||
++ [
|
++ [
|
||||||
(import ./pkgs)
|
(import ./pkgs)
|
||||||
]
|
]
|
||||||
)
|
) final prev
|
||||||
final
|
|
||||||
prev
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue