diff options
author | Gregor Kleen <gkleen@yggdrasil.li> | 2021-10-17 20:50:47 +0200 |
---|---|---|
committer | Gregor Kleen <gkleen@yggdrasil.li> | 2021-10-17 20:50:47 +0200 |
commit | 3a2161ef205c432db0053e9a82893069b54e55ed (patch) | |
tree | 4557c7c6c97fba7de6585729210a0ab8c1eec3cf /modules/yggdrasil-wg/default.nix | |
parent | 65d0b738ab8e25042f1568ed313b001101c3f628 (diff) | |
download | nixos-3a2161ef205c432db0053e9a82893069b54e55ed.tar nixos-3a2161ef205c432db0053e9a82893069b54e55ed.tar.gz nixos-3a2161ef205c432db0053e9a82893069b54e55ed.tar.bz2 nixos-3a2161ef205c432db0053e9a82893069b54e55ed.tar.xz nixos-3a2161ef205c432db0053e9a82893069b54e55ed.zip |
yggdrasil-wg: ...
Diffstat (limited to 'modules/yggdrasil-wg/default.nix')
-rw-r--r-- | modules/yggdrasil-wg/default.nix | 234 |
1 files changed, 168 insertions, 66 deletions
diff --git a/modules/yggdrasil-wg/default.nix b/modules/yggdrasil-wg/default.nix index 8e2ba7a4..80443644 100644 --- a/modules/yggdrasil-wg/default.nix +++ b/modules/yggdrasil-wg/default.nix | |||
@@ -5,86 +5,183 @@ with lib; | |||
5 | let | 5 | let |
6 | listenPort = 51820; | 6 | listenPort = 51820; |
7 | udp2rawPort = 51821; | 7 | udp2rawPort = 51821; |
8 | subnet = "2a03:4000:52:ada:1"; | 8 | wgSubnet = "2a03:4000:52:ada:1"; |
9 | subnetLength = 80; | 9 | wgSubnetLength = 80; |
10 | hostLength = subnetLength + 16; | 10 | wgHostLength = wgSubnetLength + 16; |
11 | batSubnet = "2a03:4000:52:ada:2"; | ||
12 | batSubnetLength = 80; | ||
13 | batHostLength = batSubnetLength + 16; | ||
11 | 14 | ||
12 | links = [ | 15 | links = mkLinks [ |
13 | { from = "vidhar"; | 16 | { from = "vidhar"; |
14 | to = "surtr"; | 17 | to = "surtr"; |
15 | endpointHost = "202.61.241.61"; | 18 | endpointHost = "202.61.241.61"; |
16 | persistentKeepalive = 25; | 19 | udp2raw = true; |
17 | dynamicEndpointRefreshSeconds = 86400; | 20 | PersistentKeepalive = 25; |
18 | } | 21 | } |
19 | { from = "sif"; | 22 | { from = "sif"; |
20 | to = "surtr"; | 23 | to = "surtr"; |
21 | endpointHost = "202.61.241.61"; | 24 | endpointHost = "202.61.241.61"; |
22 | persistentKeepalive = 25; | 25 | udp2raw = true; |
23 | dynamicEndpointRefreshSeconds = 86400; | 26 | PersistentKeepalive = 25; |
24 | } | 27 | } |
25 | ]; | ||
26 | routes = [ | ||
27 | { from = "sif"; | 28 | { from = "sif"; |
28 | to = "vidhar"; | 29 | to = "vidhar"; |
29 | via = "surtr"; | 30 | endpointHost = "192.168.2.168"; |
30 | } | 31 | PersistentKeepalive = 25; |
31 | { from = "vidhar"; | ||
32 | to = "sif"; | ||
33 | via = "surtr"; | ||
34 | } | 32 | } |
35 | ]; | 33 | ]; |
36 | hostIPs = { | 34 | wgHostIPs = { |
37 | surtr = ["${subnet}::/${toString hostLength}"]; | 35 | surtr = "${wgSubnet}::/${toString wgHostLength}"; |
38 | vidhar = ["${subnet}:1::/${toString hostLength}"]; | 36 | vidhar = "${wgSubnet}:1::/${toString wgHostLength}"; |
39 | sif = ["${subnet}:2::/${toString hostLength}"]; | 37 | sif = "${wgSubnet}:2::/${toString wgHostLength}"; |
38 | }; | ||
39 | greHostMACPrefixes = { | ||
40 | surtr = "02:00:00:00:00"; | ||
41 | vidhar = "02:00:00:00:01"; | ||
42 | sif = "02:00:00:00:02"; | ||
43 | }; | ||
44 | batHostIPs = { | ||
45 | surtr = ["${batSubnet}::/${toString batHostLength}"]; | ||
46 | vidhar = ["${batSubnet}:1::/${toString batHostLength}"]; | ||
47 | sif = ["${batSubnet}:2::/${toString batHostLength}"]; | ||
40 | }; | 48 | }; |
41 | 49 | ||
42 | mkPublicKeyPath = host: ./hosts + "/${host}.pub"; | 50 | mkPublicKeyPath = host: ./hosts + "/${host}.pub"; |
43 | mkPrivateKeyPath = host: ./hosts + "/${host}.priv"; | 51 | mkPrivateKeyPath = host: ./hosts + "/${host}.priv"; |
52 | |||
53 | kernel = config.boot.kernelPackages; | ||
44 | 54 | ||
45 | publicKeyPath = mkPublicKeyPath hostName; | 55 | publicKeyPath = mkPublicKeyPath hostName; |
46 | privateKeyPath = mkPrivateKeyPath hostName; | 56 | privateKeyPath = mkPrivateKeyPath hostName; |
47 | inNetwork = pathExists privateKeyPath && pathExists publicKeyPath; | 57 | inNetwork = pathExists privateKeyPath && pathExists publicKeyPath; |
48 | hostLinks = filter ({ from, to, ... }: from == hostName || to == hostName) links; | 58 | hostLinks = filter ({ from, to, ... }: from == hostName || to == hostName) links; |
49 | hostRoutes = filter ({ from, to, ... }: from == hostName || to == hostName) routes; | 59 | # hostRoutes = filter ({ from, to, ... }: from == hostName || to == hostName) routes; |
50 | isRouter = inNetwork && any ({via, ...}: via == hostName) routes; | 60 | # isRouter = inNetwork && any ({via, ...}: via == hostName) routes; |
51 | linkToPeer = ix: opts@{from, to, ...}: | 61 | linkToPeer = opts@{from, to, ...}: |
52 | let | 62 | let |
53 | other = if from == hostName then to else from; | 63 | other = if from == hostName then to else from; |
54 | in { | 64 | in { |
55 | allowedIPs = hostIPs.${other} ++ concatMap (rArgs: if rArgs.from != hostName || rArgs.via != to then [] else hostIPs.${rArgs.to}) routes; | 65 | AllowedIPs = wgHostIPs.${other}; # ++ concatMap (rArgs: if rArgs.from != hostName || rArgs.via != to then [] else wgHostIPs.${rArgs.to}) routes; |
56 | publicKey = trim (readFile (mkPublicKeyPath other)); | 66 | PublicKey = trim (readFile (mkPublicKeyPath other)); |
57 | } // (optionalAttrs (from == hostName) (filterAttrs (n: _v: !(elem n ["from" "to" "endpointHost"])) opts // optionalAttrs (opts ? "endpointHost") { endpoint = "127.0.0.1:${toString (udp2rawPort + ix)}"; })); | 67 | } // (optionalAttrs (from == hostName) (filterAttrs (n: _v: !(elem n ["from" "to" "endpointHost" "udp2raw"])) opts // optionalAttrs (opts ? "endpointHost") (if opts ? "udp2raw" then { Endpoint = "127.0.0.1:${toString (udp2rawPort + opts.udp2raw)}"; } else { Endpoint = "${opts.endpointHost}:${toString listenPort}"; }))); |
58 | 68 | linkToGreDev = opts@{from, to, ...}: | |
69 | let | ||
70 | other = if from == hostName then to else from; | ||
71 | in nameValuePair "yggdrasil-gre-${other}" { | ||
72 | netdevConfig = { | ||
73 | Name = "yggdrasil-gre-${other}"; | ||
74 | Kind = "ip6gretap"; | ||
75 | MTUBytes = toString 1280; | ||
76 | }; | ||
77 | tunnelConfig = { | ||
78 | Local = stripSubnet wgHostIPs.${hostName}; | ||
79 | Remote = stripSubnet wgHostIPs.${other}; | ||
80 | }; | ||
81 | }; | ||
82 | linkToGreNetwork = ix: opts@{from, to, ...}: | ||
83 | let | ||
84 | other = if from == hostName then to else from; | ||
85 | hexIx = let | ||
86 | hexIx' = toHexString ix; | ||
87 | in if (stringLength hexIx' < 2) then "0${hexIx'}" else hexIx'; | ||
88 | in nameValuePair "yggdrasil-gre-${other}" { | ||
89 | matchConfig = { | ||
90 | Name = "yggdrasil-gre-${other}"; | ||
91 | }; | ||
92 | linkConfig = { | ||
93 | MACAddress = "${greHostMACPrefixes.${hostName}}:${hexIx}"; | ||
94 | }; | ||
95 | networkConfig = { | ||
96 | Tunnel = "yggdrasil-gre-${other}"; | ||
97 | BatmanAdvanced = "yggdrasil"; | ||
98 | }; | ||
99 | linkConfig = { | ||
100 | RequiredForOnline = false; | ||
101 | }; | ||
102 | }; | ||
103 | |||
59 | trim = str: if hasSuffix "\n" str then trim (removeSuffix "\n" str) else str; | 104 | trim = str: if hasSuffix "\n" str then trim (removeSuffix "\n" str) else str; |
60 | stripSubnet = addr: let matchRes = builtins.match "^(.*)/[0-9]+$" addr; in if matchRes == null then addr else elemAt matchRes 0; | 105 | stripSubnet = addr: let matchRes = builtins.match "^(.*)/[0-9]+$" addr; in if matchRes == null then addr else elemAt matchRes 0; |
106 | optIx = optName: xs: let | ||
107 | 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))); | ||
108 | withoutOpts = listToAttrs (map (nv: nameValuePair nv.name (removeAttrs nv.value [optName])) (filter (x: !(x.value.${optName} or false)) (imap0 (ix: nameValuePair (toString ix)) xs))); | ||
109 | in genList (ix: withOpts.${toString ix} or withoutOpts.${toString ix}) (length xs); | ||
110 | mkLinks = optIx "udp2raw"; | ||
61 | in { | 111 | in { |
62 | config = { | 112 | config = { |
63 | assertions = [ | 113 | assertions = [ |
64 | { assertion = inNetwork || !(pathExists privateKeyPath || pathExists publicKeyPath); | 114 | { assertion = inNetwork || !(pathExists privateKeyPath || pathExists publicKeyPath); |
65 | message = "yggdrasil-wg: Either both public and private keys must exist or neither."; | 115 | message = "yggdrasil-wg: Either both public and private keys must exist or neither."; |
66 | } | 116 | } |
67 | { assertion = !inNetwork || (hostIPs ? "${hostName}"); | 117 | { assertion = !inNetwork || (wgHostIPs ? "${hostName}"); |
68 | message = "yggdrasil-wg: Entry in hostIPs must exist."; | 118 | message = "yggdrasil-wg: Entry in wgHostIPs must exist."; |
69 | } | 119 | } |
70 | ] ++ map ({from, to, ...}: let other = if from == hostName 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; | 120 | ] ++ map ({from, to, ...}: let other = if from == hostName 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; |
71 | 121 | ||
72 | networking.wireguard.interfaces = mkIf inNetwork { | 122 | systemd.network = mkIf inNetwork { |
73 | yggdrasil = { | 123 | enable = true; |
74 | allowedIPsAsRoutes = false; | 124 | netdevs = { |
75 | inherit listenPort; | 125 | yggdrasil-wg = { |
76 | ips = hostIPs.${hostName}; | 126 | netdevConfig = { |
77 | peers = filter (value: value != null) (imap0 (ix: opts@{to, from, ...}: if from == hostName || to == hostName then linkToPeer ix opts else null) links); | 127 | Name = "yggdrasil-wg"; |
78 | privateKeyFile = config.sops.secrets."yggdrasil-wg.priv".path; | 128 | Kind = "wireguard"; |
79 | postSetup = '' | 129 | MTUBytes = toString (1280 + 70); |
80 | ip li set mtu 1280 dev yggdrasil | 130 | }; |
81 | ${concatMapStringsSep "\n" (linkArgs: let other = if linkArgs.from == hostName then linkArgs.to else linkArgs.from; in concatMapStringsSep "\n" (otherIP: "ip route replace \"${otherIP}\" dev \"yggdrasil\" table \"main\"") hostIPs.${other}) hostLinks} | 131 | wireguardConfig = { |
82 | ${concatMapStringsSep "\n" (routeArgs: let other = if routeArgs.from == hostName then routeArgs.to else routeArgs.from; in concatMapStringsSep "\n" (otherIP: concatMapStringsSep "\n" (viaIP: "ip route replace \"${otherIP}\" via \"${viaIP}\" dev \"yggdrasil\" table \"main\"") (map stripSubnet hostIPs.${routeArgs.via})) hostIPs.${other}) hostRoutes} | 132 | PrivateKeyFile = config.sops.secrets."yggdrasil-wg.priv".path; |
83 | ''; | 133 | ListenPort = listenPort; |
84 | }; | 134 | }; |
135 | wireguardPeers = map (opts@{to, from, ...}: { wireguardPeerConfig = linkToPeer opts; }) hostLinks; | ||
136 | }; | ||
137 | yggdrasil = { | ||
138 | netdevConfig = { | ||
139 | Name = "yggdrasil"; | ||
140 | Kind = "batadv"; | ||
141 | }; | ||
142 | }; | ||
143 | } // listToAttrs (map linkToGreDev hostLinks); | ||
144 | |||
145 | networks = { | ||
146 | yggdrasil-wg = { | ||
147 | name = "yggdrasil-wg"; | ||
148 | matchConfig = { | ||
149 | Name = "yggdrasil-wg"; | ||
150 | }; | ||
151 | address = [wgHostIPs.${hostName}]; | ||
152 | linkConfig = { | ||
153 | RequiredForOnline = false; | ||
154 | }; | ||
155 | }; | ||
156 | yggdrasil = { | ||
157 | name = "yggdrasil"; | ||
158 | matchConfig = { | ||
159 | Name = "yggdrasil"; | ||
160 | }; | ||
161 | address = batHostIPs.${hostName}; | ||
162 | linkConfig = { | ||
163 | RequiredForOnline = false; | ||
164 | }; | ||
165 | }; | ||
166 | } // listToAttrs (imap0 linkToGreNetwork hostLinks); | ||
85 | }; | 167 | }; |
86 | 168 | ||
87 | systemd.services = listToAttrs (filter ({ value, ...}: value != null) (imap0 (ix: opts@{to, from, ...}: let other = if from == hostName then to else from; in nameValuePair "yggdrasil-udp2raw@${other}" (if opts ? "endpointHost" && (from == hostName || to == hostName) then { | 169 | # networking.wireguard.interfaces = mkIf inNetwork { |
170 | # yggdrasil = { | ||
171 | # allowedIPsAsRoutes = false; | ||
172 | # inherit listenPort; | ||
173 | # ips = wgHostIPs.${hostName}; | ||
174 | # peers = filter (value: value != null) (map (opts@{to, from, ...}: if from == hostName || to == hostName then linkToPeer opts else null) links); | ||
175 | # privateKeyFile = config.sops.secrets."yggdrasil-wg.priv".path; | ||
176 | # postSetup = '' | ||
177 | # ip li set mtu 1280 dev yggdrasil | ||
178 | # ${concatMapStringsSep "\n" (linkArgs: let other = if linkArgs.from == hostName then linkArgs.to else linkArgs.from; in concatMapStringsSep "\n" (otherIP: "ip route replace \"${otherIP}\" dev \"yggdrasil\" table \"main\"") wgHostIPs.${other}) hostLinks} | ||
179 | # ${concatMapStringsSep "\n" (routeArgs: let other = if routeArgs.from == hostName then routeArgs.to else routeArgs.from; in concatMapStringsSep "\n" (otherIP: concatMapStringsSep "\n" (viaIP: "ip route replace \"${otherIP}\" via \"${viaIP}\" dev \"yggdrasil\" table \"main\"") (map stripSubnet wgHostIPs.${routeArgs.via})) wgHostIPs.${other}) hostRoutes} | ||
180 | # ''; | ||
181 | # }; | ||
182 | # }; | ||
183 | |||
184 | systemd.services = listToAttrs (filter ({ value, ...}: value != null) (map (opts@{to, from, ...}: let other = if from == hostName then to else from; in nameValuePair "yggdrasil-udp2raw@${other}" (if opts ? "endpointHost" && opts ? "udp2raw" then { | ||
88 | path = with pkgs; [iptables]; | 185 | path = with pkgs; [iptables]; |
89 | serviceConfig = { | 186 | serviceConfig = { |
90 | RuntimeDirectory = ["udp2raw-config-${other}"]; | 187 | RuntimeDirectory = ["udp2raw-config-${other}"]; |
@@ -95,16 +192,17 @@ in { | |||
95 | cat >''${RUNTIME_DIRECTORY}/udp2raw.conf <<EOF | 192 | cat >''${RUNTIME_DIRECTORY}/udp2raw.conf <<EOF |
96 | ${if from == hostName then '' | 193 | ${if from == hostName then '' |
97 | -c | 194 | -c |
98 | -l 127.0.0.1:${toString (udp2rawPort + ix)} | 195 | -l 127.0.0.1:${toString (udp2rawPort + opts.udp2raw)} |
99 | -r ${opts.endpointHost}:${toString (udp2rawPort + ix)} | 196 | -r ${opts.endpointHost}:${toString (udp2rawPort + opts.udp2raw)} |
100 | '' else '' | 197 | '' else '' |
101 | -s | 198 | -s |
102 | -l 0.0.0.0:${toString (udp2rawPort + ix)} | 199 | -l 0.0.0.0:${toString (udp2rawPort + opts.udp2raw)} |
103 | -r 127.0.0.1:${toString listenPort} | 200 | -r 127.0.0.1:${toString listenPort} |
104 | ''} | 201 | ''} |
105 | -k $secret | 202 | -k $secret |
106 | --auth-mode hmac_sha1 | 203 | --auth-mode hmac_sha1 |
107 | --raw-mode faketcp | 204 | --raw-mode faketcp |
205 | --seq-mode 4 | ||
108 | -a | 206 | -a |
109 | --retry-on-error | 207 | --retry-on-error |
110 | EOF | 208 | EOF |
@@ -112,38 +210,42 @@ in { | |||
112 | ExecStart = "${pkgs.udp2raw}/bin/udp2raw --conf-file \${RUNTIME_DIRECTORY}/udp2raw.conf"; | 210 | ExecStart = "${pkgs.udp2raw}/bin/udp2raw --conf-file \${RUNTIME_DIRECTORY}/udp2raw.conf"; |
113 | Restart = "always"; | 211 | Restart = "always"; |
114 | }; | 212 | }; |
115 | } else null)) links)) // { | 213 | } else null)) hostLinks)); |
116 | "wireguard-yggdrasil" = { | 214 | # // { |
117 | bindsTo = filter (value: value != null) (map (opts@{to, from, ...}: let other = if from == hostName then to else from; in if opts ? "endpointHost" then "yggdrasil-udp2raw@${other}.service" else null) hostLinks); | 215 | # "wireguard-yggdrasil" = { |
118 | after = filter (value: value != null) (map (opts@{to, from, ...}: let other = if from == hostName then to else from; in if opts ? "endpointHost" then "yggdrasil-udp2raw@${other}.service" else null) hostLinks); | 216 | # bindsTo = filter (value: value != null) (map (opts@{to, from, ...}: let other = if from == hostName then to else from; in if opts ? "endpointHost" && opts ? "udp2raw" then "yggdrasil-udp2raw@${other}.service" else null) hostLinks); |
119 | }; | 217 | # after = filter (value: value != null) (map (opts@{to, from, ...}: let other = if from == hostName then to else from; in if opts ? "endpointHost" && opts ? "udp2raw" then "yggdrasil-udp2raw@${other}.service" else null) hostLinks); |
120 | firewall.path = optionals isRouter [pkgs.procps]; | 218 | # }; |
121 | }; | 219 | # firewall.path = optionals isRouter [pkgs.procps]; |
220 | # }; | ||
122 | 221 | ||
123 | sops.secrets = { | 222 | sops.secrets = { |
124 | "yggdrasil-wg.priv" = mkIf (pathExists privateKeyPath) { | 223 | "yggdrasil-wg.priv" = mkIf (pathExists privateKeyPath) { |
125 | format = "binary"; | 224 | format = "binary"; |
126 | sopsFile = privateKeyPath; | 225 | sopsFile = privateKeyPath; |
127 | }; | 226 | }; |
128 | "yggdrasil-udp2raw-secret" = mkIf (any (opts@{to, from, ...}: (to == hostName || from == hostName) && opts ? "endpointHost") links) { | 227 | "yggdrasil-udp2raw-secret" = mkIf (any (opts@{to, from, ...}: opts ? "endpointHost" && opts ? "udp2raw") hostLinks) { |
129 | format = "binary"; | 228 | format = "binary"; |
130 | sopsFile = ./udp2raw-secret; | 229 | sopsFile = ./udp2raw-secret; |
131 | }; | 230 | }; |
132 | }; | 231 | }; |
133 | 232 | ||
134 | networking.hosts = mkIf inNetwork (listToAttrs (concatMap ({name, value}: map (ip: nameValuePair (stripSubnet ip) ["${name}.yggdrasil"]) value) (mapAttrsToList nameValuePair hostIPs))); | 233 | networking.hosts = mkIf inNetwork (listToAttrs (concatMap ({name, value}: map (ip: nameValuePair (stripSubnet ip) ["${name}.yggdrasil"]) value) (mapAttrsToList nameValuePair batHostIPs))); |
135 | 234 | ||
136 | networking.firewall = mkIf isRouter { | 235 | # networking.firewall = mkIf isRouter { |
137 | extraCommands = '' | 236 | # extraCommands = '' |
138 | ip6tables -A FORWARD -i yggdrasil -o yggdrasil -j nixos-fw-accept | 237 | # ip6tables -A FORWARD -i yggdrasil -o yggdrasil -j nixos-fw-accept |
139 | ip46tables -A FORWARD -j nixos-fw-log-refuse | 238 | # ip46tables -A FORWARD -j nixos-fw-log-refuse |
140 | sysctl net.ipv6.conf.all.forwarding=1 | 239 | # sysctl net.ipv6.conf.all.forwarding=1 |
141 | ''; | 240 | # ''; |
142 | extraStopCommands = '' | 241 | # extraStopCommands = '' |
143 | sysctl net.ipv6.conf.all.forwarding=0 | 242 | # sysctl net.ipv6.conf.all.forwarding=0 |
144 | ip46tables -D FORWARD -j nixos-fw-log-refuse || true | 243 | # ip46tables -D FORWARD -j nixos-fw-log-refuse || true |
145 | ip6tables -D FORWARD -i yggdrasil -o yggdrasil -j nixos-fw-accept || true | 244 | # ip6tables -D FORWARD -i yggdrasil -o yggdrasil -j nixos-fw-accept || true |
146 | ''; | 245 | # ''; |
147 | }; | 246 | # }; |
247 | |||
248 | boot.extraModulePackages = optional (versionOlder kernel.kernel.version "5.6") kernel.wireguard ++ [kernel.batman_adv]; | ||
249 | environment.systemPackages = with pkgs; [ wireguard-tools batctl ]; | ||
148 | }; | 250 | }; |
149 | } | 251 | } |