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:
- 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
- Single-Sign-On for all services using oauth2 via kanidm
- 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)
- Support for repository-wide secrets at evaluation time (hides PII like MACs)

View file

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

View file

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

View file

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

View file

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

View file

@ -1,15 +1,9 @@
{
config,
inputs,
lib,
utils,
...
}: let
inherit
(import ../../lib/net.nix inputs)
cidr
;
lanCidrv4 = "192.168.100.0/24";
lanCidrv6 = "fd10::/64";
in {
@ -60,8 +54,8 @@ in {
};
"20-lan-self" = {
address = [
(cidr.hostCidr 1 lanCidrv4)
(cidr.hostCidr 1 lanCidrv6)
(lib.net.cidr.hostCidr 1 lanCidrv4)
(lib.net.cidr.hostCidr 1 lanCidrv6)
];
matchConfig.Name = "lan-self";
networkConfig = {
@ -84,7 +78,7 @@ in {
ipv6SendRAConfig = {
EmitDNS = true;
# 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"];
};
linkConfig.RequiredForOnline = "routable";
@ -160,12 +154,12 @@ in {
interface = "lan-self";
subnet = lanCidrv4;
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 = [
{
name = "routers";
data = cidr.host 1 lanCidrv4;
data = lib.net.cidr.host 1 lanCidrv4;
}
];
}

View file

@ -1,14 +1,8 @@
{
config,
inputs,
lib,
...
}: let
inherit
(import ../../lib/net.nix inputs)
cidr
;
iotCidrv4 = "10.90.0.0/24";
iotCidrv6 = "fd00:90::/64";
in {
@ -31,8 +25,8 @@ in {
};
"10-wlan1" = {
address = [
(cidr.hostCidr 1 iotCidrv4)
(cidr.hostCidr 1 iotCidrv6)
(lib.net.cidr.hostCidr 1 iotCidrv4)
(lib.net.cidr.hostCidr 1 iotCidrv6)
];
matchConfig.MACAddress = config.repo.secrets.local.networking.interfaces.wlan1.mac;
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: {
gpt = {
partGrub = name: start: end: {
inherit name start end;
part-type = "primary";
flags = ["bios_grub"];
};
partEfi = name: start: end: {
inherit name start end;
fs-type = "fat32";
bootable = true;
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
};
};
partSwap = name: start: end: {
inherit name start end;
fs-type = "linux-swap";
content = {
type = "swap";
randomEncryption = true;
};
};
partLuksZfs = name: start: end: {
inherit start end;
name = "enc-${name}";
content = {
type = "luks";
name = "enc-${name}";
extraOpenArgs = ["--allow-discards"];
content = {
type = "zfs";
pool = name;
inputs: self: super: {
lib =
super.lib
// {
disko = {
gpt = {
partGrub = name: start: end: {
inherit name start end;
part-type = "primary";
flags = ["bios_grub"];
};
partEfi = name: start: end: {
inherit name start end;
fs-type = "fat32";
bootable = true;
content = {
type = "filesystem";
format = "vfat";
mountpoint = "/boot";
};
};
partSwap = name: start: end: {
inherit name start end;
fs-type = "linux-swap";
content = {
type = "swap";
randomEncryption = true;
};
};
partLuksZfs = name: start: end: {
inherit start end;
name = "enc-${name}";
content = {
type = "luks";
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
(inputs.nixpkgs.lib)
all
any
assertMsg
attrNames
attrValues
concatLists
concatMap
(super.lib)
concatMapStrings
concatStringsSep
elem
escapeShellArg
filter
flatten
flip
foldAttrs
foldl'
genAttrs
genList
hasInfix
head
isAttrs
mapAttrs'
mergeAttrs
min
mkMerge
mkOptionType
nameValuePair
optionalAttrs
partition
range
recursiveUpdate
removeSuffix
reverseList
showOption
splitString
stringToCharacters
substring
types
unique
warnIf
;
in rec {
# Counts how often each element occurrs in xs
countOccurrences = let
addOrUpdate = acc: x:
acc // {${x} = (acc.${x} or 0) + 1;};
in
foldl' addOrUpdate {};
# Counts how often each element occurrs in xs.
# Elements must be strings.
countOccurrences =
foldl'
(acc: x: acc // {${x} = (acc.${x} or 0) + 1;})
{};
# Returns all elements in xs that occur at least twice
duplicates = xs: let
@ -70,50 +41,67 @@ in rec {
# Calculates base^exp, but careful, this overflows for results > 2^62
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),
# after that the sign bit will overflow.
hexToDec = v: let
literalValues = {
"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;
};
in
foldl' (acc: x: acc * 16 + literalValues.${x}) 0 (stringToCharacters v);
hexToDec = v: foldl' (acc: x: acc * 16 + hexLiteralValues.${x}) 0 (stringToCharacters v);
in {
lib =
super.lib
// {
inherit
concatAttrs
countOccurrences
duplicates
hexToDec
isAbsolutePath
mergeToplevelConfigs
pow
;
secrets = let
rageMasterIdentityArgs = concatMapStrings (x: "-i ${escapeShellArg x} ") inputs.self.secretsConfig.masterIdentities;
rageExtraEncryptionPubkeys =
concatMapStrings (
x:
if isAbsolutePath x
then "-R ${escapeShellArg x} "
else "-r ${escapeShellArg x} "
)
inputs.self.secretsConfig.extraEncryptionPubkeys;
in {
# TODO replace these by lib.agenix-rekey
# The arguments required to de-/encrypt a secret in this repository
rageDecryptArgs = "${rageMasterIdentityArgs}";
rageEncryptArgs = "${rageMasterIdentityArgs} ${rageExtraEncryptionPubkeys}";
};
# TODO separate this or get rid of it
# TODO separate this or get rid of it
# TODO separate this or get rid of it
# TODO separate this or get rid of it
secrets = let
rageMasterIdentityArgs = concatMapStrings (x: "-i ${escapeShellArg x} ") inputs.self.secretsConfig.masterIdentities;
rageExtraEncryptionPubkeys =
concatMapStrings (
x:
if isAbsolutePath x
then "-R ${escapeShellArg x} "
else "-r ${escapeShellArg x} "
)
inputs.self.secretsConfig.extraEncryptionPubkeys;
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
(inputs.nixpkgs.lib)
all
any
assertMsg
attrNames
attrValues
concatLists
concatMap
concatMapStrings
concatStringsSep
elem
escapeShellArg
filter
flatten
flip
foldAttrs
foldl'
genAttrs
genList
hasInfix
head
isAttrs
mapAttrs'
mergeAttrs
min
mkMerge
mkOptionType
nameValuePair
optionalAttrs
partition
range
recursiveUpdate
removeSuffix
reverseList
showOption
splitString
stringToCharacters
substring
types
unique
warnIf
;
inherit
(import ./misc.nix inputs)
(self.lib)
hexToDec
pow
;
@ -58,321 +36,325 @@ inputs: let
})
.lib
.net;
in
recursiveUpdate libNet {
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.
#
# Examples:
#
# > net.cidr.host 255 "192.168.1.0/24"
# "192.168.1.255"
# > net.cidr.host (256) "192.168.1.0/24"
# <fails with an error message>
# > net.cidr.host (-1) "192.168.1.0/24"
# "192.168.1.255"
# > net.cidr.host (-256) "192.168.1.0/24"
# "192.168.1.0"
# > net.cidr.host (-257) "192.168.1.0/24"
# <fails with an error message>
host = i: n: let
cap = libNet.cidr.capacity n;
in
assert assertMsg (i >= (-cap) && i < cap) "The host ${toString i} lies outside of ${n}";
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.
#
# Examples:
#
# > net.cidr.hostCidr 2 "192.168.1.0/24"
# "192.168.1.2/24"
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.
#
# Examples:
#
# > net.cidr.ip "192.168.1.100/24"
# "192.168.1.100"
# > net.cidr.ip "192.168.1.100"
# "192.168.1.100"
ip = x: head (splitString "/" x);
# canonicalize :: cidr -> cidr
#
# Replaces the ip of the cidr with the canonical network address
# (first contained address in range)
#
# Examples:
#
# > net.cidr.canonicalize "192.168.1.100/24"
# "192.168.1.0/24"
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.
#
# Examples:
#
# > net.cidr.mergev4 ["192.168.1.1/24" "192.168.6.1/32"]
# "192.168.0.0/21"
mergev4 = addrs_: let
# Append /32 if necessary
addrs = map (x:
if hasInfix "/" x
then x
else "${x}/32")
addrs_;
# The smallest occurring length is the first we need to start checking, since
# any greater cidr length represents a smaller address range which
# wouldn't contain all of the original addresses.
startLength = foldl' min 32 (map libNet.cidr.length addrs);
possibleLengths = reverseList (range 0 startLength);
# The first ip address will be "expanded" in cidr length until it covers all other
# used addresses.
firstIp = ip (head addrs);
# Return the first (i.e. greatest length -> smallest prefix) cidr length
# in the list that covers all used addresses
bestLength = head (filter
# All given addresses must be contained by the generated address.
(len:
all (x:
libNet.cidr.contains
(ip x)
(libNet.cidr.make len firstIp))
addrs)
possibleLengths);
in
assert assertMsg (!any (hasInfix ":") addrs) "mergev4 cannot operate on ipv6 addresses";
if addrs == []
then 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.
#
# Examples:
#
# > net.cidr.mergev6 ["fd00:dead:cafe::/64" "fd00:fd12:3456:7890::/56"]
# "fd00:c000::/18"
mergev6 = addrs_: let
# Append /128 if necessary
addrs = map (x:
if hasInfix "/" x
then x
else "${x}/128")
addrs_;
# The smallest occurring length is the first we need to start checking, since
# any greater cidr length represents a smaller address range which
# wouldn't contain all of the original addresses.
startLength = foldl' min 128 (map libNet.cidr.length addrs);
possibleLengths = reverseList (range 0 startLength);
# The first ip address will be "expanded" in cidr length until it covers all other
# used addresses.
firstIp = ip (head addrs);
# Return the first (i.e. greatest length -> smallest prefix) cidr length
# in the list that covers all used addresses
bestLength = head (filter
# All given addresses must be contained by the generated address.
(len:
all (x:
libNet.cidr.contains
(ip x)
(libNet.cidr.make len firstIp))
addrs)
possibleLengths);
in
assert assertMsg (all (hasInfix ":") addrs) "mergev6 cannot operate on ipv4 addresses";
if addrs == []
then 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.
# Equivalent to calling mergev4 and mergev6 on a partition individually.
merge = addrs: let
v4_and_v6 = partition (hasInfix ":") addrs;
in {
cidrv4 = mergev4 v4_and_v6.wrong;
cidrv6 = mergev6 v4_and_v6.right;
in {
lib = recursiveUpdate super.lib {
net = recursiveUpdate (removeAttrs libNet ["types"]) {
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.
#
# Examples:
#
# > net.cidr.host 255 "192.168.1.0/24"
# "192.168.1.255"
# > net.cidr.host (256) "192.168.1.0/24"
# <fails with an error message>
# > net.cidr.host (-1) "192.168.1.0/24"
# "192.168.1.255"
# > net.cidr.host (-256) "192.168.1.0/24"
# "192.168.1.0"
# > net.cidr.host (-257) "192.168.1.0/24"
# <fails with an error message>
host = i: n: let
cap = libNet.cidr.capacity n;
in
assert assertMsg (i >= (-cap) && i < cap) "The host ${toString i} lies outside of ${n}";
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.
#
# Examples:
#
# > net.cidr.hostCidr 2 "192.168.1.0/24"
# "192.168.1.2/24"
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.
#
# Examples:
#
# > net.cidr.ip "192.168.1.100/24"
# "192.168.1.100"
# > net.cidr.ip "192.168.1.100"
# "192.168.1.100"
ip = x: head (splitString "/" x);
# canonicalize :: cidr -> cidr
#
# Replaces the ip of the cidr with the canonical network address
# (first contained address in range)
#
# Examples:
#
# > net.cidr.canonicalize "192.168.1.100/24"
# "192.168.1.0/24"
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.
#
# Examples:
#
# > net.cidr.mergev4 ["192.168.1.1/24" "192.168.6.1/32"]
# "192.168.0.0/21"
mergev4 = addrs_: let
# Append /32 if necessary
addrs = map (x:
if hasInfix "/" x
then x
else "${x}/32")
addrs_;
# The smallest occurring length is the first we need to start checking, since
# any greater cidr length represents a smaller address range which
# wouldn't contain all of the original addresses.
startLength = foldl' min 32 (map libNet.cidr.length addrs);
possibleLengths = reverseList (range 0 startLength);
# The first ip address will be "expanded" in cidr length until it covers all other
# used addresses.
firstIp = ip (head addrs);
# Return the first (i.e. greatest length -> smallest prefix) cidr length
# in the list that covers all used addresses
bestLength = head (filter
# All given addresses must be contained by the generated address.
(len:
all (x:
libNet.cidr.contains
(ip x)
(libNet.cidr.make len firstIp))
addrs)
possibleLengths);
in
assert assertMsg (!any (hasInfix ":") addrs) "mergev4 cannot operate on ipv6 addresses";
if addrs == []
then 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.
#
# Examples:
#
# > net.cidr.mergev6 ["fd00:dead:cafe::/64" "fd00:fd12:3456:7890::/56"]
# "fd00:c000::/18"
mergev6 = addrs_: let
# Append /128 if necessary
addrs = map (x:
if hasInfix "/" x
then x
else "${x}/128")
addrs_;
# The smallest occurring length is the first we need to start checking, since
# any greater cidr length represents a smaller address range which
# wouldn't contain all of the original addresses.
startLength = foldl' min 128 (map libNet.cidr.length addrs);
possibleLengths = reverseList (range 0 startLength);
# The first ip address will be "expanded" in cidr length until it covers all other
# used addresses.
firstIp = ip (head addrs);
# Return the first (i.e. greatest length -> smallest prefix) cidr length
# in the list that covers all used addresses
bestLength = head (filter
# All given addresses must be contained by the generated address.
(len:
all (x:
libNet.cidr.contains
(ip x)
(libNet.cidr.make len firstIp))
addrs)
possibleLengths);
in
assert assertMsg (all (hasInfix ":") addrs) "mergev6 cannot operate on ipv4 addresses";
if addrs == []
then 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.
# Equivalent to calling mergev4 and mergev6 on a partition individually.
merge = addrs: let
v4_and_v6 = partition (hasInfix ":") addrs;
in {
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 {
# 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;
};
}
types.net = libNet.types;
};
}

View file

@ -1,48 +1,15 @@
inputs: let
inputs: self: super: let
inherit
(inputs.nixpkgs.lib)
all
any
assertMsg
attrNames
attrValues
concatLists
concatMap
concatMapStrings
concatStringsSep
elem
escapeShellArg
filter
flatten
flip
foldAttrs
foldl'
genAttrs
genList
hasInfix
head
isAttrs
mapAttrs'
mergeAttrs
min
mkMerge
mkOptionType
nameValuePair
optionalAttrs
partition
range
recursiveUpdate
removeSuffix
reverseList
showOption
splitString
stringToCharacters
substring
types
unique
warnIf
;
in rec {
# Checks whether the value is a lazy value without causing
# it's value to be evaluated
isLazyValue = x: isAttrs x && x ? _lazyValue;
@ -71,4 +38,15 @@ in rec {
# Represents a value or lazy value of the given type that will
# automatically be coerced to the given type when merged.
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
(inputs.nixpkgs.lib)
all
any
assertMsg
attrNames
attrValues
concatLists
concatMap
concatMapStrings
concatStringsSep
elem
escapeShellArg
filter
flatten
flip
foldAttrs
foldl'
genAttrs
genList
hasInfix
head
isAttrs
mapAttrs'
mergeAttrs
min
mkMerge
mkOptionType
nameValuePair
optionalAttrs
partition
range
recursiveUpdate
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
(misc)
(self.lib)
net
concatAttrs
types
;
inherit
(misc.secrets)
(self.lib.secrets)
rageDecryptArgs
;
inherit (inputs.self) nodes;
in rec {
# Returns the given node's wireguard configuration of this network
wgCfgOf = node: nodes.${node}.config.meta.wireguard.${wgName};
in {
lib =
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:
if peerA < peerB
then {
peer1 = peerA;
peer2 = peerB;
}
else {
peer1 = peerB;
peer2 = peerA;
sortedPeers = peerA: peerB:
if peerA < peerB
then {
peer1 = peerA;
peer2 = peerB;
}
else {
peer1 = peerB;
peer2 = peerA;
};
peerPublicKeyFile = peerName: "/secrets/wireguard/${wgName}/keys/${peerName}.pub";
peerPublicKeyPath = peerName: 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
attrValues
concatStringsSep
disko
escapeShellArg
filterAttrs
foldl'
makeBinPath
mapAttrsToList
mdDoc
mergeToplevelConfigs
mkDefault
mkEnableOption
mkForce
mkIf
mkMerge
mkOption
net
optional
optionalAttrs
recursiveUpdate
types
;
inherit
(import ../../lib/misc.nix inputs)
mergeToplevelConfigs
;
net = import ../../lib/net.nix inputs;
disko = import ../../lib/disko.nix inputs;
parentConfig = config;
cfg = config.meta.microvms;
nodeName = config.node.name;
@ -236,7 +231,7 @@ in {
networking = {
baseMac = mkOption {
type = net.types.mac;
type = types.net.mac;
description = mdDoc ''
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.
@ -250,13 +245,13 @@ in {
wireguard = {
cidrv4 = mkOption {
type = net.types.cidrv4;
type = types.net.cidrv4;
description = mdDoc "The ipv4 network address range to use for internal vm traffic.";
default = "172.31.0.0/24";
};
cidrv6 = mkOption {
type = net.types.cidrv6;
type = types.net.cidrv6;
description = mdDoc "The ipv6 network address range to use for internal vm traffic.";
default = "fd00:172:31::/120";
};

View file

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

View file

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

View file

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

View file

@ -1,6 +1,6 @@
final: prev: let
self: super: let
inherit
(final.lib)
(self.lib)
escapeShellArg
concatMapStrings
flatten
@ -21,9 +21,9 @@ final: prev: let
version,
}: "go get ${escapeShellArg name}@${escapeShellArg version}\n");
in
prev.caddy.override {
super.caddy.override {
buildGoModule = args:
prev.buildGoModule (args
super.buildGoModule (args
// {
inherit vendorHash;
passthru.plugins = plugins;
@ -45,5 +45,5 @@ in {
# ];
# 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: {
oauth2-proxy = prev.oauth2-proxy.overrideAttrs (_: {
self: super: {
oauth2-proxy = super.oauth2-proxy.overrideAttrs (_: {
patches = [./0001-scopes-as-groups.patch];
});
}