diff options
author | Gregor Kleen <gkleen@yggdrasil.li> | 2021-11-03 23:28:00 +0100 |
---|---|---|
committer | Gregor Kleen <gkleen@yggdrasil.li> | 2021-11-03 23:28:00 +0100 |
commit | 3d5b1509be78c0e9d8923af7e63f38d9dcbdefdf (patch) | |
tree | ab98231775abe2dc7ec29b98d0f6444a7b7d56a7 /modules/yggdrasil-wg/default.nix | |
parent | c47b09353f424eed1ef99bb41a9285ac87b051f2 (diff) | |
download | nixos-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.nix | 173 |
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 | ||
5 | let | 5 | let |
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; |
111 | in { | 177 | in { |
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 | ||