From eebfe0864f92f5c390ed0e4a6959cb69f7f7f40e Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Wed, 29 Sep 2021 21:55:16 +0200 Subject: yggdrasil-wg: init --- modules/yggdrasil-wg/default.nix | 69 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 modules/yggdrasil-wg/default.nix (limited to 'modules/yggdrasil-wg/default.nix') diff --git a/modules/yggdrasil-wg/default.nix b/modules/yggdrasil-wg/default.nix new file mode 100644 index 00000000..08665e33 --- /dev/null +++ b/modules/yggdrasil-wg/default.nix @@ -0,0 +1,69 @@ +{ config, hostName, lib, ... }: + +with lib; + +let + listenPort = 51820; + subnet = "2a03:4000:52:ada:1"; + + links = [ + { from = "vidhar"; + to = "surtr"; + endpointHost = "surtr.yggdrasil.li"; + persistentKeepalive = 25; + dynamicEndpointRefreshSeconds = 86400; + } + ]; + hostIPs = { + surtr = ["${subnet}::/32"]; + vidhar = ["${subnet}:1::/32"]; + }; + + mkPublicKeyPath = host: ./hosts + "/${host}.pub"; + mkPrivateKeyPath = host: ./hosts + "/${host}.priv"; + + publicKeyPath = mkPublicKeyPath hostName; + privateKeyPath = mkPrivateKeyPath hostName; + inNetwork = pathExists privateKeyPath && pathExists publicKeyPath; + hostLinks = filter ({ from, to, ... }: from == hostName || to == hostName) links; + linkToPeer = opts@{from, to, ...}: + let + other = if from == hostName then to else from; + in { + allowedIPs = hostIPs.${other}; + publicKey = trim (readFile (mkPublicKeyPath other)); + } // (optionalAttrs (from == hostName) (filterAttrs (n: _v: !(elem n ["from" "to" "endpointHost"])) opts // optionalAttrs (opts ? "endpointHost") { endpoint = "${opts.endpointHost}:${toString listenPort}"; })); + + trim = str: if hasSuffix "\n" str then trim (removeSuffix "\n" str) else str; + stripSubnet = addr: let matchRes = builtins.match "^(.*)/[0-9]+$" addr; in if matchRes == null then addr else elemAt matchRes 0; +in { + config = { + assertions = [ + { assertion = inNetwork || !(pathExists privateKeyPath || pathExists publicKeyPath); + message = "yggdrasil-wg: Either both public and private keys must exist or neither."; + } + { assertion = !inNetwork || (hostIPs ? "${hostName}"); + message = "yggdrasil-wg: Entry in hostIPs must exist."; + } + ] ++ 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; + + networking.wireguard.interfaces = mkIf inNetwork { + yggdrasil = { + allowedIPsAsRoutes = true; + inherit listenPort; + ips = hostIPs.${hostName}; + peers = map linkToPeer hostLinks; + privateKeyFile = config.sops.secrets."yggdrasil-wg.priv".path; + }; + }; + + sops.secrets = mkIf (pathExists privateKeyPath) { + "yggdrasil-wg.priv" = { + format = "binary"; + sopsFile = privateKeyPath; + }; + }; + + networking.hosts = mkIf inNetwork (listToAttrs (concatMap ({name, value}: map (ip: nameValuePair (stripSubnet ip) ["${name}.yggdrasil"]) value) (mapAttrsToList nameValuePair hostIPs))); + }; +} -- cgit v1.2.3