summaryrefslogtreecommitdiff
path: root/modules/yggdrasil-wg/default.nix
diff options
context:
space:
mode:
authorGregor Kleen <gkleen@yggdrasil.li>2021-11-03 23:28:00 +0100
committerGregor Kleen <gkleen@yggdrasil.li>2021-11-03 23:28:00 +0100
commit3d5b1509be78c0e9d8923af7e63f38d9dcbdefdf (patch)
treeab98231775abe2dc7ec29b98d0f6444a7b7d56a7 /modules/yggdrasil-wg/default.nix
parentc47b09353f424eed1ef99bb41a9285ac87b051f2 (diff)
downloadnixos-3d5b1509be78c0e9d8923af7e63f38d9dcbdefdf.tar
nixos-3d5b1509be78c0e9d8923af7e63f38d9dcbdefdf.tar.gz
nixos-3d5b1509be78c0e9d8923af7e63f38d9dcbdefdf.tar.bz2
nixos-3d5b1509be78c0e9d8923af7e63f38d9dcbdefdf.tar.xz
nixos-3d5b1509be78c0e9d8923af7e63f38d9dcbdefdf.zip
yggdrasil-wg: dual stack
Diffstat (limited to 'modules/yggdrasil-wg/default.nix')
-rw-r--r--modules/yggdrasil-wg/default.nix173
1 files changed, 96 insertions, 77 deletions
diff --git a/modules/yggdrasil-wg/default.nix b/modules/yggdrasil-wg/default.nix
index cef1ce4e..55064baa 100644
--- a/modules/yggdrasil-wg/default.nix
+++ b/modules/yggdrasil-wg/default.nix
@@ -4,10 +4,13 @@ with lib;
4 4
5let 5let
6 listenPort = 51820; 6 listenPort = 51820;
7 wgSubnet = "2a03:4000:52:ada:1"; 7 wgSubnet = {
8 "4" = "2a03:4000:52:ada:2";
9 "6" = "2a03:4000:52:ada:3";
10 };
8 wgSubnetLength = 80; 11 wgSubnetLength = 80;
9 wgHostLength = wgSubnetLength + 16; 12 wgHostLength = wgSubnetLength + 16;
10 batSubnet = "2a03:4000:52:ada:2"; 13 batSubnet = "2a03:4000:52:ada:1";
11 batSubnetLength = 80; 14 batSubnetLength = 80;
12 batHostLength = batSubnetLength + 16; 15 batHostLength = batSubnetLength + 16;
13 16
@@ -16,27 +19,49 @@ let
16 to = "surtr"; 19 to = "surtr";
17 endpointHost = "202.61.241.61"; 20 endpointHost = "202.61.241.61";
18 PersistentKeepalive = 25; 21 PersistentKeepalive = 25;
22 family = "4";
23 }
24 { from = "vidhar";
25 to = "surtr";
26 endpointHost = "2a03:4000:52:ada::";
27 PersistentKeepalive = 25;
28 family = "6";
29 }
30 { from = "sif";
31 to = "surtr";
32 endpointHost = "202.61.241.61";
33 PersistentKeepalive = 25;
34 family = "4";
19 } 35 }
20 { from = "sif"; 36 { from = "sif";
21 to = "surtr"; 37 to = "surtr";
22 endpointHost = "2a03:4000:52:ada::"; 38 endpointHost = "2a03:4000:52:ada::";
23 PersistentKeepalive = 25; 39 PersistentKeepalive = 25;
40 family = "6";
24 } 41 }
25 { from = "sif"; 42 { from = "sif";
26 to = "vidhar"; 43 to = "vidhar";
27 endpointHost = "192.168.2.168"; 44 endpointHost = "192.168.2.168";
28 PersistentKeepalive = 25; 45 PersistentKeepalive = 25;
46 family = "4";
29 } 47 }
30 ]; 48 ];
31 wgHostIPs = { 49 wgHostIPs = mapAttrs (_family: wgSubnet: {
32 surtr = "${wgSubnet}::/${toString wgHostLength}"; 50 surtr = "${wgSubnet}::/${toString wgHostLength}";
33 vidhar = "${wgSubnet}:1::/${toString wgHostLength}"; 51 vidhar = "${wgSubnet}:1::/${toString wgHostLength}";
34 sif = "${wgSubnet}:2::/${toString wgHostLength}"; 52 sif = "${wgSubnet}:2::/${toString wgHostLength}";
35 }; 53 }) wgSubnet;
36 greHostMACPrefixes = { 54 greHostMACPrefixes = {
37 surtr = "02:00:01:00:00"; 55 "4" = {
38 vidhar = "02:00:01:00:01"; 56 surtr = "02:00:01:00:00";
39 sif = "02:00:01:00:02"; 57 vidhar = "02:00:01:00:01";
58 sif = "02:00:01:00:02";
59 };
60 "6" = {
61 surtr = "02:00:02:00:00";
62 vidhar = "02:00:02:00:01";
63 sif = "02:00:02:00:02";
64 };
40 }; 65 };
41 batHostMACs = { 66 batHostMACs = {
42 surtr = "02:00:00:00:00:00"; 67 surtr = "02:00:00:00:00:00";
@@ -49,46 +74,47 @@ let
49 sif = ["${batSubnet}:2::/${toString batHostLength}"]; 74 sif = ["${batSubnet}:2::/${toString batHostLength}"];
50 }; 75 };
51 76
52 mkPublicKeyPath = host: ./hosts + "/${host}.pub"; 77 mkPublicKeyPath = family: host: ./hosts + "/${family}" + "/${host}.pub";
53 mkPrivateKeyPath = host: ./hosts + "/${host}.priv"; 78 mkPrivateKeyPath = family: host: ./hosts + "/${family}" + "/${host}.priv";
54 79
55 kernel = config.boot.kernelPackages; 80 kernel = config.boot.kernelPackages;
56 81
57 publicKeyPath = mkPublicKeyPath hostName; 82 publicKeyPath = family: mkPublicKeyPath family hostName;
58 privateKeyPath = mkPrivateKeyPath hostName; 83 privateKeyPath = family: mkPrivateKeyPath family hostName;
59 inNetwork = pathExists privateKeyPath && pathExists publicKeyPath; 84 inNetwork' = family: pathExists (privateKeyPath family) && pathExists (publicKeyPath family);
60 hostLinks = filter ({ from, to, ... }: thisHost from || thisHost to) links; 85 inNetwork = any inNetwork' families;
61 linkToPeer = opts@{from, to, ...}: 86 hostLinks = filterAttrs (_family: links: links != []) (mapAttrs (_family: filter ({ from, to, ... }: thisHost from || thisHost to)) links);
87 linkToPeer = family: opts@{from, to, ...}:
62 let 88 let
63 other = if thisHost from then to else from; 89 other = if thisHost from then to else from;
64 in { 90 in {
65 AllowedIPs = wgHostIPs.${other}; 91 AllowedIPs = wgHostIPs.${family}.${other};
66 PublicKey = trim (readFile (mkPublicKeyPath other)); 92 PublicKey = trim (readFile (mkPublicKeyPath family other));
67 } // (optionalAttrs (thisHost from) (linkCfgFilterCustom opts // linkMkEndpointCfg opts)); 93 } // (optionalAttrs (thisHost from) (linkCfgFilterCustom opts // linkMkEndpointCfg opts));
68 linkCfgFilterCustom = filterAttrs (n: _v: !(elem n ["from" "to" "endpointHost"])); 94 linkCfgFilterCustom = filterAttrs (n: _v: !(elem n ["from" "to" "endpointHost"]));
69 linkMkEndpointCfg = opts@{from, ...}: optionalAttrs (opts ? "endpointHost" && thisHost from) { Endpoint = "${opts.endpointHost}:${toString listenPort}"; }; 95 linkMkEndpointCfg = opts@{from, ...}: optionalAttrs (opts ? "endpointHost" && thisHost from) { Endpoint = "${opts.endpointHost}:${toString listenPort}"; };
70 linkToGreDev = opts@{from, to, ...}: 96 linkToGreDev = family: opts@{from, to, ...}:
71 let 97 let
72 other = if thisHost from then to else from; 98 other = if thisHost from then to else from;
73 in nameValuePair "yggre-${other}" { 99 in nameValuePair "yggre-${other}-${family}" {
74 netdevConfig = { 100 netdevConfig = {
75 Name = "yggre-${other}"; 101 Name = "yggre-${other}-${family}";
76 Kind = "ip6gretap"; 102 Kind = "ip6gretap";
77 }; 103 };
78 tunnelConfig = { 104 tunnelConfig = {
79 Local = stripSubnet wgHostIPs.${hostName}; 105 Local = stripSubnet wgHostIPs.${family}.${hostName};
80 Remote = stripSubnet wgHostIPs.${other}; 106 Remote = stripSubnet wgHostIPs.${family}.${other};
81 }; 107 };
82 }; 108 };
83 linkToGreNetwork = ix: opts@{from, to, ...}: 109 linkToGreNetwork = family: ix: opts@{from, to, ...}:
84 let 110 let
85 other = if thisHost from then to else from; 111 other = if thisHost from then to else from;
86 in nameValuePair "yggre-${other}" { 112 in nameValuePair "yggre-${other}" {
87 matchConfig = { 113 matchConfig = {
88 Name = "yggre-${other}"; 114 Name = "yggre-${other}-${family}";
89 }; 115 };
90 linkConfig = { 116 linkConfig = {
91 MACAddress = "${greHostMACPrefixes.${hostName}}:${toHexByte ix}"; 117 MACAddress = "${greHostMACPrefixes.${family}.${hostName}}:${toHexByte ix}";
92 RequiredForOnline = false; 118 RequiredForOnline = false;
93 }; 119 };
94 networkConfig = { 120 networkConfig = {
@@ -96,71 +122,72 @@ let
96 LinkLocalAddressing = "no"; 122 LinkLocalAddressing = "no";
97 }; 123 };
98 }; 124 };
99 125 familyToYggdrasilDev = family: nameValuePair "yggdrasil-wg-${family}" {
100 thisHost = host: builtins.match "^(ipv(4|6)\.)?${hostName}$" host != null; 126 netdevConfig = {
127 Name = "yggdrasil-wg-${family}";
128 Kind = "wireguard";
129 };
130 wireguardConfig = {
131 PrivateKeyFile = config.sops.secrets."yggdrasil-wg-${family}.priv".path;
132 ListenPort = listenPort;
133 };
134 wireguardPeers = map (opts@{to, from, ...}: { wireguardPeerConfig = linkToPeer family opts; }) hostLinks.${family};
135 };
136 familyToYggdrasilNetwork = family: nameValuePair "yggdrasil-wg-${family}" {
137 name = "yggdrasil-wg-${family}";
138 matchConfig = {
139 Name = "yggdrasil-wg-${family}";
140 };
141 address = [wgHostIPs.${family}.${hostName}];
142 routes = [
143 { routeConfig = {
144 Destination = "${wgSubnet.${family}}::/${toString wgSubnetLength}";
145 };
146 }
147 ];
148 linkConfig = {
149 RequiredForOnline = false;
150 };
151 networkConfig = {
152 Tunnel = map (opts@{from, to, ...}: let other = if thisHost from then to else from; in "yggre-${other}-${family}") hostLinks.${family};
153 };
154 };
155 familyToSopsSecret = family: nameValuePair "yggdrasil-wg-${family}.priv" (mkIf (pathExists (privateKeyPath family)) {
156 format = "binary";
157 sopsFile = privateKeyPath family;
158 mode = "0640";
159 owner = "root";
160 group = "systemd-network";
161 });
162
163 thisHost = host: host == hostName;
101 trim = str: if hasSuffix "\n" str then trim (removeSuffix "\n" str) else str; 164 trim = str: if hasSuffix "\n" str then trim (removeSuffix "\n" str) else str;
102 stripSubnet = addr: let matchRes = builtins.match "^(.*)/[0-9]+$" addr; in if matchRes == null then addr else elemAt matchRes 0; 165 stripSubnet = addr: let matchRes = builtins.match "^(.*)/[0-9]+$" addr; in if matchRes == null then addr else elemAt matchRes 0;
103 optIx = optName: xs: let 166 optIx = optName: xs: let
104 withOpts = listToAttrs (imap0 (ix: x: nameValuePair x.name (x.value // { ${optName} = ix; })) (filter (x: x.value.${optName} or false) (imap0 (ix: nameValuePair (toString ix)) xs))); 167 withOpts = listToAttrs (imap0 (ix: x: nameValuePair x.name (x.value // { ${optName} = ix; })) (filter (x: x.value.${optName} or false) (imap0 (ix: nameValuePair (toString ix)) xs)));
105 withoutOpts = listToAttrs (map (nv: nameValuePair nv.name (removeAttrs nv.value [optName])) (filter (x: !(x.value.${optName} or false)) (imap0 (ix: nameValuePair (toString ix)) xs))); 168 withoutOpts = listToAttrs (map (nv: nameValuePair nv.name (removeAttrs nv.value [optName])) (filter (x: !(x.value.${optName} or false)) (imap0 (ix: nameValuePair (toString ix)) xs)));
106 in genList (ix: withOpts.${toString ix} or withoutOpts.${toString ix}) (length xs); 169 in genList (ix: withOpts.${toString ix} or withoutOpts.${toString ix}) (length xs);
107 mkLinks = id; 170 groupFamilies = links: mapAttrs (_name: value: map (filterAttrs (k: _v: k != "family")) value) (groupBy (x: x.family) links);
171 mkLinks = groupFamilies;
172 families = attrNames links;
173 hostFamilies = attrNames hostLinks;
108 toHexByte = n: let 174 toHexByte = n: let
109 hex = toHexString n; 175 hex = toHexString n;
110 in if (stringLength hex < 2) then "0${hex}" else hex; 176 in if (stringLength hex < 2) then "0${hex}" else hex;
111in { 177in {
112 config = { 178 config = {
113 assertions = [
114 { assertion = inNetwork || !(pathExists privateKeyPath || pathExists publicKeyPath);
115 message = "yggdrasil-wg: Either both public and private keys must exist or neither.";
116 }
117 { assertion = !inNetwork || (wgHostIPs ? "${hostName}");
118 message = "yggdrasil-wg: Entry in wgHostIPs must exist.";
119 }
120 ] ++ map ({from, to, ...}: let other = if thisHost from then to else from; in { assertion = pathExists (mkPublicKeyPath other); message = "yggdrasil-wg: This host (${hostName}) has a link with ‘${other}’, but no public key is available for ‘${other}’."; }) hostLinks;
121
122 systemd.network = mkIf inNetwork { 179 systemd.network = mkIf inNetwork {
123 enable = true; 180 enable = true;
124 netdevs = { 181 netdevs = {
125 yggdrasil-wg = {
126 netdevConfig = {
127 Name = "yggdrasil-wg";
128 Kind = "wireguard";
129 };
130 wireguardConfig = {
131 PrivateKeyFile = config.sops.secrets."yggdrasil-wg.priv".path;
132 ListenPort = listenPort;
133 };
134 wireguardPeers = map (opts@{to, from, ...}: { wireguardPeerConfig = linkToPeer opts; }) hostLinks;
135 };
136 yggdrasil = { 182 yggdrasil = {
137 netdevConfig = { 183 netdevConfig = {
138 Name = "yggdrasil"; 184 Name = "yggdrasil";
139 Kind = "batadv"; 185 Kind = "batadv";
140 }; 186 };
141 }; 187 };
142 } // listToAttrs (map linkToGreDev hostLinks); 188 } // listToAttrs (map familyToYggdrasilDev hostFamilies) // listToAttrs (concatMap (family: map (linkToGreDev family) hostLinks.${family}) hostFamilies);
143 189
144 networks = { 190 networks = {
145 yggdrasil-wg = {
146 name = "yggdrasil-wg";
147 matchConfig = {
148 Name = "yggdrasil-wg";
149 };
150 address = [wgHostIPs.${hostName}];
151 routes = [
152 { routeConfig = {
153 Destination = "${wgSubnet}::/${toString wgSubnetLength}";
154 };
155 }
156 ];
157 linkConfig = {
158 RequiredForOnline = false;
159 };
160 networkConfig = {
161 Tunnel = map (opts@{from, to, ...}: let other = if thisHost from then to else from; in "yggre-${other}") hostLinks;
162 };
163 };
164 yggdrasil = { 191 yggdrasil = {
165 name = "yggdrasil"; 192 name = "yggdrasil";
166 matchConfig = { 193 matchConfig = {
@@ -178,18 +205,10 @@ in {
178 RequiredForOnline = false; 205 RequiredForOnline = false;
179 }; 206 };
180 }; 207 };
181 } // listToAttrs (imap0 linkToGreNetwork hostLinks); 208 } // listToAttrs (map familyToYggdrasilNetwork hostFamilies) // listToAttrs (concatMap (family: imap0 (linkToGreNetwork family) hostLinks.${family}) hostFamilies);
182 }; 209 };
183 210
184 sops.secrets = { 211 sops.secrets = listToAttrs (map familyToSopsSecret hostFamilies);
185 "yggdrasil-wg.priv" = mkIf (pathExists privateKeyPath) {
186 format = "binary";
187 sopsFile = privateKeyPath;
188 mode = "0640";
189 owner = "root";
190 group = "systemd-network";
191 };
192 };
193 212
194 networking.hosts = mkIf inNetwork (listToAttrs (concatMap ({name, value}: map (ip: nameValuePair (stripSubnet ip) ["${name}.yggdrasil"]) value) (mapAttrsToList nameValuePair batHostIPs))); 213 networking.hosts = mkIf inNetwork (listToAttrs (concatMap ({name, value}: map (ip: nameValuePair (stripSubnet ip) ["${name}.yggdrasil"]) value) (mapAttrsToList nameValuePair batHostIPs)));
195 214