1
1
Fork 1
mirror of https://github.com/oddlama/nix-config.git synced 2025-10-11 07:10:39 +02:00

refactor: add lib extensions to nixpkgs.lib as overlays

This commit is contained in:
oddlama 2023-07-02 00:08:17 +02:00
parent 385d8178a2
commit e1e7516e1a
No known key found for this signature in database
GPG key ID: 14EFE510775FE39A
19 changed files with 743 additions and 813 deletions

View file

@ -2,12 +2,12 @@
This is my personal nix config. It's still in the making, but this is what I got so far: This is my personal nix config. It's still in the making, but this is what I got so far:
- Full disk encryption using [disko](https://github.com/nix-community/disko), remotely unlockable via ssh
- Zoned nftables firewall
- Service isolation using [microvms](https://github.com/astro/microvm.nix) instead of containers
- Log and system monitoring via loki, telegraf, influxdb, promtail and grafana - Log and system monitoring via loki, telegraf, influxdb, promtail and grafana
- Single-Sign-On for all services using oauth2 via kanidm - Single-Sign-On for all services using oauth2 via kanidm
- Automatic wireguard mesh generation - Automatic wireguard mesh generation
- Full disk encryption using [disko](https://github.com/nix-community/disko), remotely unlockable via ssh
- Zoned nftables firewall via [nixos-nftables-firewall](https://github.com/thelegy/nixos-nftables-firewall)
- Service isolation using [microvms](https://github.com/astro/microvm.nix) instead of containers
- Secret rekeying, generation and bootstrapping using [agenix-rekey](https://github.com/oddlama/agenix-rekey) - Secret rekeying, generation and bootstrapping using [agenix-rekey](https://github.com/oddlama/agenix-rekey)
- Support for repository-wide secrets at evaluation time (hides PII like MACs) - Support for repository-wide secrets at evaluation time (hides PII like MACs)

View file

@ -131,7 +131,10 @@
pkgs = import nixpkgs { pkgs = import nixpkgs {
localSystem = system; localSystem = system;
config.allowUnfree = true; config.allowUnfree = true;
overlays = [microvm.overlay] ++ import ./pkgs/default.nix; overlays =
import ./lib inputs
++ import ./pkgs/default.nix
++ [microvm.overlay];
}; };
apps = apps =

View file

@ -1,16 +1,14 @@
{ {
inputs,
config, config,
lib,
... ...
}: let }: {
disko = import ../../lib/disko.nix inputs;
in {
disko.devices = { disko.devices = {
disk = { disk = {
m2-ssd = { m2-ssd = {
type = "disk"; type = "disk";
device = "/dev/disk/by-id/${config.repo.secrets.local.disk.m2-ssd}"; device = "/dev/disk/by-id/${config.repo.secrets.local.disk.m2-ssd}";
content = with disko.gpt; { content = with lib.disko.gpt; {
type = "table"; type = "table";
format = "gpt"; format = "gpt";
partitions = [ partitions = [
@ -21,7 +19,7 @@ in {
boot-ssd = { boot-ssd = {
type = "disk"; type = "disk";
device = "/dev/disk/by-id/${config.repo.secrets.local.disk.boot-ssd}"; device = "/dev/disk/by-id/${config.repo.secrets.local.disk.boot-ssd}";
content = with disko.gpt; { content = with lib.disko.gpt; {
type = "table"; type = "table";
format = "gpt"; format = "gpt";
partitions = [ partitions = [
@ -31,7 +29,7 @@ in {
}; };
}; };
}; };
zpool = with disko.zfs; { zpool = with lib.disko.zfs; {
rpool = defaultZpoolOptions // {datasets = defaultZfsDatasets;}; rpool = defaultZpoolOptions // {datasets = defaultZfsDatasets;};
}; };
}; };

View file

@ -1,16 +1,14 @@
{ {
config, config,
inputs, lib,
... ...
}: let }: {
disko = import ../../lib/disko.nix inputs;
in {
disko.devices = { disko.devices = {
disk = { disk = {
main = { main = {
type = "disk"; type = "disk";
device = "/dev/disk/by-id/${config.repo.secrets.local.disk.main}"; device = "/dev/disk/by-id/${config.repo.secrets.local.disk.main}";
content = with disko.gpt; { content = with lib.disko.gpt; {
type = "table"; type = "table";
format = "gpt"; format = "gpt";
partitions = [ partitions = [
@ -21,7 +19,7 @@ in {
}; };
}; };
}; };
zpool = with disko.zfs; { zpool = with lib.disko.zfs; {
rpool = defaultZpoolOptions // {datasets = defaultZfsDatasets;}; rpool = defaultZpoolOptions // {datasets = defaultZfsDatasets;};
}; };
}; };

View file

@ -1,18 +1,15 @@
{ {
config, config,
inputs,
lib, lib,
pkgs, pkgs,
... ...
}: let }: {
disko = import ../../lib/disko.nix inputs;
in {
disko.devices = { disko.devices = {
disk = { disk = {
m2-ssd = { m2-ssd = {
type = "disk"; type = "disk";
device = "/dev/disk/by-id/${config.repo.secrets.local.disk.m2-ssd}"; device = "/dev/disk/by-id/${config.repo.secrets.local.disk.m2-ssd}";
content = with disko.gpt; { content = with lib.disko.gpt; {
type = "table"; type = "table";
format = "gpt"; format = "gpt";
partitions = [ partitions = [
@ -23,7 +20,7 @@ in {
}; };
}; };
}; };
zpool = with disko.zfs; { zpool = with lib.disko.zfs; {
rpool = rpool =
defaultZpoolOptions defaultZpoolOptions
// { // {

View file

@ -1,15 +1,9 @@
{ {
config, config,
inputs,
lib, lib,
utils, utils,
... ...
}: let }: let
inherit
(import ../../lib/net.nix inputs)
cidr
;
lanCidrv4 = "192.168.100.0/24"; lanCidrv4 = "192.168.100.0/24";
lanCidrv6 = "fd10::/64"; lanCidrv6 = "fd10::/64";
in { in {
@ -60,8 +54,8 @@ in {
}; };
"20-lan-self" = { "20-lan-self" = {
address = [ address = [
(cidr.hostCidr 1 lanCidrv4) (lib.net.cidr.hostCidr 1 lanCidrv4)
(cidr.hostCidr 1 lanCidrv6) (lib.net.cidr.hostCidr 1 lanCidrv6)
]; ];
matchConfig.Name = "lan-self"; matchConfig.Name = "lan-self";
networkConfig = { networkConfig = {
@ -84,7 +78,7 @@ in {
ipv6SendRAConfig = { ipv6SendRAConfig = {
EmitDNS = true; EmitDNS = true;
# TODO change to self later # TODO change to self later
#DNS = cidr.host 1 net.lan.ipv6cidr; #DNS = lib.net.cidr.host 1 net.lan.ipv6cidr;
DNS = ["2606:4700:4700::1111" "2001:4860:4860::8888"]; DNS = ["2606:4700:4700::1111" "2001:4860:4860::8888"];
}; };
linkConfig.RequiredForOnline = "routable"; linkConfig.RequiredForOnline = "routable";
@ -160,12 +154,12 @@ in {
interface = "lan-self"; interface = "lan-self";
subnet = lanCidrv4; subnet = lanCidrv4;
pools = [ pools = [
{pool = "${cidr.host 20 lanCidrv4} - ${cidr.host (-6) lanCidrv4}";} {pool = "${lib.net.cidr.host 20 lanCidrv4} - ${lib.net.cidr.host (-6) lanCidrv4}";}
]; ];
option-data = [ option-data = [
{ {
name = "routers"; name = "routers";
data = cidr.host 1 lanCidrv4; data = lib.net.cidr.host 1 lanCidrv4;
} }
]; ];
} }

View file

@ -1,14 +1,8 @@
{ {
config, config,
inputs,
lib, lib,
... ...
}: let }: let
inherit
(import ../../lib/net.nix inputs)
cidr
;
iotCidrv4 = "10.90.0.0/24"; iotCidrv4 = "10.90.0.0/24";
iotCidrv6 = "fd00:90::/64"; iotCidrv6 = "fd00:90::/64";
in { in {
@ -31,8 +25,8 @@ in {
}; };
"10-wlan1" = { "10-wlan1" = {
address = [ address = [
(cidr.hostCidr 1 iotCidrv4) (lib.net.cidr.hostCidr 1 iotCidrv4)
(cidr.hostCidr 1 iotCidrv6) (lib.net.cidr.hostCidr 1 iotCidrv6)
]; ];
matchConfig.MACAddress = config.repo.secrets.local.networking.interfaces.wlan1.mac; matchConfig.MACAddress = config.repo.secrets.local.networking.interfaces.wlan1.mac;
linkConfig.RequiredForOnline = "no"; linkConfig.RequiredForOnline = "no";

7
lib/default.nix Normal file
View file

@ -0,0 +1,7 @@
inputs: [
(import ./disko.nix inputs)
(import ./misc.nix inputs)
(import ./net.nix inputs)
(import ./types.nix inputs)
(import ./wireguard.nix inputs)
]

View file

@ -1,81 +1,87 @@
inputs: { inputs: self: super: {
gpt = { lib =
partGrub = name: start: end: { super.lib
inherit name start end; // {
part-type = "primary"; disko = {
flags = ["bios_grub"]; gpt = {
}; partGrub = name: start: end: {
partEfi = name: start: end: { inherit name start end;
inherit name start end; part-type = "primary";
fs-type = "fat32"; flags = ["bios_grub"];
bootable = true; };
content = { partEfi = name: start: end: {
type = "filesystem"; inherit name start end;
format = "vfat"; fs-type = "fat32";
mountpoint = "/boot"; bootable = true;
}; content = {
}; type = "filesystem";
partSwap = name: start: end: { format = "vfat";
inherit name start end; mountpoint = "/boot";
fs-type = "linux-swap"; };
content = { };
type = "swap"; partSwap = name: start: end: {
randomEncryption = true; inherit name start end;
}; fs-type = "linux-swap";
}; content = {
partLuksZfs = name: start: end: { type = "swap";
inherit start end; randomEncryption = true;
name = "enc-${name}"; };
content = { };
type = "luks"; partLuksZfs = name: start: end: {
name = "enc-${name}"; inherit start end;
extraOpenArgs = ["--allow-discards"]; name = "enc-${name}";
content = { content = {
type = "zfs"; type = "luks";
pool = name; name = "enc-${name}";
extraOpenArgs = ["--allow-discards"];
content = {
type = "zfs";
pool = name;
};
};
};
};
zfs = rec {
defaultZpoolOptions = {
type = "zpool";
mountRoot = "/mnt";
rootFsOptions = {
compression = "zstd";
acltype = "posix";
atime = "off";
xattr = "sa";
dnodesize = "auto";
mountpoint = "none";
canmount = "off";
devices = "off";
};
options.ashift = "12";
};
defaultZfsDatasets = {
"local" = unmountable;
"local/root" =
filesystem "/"
// {
postCreateHook = "zfs snapshot rpool/local/root@blank";
};
"local/nix" = filesystem "/nix";
"local/state" = filesystem "/state";
"safe" = unmountable;
"safe/persist" = filesystem "/persist";
};
unmountable = {type = "zfs_fs";};
filesystem = mountpoint: {
type = "zfs_fs";
options = {
canmount = "noauto";
inherit mountpoint;
};
# Required to add dependencies for initrd
inherit mountpoint;
};
}; };
}; };
}; };
};
zfs = rec {
defaultZpoolOptions = {
type = "zpool";
mountRoot = "/mnt";
rootFsOptions = {
compression = "zstd";
acltype = "posix";
atime = "off";
xattr = "sa";
dnodesize = "auto";
mountpoint = "none";
canmount = "off";
devices = "off";
};
options.ashift = "12";
};
defaultZfsDatasets = {
"local" = unmountable;
"local/root" =
filesystem "/"
// {
postCreateHook = "zfs snapshot rpool/local/root@blank";
};
"local/nix" = filesystem "/nix";
"local/state" = filesystem "/state";
"safe" = unmountable;
"safe/persist" = filesystem "/persist";
};
unmountable = {type = "zfs_fs";};
filesystem = mountpoint: {
type = "zfs_fs";
options = {
canmount = "noauto";
inherit mountpoint;
};
# Required to add dependencies for initrd
inherit mountpoint;
};
};
} }

View file

@ -1,54 +1,25 @@
inputs: let inputs: self: super: let
inherit inherit
(inputs.nixpkgs.lib) (super.lib)
all
any
assertMsg
attrNames
attrValues
concatLists
concatMap
concatMapStrings concatMapStrings
concatStringsSep
elem
escapeShellArg escapeShellArg
filter filter
flatten
flip
foldAttrs
foldl' foldl'
genAttrs genAttrs
genList genList
hasInfix
head
isAttrs
mapAttrs'
mergeAttrs mergeAttrs
min
mkMerge mkMerge
mkOptionType
nameValuePair
optionalAttrs
partition
range
recursiveUpdate
removeSuffix
reverseList
showOption
splitString
stringToCharacters stringToCharacters
substring substring
types
unique unique
warnIf
; ;
in rec {
# Counts how often each element occurrs in xs # Counts how often each element occurrs in xs.
countOccurrences = let # Elements must be strings.
addOrUpdate = acc: x: countOccurrences =
acc // {${x} = (acc.${x} or 0) + 1;}; foldl'
in (acc: x: acc // {${x} = (acc.${x} or 0) + 1;})
foldl' addOrUpdate {}; {};
# Returns all elements in xs that occur at least twice # Returns all elements in xs that occur at least twice
duplicates = xs: let duplicates = xs: let
@ -70,50 +41,67 @@ in rec {
# Calculates base^exp, but careful, this overflows for results > 2^62 # Calculates base^exp, but careful, this overflows for results > 2^62
pow = base: exp: foldl' (a: x: x * a) 1 (genList (_: base) exp); pow = base: exp: foldl' (a: x: x * a) 1 (genList (_: base) exp);
hexLiteralValues = {
"0" = 0;
"1" = 1;
"2" = 2;
"3" = 3;
"4" = 4;
"5" = 5;
"6" = 6;
"7" = 7;
"8" = 8;
"9" = 9;
"a" = 10;
"b" = 11;
"c" = 12;
"d" = 13;
"e" = 14;
"f" = 15;
"A" = 10;
"B" = 11;
"C" = 12;
"D" = 13;
"E" = 14;
"F" = 15;
};
# Converts the given hex string to an integer. Only reliable for inputs in [0, 2^63), # Converts the given hex string to an integer. Only reliable for inputs in [0, 2^63),
# after that the sign bit will overflow. # after that the sign bit will overflow.
hexToDec = v: let hexToDec = v: foldl' (acc: x: acc * 16 + hexLiteralValues.${x}) 0 (stringToCharacters v);
literalValues = { in {
"0" = 0; lib =
"1" = 1; super.lib
"2" = 2; // {
"3" = 3; inherit
"4" = 4; concatAttrs
"5" = 5; countOccurrences
"6" = 6; duplicates
"7" = 7; hexToDec
"8" = 8; isAbsolutePath
"9" = 9; mergeToplevelConfigs
"a" = 10; pow
"b" = 11; ;
"c" = 12;
"d" = 13;
"e" = 14;
"f" = 15;
"A" = 10;
"B" = 11;
"C" = 12;
"D" = 13;
"E" = 14;
"F" = 15;
};
in
foldl' (acc: x: acc * 16 + literalValues.${x}) 0 (stringToCharacters v);
secrets = let # TODO separate this or get rid of it
rageMasterIdentityArgs = concatMapStrings (x: "-i ${escapeShellArg x} ") inputs.self.secretsConfig.masterIdentities; # TODO separate this or get rid of it
rageExtraEncryptionPubkeys = # TODO separate this or get rid of it
concatMapStrings ( # TODO separate this or get rid of it
x: secrets = let
if isAbsolutePath x rageMasterIdentityArgs = concatMapStrings (x: "-i ${escapeShellArg x} ") inputs.self.secretsConfig.masterIdentities;
then "-R ${escapeShellArg x} " rageExtraEncryptionPubkeys =
else "-r ${escapeShellArg x} " concatMapStrings (
) x:
inputs.self.secretsConfig.extraEncryptionPubkeys; if isAbsolutePath x
in { then "-R ${escapeShellArg x} "
# TODO replace these by lib.agenix-rekey else "-r ${escapeShellArg x} "
# The arguments required to de-/encrypt a secret in this repository )
rageDecryptArgs = "${rageMasterIdentityArgs}"; inputs.self.secretsConfig.extraEncryptionPubkeys;
rageEncryptArgs = "${rageMasterIdentityArgs} ${rageExtraEncryptionPubkeys}"; in {
}; # TODO replace these by lib.agenix-rekey
# The arguments required to de-/encrypt a secret in this repository
rageDecryptArgs = "${rageMasterIdentityArgs}";
rageEncryptArgs = "${rageMasterIdentityArgs} ${rageExtraEncryptionPubkeys}";
};
};
} }

View file

@ -1,50 +1,28 @@
inputs: let inputs: self: super: let
inherit inherit
(inputs.nixpkgs.lib) (inputs.nixpkgs.lib)
all all
any any
assertMsg assertMsg
attrNames
attrValues
concatLists
concatMap
concatMapStrings
concatStringsSep
elem elem
escapeShellArg
filter filter
flatten
flip flip
foldAttrs
foldl' foldl'
genAttrs
genList
hasInfix hasInfix
head head
isAttrs
mapAttrs'
mergeAttrs
min min
mkMerge
mkOptionType
nameValuePair
optionalAttrs
partition partition
range range
recursiveUpdate recursiveUpdate
removeSuffix
reverseList reverseList
showOption
splitString splitString
stringToCharacters
substring substring
types
unique unique
warnIf warnIf
; ;
inherit inherit
(import ./misc.nix inputs) (self.lib)
hexToDec hexToDec
pow pow
; ;
@ -58,321 +36,325 @@ inputs: let
}) })
.lib .lib
.net; .net;
in in {
recursiveUpdate libNet { lib = recursiveUpdate super.lib {
cidr = rec { net = recursiveUpdate (removeAttrs libNet ["types"]) {
# host :: (ip | mac | integer) -> cidr -> ip cidr = rec {
# # host :: (ip | mac | integer) -> cidr -> ip
# Wrapper that extends the original host function to #
# check whether the argument `n` is in-range for the given cidr. # Wrapper that extends the original host function to
# # check whether the argument `n` is in-range for the given cidr.
# Examples: #
# # Examples:
# > net.cidr.host 255 "192.168.1.0/24" #
# "192.168.1.255" # > net.cidr.host 255 "192.168.1.0/24"
# > net.cidr.host (256) "192.168.1.0/24" # "192.168.1.255"
# <fails with an error message> # > net.cidr.host (256) "192.168.1.0/24"
# > net.cidr.host (-1) "192.168.1.0/24" # <fails with an error message>
# "192.168.1.255" # > net.cidr.host (-1) "192.168.1.0/24"
# > net.cidr.host (-256) "192.168.1.0/24" # "192.168.1.255"
# "192.168.1.0" # > net.cidr.host (-256) "192.168.1.0/24"
# > net.cidr.host (-257) "192.168.1.0/24" # "192.168.1.0"
# <fails with an error message> # > net.cidr.host (-257) "192.168.1.0/24"
host = i: n: let # <fails with an error message>
cap = libNet.cidr.capacity n; host = i: n: let
in cap = libNet.cidr.capacity n;
assert assertMsg (i >= (-cap) && i < cap) "The host ${toString i} lies outside of ${n}"; in
libNet.cidr.host i n; assert assertMsg (i >= (-cap) && i < cap) "The host ${toString i} lies outside of ${n}";
# hostCidr :: (ip | mac | integer) -> cidr -> cidr libNet.cidr.host i n;
# # hostCidr :: (ip | mac | integer) -> cidr -> cidr
# Returns the nth host in the given cidr range (like cidr.host) #
# but as a cidr that retains the original prefix length. # Returns the nth host in the given cidr range (like cidr.host)
# # but as a cidr that retains the original prefix length.
# Examples: #
# # Examples:
# > net.cidr.hostCidr 2 "192.168.1.0/24" #
# "192.168.1.2/24" # > net.cidr.hostCidr 2 "192.168.1.0/24"
hostCidr = n: x: "${libNet.cidr.host n x}/${toString (libNet.cidr.length x)}"; # "192.168.1.2/24"
# ip :: (cidr | ip) -> ip hostCidr = n: x: "${libNet.cidr.host n x}/${toString (libNet.cidr.length x)}";
# # ip :: (cidr | ip) -> ip
# Returns just the ip part of the cidr. #
# # Returns just the ip part of the cidr.
# Examples: #
# # Examples:
# > net.cidr.ip "192.168.1.100/24" #
# "192.168.1.100" # > net.cidr.ip "192.168.1.100/24"
# > net.cidr.ip "192.168.1.100" # "192.168.1.100"
# "192.168.1.100" # > net.cidr.ip "192.168.1.100"
ip = x: head (splitString "/" x); # "192.168.1.100"
# canonicalize :: cidr -> cidr ip = x: head (splitString "/" x);
# # canonicalize :: cidr -> cidr
# Replaces the ip of the cidr with the canonical network address #
# (first contained address in range) # Replaces the ip of the cidr with the canonical network address
# # (first contained address in range)
# Examples: #
# # Examples:
# > net.cidr.canonicalize "192.168.1.100/24" #
# "192.168.1.0/24" # > net.cidr.canonicalize "192.168.1.100/24"
canonicalize = x: libNet.cidr.make (libNet.cidr.length x) (ip x); # "192.168.1.0/24"
# mergev4 :: [cidrv4 | ipv4] -> (cidrv4 | null) canonicalize = x: libNet.cidr.make (libNet.cidr.length x) (ip x);
# # mergev4 :: [cidrv4 | ipv4] -> (cidrv4 | null)
# Returns the smallest cidr network that includes all given networks. #
# If no cidr mask is given, /32 is assumed. # Returns the smallest cidr network that includes all given networks.
# # If no cidr mask is given, /32 is assumed.
# Examples: #
# # Examples:
# > net.cidr.mergev4 ["192.168.1.1/24" "192.168.6.1/32"] #
# "192.168.0.0/21" # > net.cidr.mergev4 ["192.168.1.1/24" "192.168.6.1/32"]
mergev4 = addrs_: let # "192.168.0.0/21"
# Append /32 if necessary mergev4 = addrs_: let
addrs = map (x: # Append /32 if necessary
if hasInfix "/" x addrs = map (x:
then x if hasInfix "/" x
else "${x}/32") then x
addrs_; else "${x}/32")
# The smallest occurring length is the first we need to start checking, since addrs_;
# any greater cidr length represents a smaller address range which # The smallest occurring length is the first we need to start checking, since
# wouldn't contain all of the original addresses. # any greater cidr length represents a smaller address range which
startLength = foldl' min 32 (map libNet.cidr.length addrs); # wouldn't contain all of the original addresses.
possibleLengths = reverseList (range 0 startLength); startLength = foldl' min 32 (map libNet.cidr.length addrs);
# The first ip address will be "expanded" in cidr length until it covers all other possibleLengths = reverseList (range 0 startLength);
# used addresses. # The first ip address will be "expanded" in cidr length until it covers all other
firstIp = ip (head addrs); # used addresses.
# Return the first (i.e. greatest length -> smallest prefix) cidr length firstIp = ip (head addrs);
# in the list that covers all used addresses # Return the first (i.e. greatest length -> smallest prefix) cidr length
bestLength = head (filter # in the list that covers all used addresses
# All given addresses must be contained by the generated address. bestLength = head (filter
(len: # All given addresses must be contained by the generated address.
all (x: (len:
libNet.cidr.contains all (x:
(ip x) libNet.cidr.contains
(libNet.cidr.make len firstIp)) (ip x)
addrs) (libNet.cidr.make len firstIp))
possibleLengths); addrs)
in possibleLengths);
assert assertMsg (!any (hasInfix ":") addrs) "mergev4 cannot operate on ipv6 addresses"; in
if addrs == [] assert assertMsg (!any (hasInfix ":") addrs) "mergev4 cannot operate on ipv6 addresses";
then null if addrs == []
else libNet.cidr.make bestLength firstIp; then null
# mergev6 :: [cidrv6 | ipv6] -> (cidrv6 | null) else libNet.cidr.make bestLength firstIp;
# # mergev6 :: [cidrv6 | ipv6] -> (cidrv6 | null)
# Returns the smallest cidr network that includes all given networks. #
# If no cidr mask is given, /128 is assumed. # Returns the smallest cidr network that includes all given networks.
# # If no cidr mask is given, /128 is assumed.
# Examples: #
# # Examples:
# > net.cidr.mergev6 ["fd00:dead:cafe::/64" "fd00:fd12:3456:7890::/56"] #
# "fd00:c000::/18" # > net.cidr.mergev6 ["fd00:dead:cafe::/64" "fd00:fd12:3456:7890::/56"]
mergev6 = addrs_: let # "fd00:c000::/18"
# Append /128 if necessary mergev6 = addrs_: let
addrs = map (x: # Append /128 if necessary
if hasInfix "/" x addrs = map (x:
then x if hasInfix "/" x
else "${x}/128") then x
addrs_; else "${x}/128")
# The smallest occurring length is the first we need to start checking, since addrs_;
# any greater cidr length represents a smaller address range which # The smallest occurring length is the first we need to start checking, since
# wouldn't contain all of the original addresses. # any greater cidr length represents a smaller address range which
startLength = foldl' min 128 (map libNet.cidr.length addrs); # wouldn't contain all of the original addresses.
possibleLengths = reverseList (range 0 startLength); startLength = foldl' min 128 (map libNet.cidr.length addrs);
# The first ip address will be "expanded" in cidr length until it covers all other possibleLengths = reverseList (range 0 startLength);
# used addresses. # The first ip address will be "expanded" in cidr length until it covers all other
firstIp = ip (head addrs); # used addresses.
# Return the first (i.e. greatest length -> smallest prefix) cidr length firstIp = ip (head addrs);
# in the list that covers all used addresses # Return the first (i.e. greatest length -> smallest prefix) cidr length
bestLength = head (filter # in the list that covers all used addresses
# All given addresses must be contained by the generated address. bestLength = head (filter
(len: # All given addresses must be contained by the generated address.
all (x: (len:
libNet.cidr.contains all (x:
(ip x) libNet.cidr.contains
(libNet.cidr.make len firstIp)) (ip x)
addrs) (libNet.cidr.make len firstIp))
possibleLengths); addrs)
in possibleLengths);
assert assertMsg (all (hasInfix ":") addrs) "mergev6 cannot operate on ipv4 addresses"; in
if addrs == [] assert assertMsg (all (hasInfix ":") addrs) "mergev6 cannot operate on ipv4 addresses";
then null if addrs == []
else libNet.cidr.make bestLength firstIp; then null
# merge :: [cidr] -> { cidrv4 = (cidrv4 | null); cidrv6 = (cidrv4 | null); } else libNet.cidr.make bestLength firstIp;
# # merge :: [cidr] -> { cidrv4 = (cidrv4 | null); cidrv6 = (cidrv4 | null); }
# Returns the smallest cidr network that includes all given networks, #
# but yields two separate result for all given ipv4 and ipv6 addresses. # Returns the smallest cidr network that includes all given networks,
# Equivalent to calling mergev4 and mergev6 on a partition individually. # but yields two separate result for all given ipv4 and ipv6 addresses.
merge = addrs: let # Equivalent to calling mergev4 and mergev6 on a partition individually.
v4_and_v6 = partition (hasInfix ":") addrs; merge = addrs: let
in { v4_and_v6 = partition (hasInfix ":") addrs;
cidrv4 = mergev4 v4_and_v6.wrong; in {
cidrv6 = mergev6 v4_and_v6.right; cidrv4 = mergev4 v4_and_v6.wrong;
cidrv6 = mergev6 v4_and_v6.right;
};
# assignIps :: cidr -> [int | ip] -> [string] -> [ip]
#
# Assigns a semi-stable ip address from the given cidr network to each hostname.
# The algorithm is based on hashing (abusing sha256) with linear probing.
# The order of hosts doesn't matter. No ip (or offset) from the reserved list
# will be assigned. The network address and broadcast address will always be reserved
# automatically.
#
# Examples:
#
# > net.cidr.assignIps "192.168.100.1/24" [] ["a" "b" "c"]
# { a = "192.168.100.202"; b = "192.168.100.74"; c = "192.168.100.226"; }
#
# > net.cidr.assignIps "192.168.100.1/24" [] ["a" "b" "c" "a-new-elem"]
# { a = "192.168.100.202"; a-new-elem = "192.168.100.88"; b = "192.168.100.74"; c = "192.168.100.226"; }
#
# > net.cidr.assignIps "192.168.100.1/24" [202 "192.168.100.74"] ["a" "b" "c"]
# { a = "192.168.100.203"; b = "192.168.100.75"; c = "192.168.100.226"; }
assignIps = net: reserved: hosts: let
cidrSize = libNet.cidr.size net;
capacity = libNet.cidr.capacity net;
# The base address of the network. Used to convert ip-based reservations to offsets
baseAddr = host 0 net;
# Reserve some values for the network, host and broadcast address.
# The network and broadcast address should never be used, and we
# want to reserve the host address for the host. We also convert
# any ips to offsets here.
init = unique (
[0 (capacity - 1)]
++ flip map reserved (x:
if builtins.typeOf x == "int"
then x
else -(libNet.ip.diff baseAddr x))
);
nHosts = builtins.length hosts;
nInit = builtins.length init;
# Pre-sort all hosts, to ensure ordering invariance
sortedHosts =
warnIf
((nInit + nHosts) > 0.3 * capacity)
"assignIps: hash stability may be degraded since utilization is >30%"
(builtins.sort builtins.lessThan hosts);
# Generates a hash (i.e. offset value) for a given hostname
hashElem = x:
builtins.bitAnd (capacity - 1)
(hexToDec (builtins.substring 0 16 (builtins.hashString "sha256" x)));
# Do linear probing. Returns the first unused value at or after the given value.
probe = avoid: value:
if elem value avoid
# TODO lib.mod
# Poor man's modulo, because nix has no modulo. Luckily we operate on a residue
# class of x modulo 2^n, so we can use bitAnd instead.
then probe avoid (builtins.bitAnd (capacity - 1) (value + 1))
else value;
# Hash a new element and avoid assigning any existing values.
assignOne = {
assigned,
used,
}: x: let
value = probe used (hashElem x);
in {
assigned =
assigned
// {
${x} = host value net;
};
used = [value] ++ used;
};
in
assert assertMsg (cidrSize >= 2 && cidrSize <= 62)
"assignIps: cidrSize=${toString cidrSize} is not in [2, 62].";
assert 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
(foldl' assignOne {
assigned = {};
used = init;
}
sortedHosts)
.assigned;
};
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 = hasInfix ":";
};
mac = {
# Adds offset to the given base address and ensures the result is in
# a locally administered range by replacing the second nibble with a 2.
addPrivate = base: offset: let
added = libNet.mac.add base offset;
pre = substring 0 1 added;
suf = substring 2 (-1) added;
in "${pre}2${suf}";
# assignMacs :: mac (base) -> int (size) -> [int | mac] (reserved) -> [string] (hosts) -> [mac]
#
# Assigns a semi-stable MAC address starting in [base, base + 2^size) to each hostname.
# The algorithm is based on hashing (abusing sha256) with linear probing.
# The order of hosts doesn't matter. No mac (or offset) from the reserved list
# will be assigned.
#
# Examples:
#
# > net.mac.assignMacs "11:22:33:00:00:00" 24 [] ["a" "b" "c"]
# { a = "11:22:33:1b:bd:ca"; b = "11:22:33:39:59:4a"; c = "11:22:33:50:7a:e2"; }
#
# > net.mac.assignMacs "11:22:33:00:00:00" 24 [] ["a" "b" "c" "a-new-elem"]
# { a = "11:22:33:1b:bd:ca"; a-new-elem = "11:22:33:d6:5d:58"; b = "11:22:33:39:59:4a"; c = "11:22:33:50:7a:e2"; }
#
# > net.mac.assignMacs "11:22:33:00:00:00" 24 ["11:22:33:1b:bd:ca"] ["a" "b" "c"]
# { a = "11:22:33:1b:bd:cb"; b = "11:22:33:39:59:4a"; c = "11:22:33:50:7a:e2"; }
assignMacs = base: size: reserved: hosts: let
capacity = pow 2 size;
baseAsInt = libNet.mac.diff base "00:00:00:00:00:00";
init = unique (
flip map reserved (x:
if builtins.typeOf x == "int"
then x
else libNet.mac.diff x base)
);
nHosts = builtins.length hosts;
nInit = builtins.length init;
# Pre-sort all hosts, to ensure ordering invariance
sortedHosts =
warnIf
((nInit + nHosts) > 0.3 * capacity)
"assignMacs: hash stability may be degraded since utilization is >30%"
(builtins.sort builtins.lessThan hosts);
# Generates a hash (i.e. offset value) for a given hostname
hashElem = x:
builtins.bitAnd (capacity - 1)
(hexToDec (substring 0 16 (builtins.hashString "sha256" x)));
# Do linear probing. Returns the first unused value at or after the given value.
probe = avoid: value:
if elem value avoid
# TODO lib.mod
# Poor man's modulo, because nix has no modulo. Luckily we operate on a residue
# class of x modulo 2^n, so we can use bitAnd instead.
then probe avoid (builtins.bitAnd (capacity - 1) (value + 1))
else value;
# Hash a new element and avoid assigning any existing values.
assignOne = {
assigned,
used,
}: x: let
value = probe used (hashElem x);
in {
assigned =
assigned
// {
${x} = libNet.mac.add value base;
};
used = [value] ++ used;
};
in
assert assertMsg (size >= 2 && size <= 62)
"assignMacs: size=${toString size} is not in [2, 62].";
assert assertMsg (builtins.bitAnd (capacity - 1) baseAsInt == 0)
"assignMacs: the size=${toString size} least significant bits of the base mac address must be 0.";
assert assertMsg (nHosts <= capacity - nInit)
"assignMacs: number of hosts (${toString nHosts}) must be <= capacity (${toString capacity}) - reserved (${toString nInit})";
# Assign an ip in the subnet to each element, in order
(foldl' assignOne {
assigned = {};
used = init;
}
sortedHosts)
.assigned;
}; };
# assignIps :: cidr -> [int | ip] -> [string] -> [ip]
#
# Assigns a semi-stable ip address from the given cidr network to each hostname.
# The algorithm is based on hashing (abusing sha256) with linear probing.
# The order of hosts doesn't matter. No ip (or offset) from the reserved list
# will be assigned. The network address and broadcast address will always be reserved
# automatically.
#
# Examples:
#
# > net.cidr.assignIps "192.168.100.1/24" [] ["a" "b" "c"]
# { a = "192.168.100.202"; b = "192.168.100.74"; c = "192.168.100.226"; }
#
# > net.cidr.assignIps "192.168.100.1/24" [] ["a" "b" "c" "a-new-elem"]
# { a = "192.168.100.202"; a-new-elem = "192.168.100.88"; b = "192.168.100.74"; c = "192.168.100.226"; }
#
# > net.cidr.assignIps "192.168.100.1/24" [202 "192.168.100.74"] ["a" "b" "c"]
# { a = "192.168.100.203"; b = "192.168.100.75"; c = "192.168.100.226"; }
assignIps = net: reserved: hosts: let
cidrSize = libNet.cidr.size net;
capacity = libNet.cidr.capacity net;
# The base address of the network. Used to convert ip-based reservations to offsets
baseAddr = host 0 net;
# Reserve some values for the network, host and broadcast address.
# The network and broadcast address should never be used, and we
# want to reserve the host address for the host. We also convert
# any ips to offsets here.
init = unique (
[0 (capacity - 1)]
++ flip map reserved (x:
if builtins.typeOf x == "int"
then x
else -(libNet.ip.diff baseAddr x))
);
nHosts = builtins.length hosts;
nInit = builtins.length init;
# Pre-sort all hosts, to ensure ordering invariance
sortedHosts =
warnIf
((nInit + nHosts) > 0.3 * capacity)
"assignIps: hash stability may be degraded since utilization is >30%"
(builtins.sort builtins.lessThan hosts);
# Generates a hash (i.e. offset value) for a given hostname
hashElem = x:
builtins.bitAnd (capacity - 1)
(hexToDec (builtins.substring 0 16 (builtins.hashString "sha256" x)));
# Do linear probing. Returns the first unused value at or after the given value.
probe = avoid: value:
if elem value avoid
# TODO lib.mod
# Poor man's modulo, because nix has no modulo. Luckily we operate on a residue
# class of x modulo 2^n, so we can use bitAnd instead.
then probe avoid (builtins.bitAnd (capacity - 1) (value + 1))
else value;
# Hash a new element and avoid assigning any existing values.
assignOne = {
assigned,
used,
}: x: let
value = probe used (hashElem x);
in {
assigned =
assigned
// {
${x} = host value net;
};
used = [value] ++ used;
};
in
assert assertMsg (cidrSize >= 2 && cidrSize <= 62)
"assignIps: cidrSize=${toString cidrSize} is not in [2, 62].";
assert 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
(foldl' assignOne {
assigned = {};
used = init;
}
sortedHosts)
.assigned;
}; };
ip = rec { types.net = libNet.types;
# 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 = hasInfix ":";
};
mac = {
# Adds offset to the given base address and ensures the result is in
# a locally administered range by replacing the second nibble with a 2.
addPrivate = base: offset: let
added = libNet.mac.add base offset;
pre = substring 0 1 added;
suf = substring 2 (-1) added;
in "${pre}2${suf}";
# assignMacs :: mac (base) -> int (size) -> [int | mac] (reserved) -> [string] (hosts) -> [mac]
#
# Assigns a semi-stable MAC address starting in [base, base + 2^size) to each hostname.
# The algorithm is based on hashing (abusing sha256) with linear probing.
# The order of hosts doesn't matter. No mac (or offset) from the reserved list
# will be assigned.
#
# Examples:
#
# > net.mac.assignMacs "11:22:33:00:00:00" 24 [] ["a" "b" "c"]
# { a = "11:22:33:1b:bd:ca"; b = "11:22:33:39:59:4a"; c = "11:22:33:50:7a:e2"; }
#
# > net.mac.assignMacs "11:22:33:00:00:00" 24 [] ["a" "b" "c" "a-new-elem"]
# { a = "11:22:33:1b:bd:ca"; a-new-elem = "11:22:33:d6:5d:58"; b = "11:22:33:39:59:4a"; c = "11:22:33:50:7a:e2"; }
#
# > net.mac.assignMacs "11:22:33:00:00:00" 24 ["11:22:33:1b:bd:ca"] ["a" "b" "c"]
# { a = "11:22:33:1b:bd:cb"; b = "11:22:33:39:59:4a"; c = "11:22:33:50:7a:e2"; }
assignMacs = base: size: reserved: hosts: let
capacity = pow 2 size;
baseAsInt = libNet.mac.diff base "00:00:00:00:00:00";
init = unique (
flip map reserved (x:
if builtins.typeOf x == "int"
then x
else libNet.mac.diff x base)
);
nHosts = builtins.length hosts;
nInit = builtins.length init;
# Pre-sort all hosts, to ensure ordering invariance
sortedHosts =
warnIf
((nInit + nHosts) > 0.3 * capacity)
"assignMacs: hash stability may be degraded since utilization is >30%"
(builtins.sort builtins.lessThan hosts);
# Generates a hash (i.e. offset value) for a given hostname
hashElem = x:
builtins.bitAnd (capacity - 1)
(hexToDec (builtins.substring 0 16 (builtins.hashString "sha256" x)));
# Do linear probing. Returns the first unused value at or after the given value.
probe = avoid: value:
if elem value avoid
# TODO lib.mod
# Poor man's modulo, because nix has no modulo. Luckily we operate on a residue
# class of x modulo 2^n, so we can use bitAnd instead.
then probe avoid (builtins.bitAnd (capacity - 1) (value + 1))
else value;
# Hash a new element and avoid assigning any existing values.
assignOne = {
assigned,
used,
}: x: let
value = probe used (hashElem x);
in {
assigned =
assigned
// {
${x} = libNet.mac.add value base;
};
used = [value] ++ used;
};
in
assert assertMsg (size >= 2 && size <= 62)
"assignMacs: size=${toString size} is not in [2, 62].";
assert assertMsg (builtins.bitAnd (capacity - 1) baseAsInt == 0)
"assignMacs: the size=${toString size} least significant bits of the base mac address must be 0.";
assert assertMsg (nHosts <= capacity - nInit)
"assignMacs: number of hosts (${toString nHosts}) must be <= capacity (${toString capacity}) - reserved (${toString nInit})";
# Assign an ip in the subnet to each element, in order
(foldl' assignOne {
assigned = {};
used = init;
}
sortedHosts)
.assigned;
};
}

View file

@ -1,48 +1,15 @@
inputs: let inputs: self: super: let
inherit inherit
(inputs.nixpkgs.lib) (inputs.nixpkgs.lib)
all all
any
assertMsg assertMsg
attrNames
attrValues
concatLists
concatMap
concatMapStrings
concatStringsSep
elem
escapeShellArg
filter
flatten
flip
foldAttrs
foldl'
genAttrs
genList
hasInfix
head
isAttrs isAttrs
mapAttrs'
mergeAttrs
min
mkMerge
mkOptionType mkOptionType
nameValuePair
optionalAttrs
partition
range
recursiveUpdate recursiveUpdate
removeSuffix
reverseList
showOption showOption
splitString
stringToCharacters
substring
types types
unique
warnIf
; ;
in rec {
# Checks whether the value is a lazy value without causing # Checks whether the value is a lazy value without causing
# it's value to be evaluated # it's value to be evaluated
isLazyValue = x: isAttrs x && x ? _lazyValue; isLazyValue = x: isAttrs x && x ? _lazyValue;
@ -71,4 +38,15 @@ in rec {
# Represents a value or lazy value of the given type that will # Represents a value or lazy value of the given type that will
# automatically be coerced to the given type when merged. # automatically be coerced to the given type when merged.
lazyOf = type: types.coercedTo (lazyValueOf type) (x: x._lazyValue) type; lazyOf = type: types.coercedTo (lazyValueOf type) (x: x._lazyValue) type;
in {
lib = recursiveUpdate super.lib {
types = {
inherit
isLazyValue
lazyValue
lazyValueOf
lazyOf
;
};
};
} }

View file

@ -1,218 +1,225 @@
inputs: wgName: let inputs: self: super: let
inherit inherit
(inputs.nixpkgs.lib) (inputs.nixpkgs.lib)
all
any
assertMsg assertMsg
attrNames attrNames
attrValues attrValues
concatLists concatLists
concatMap concatMap
concatMapStrings
concatStringsSep concatStringsSep
elem
escapeShellArg escapeShellArg
filter filter
flatten flatten
flip flip
foldAttrs
foldl'
genAttrs genAttrs
genList
hasInfix
head
isAttrs
mapAttrs' mapAttrs'
mergeAttrs
min
mkMerge
mkOptionType
nameValuePair nameValuePair
optionalAttrs
partition partition
range
recursiveUpdate
removeSuffix removeSuffix
reverseList
showOption
splitString
stringToCharacters
substring
types
unique
warnIf
;
net = import ./net.nix inputs;
misc = import ./misc.nix inputs;
inherit
(import ./types.nix inputs)
isLazyValue
; ;
inherit inherit
(misc) (self.lib)
net
concatAttrs concatAttrs
types
; ;
inherit inherit
(misc.secrets) (self.lib.secrets)
rageDecryptArgs rageDecryptArgs
; ;
inherit (inputs.self) nodes; inherit (inputs.self) nodes;
in rec { in {
# Returns the given node's wireguard configuration of this network lib =
wgCfgOf = node: nodes.${node}.config.meta.wireguard.${wgName}; super.lib
// {
wireguard = wgName: let
# Returns the given node's wireguard configuration of this network
wgCfgOf = node: nodes.${node}.config.meta.wireguard.${wgName};
sortedPeers = peerA: peerB: sortedPeers = peerA: peerB:
if peerA < peerB if peerA < peerB
then { then {
peer1 = peerA; peer1 = peerA;
peer2 = peerB; peer2 = peerB;
} }
else { else {
peer1 = peerB; peer1 = peerB;
peer2 = peerA; peer2 = peerA;
};
peerPublicKeyFile = peerName: "/secrets/wireguard/${wgName}/keys/${peerName}.pub";
peerPublicKeyPath = peerName: inputs.self.outPath + peerPublicKeyFile peerName;
peerPrivateKeyFile = peerName: "/secrets/wireguard/${wgName}/keys/${peerName}.age";
peerPrivateKeyPath = peerName: inputs.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: inputs.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.meta.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);
externalPeerName = p: "external-${p}";
# Only peers that are defined as externalPeers on the given node.
# Prepends "external-" to their name.
externalPeersForNode = node:
mapAttrs' (p: nameValuePair (externalPeerName p)) (wgCfgOf node).server.externalPeers;
# All peers that are defined as externalPeers on any node.
# Prepends "external-" to their name.
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) participatingNodes;
# A list of all occurring addresses.
usedAddresses =
concatMap (n: (wgCfgOf n).addresses) participatingNodes
++ flatten (concatMap (n: attrValues (wgCfgOf n).server.externalPeers) 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.meta.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) 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
relevantNetworkAddr =
if net.ip.isv6 addr
then networkAddresses.cidrv6
else networkAddresses.cidrv4;
in "${net.cidr.ip addr}/${toString (net.cidr.length relevantNetworkAddr)}";
# Creates a script that when executed outputs a wg-quick compatible configuration
# file for use with external peers. This is a script so we can access secrets without
# storing them in the nix-store.
wgQuickConfigScript = system: serverNode: extPeer: let
pkgs = inputs.self.pkgs.${system};
snCfg = wgCfgOf serverNode;
peerName = externalPeerName extPeer;
addresses = map toNetworkAddr snCfg.server.externalPeers.${extPeer};
in
pkgs.writeShellScript "create-wg-conf-${wgName}-${serverNode}-${extPeer}" ''
privKey=$(${pkgs.rage}/bin/rage -d ${rageDecryptArgs} ${escapeShellArg (peerPrivateKeyPath peerName)}) \
|| { echo "error: Failed to decrypt!" >&2; exit 1; }
serverPsk=$(${pkgs.rage}/bin/rage -d ${rageDecryptArgs} ${escapeShellArg (peerPresharedKeyPath serverNode peerName)}) \
|| { echo "error: Failed to decrypt!" >&2; exit 1; }
cat <<EOF
[Interface]
Address = ${concatStringsSep ", " addresses}
PrivateKey = $privKey
[Peer]
PublicKey = ${removeSuffix "\n" (builtins.readFile (peerPublicKeyPath serverNode))}
PresharedKey = $serverPsk
AllowedIPs = ${concatStringsSep ", " networkCidrs}
Endpoint = ${snCfg.server.host}:${toString snCfg.server.port}
PersistentKeepalive = 25
EOF
'';
in {
inherit
allExternalPeers
allPeers
assignedIpv4Addresses
assignedIpv6Addresses
explicitlyUsedAddresses
externalPeerName
externalPeerNamesRaw
externalPeersForNode
networkAddresses
networkCidrs
nodePeers
participatingClientNodes
participatingNodes
participatingServerNodes
peerPresharedKeyFile
peerPresharedKeyPath
peerPresharedKeySecret
peerPrivateKeyFile
peerPrivateKeyPath
peerPrivateKeySecret
peerPublicKeyFile
peerPublicKeyPath
sortedPeers
spannedReservedNetwork
toNetworkAddr
usedAddresses
wgCfgOf
wgQuickConfigScript
;
};
}; };
peerPublicKeyFile = peerName: "/secrets/wireguard/${wgName}/keys/${peerName}.pub";
peerPublicKeyPath = peerName: inputs.self.outPath + peerPublicKeyFile peerName;
peerPrivateKeyFile = peerName: "/secrets/wireguard/${wgName}/keys/${peerName}.age";
peerPrivateKeyPath = peerName: inputs.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: inputs.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.meta.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);
externalPeerName = p: "external-${p}";
# Only peers that are defined as externalPeers on the given node.
# Prepends "external-" to their name.
externalPeersForNode = node:
mapAttrs' (p: nameValuePair (externalPeerName p)) (wgCfgOf node).server.externalPeers;
# All peers that are defined as externalPeers on any node.
# Prepends "external-" to their name.
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) participatingNodes;
# A list of all occurring addresses.
usedAddresses =
concatMap (n: (wgCfgOf n).addresses) participatingNodes
++ flatten (concatMap (n: attrValues (wgCfgOf n).server.externalPeers) participatingNodes);
# A list of all occurring addresses, but only includes addresses that
# are not assigned automatically.
explicitlyUsedAddresses =
flip concatMap participatingNodes
(n:
filter (x: !isLazyValue x)
(concatLists
(nodes.${n}.options.meta.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) 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
relevantNetworkAddr =
if net.ip.isv6 addr
then networkAddresses.cidrv6
else networkAddresses.cidrv4;
in "${net.cidr.ip addr}/${toString (net.cidr.length relevantNetworkAddr)}";
# Creates a script that when executed outputs a wg-quick compatible configuration
# file for use with external peers. This is a script so we can access secrets without
# storing them in the nix-store.
wgQuickConfigScript = system: serverNode: extPeer: let
pkgs = inputs.self.pkgs.${system};
snCfg = wgCfgOf serverNode;
peerName = externalPeerName extPeer;
addresses = map toNetworkAddr snCfg.server.externalPeers.${extPeer};
in
pkgs.writeShellScript "create-wg-conf-${wgName}-${serverNode}-${extPeer}" ''
privKey=$(${pkgs.rage}/bin/rage -d ${rageDecryptArgs} ${escapeShellArg (peerPrivateKeyPath peerName)}) \
|| { echo "error: Failed to decrypt!" >&2; exit 1; }
serverPsk=$(${pkgs.rage}/bin/rage -d ${rageDecryptArgs} ${escapeShellArg (peerPresharedKeyPath serverNode peerName)}) \
|| { echo "error: Failed to decrypt!" >&2; exit 1; }
cat <<EOF
[Interface]
Address = ${concatStringsSep ", " addresses}
PrivateKey = $privKey
[Peer]
PublicKey = ${removeSuffix "\n" (builtins.readFile (peerPublicKeyPath serverNode))}
PresharedKey = $serverPsk
AllowedIPs = ${concatStringsSep ", " networkCidrs}
Endpoint = ${snCfg.server.host}:${toString snCfg.server.port}
PersistentKeepalive = 25
EOF
'';
} }

View file

@ -12,32 +12,27 @@
attrNames attrNames
attrValues attrValues
concatStringsSep concatStringsSep
disko
escapeShellArg escapeShellArg
filterAttrs filterAttrs
foldl' foldl'
makeBinPath makeBinPath
mapAttrsToList mapAttrsToList
mdDoc mdDoc
mergeToplevelConfigs
mkDefault mkDefault
mkEnableOption mkEnableOption
mkForce mkForce
mkIf mkIf
mkMerge mkMerge
mkOption mkOption
net
optional optional
optionalAttrs optionalAttrs
recursiveUpdate recursiveUpdate
types types
; ;
inherit
(import ../../lib/misc.nix inputs)
mergeToplevelConfigs
;
net = import ../../lib/net.nix inputs;
disko = import ../../lib/disko.nix inputs;
parentConfig = config; parentConfig = config;
cfg = config.meta.microvms; cfg = config.meta.microvms;
nodeName = config.node.name; nodeName = config.node.name;
@ -236,7 +231,7 @@ in {
networking = { networking = {
baseMac = mkOption { baseMac = mkOption {
type = net.types.mac; type = types.net.mac;
description = mdDoc '' description = mdDoc ''
This MAC address will be used as a base address to derive all MicroVM MAC addresses from. This MAC address will be used as a base address to derive all MicroVM MAC addresses from.
A good practise is to use the physical address of the macvtap interface. A good practise is to use the physical address of the macvtap interface.
@ -250,13 +245,13 @@ in {
wireguard = { wireguard = {
cidrv4 = mkOption { cidrv4 = mkOption {
type = net.types.cidrv4; type = types.net.cidrv4;
description = mdDoc "The ipv4 network address range to use for internal vm traffic."; description = mdDoc "The ipv4 network address range to use for internal vm traffic.";
default = "172.31.0.0/24"; default = "172.31.0.0/24";
}; };
cidrv6 = mkOption { cidrv6 = mkOption {
type = net.types.cidrv6; type = types.net.cidrv6;
description = mdDoc "The ipv6 network address range to use for internal vm traffic."; description = mdDoc "The ipv6 network address range to use for internal vm traffic.";
default = "fd00:172:31::/120"; default = "fd00:172:31::/120";
}; };

View file

@ -1,6 +1,5 @@
{ {
config, config,
inputs,
lib, lib,
nodes, nodes,
pkgs, pkgs,
@ -12,10 +11,12 @@
assertMsg assertMsg
attrNames attrNames
attrValues attrValues
concatAttrs
concatLists concatLists
concatMap concatMap
concatMapStrings concatMapStrings
concatStringsSep concatStringsSep
duplicates
escapeShellArg escapeShellArg
filter filter
filterAttrs filterAttrs
@ -27,41 +28,28 @@
mapAttrsToList mapAttrsToList
mdDoc mdDoc
mergeAttrs mergeAttrs
mergeToplevelConfigs
mkForce mkForce
mkIf mkIf
mkMerge mkMerge
mkOption mkOption
nameValuePair nameValuePair
net
optionalAttrs optionalAttrs
optionals optionals
partition partition
removeSuffix removeSuffix
stringLength stringLength
types types
wireguard
; ;
inherit
(import ../../lib/misc.nix inputs)
concatAttrs
duplicates
mergeToplevelConfigs
;
inherit
(import ../../lib/types.nix inputs)
lazyOf
lazyValue
;
net = import ../../lib/net.nix inputs;
wgLibFor = import ../../lib/wireguard.nix inputs;
cfg = config.meta.wireguard; cfg = config.meta.wireguard;
nodeName = config.node.name; nodeName = config.node.name;
configForNetwork = wgName: wgCfg: let configForNetwork = wgName: wgCfg: let
inherit inherit
(wgLibFor wgName) (wireguard wgName)
externalPeerName externalPeerName
externalPeerNamesRaw externalPeerNamesRaw
networkCidrs networkCidrs
@ -307,7 +295,7 @@ in {
}; };
externalPeers = mkOption { externalPeers = mkOption {
type = types.attrsOf (types.listOf (net.types.ip-in config.addresses)); type = types.attrsOf (types.listOf (types.net.ip-in config.addresses));
default = {}; default = {};
example = {my-android-phone = ["10.0.0.97"];}; example = {my-android-phone = ["10.0.0.97"];};
description = mdDoc '' description = mdDoc ''
@ -321,7 +309,7 @@ in {
}; };
reservedAddresses = mkOption { reservedAddresses = mkOption {
type = types.listOf net.types.cidr; type = types.listOf types.net.cidr;
default = []; default = [];
example = ["10.0.0.1/24" "fd00:cafe::/64"]; example = ["10.0.0.1/24" "fd00:cafe::/64"];
description = mdDoc '' description = mdDoc ''
@ -377,8 +365,8 @@ in {
}; };
ipv4 = mkOption { ipv4 = mkOption {
type = lazyOf net.types.ipv4; type = types.lazyOf types.net.ipv4;
default = lazyValue (wgLibFor name).assignedIpv4Addresses.${nodeName}; default = types.lazyValue (wireguard name).assignedIpv4Addresses.${nodeName};
description = mdDoc '' description = mdDoc ''
The ipv4 address for this machine. If you do not set this explicitly, 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 a semi-stable ipv4 address will be derived automatically based on the
@ -389,8 +377,8 @@ in {
}; };
ipv6 = mkOption { ipv6 = mkOption {
type = lazyOf net.types.ipv6; type = types.lazyOf types.net.ipv6;
default = lazyValue (wgLibFor name).assignedIpv6Addresses.${nodeName}; default = types.lazyValue (wireguard name).assignedIpv6Addresses.${nodeName};
description = mdDoc '' description = mdDoc ''
The ipv6 address for this machine. If you do not set this explicitly, 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 a semi-stable ipv6 address will be derived automatically based on the
@ -401,7 +389,7 @@ in {
}; };
addresses = mkOption { addresses = mkOption {
type = types.listOf (lazyOf net.types.ip); type = types.listOf (types.lazyOf types.net.ip);
default = [ default = [
(head options.ipv4.definitions) (head options.ipv4.definitions)
(head options.ipv6.definitions) (head options.ipv6.definitions)
@ -420,7 +408,7 @@ in {
# to use the network without routing additional stuff. # to use the network without routing additional stuff.
# - allow specifying the route metric. # - allow specifying the route metric.
routedAddresses = mkOption { routedAddresses = mkOption {
type = types.listOf net.types.cidr; type = types.listOf types.net.cidr;
default = []; default = [];
example = ["0.0.0.0/0"]; example = ["0.0.0.0/0"];
description = mdDoc '' description = mdDoc ''

View file

@ -1,7 +1,6 @@
# Provides an option to easily rename interfaces by their mac addresses. # Provides an option to easily rename interfaces by their mac addresses.
{ {
config, config,
inputs,
lib, lib,
pkgs, pkgs,
... ...
@ -10,17 +9,13 @@
(lib) (lib)
attrValues attrValues
concatStringsSep concatStringsSep
duplicates
mapAttrsToList mapAttrsToList
mkIf mkIf
mkOption mkOption
types types
; ;
inherit
(import ../../lib/misc.nix inputs)
duplicates
;
cfg = config.networking.renameInterfacesByMac; cfg = config.networking.renameInterfacesByMac;
interfaceNamesUdevRules = pkgs.writeTextFile { interfaceNamesUdevRules = pkgs.writeTextFile {

View file

@ -22,7 +22,7 @@
inherit system; inherit system;
pkgs = self.pkgs.${system}; pkgs = self.pkgs.${system};
specialArgs = { specialArgs = {
inherit (nixpkgs) lib; inherit (self.pkgs.${system}) lib;
inherit (self) nodes; inherit (self) nodes;
inherit inputs; inherit inputs;
}; };

View file

@ -1,6 +1,6 @@
final: prev: let self: super: let
inherit inherit
(final.lib) (self.lib)
escapeShellArg escapeShellArg
concatMapStrings concatMapStrings
flatten flatten
@ -21,9 +21,9 @@ final: prev: let
version, version,
}: "go get ${escapeShellArg name}@${escapeShellArg version}\n"); }: "go get ${escapeShellArg name}@${escapeShellArg version}\n");
in in
prev.caddy.override { super.caddy.override {
buildGoModule = args: buildGoModule = args:
prev.buildGoModule (args super.buildGoModule (args
// { // {
inherit vendorHash; inherit vendorHash;
passthru.plugins = plugins; passthru.plugins = plugins;
@ -45,5 +45,5 @@ in {
# ]; # ];
# vendorHash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; # vendorHash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
# } # }
caddy = prev.caddy.overrideAttrs (_: {passthru.withPackages = make-custom-caddy;}); caddy = super.caddy.overrideAttrs (_: {passthru.withPackages = make-custom-caddy;});
} }

View file

@ -1,5 +1,5 @@
final: prev: { self: super: {
oauth2-proxy = prev.oauth2-proxy.overrideAttrs (_: { oauth2-proxy = super.oauth2-proxy.overrideAttrs (_: {
patches = [./0001-scopes-as-groups.patch]; patches = [./0001-scopes-as-groups.patch];
}); });
} }