mirror of
https://github.com/oddlama/nix-config.git
synced 2025-10-11 07:10:39 +02:00
feat: refactor and integrate wireguard module into microvm module
This commit is contained in:
parent
e5f3ffd288
commit
78cdcd3c69
10 changed files with 385 additions and 256 deletions
57
flake.lock
generated
57
flake.lock
generated
|
@ -3,7 +3,9 @@
|
||||||
"agenix": {
|
"agenix": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"darwin": "darwin",
|
"darwin": "darwin",
|
||||||
"home-manager": "home-manager",
|
"home-manager": [
|
||||||
|
"home-manager"
|
||||||
|
],
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
]
|
]
|
||||||
|
@ -54,15 +56,15 @@
|
||||||
"stable": "stable"
|
"stable": "stable"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1684127527,
|
"lastModified": 1684497694,
|
||||||
"narHash": "sha256-tAzgb2jgmRaX9HETry38h2OvBf9YkHEH1fFvIJQV9A0=",
|
"narHash": "sha256-vFIB57ZqUftCfJcjkzEkvNVAdCbn80A2HXZ2OXl6wtA=",
|
||||||
"owner": "zhaofengli",
|
"owner": "oddlama",
|
||||||
"repo": "colmena",
|
"repo": "colmena",
|
||||||
"rev": "caf33af7d854c8d9b88a8f3dae7adb1c24c1407b",
|
"rev": "888e238953cf4ceb2577b668ea78318849a07529",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "zhaofengli",
|
"owner": "oddlama",
|
||||||
"repo": "colmena",
|
"repo": "colmena",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
|
@ -117,11 +119,11 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1684170997,
|
"lastModified": 1684472660,
|
||||||
"narHash": "sha256-WgwqHeYv2sDA0eWghnYCUNx7dm5S8lqDVZjp7ufzm30=",
|
"narHash": "sha256-P4sR6f27FKoQuGnThELALUuJeu9mZ9Zh7/dYdaAd2ek=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "disko",
|
"repo": "disko",
|
||||||
"rev": "10402e31443941b50bf62e67900743dcb26b3b27",
|
"rev": "efb2016c8e6a91ea64e0604d69e332d8aceabb95",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -204,36 +206,15 @@
|
||||||
"home-manager": {
|
"home-manager": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"agenix",
|
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1682203081,
|
"lastModified": 1684484967,
|
||||||
"narHash": "sha256-kRL4ejWDhi0zph/FpebFYhzqlOBrk0Pl3dzGEKSAlEw=",
|
"narHash": "sha256-P3ftCqeJmDYS9LSr2gGC4XGGcp5vv8TOasJX6fVHWsw=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "home-manager",
|
"repo": "home-manager",
|
||||||
"rev": "32d3e39c491e2f91152c84f8ad8b003420eab0a1",
|
"rev": "b9a52ad20e58ebd003444915e35e3dd2c18fc715",
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "home-manager",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"home-manager_2": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1684157850,
|
|
||||||
"narHash": "sha256-xGHTCgvAxO5CgAL6IAgE/VGRX2wob2Y+DPyqpXJ32oQ=",
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "home-manager",
|
|
||||||
"rev": "c0deab0effd576e70343cb5df0c64428e0e0d010",
|
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -244,11 +225,11 @@
|
||||||
},
|
},
|
||||||
"impermanence": {
|
"impermanence": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1684144492,
|
"lastModified": 1684264534,
|
||||||
"narHash": "sha256-5TBG9kZGdKrZGHdyjLA04ODSzhx1Bx/vwMxfRgWF+JU=",
|
"narHash": "sha256-K0zr+ry3FwIo3rN2U/VWAkCJSgBslBisvfRIPwMbuCQ=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "impermanence",
|
"repo": "impermanence",
|
||||||
"rev": "ec1a8e70d61261f9ada30f4e450ea7230d9efb62",
|
"rev": "89253fb1518063556edd5e54509c30ac3089d5e6",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -368,7 +349,7 @@
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1684049129,
|
"lastModified": 1684049129,
|
||||||
"narHash": "sha256-dyq0Cc+C/WaVHWSIICqIlteLzzQyRAfw3rQQGrBAzWM=",
|
"narHash": "sha256-FfWznSgzYGFYpbcVcI6QHHiBc8x4EOxaB6U8RtOtFOU=",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "file:///root/projects/nixpkgs-test"
|
"url": "file:///root/projects/nixpkgs-test"
|
||||||
},
|
},
|
||||||
|
@ -428,7 +409,7 @@
|
||||||
"colmena": "colmena",
|
"colmena": "colmena",
|
||||||
"disko": "disko",
|
"disko": "disko",
|
||||||
"flake-utils": "flake-utils",
|
"flake-utils": "flake-utils",
|
||||||
"home-manager": "home-manager_2",
|
"home-manager": "home-manager",
|
||||||
"impermanence": "impermanence",
|
"impermanence": "impermanence",
|
||||||
"lib-net": "lib-net",
|
"lib-net": "lib-net",
|
||||||
"microvm": "microvm",
|
"microvm": "microvm",
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
colmena = {
|
colmena = {
|
||||||
url = "github:zhaofengli/colmena";
|
url = "github:oddlama/colmena";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
inputs.flake-utils.follows = "flake-utils";
|
inputs.flake-utils.follows = "flake-utils";
|
||||||
};
|
};
|
||||||
|
@ -53,6 +53,7 @@
|
||||||
|
|
||||||
agenix = {
|
agenix = {
|
||||||
url = "github:ryantm/agenix";
|
url = "github:ryantm/agenix";
|
||||||
|
inputs.home-manager.follows = "home-manager";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
agenix-rekey = {
|
agenix-rekey = {
|
||||||
|
|
|
@ -14,7 +14,12 @@
|
||||||
lib.recursiveUpdate libWithNet {
|
lib.recursiveUpdate libWithNet {
|
||||||
net = {
|
net = {
|
||||||
cidr = rec {
|
cidr = rec {
|
||||||
hostCidr = n: x: "${libWithNet.net.cidr.host n x}/${libWithNet.net.cidr.length x}";
|
host = i: n: let
|
||||||
|
cap = libWithNet.net.cidr.capacity n;
|
||||||
|
in
|
||||||
|
assert lib.assertMsg (i >= (-cap) && i < cap) "The host ${toString i} lies outside of ${n}";
|
||||||
|
libWithNet.net.cidr.host i n;
|
||||||
|
hostCidr = n: x: "${libWithNet.net.cidr.host n x}/${toString (libWithNet.net.cidr.length x)}";
|
||||||
ip = x: lib.head (lib.splitString "/" x);
|
ip = x: lib.head (lib.splitString "/" x);
|
||||||
canonicalize = x: libWithNet.net.cidr.make (libWithNet.net.cidr.length x) (ip x);
|
canonicalize = x: libWithNet.net.cidr.make (libWithNet.net.cidr.length x) (ip x);
|
||||||
};
|
};
|
||||||
|
@ -50,6 +55,7 @@
|
||||||
|
|
||||||
boot = {
|
boot = {
|
||||||
initrd.systemd.enable = true;
|
initrd.systemd.enable = true;
|
||||||
|
# Add "rd.systemd.unit=rescue.target" to debug initrd
|
||||||
kernelParams = ["log_buf_len=10M"];
|
kernelParams = ["log_buf_len=10M"];
|
||||||
tmp.useTmpfs = true;
|
tmp.useTmpfs = true;
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
inputs,
|
inputs,
|
||||||
lib,
|
lib,
|
||||||
nixos-hardware,
|
nixos-hardware,
|
||||||
nodeSecrets,
|
|
||||||
pkgs,
|
pkgs,
|
||||||
...
|
...
|
||||||
}: {
|
}: {
|
||||||
|
@ -26,20 +25,18 @@
|
||||||
|
|
||||||
boot.initrd.availableKernelModules = ["xhci_pci" "ahci" "nvme" "usbhid" "usb_storage" "sd_mod" "sdhci_pci" "r8169"];
|
boot.initrd.availableKernelModules = ["xhci_pci" "ahci" "nvme" "usbhid" "usb_storage" "sd_mod" "sdhci_pci" "r8169"];
|
||||||
|
|
||||||
extra.microvms = let
|
extra.microvms = {
|
||||||
macOffset = config.lib.net.mac.addPrivate nodeSecrets.networking.interfaces.lan.mac;
|
vms.test = {
|
||||||
in {
|
id = 11;
|
||||||
test = {
|
host = "test.local";
|
||||||
|
system = "x86_64-linux";
|
||||||
|
autostart = true;
|
||||||
zfs = {
|
zfs = {
|
||||||
enable = true;
|
enable = true;
|
||||||
pool = "rpool";
|
pool = "rpool";
|
||||||
dataset = "safe/vms/test";
|
dataset = "safe/vms/test";
|
||||||
mountpoint = "/persist/vms/test";
|
mountpoint = "/persist/vms/test";
|
||||||
};
|
};
|
||||||
autostart = true;
|
|
||||||
mac = macOffset "00:00:00:00:00:11";
|
|
||||||
macvtap = "lan";
|
|
||||||
system = "x86_64-linux";
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -99,10 +96,4 @@
|
||||||
# };
|
# };
|
||||||
# };
|
# };
|
||||||
#};
|
#};
|
||||||
|
|
||||||
#microvm.vms.agag = {
|
|
||||||
# flake = self;
|
|
||||||
# updateFlake = microvm;
|
|
||||||
#};
|
|
||||||
#microvm.autostart = ["guest"];
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
};
|
};
|
||||||
zpool = with extraLib.disko.zfs; {
|
zpool = with extraLib.disko.zfs; {
|
||||||
rpool =
|
rpool =
|
||||||
encryptedZpool
|
defaultZpoolOptions
|
||||||
// {
|
// {
|
||||||
datasets = {
|
datasets = {
|
||||||
"local" = unmountable;
|
"local" = unmountable;
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
nodeSecrets,
|
nodeSecrets,
|
||||||
...
|
...
|
||||||
}: let
|
}: let
|
||||||
inherit (config.lib.net) cidr;
|
inherit (config.lib.net) ip cidr;
|
||||||
|
|
||||||
net.lan.ipv4cidr = "192.168.100.1/24";
|
net.lan.ipv4cidr = "192.168.100.1/24";
|
||||||
net.lan.ipv6cidr = "fd00::1/64";
|
net.lan.ipv6cidr = "fd00::1/64";
|
||||||
|
@ -94,6 +94,7 @@ in {
|
||||||
zones = lib.mkForce {
|
zones = lib.mkForce {
|
||||||
lan.interfaces = ["lan-self"];
|
lan.interfaces = ["lan-self"];
|
||||||
wan.interfaces = ["wan"];
|
wan.interfaces = ["wan"];
|
||||||
|
"local-vms".interfaces = ["wg-local-vms"];
|
||||||
};
|
};
|
||||||
|
|
||||||
rules = lib.mkForce {
|
rules = lib.mkForce {
|
||||||
|
@ -133,7 +134,6 @@ in {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
# TODO to microvm!
|
|
||||||
services.kea = {
|
services.kea = {
|
||||||
dhcp4 = {
|
dhcp4 = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
@ -153,7 +153,7 @@ in {
|
||||||
option-data = [
|
option-data = [
|
||||||
{
|
{
|
||||||
name = "domain-name-servers";
|
name = "domain-name-servers";
|
||||||
# TODO pihole self
|
# TODO pihole via self
|
||||||
data = "1.1.1.1, 8.8.8.8";
|
data = "1.1.1.1, 8.8.8.8";
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
@ -161,10 +161,8 @@ in {
|
||||||
{
|
{
|
||||||
interface = "lan-self";
|
interface = "lan-self";
|
||||||
subnet = cidr.canonicalize net.lan.ipv4cidr;
|
subnet = cidr.canonicalize net.lan.ipv4cidr;
|
||||||
# TODO calculate this automatically, start at 40 or so
|
|
||||||
# to have enough for reservations
|
|
||||||
pools = [
|
pools = [
|
||||||
{pool = "192.168.100.20 - 192.168.100.250";}
|
{pool = "${cidr.host 20 net.lan.ipv4cidr} - ${cidr.host (-6) net.lan.ipv4cidr}";}
|
||||||
];
|
];
|
||||||
option-data = [
|
option-data = [
|
||||||
{
|
{
|
||||||
|
@ -172,13 +170,6 @@ in {
|
||||||
data = cidr.ip net.lan.ipv4cidr;
|
data = cidr.ip net.lan.ipv4cidr;
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
# TODO reserve addresses for each VM
|
|
||||||
#reservations = [
|
|
||||||
# {
|
|
||||||
# duid = "aa:bb:cc:dd:ee:ff";
|
|
||||||
# ip-address = cidr.ip net.lan.ipv4cidr;
|
|
||||||
# }
|
|
||||||
#];
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
@ -187,13 +178,9 @@ in {
|
||||||
|
|
||||||
systemd.services.kea-dhcp4-server.after = ["sys-subsystem-net-devices-lan.device"];
|
systemd.services.kea-dhcp4-server.after = ["sys-subsystem-net-devices-lan.device"];
|
||||||
|
|
||||||
#extra.wireguard.vms = {
|
extra.microvms.networking = {
|
||||||
# server = {
|
baseMac = nodeSecrets.networking.interfaces.lan.mac;
|
||||||
# enable = true;
|
host = cidr.ip net.lan.ipv4cidr;
|
||||||
# host = "192.168.1.231";
|
macvtapInterface = "lan";
|
||||||
# port = 51822;
|
};
|
||||||
# openFirewall = true;
|
|
||||||
# };
|
|
||||||
# addresses = ["10.0.0.1/24"];
|
|
||||||
#};
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,11 +25,13 @@
|
||||||
mkMerge
|
mkMerge
|
||||||
mkOption
|
mkOption
|
||||||
optional
|
optional
|
||||||
|
optionalAttrs
|
||||||
recursiveUpdate
|
recursiveUpdate
|
||||||
types
|
types
|
||||||
;
|
;
|
||||||
|
|
||||||
cfg = config.extra.microvms;
|
cfg = config.extra.microvms;
|
||||||
|
inherit (config.extra.microvms) vms;
|
||||||
|
|
||||||
# Configuration for each microvm
|
# Configuration for each microvm
|
||||||
microvmConfig = vmName: vmCfg: {
|
microvmConfig = vmName: vmCfg: {
|
||||||
|
@ -61,8 +63,16 @@
|
||||||
inherit (vmCfg) system;
|
inherit (vmCfg) system;
|
||||||
config = nodePath + "/microvms/${vmName}";
|
config = nodePath + "/microvms/${vmName}";
|
||||||
};
|
};
|
||||||
|
mac = config.lib.net.mac.addPrivate vmCfg.id cfg.networking.baseMac;
|
||||||
in {
|
in {
|
||||||
inherit (node) pkgs specialArgs;
|
# Allow children microvms to know which node is their parent
|
||||||
|
specialArgs =
|
||||||
|
{
|
||||||
|
parentNode = config;
|
||||||
|
parentNodeName = nodeName;
|
||||||
|
}
|
||||||
|
// node.specialArgs;
|
||||||
|
inherit (node) pkgs;
|
||||||
inherit (vmCfg) autostart;
|
inherit (vmCfg) autostart;
|
||||||
config = {
|
config = {
|
||||||
imports = [microvm.microvm] ++ node.imports;
|
imports = [microvm.microvm] ++ node.imports;
|
||||||
|
@ -75,11 +85,11 @@
|
||||||
{
|
{
|
||||||
type = "macvtap";
|
type = "macvtap";
|
||||||
id = "vm-${vmName}";
|
id = "vm-${vmName}";
|
||||||
|
inherit mac;
|
||||||
macvtap = {
|
macvtap = {
|
||||||
link = vmCfg.macvtap;
|
link = cfg.macvtapInterface;
|
||||||
mode = "bridge";
|
mode = "bridge";
|
||||||
};
|
};
|
||||||
inherit (vmCfg) mac;
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -114,7 +124,7 @@
|
||||||
gc.automatic = mkForce false;
|
gc.automatic = mkForce false;
|
||||||
};
|
};
|
||||||
|
|
||||||
extra.networking.renameInterfacesByMac.${vmCfg.linkName} = vmCfg.mac;
|
extra.networking.renameInterfacesByMac.${vmCfg.linkName} = mac;
|
||||||
|
|
||||||
systemd.network.networks = {
|
systemd.network.networks = {
|
||||||
"10-${vmCfg.linkName}" = {
|
"10-${vmCfg.linkName}" = {
|
||||||
|
@ -130,6 +140,46 @@
|
||||||
|
|
||||||
# TODO change once microvms are compatible with stage-1 systemd
|
# TODO change once microvms are compatible with stage-1 systemd
|
||||||
boot.initrd.systemd.enable = mkForce false;
|
boot.initrd.systemd.enable = mkForce false;
|
||||||
|
|
||||||
|
# Create a firewall zone for the bridged traffic and secure vm traffic
|
||||||
|
networking.nftables.firewall = {
|
||||||
|
zones = lib.mkForce {
|
||||||
|
"${vmCfg.linkName}".interfaces = [vmCfg.linkName];
|
||||||
|
"local-vms".interfaces = ["wg-local-vms"];
|
||||||
|
};
|
||||||
|
|
||||||
|
rules = lib.mkForce {
|
||||||
|
"${vmCfg.linkName}-to-local" = {
|
||||||
|
from = [vmCfg.linkName];
|
||||||
|
to = ["local"];
|
||||||
|
};
|
||||||
|
|
||||||
|
local-vms-to-local = {
|
||||||
|
from = ["wg-local-vms"];
|
||||||
|
to = ["local"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
extra.wireguard."local-vms" = {
|
||||||
|
# We have a resolvable hostname / static ip, so all peers can directly communicate with us
|
||||||
|
server = optionalAttrs (cfg.networking.host != null) {
|
||||||
|
inherit (vmCfg) host;
|
||||||
|
port = 51829;
|
||||||
|
openFirewallInRules = ["${vmCfg.linkName}-to-local"];
|
||||||
|
};
|
||||||
|
# We have no static hostname, so we must use a client-server architecture.
|
||||||
|
client = optionalAttrs (cfg.networking.host == null) {
|
||||||
|
via = nodeName;
|
||||||
|
keepalive = false;
|
||||||
|
};
|
||||||
|
# TODO check error: addresses = ["10.22.22.2/30"];
|
||||||
|
# TODO switch wg module to explicit v4 and v6
|
||||||
|
addresses = [
|
||||||
|
"${config.lib.net.cidr.host vmCfg.id cfg.networking.wireguard.netv4}/32"
|
||||||
|
"${config.lib.net.cidr.host vmCfg.id cfg.networking.wireguard.netv6}/128"
|
||||||
|
];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -138,78 +188,151 @@ in {
|
||||||
# Add the host module, but only enable if it necessary
|
# Add the host module, but only enable if it necessary
|
||||||
microvm.host
|
microvm.host
|
||||||
# This is opt-out, so we can't put this into the mkIf below
|
# This is opt-out, so we can't put this into the mkIf below
|
||||||
{microvm.host.enable = cfg != {};}
|
{microvm.host.enable = vms != {};}
|
||||||
];
|
];
|
||||||
|
|
||||||
options.extra.microvms = mkOption {
|
options.extra.microvms = {
|
||||||
default = {};
|
networking = {
|
||||||
description = "Handles the necessary base setup for MicroVMs.";
|
baseMac = mkOption {
|
||||||
type = types.attrsOf (types.submodule {
|
type = config.lib.net.types.mac;
|
||||||
options = {
|
description = mdDoc ''
|
||||||
zfs = {
|
This MAC address will be used as a base address to derive all MicroVM MAC addresses from.
|
||||||
enable = mkEnableOption (mdDoc "Enable persistent data on separate zfs dataset");
|
A good practise is to use the physical address of the macvtap interface.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
pool = mkOption {
|
host = mkOption {
|
||||||
type = types.str;
|
type = types.str;
|
||||||
description = mdDoc "The host's zfs pool on which the dataset resides";
|
description = mdDoc ''
|
||||||
};
|
The host as which this machine can be reached from other participants of the bridged macvtap network.
|
||||||
|
This can either be a resolvable hostname or an IP address.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
dataset = mkOption {
|
macvtapInterface = mkOption {
|
||||||
type = types.str;
|
type = types.str;
|
||||||
description = mdDoc "The host's dataset that should be used for this vm's state (will automatically be created, parent dataset must exist)";
|
description = mdDoc "The macvtap interface to which MicroVMs should be attached";
|
||||||
};
|
};
|
||||||
|
|
||||||
mountpoint = mkOption {
|
wireguard = {
|
||||||
type = types.str;
|
netv4 = mkOption {
|
||||||
description = mdDoc "The host's mountpoint for the vm's dataset (will be shared via virtofs as /persist in the vm)";
|
type = config.lib.net.types.cidrv4;
|
||||||
};
|
description = mdDoc "The ipv4 network address range to use for internal vm traffic.";
|
||||||
|
default = "172.31.0.0/24";
|
||||||
};
|
};
|
||||||
|
|
||||||
autostart = mkOption {
|
netv6 = mkOption {
|
||||||
type = types.bool;
|
type = config.lib.net.types.cidrv6;
|
||||||
default = false;
|
description = mdDoc "The ipv6 network address range to use for internal vm traffic.";
|
||||||
description = mdDoc "Whether this VM should be started automatically with the host";
|
default = "fddd::/64";
|
||||||
};
|
|
||||||
|
|
||||||
linkName = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
default = "wan";
|
|
||||||
description = mdDoc "The main ethernet link name inside of the VM";
|
|
||||||
};
|
|
||||||
|
|
||||||
mac = mkOption {
|
|
||||||
type = config.lib.net.types.mac;
|
|
||||||
description = mdDoc "The MAC address to assign to this VM";
|
|
||||||
};
|
|
||||||
|
|
||||||
macvtap = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
description = mdDoc "The macvtap interface to attach to";
|
|
||||||
};
|
|
||||||
|
|
||||||
system = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
description = mdDoc "The system that this microvm should use";
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
});
|
# TODO check plus no overflow
|
||||||
|
};
|
||||||
|
|
||||||
|
vms = mkOption {
|
||||||
|
default = {};
|
||||||
|
description = "Defines the actual vms and handles the necessary base setup for them.";
|
||||||
|
type = types.attrsOf (types.submodule {
|
||||||
|
options = {
|
||||||
|
id = mkOption {
|
||||||
|
type =
|
||||||
|
types.addCheck types.int (x: x > 1)
|
||||||
|
// {
|
||||||
|
name = "positiveInt1";
|
||||||
|
description = "positive integer greater than 1";
|
||||||
|
};
|
||||||
|
description = mdDoc ''
|
||||||
|
A unique id for this VM. It will be used to derive a MAC address from the host's
|
||||||
|
base MAC, and may be used as a stable id by your MicroVM config if necessary.
|
||||||
|
|
||||||
|
Ids don't need to be contiguous. It is recommended to use small numbers here to not
|
||||||
|
overflow any offset calculations. Consider that this is used for example to determine a
|
||||||
|
static ip-address by means of (baseIp + vm.id) for a wireguard network. That's also
|
||||||
|
why id 1 is reserved for the host. While this is usually checked to be in-range,
|
||||||
|
it might still be a good idea to assign greater ids with care.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
zfs = {
|
||||||
|
enable = mkEnableOption (mdDoc "Enable persistent data on separate zfs dataset");
|
||||||
|
|
||||||
|
pool = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = mdDoc "The host's zfs pool on which the dataset resides";
|
||||||
|
};
|
||||||
|
|
||||||
|
dataset = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = mdDoc "The host's dataset that should be used for this vm's state (will automatically be created, parent dataset must exist)";
|
||||||
|
};
|
||||||
|
|
||||||
|
mountpoint = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = mdDoc "The host's mountpoint for the vm's dataset (will be shared via virtofs as /persist in the vm)";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
autostart = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = mdDoc "Whether this VM should be started automatically with the host";
|
||||||
|
};
|
||||||
|
|
||||||
|
# TODO allow configuring static ipv4 and ipv6 instead of dhcp?
|
||||||
|
# maybe create networking. namespace and have options = dhcpwithRA and static.
|
||||||
|
|
||||||
|
linkName = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "wan";
|
||||||
|
description = mdDoc "The main ethernet link name inside of the VM";
|
||||||
|
};
|
||||||
|
|
||||||
|
host = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = mdDoc ''
|
||||||
|
The host as which this VM can be reached from other participants of the bridged macvtap network.
|
||||||
|
If this is unset, the wireguard connection will use a client-server architecture with the host as the server.
|
||||||
|
Otherwise, all clients will communicate directly, meaning the host cannot listen to traffic.
|
||||||
|
|
||||||
|
This can either be a resolvable hostname or an IP address.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
system = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = mdDoc "The system that this microvm should use";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = mkIf (cfg != {}) (
|
config = mkIf (vms != {}) (
|
||||||
{
|
{
|
||||||
assertions = let
|
assertions = let
|
||||||
duplicateMacs = extraLib.duplicates (mapAttrsToList (_: vmCfg: vmCfg.mac) cfg);
|
duplicateIds = extraLib.duplicates (mapAttrsToList (_: vmCfg: toString vmCfg.id) vms);
|
||||||
in [
|
in [
|
||||||
{
|
{
|
||||||
assertion = duplicateMacs == [];
|
assertion = duplicateIds == [];
|
||||||
message = "Duplicate MicroVM MAC addresses: ${concatStringsSep ", " duplicateMacs}";
|
message = "Duplicate MicroVM ids: ${concatStringsSep ", " duplicateIds}";
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
# Define a local wireguard server to communicate with vms securely
|
||||||
|
extra.wireguard."local-vms" = {
|
||||||
|
server = {
|
||||||
|
inherit (cfg.networking) host;
|
||||||
|
port = 51829;
|
||||||
|
openFirewallInRules = ["lan-to-local"];
|
||||||
|
};
|
||||||
|
addresses = [
|
||||||
|
(config.lib.net.cidr.hostCidr 1 cfg.networking.wireguard.netv4)
|
||||||
|
(config.lib.net.cidr.hostCidr 1 cfg.networking.wireguard.netv6)
|
||||||
|
];
|
||||||
|
};
|
||||||
}
|
}
|
||||||
// lib.genAttrs ["disko" "microvm" "systemd"]
|
// extraLib.mergeToplevelConfigs ["disko" "microvm" "systemd"] (mapAttrsToList microvmConfig vms)
|
||||||
(attr:
|
|
||||||
mkMerge (map
|
|
||||||
(c: c.${attr})
|
|
||||||
(mapAttrsToList microvmConfig cfg)))
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,14 +17,13 @@
|
||||||
concatStringsSep
|
concatStringsSep
|
||||||
filter
|
filter
|
||||||
filterAttrs
|
filterAttrs
|
||||||
|
genAttrs
|
||||||
head
|
head
|
||||||
mapAttrsToList
|
mapAttrsToList
|
||||||
mdDoc
|
mdDoc
|
||||||
mergeAttrs
|
mergeAttrs
|
||||||
mkIf
|
mkIf
|
||||||
mkOption
|
mkOption
|
||||||
mkEnableOption
|
|
||||||
net
|
|
||||||
optionalAttrs
|
optionalAttrs
|
||||||
optionals
|
optionals
|
||||||
splitString
|
splitString
|
||||||
|
@ -35,57 +34,102 @@
|
||||||
(extraLib)
|
(extraLib)
|
||||||
concatAttrs
|
concatAttrs
|
||||||
duplicates
|
duplicates
|
||||||
|
mergeToplevelConfigs
|
||||||
;
|
;
|
||||||
|
|
||||||
|
inherit (config.lib) net;
|
||||||
cfg = config.extra.wireguard;
|
cfg = config.extra.wireguard;
|
||||||
|
|
||||||
# TODO use netlib types!!!!!!
|
|
||||||
# TODO use netlib types!!!!!!
|
|
||||||
# TODO use netlib types!!!!!!
|
|
||||||
# TODO use netlib types!!!!!!
|
|
||||||
# TODO use netlib types!!!!!!
|
|
||||||
# TODO use netlib types!!!!!!
|
|
||||||
# TODO use netlib types!!!!!!
|
|
||||||
# TODO use netlib types!!!!!!
|
|
||||||
configForNetwork = wgName: wgCfg: let
|
configForNetwork = wgName: wgCfg: let
|
||||||
inherit
|
inherit
|
||||||
(extraLib.wireguard wgName)
|
(extraLib.wireguard wgName)
|
||||||
|
associatedNodes
|
||||||
associatedServerNodes
|
associatedServerNodes
|
||||||
associatedClientNodes
|
associatedClientNodes
|
||||||
externalPeerName
|
externalPeerName
|
||||||
|
externalPeerNamesRaw
|
||||||
peerPresharedKeyPath
|
peerPresharedKeyPath
|
||||||
peerPresharedKeySecret
|
peerPresharedKeySecret
|
||||||
peerPrivateKeyPath
|
peerPrivateKeyPath
|
||||||
peerPrivateKeySecret
|
peerPrivateKeySecret
|
||||||
peerPublicKeyPath
|
peerPublicKeyPath
|
||||||
|
usedAddresses
|
||||||
;
|
;
|
||||||
|
|
||||||
|
isServer = wgCfg.server.host != null;
|
||||||
|
isClient = wgCfg.client.via != null;
|
||||||
|
|
||||||
filterSelf = filter (x: x != nodeName);
|
filterSelf = filter (x: x != nodeName);
|
||||||
wgCfgOf = node: nodes.${node}.config.extra.wireguard.${wgName};
|
wgCfgOf = node: nodes.${node}.config.extra.wireguard.${wgName};
|
||||||
|
|
||||||
|
# All nodes that use our node as the via into the wireguard network
|
||||||
ourClientNodes =
|
ourClientNodes =
|
||||||
optionals wgCfg.server.enable
|
optionals isServer
|
||||||
(filter (n: (wgCfgOf n).via == nodeName) associatedClientNodes);
|
(filter (n: (wgCfgOf n).client.via == nodeName) associatedClientNodes);
|
||||||
|
|
||||||
# The list of peers that we have to know the psk to.
|
# The list of peers for which we have to know the psk.
|
||||||
neededPeers =
|
neededPeers =
|
||||||
if wgCfg.server.enable
|
if isServer
|
||||||
then
|
then
|
||||||
|
# Other servers in the same network
|
||||||
filterSelf associatedServerNodes
|
filterSelf associatedServerNodes
|
||||||
|
# Our external peers
|
||||||
++ map externalPeerName (attrNames wgCfg.server.externalPeers)
|
++ map externalPeerName (attrNames wgCfg.server.externalPeers)
|
||||||
|
# Our clients
|
||||||
++ ourClientNodes
|
++ ourClientNodes
|
||||||
else [wgCfg.via];
|
else [wgCfg.client.via];
|
||||||
in {
|
|
||||||
secrets =
|
|
||||||
concatAttrs (map (other: {
|
|
||||||
${peerPresharedKeySecret nodeName other}.file = peerPresharedKeyPath nodeName other;
|
|
||||||
})
|
|
||||||
neededPeers)
|
|
||||||
// {
|
|
||||||
${peerPrivateKeySecret nodeName}.file = peerPrivateKeyPath nodeName;
|
|
||||||
};
|
|
||||||
|
|
||||||
netdevs."${wgCfg.priority}-${wgName}" = {
|
# 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);
|
||||||
|
|
||||||
|
# Adds context information to the assertions for this network
|
||||||
|
assertionPrefix = "Wireguard network '${wgName}' on '${nodeName}'";
|
||||||
|
in {
|
||||||
|
assertions = [
|
||||||
|
{
|
||||||
|
assertion = any (n: (wgCfgOf n).server.host != null) associatedNodes;
|
||||||
|
message = "${assertionPrefix}: At least one node in a network must be a server.";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
assertion = duplicatePeers == [];
|
||||||
|
message = "${assertionPrefix}: Multiple definitions for external peer(s):${concatMapStrings (x: " '${x}'") duplicatePeers}";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
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.";
|
||||||
|
}
|
||||||
|
# TODO externalPeers != {} -> ip forwarding
|
||||||
|
# TODO no overlapping cidrs in (external peers + peers using via = this).
|
||||||
|
# TODO no overlapping cidrs between server nodes
|
||||||
|
];
|
||||||
|
|
||||||
|
networking.firewall.allowedUDPPorts =
|
||||||
|
mkIf
|
||||||
|
(isServer && wgCfg.server.openFirewall)
|
||||||
|
[wgCfg.server.port];
|
||||||
|
|
||||||
|
networking.nftables.firewall.rules =
|
||||||
|
mkIf
|
||||||
|
(isServer && wgCfg.server.openFirewallInRules != [])
|
||||||
|
(genAttrs wgCfg.server.openFirewallInRules (_: {allowedUDPPorts = [wgCfg.server.port];}));
|
||||||
|
|
||||||
|
rekey.secrets =
|
||||||
|
concatAttrs (map
|
||||||
|
(other: {${peerPresharedKeySecret nodeName other}.file = peerPresharedKeyPath nodeName other;})
|
||||||
|
neededPeers)
|
||||||
|
// {${peerPrivateKeySecret nodeName}.file = peerPrivateKeyPath nodeName;};
|
||||||
|
|
||||||
|
systemd.network.netdevs."${toString wgCfg.priority}-${wgName}" = {
|
||||||
netdevConfig = {
|
netdevConfig = {
|
||||||
Kind = "wireguard";
|
Kind = "wireguard";
|
||||||
Name = "${wgName}";
|
Name = "${wgName}";
|
||||||
|
@ -95,17 +139,17 @@
|
||||||
{
|
{
|
||||||
PrivateKeyFile = config.rekey.secrets.${peerPrivateKeySecret nodeName}.path;
|
PrivateKeyFile = config.rekey.secrets.${peerPrivateKeySecret nodeName}.path;
|
||||||
}
|
}
|
||||||
// optionalAttrs wgCfg.server.enable {
|
// optionalAttrs isServer {
|
||||||
ListenPort = wgCfg.server.port;
|
ListenPort = wgCfg.server.port;
|
||||||
};
|
};
|
||||||
wireguardPeers =
|
wireguardPeers =
|
||||||
if wgCfg.server.enable
|
if isServer
|
||||||
then
|
then
|
||||||
# Always include all other server nodes.
|
# Always include all other server nodes.
|
||||||
map (serverNode: let
|
map (serverNode: {
|
||||||
snCfg = wgCfgOf serverNode;
|
wireguardPeerConfig = let
|
||||||
in {
|
snCfg = wgCfgOf serverNode;
|
||||||
wireguardPeerConfig = {
|
in {
|
||||||
PublicKey = builtins.readFile (peerPublicKeyPath serverNode);
|
PublicKey = builtins.readFile (peerPublicKeyPath serverNode);
|
||||||
PresharedKeyFile = config.rekey.secrets.${peerPresharedKeySecret nodeName serverNode}.path;
|
PresharedKeyFile = config.rekey.secrets.${peerPresharedKeySecret nodeName serverNode}.path;
|
||||||
# The allowed ips of a server node are it's own addreses,
|
# The allowed ips of a server node are it's own addreses,
|
||||||
|
@ -117,7 +161,8 @@
|
||||||
# ++ map (n: (wgCfgOf n).addresses) snCfg.ourClientNodes;
|
# ++ map (n: (wgCfgOf n).addresses) snCfg.ourClientNodes;
|
||||||
Endpoint = "${snCfg.server.host}:${toString snCfg.server.port}";
|
Endpoint = "${snCfg.server.host}:${toString snCfg.server.port}";
|
||||||
};
|
};
|
||||||
}) (filterSelf associatedServerNodes)
|
})
|
||||||
|
(filterSelf associatedServerNodes)
|
||||||
# All our external peers
|
# All our external peers
|
||||||
++ mapAttrsToList (extPeer: allowedIPs: let
|
++ mapAttrsToList (extPeer: allowedIPs: let
|
||||||
peerName = externalPeerName extPeer;
|
peerName = externalPeerName extPeer;
|
||||||
|
@ -126,18 +171,24 @@
|
||||||
PublicKey = builtins.readFile (peerPublicKeyPath peerName);
|
PublicKey = builtins.readFile (peerPublicKeyPath peerName);
|
||||||
PresharedKeyFile = config.rekey.secrets.${peerPresharedKeySecret nodeName peerName}.path;
|
PresharedKeyFile = config.rekey.secrets.${peerPresharedKeySecret nodeName peerName}.path;
|
||||||
AllowedIPs = allowedIPs;
|
AllowedIPs = allowedIPs;
|
||||||
|
# Connections to external peers should always be kept alive
|
||||||
PersistentKeepalive = 25;
|
PersistentKeepalive = 25;
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
wgCfg.server.externalPeers
|
wgCfg.server.externalPeers
|
||||||
# All client nodes that have their via set to us.
|
# All client nodes that have their via set to us.
|
||||||
++ mapAttrsToList (clientNode: {
|
++ mapAttrsToList (clientNode: let
|
||||||
wireguardPeerConfig = {
|
clientCfg = wgCfgOf clientNode;
|
||||||
PublicKey = builtins.readFile (peerPublicKeyPath clientNode);
|
in {
|
||||||
PresharedKeyFile = config.rekey.secrets.${peerPresharedKeySecret nodeName clientNode}.path;
|
wireguardPeerConfig =
|
||||||
AllowedIPs = (wgCfgOf clientNode).addresses;
|
{
|
||||||
PersistentKeepalive = 25;
|
PublicKey = builtins.readFile (peerPublicKeyPath clientNode);
|
||||||
};
|
PresharedKeyFile = config.rekey.secrets.${peerPresharedKeySecret nodeName clientNode}.path;
|
||||||
|
AllowedIPs = clientCfg.addresses;
|
||||||
|
}
|
||||||
|
// optionalAttrs clientCfg.keepalive {
|
||||||
|
PersistentKeepalive = 25;
|
||||||
|
};
|
||||||
})
|
})
|
||||||
ourClientNodes
|
ourClientNodes
|
||||||
else
|
else
|
||||||
|
@ -145,15 +196,16 @@
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
wireguardPeerConfig = {
|
wireguardPeerConfig = {
|
||||||
PublicKey = builtins.readFile (peerPublicKeyPath wgCfg.via);
|
PublicKey = builtins.readFile (peerPublicKeyPath wgCfg.client.via);
|
||||||
PresharedKeyFile = config.rekey.secrets.${peerPresharedKeySecret nodeName wgCfg.via}.path;
|
PresharedKeyFile = config.rekey.secrets.${peerPresharedKeySecret nodeName wgCfg.client.via}.path;
|
||||||
AllowedIPs = (wgCfgOf wgCfg.via).addresses;
|
# TODO this should be 0.0.0.0 if the client wants to route all traffic
|
||||||
|
AllowedIPs = (wgCfgOf wgCfg.client.via).addresses;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
networks."${wgCfg.priority}-${wgName}" = {
|
systemd.network.networks."${toString wgCfg.priority}-${wgName}" = {
|
||||||
matchConfig.Name = wgName;
|
matchConfig.Name = wgName;
|
||||||
networkConfig.Address = wgCfg.addresses;
|
networkConfig.Address = wgCfg.addresses;
|
||||||
};
|
};
|
||||||
|
@ -162,14 +214,17 @@ in {
|
||||||
options.extra.wireguard = mkOption {
|
options.extra.wireguard = mkOption {
|
||||||
default = {};
|
default = {};
|
||||||
description = "Configures wireguard networks via systemd-networkd.";
|
description = "Configures wireguard networks via systemd-networkd.";
|
||||||
type = types.attrsOf (types.submodule {
|
type = types.lazyAttrsOf (types.submodule ({
|
||||||
|
config,
|
||||||
|
name,
|
||||||
|
...
|
||||||
|
}: {
|
||||||
options = {
|
options = {
|
||||||
server = {
|
server = {
|
||||||
enable = mkEnableOption (mdDoc "wireguard server");
|
|
||||||
|
|
||||||
host = mkOption {
|
host = mkOption {
|
||||||
type = types.str;
|
default = null;
|
||||||
description = mdDoc "The hostname or ip address which other peers can use to reach this host.";
|
type = types.nullOr types.str;
|
||||||
|
description = mdDoc "The hostname or ip address which other peers can use to reach this host. No server funnctionality will be activated if set to null.";
|
||||||
};
|
};
|
||||||
|
|
||||||
port = mkOption {
|
port = mkOption {
|
||||||
|
@ -181,11 +236,17 @@ in {
|
||||||
openFirewall = mkOption {
|
openFirewall = mkOption {
|
||||||
default = false;
|
default = false;
|
||||||
type = types.bool;
|
type = types.bool;
|
||||||
description = mdDoc "Whether to open the firewall for the specified `listenPort`, if {option}`listen` is `true`.";
|
description = mdDoc "Whether to open the firewall for the specified {option}`port`.";
|
||||||
|
};
|
||||||
|
|
||||||
|
openFirewallInRules = 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 {
|
externalPeers = mkOption {
|
||||||
type = types.attrsOf (types.listOf types.str);
|
type = types.attrsOf (types.listOf (net.types.cidr-in config.addresses));
|
||||||
default = {};
|
default = {};
|
||||||
example = {my-android-phone = ["10.0.0.97/32"];};
|
example = {my-android-phone = ["10.0.0.97/32"];};
|
||||||
description = mdDoc ''
|
description = mdDoc ''
|
||||||
|
@ -199,82 +260,55 @@ in {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
client = {
|
||||||
|
via = mkOption {
|
||||||
|
default = null;
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
description = mdDoc ''
|
||||||
|
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 = mdDoc "Whether to keep this connection alive using PersistentKeepalive. Set to false only for networks where client and server IPs are stable.";
|
||||||
|
};
|
||||||
|
|
||||||
|
# TODO one option for allowing it, but also one to allow defining two
|
||||||
|
# profiles / interfaces that can be activated manually.
|
||||||
|
#routeAllTraffic = mkOption {
|
||||||
|
# default = false;
|
||||||
|
# type = types.bool;
|
||||||
|
# description = mdDoc ''
|
||||||
|
# Whether to allow routing all traffic through the via server.
|
||||||
|
# '';
|
||||||
|
#};
|
||||||
|
};
|
||||||
|
|
||||||
priority = mkOption {
|
priority = mkOption {
|
||||||
default = "20";
|
default = 40;
|
||||||
type = types.str;
|
type = types.int;
|
||||||
description = mdDoc "The order priority used when creating systemd netdev and network files.";
|
description = mdDoc "The order priority used when creating systemd netdev and network files.";
|
||||||
};
|
};
|
||||||
|
|
||||||
via = mkOption {
|
|
||||||
default = null;
|
|
||||||
type = types.uniq (types.nullOr types.str);
|
|
||||||
description = mdDoc ''
|
|
||||||
The server node via which to connect to the network.
|
|
||||||
This must defined if and only if this node is not a server.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
addresses = mkOption {
|
addresses = mkOption {
|
||||||
type = types.listOf types.str;
|
type = types.listOf (
|
||||||
|
if config.client.via != null
|
||||||
|
then net.types.cidr-in nodes.${config.client.via}.config.extra.wireguard.${name}.addresses
|
||||||
|
else net.types.cidr
|
||||||
|
);
|
||||||
description = mdDoc ''
|
description = mdDoc ''
|
||||||
The addresses to configure for this interface. Will automatically be added
|
The addresses to configure for this interface. Will automatically be added
|
||||||
as this peer's allowed addresses to all other peers.
|
as this peer's allowed addresses on all other peers.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
});
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
config = mkIf (cfg != {}) (let
|
config = mkIf (cfg != {}) (mergeToplevelConfigs
|
||||||
networkCfgs = mapAttrsToList configForNetwork cfg;
|
["assertions" "rekey" "networking" "systemd"]
|
||||||
collectAllNetworkAttrs = x: concatAttrs (map (y: y.${x}) networkCfgs);
|
(mapAttrsToList configForNetwork cfg));
|
||||||
in {
|
|
||||||
assertions = concatMap (wgName: let
|
|
||||||
inherit
|
|
||||||
(extraLib.wireguard wgName)
|
|
||||||
externalPeerNamesRaw
|
|
||||||
usedAddresses
|
|
||||||
associatedNodes
|
|
||||||
;
|
|
||||||
|
|
||||||
wgCfg = cfg.${wgName};
|
|
||||||
wgCfgOf = node: nodes.${node}.config.extra.wireguard.${wgName};
|
|
||||||
duplicatePeers = duplicates externalPeerNamesRaw;
|
|
||||||
duplicateAddrs = duplicates (map (x: head (splitString "/" x)) usedAddresses);
|
|
||||||
in [
|
|
||||||
{
|
|
||||||
assertion = any (n: nodes.${n}.config.extra.wireguard.${wgName}.server.enable) associatedNodes;
|
|
||||||
message = "Wireguard network '${wgName}': At least one node must be a server.";
|
|
||||||
}
|
|
||||||
{
|
|
||||||
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}";
|
|
||||||
}
|
|
||||||
{
|
|
||||||
assertion = wgCfg.server.externalPeers != {} -> wgCfg.server.enable;
|
|
||||||
message = "Wireguard network '${wgName}': Defining external peers requires server.enable = true.";
|
|
||||||
}
|
|
||||||
{
|
|
||||||
assertion = wgCfg.server.enable == (wgCfg.via == null);
|
|
||||||
message = "Wireguard network '${wgName}': A via server must be defined exactly iff this isn't a server node.";
|
|
||||||
}
|
|
||||||
{
|
|
||||||
assertion = wgCfg.via != null -> (wgCfgOf wgCfg.via).server.enable;
|
|
||||||
message = "Wireguard network '${wgName}': The specified via node '${wgCfg.via}' must be a wireguard server.";
|
|
||||||
}
|
|
||||||
# TODO externalPeers != {} -> ip forwarding
|
|
||||||
# TODO no overlapping allowed ip range? 0.0.0.0 would be ok to overlap though
|
|
||||||
]) (attrNames cfg);
|
|
||||||
|
|
||||||
networking.firewall.allowedUDPPorts = mkIf (cfg.server.enable && cfg.server.openFirewall) [cfg.server.port];
|
|
||||||
rekey.secrets = collectAllNetworkAttrs "secrets";
|
|
||||||
systemd.network = {
|
|
||||||
netdevs = collectAllNetworkAttrs "netdevs";
|
|
||||||
networks = collectAllNetworkAttrs "networks";
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
# plugin-files = ${pkgs.nix-plugins}/lib/nix/plugins
|
# plugin-files = ${pkgs.nix-plugins}/lib/nix/plugins
|
||||||
# # Please adjust path accordingly, or leave this out and alternativaly
|
# # Please adjust path accordingly, or leave this out and alternativaly
|
||||||
# # pass `--option extra-builtins-file ./extra-builtins.nix` to each invocation
|
# # pass `--option extra-builtins-file ./extra-builtins.nix` to each invocation
|
||||||
# extra-builtins-file = ./extra-builtins.nix
|
# extra-builtins-file = ${./extra-builtins.nix}
|
||||||
# '';
|
# '';
|
||||||
# }
|
# }
|
||||||
# ```
|
# ```
|
||||||
|
|
10
nix/lib.nix
10
nix/lib.nix
|
@ -19,6 +19,7 @@
|
||||||
head
|
head
|
||||||
mapAttrs'
|
mapAttrs'
|
||||||
mergeAttrs
|
mergeAttrs
|
||||||
|
mkMerge
|
||||||
nameValuePair
|
nameValuePair
|
||||||
optionalAttrs
|
optionalAttrs
|
||||||
partition
|
partition
|
||||||
|
@ -50,6 +51,11 @@ in rec {
|
||||||
# True if the path or string starts with /
|
# True if the path or string starts with /
|
||||||
isAbsolutePath = x: substring 0 1 x == "/";
|
isAbsolutePath = x: substring 0 1 x == "/";
|
||||||
|
|
||||||
|
# Merges all given attributes from the given attrsets using mkMerge.
|
||||||
|
# Useful to merge several top-level configs in a module.
|
||||||
|
mergeToplevelConfigs = keys: attrs:
|
||||||
|
genAttrs keys (attr: mkMerge (map (x: x.${attr} or {}) attrs));
|
||||||
|
|
||||||
disko = {
|
disko = {
|
||||||
gpt = {
|
gpt = {
|
||||||
partEfi = name: start: end: {
|
partEfi = name: start: end: {
|
||||||
|
@ -85,7 +91,7 @@ in rec {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
zfs = {
|
zfs = {
|
||||||
encryptedZpool = {
|
defaultZpoolOptions = {
|
||||||
type = "zpool";
|
type = "zpool";
|
||||||
mountRoot = "/mnt";
|
mountRoot = "/mnt";
|
||||||
rootFsOptions = {
|
rootFsOptions = {
|
||||||
|
@ -164,7 +170,7 @@ in rec {
|
||||||
# Partition nodes by whether they are servers
|
# Partition nodes by whether they are servers
|
||||||
_associatedNodes_isServerPartition =
|
_associatedNodes_isServerPartition =
|
||||||
partition
|
partition
|
||||||
(n: self.nodes.${n}.config.extra.wireguard.${wgName}.server.enable)
|
(n: self.nodes.${n}.config.extra.wireguard.${wgName}.server.host != null)
|
||||||
associatedNodes;
|
associatedNodes;
|
||||||
|
|
||||||
associatedServerNodes = _associatedNodes_isServerPartition.right;
|
associatedServerNodes = _associatedNodes_isServerPartition.right;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue