diff options
Diffstat (limited to 'modules/yggdrasil-wg')
| -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 | } |
