diff --git a/hosts/nom/net.nix b/hosts/nom/net.nix index 3d19200..133a94e 100644 --- a/hosts/nom/net.nix +++ b/hosts/nom/net.nix @@ -20,4 +20,6 @@ dhcpV6Config.RouteMetric = 40; }; }; + + extra.wireguard.vms.address = ["10.0.0.10/32"]; } diff --git a/hosts/zackbiene/net.nix b/hosts/zackbiene/net.nix index 85fbb1f..97f4f0b 100644 --- a/hosts/zackbiene/net.nix +++ b/hosts/zackbiene/net.nix @@ -17,4 +17,16 @@ }; }; }; + + extra.wireguard.vms = { + server = { + enable = true; + port = 51822; + openFirewall = true; + }; + address = ["10.0.0.2/24"]; + externalPeers = { + zack1 = ["10.0.0.90/32"]; + }; + }; } diff --git a/modules/wireguard.nix b/modules/wireguard.nix index 6b0c78d..64c8798 100644 --- a/modules/wireguard.nix +++ b/modules/wireguard.nix @@ -37,7 +37,7 @@ configForNetwork = wgName: wg: let inherit - (extraLib.wireguard wgName) + (extraLib.wireguard wgName nodes) allPeers peerPresharedKeyPath peerPresharedKeySecret @@ -46,7 +46,7 @@ peerPublicKeyPath ; - otherPeers = filterAttrs (n: _: n != nodeName) (allPeers nodes); + otherPeers = filterAttrs (n: _: n != nodeName) allPeers; in { secrets = concatAttrs (map (other: { @@ -130,8 +130,12 @@ in { default = {}; example = {my-android-phone = ["10.0.0.97/32"];}; description = mdDoc '' - Allows defining extra set of external peers that should be added to the configuration. - For each external peers you can define one or multiple allowed ips. + Allows defining an extra set of peers that should be added to this wireguard network, + but will not be managed by this flake. (e.g. phones) + + These external peers will only know this node as a peer, which will forward + their traffic to other members of the network if required. This requires + this node to act as a server. ''; }; }; @@ -144,17 +148,17 @@ in { in { assertions = concatMap (wgName: let inherit - (extraLib.wireguard wgName) + (extraLib.wireguard wgName nodes) externalPeerNamesRaw usedAddresses associatedNodes ; - duplicatePeers = duplicates (externalPeerNamesRaw nodes); - duplicateAddrs = duplicates (map (x: head (splitString "/" x)) (usedAddresses nodes)); + duplicatePeers = duplicates externalPeerNamesRaw; + duplicateAddrs = duplicates (map (x: head (splitString "/" x)) usedAddresses); in [ { - assertion = any (n: nodes.${n}.config.extra.wireguard.${wgName}.server.enable) (associatedNodes nodes); + assertion = any (n: nodes.${n}.config.extra.wireguard.${wgName}.server.enable) associatedNodes; message = "Wireguard network '${wgName}': At least one node must be a server."; } { @@ -165,6 +169,10 @@ in { assertion = duplicateAddrs == []; message = "Wireguard network '${wgName}': Addresses used multiple times: ${concatStringsSep ", " duplicateAddrs}"; } + # TODO externalPeers != [] -> server.listen + # TODO externalPeers != [] -> ip forwarding + # TODO psks only between all nodes and each node-externalpeer pair + # TODO no overlapping allowed ip range? 0.0.0.0 would be ok to overlap though ]) (attrNames cfg); networking.firewall.allowedUDPPorts = mkIf (cfg.server.enable && cfg.server.openFirewall) [cfg.server.port]; diff --git a/nix/apps/generate-wireguard-keys.nix b/nix/apps/generate-wireguard-keys.nix index 5efdeb8..d73f1a9 100644 --- a/nix/apps/generate-wireguard-keys.nix +++ b/nix/apps/generate-wireguard-keys.nix @@ -11,6 +11,7 @@ concatStringsSep escapeShellArg filter + optionalString removeSuffix substring unique @@ -33,17 +34,19 @@ generateNetworkKeys = wgName: let inherit - (extraLib.wireguard wgName) + (extraLib.wireguard wgName self.nodes) allPeers associatedNodes + associatedServerNodes + associatedClientNodes + externalPeersForNode peerPresharedKeyFile peerPrivateKeyFile peerPublicKeyFile + sortedPeers ; - nodesWithNet = associatedNodes self.nodes; - peers = attrNames (allPeers self.nodes); - + # Every peer needs a private and public key. generatePeerKeys = peerName: let keyBasename = escapeShellArg ("./" + removeSuffix ".pub" (peerPublicKeyFile peerName)); pubkeyFile = escapeShellArg ("./" + peerPublicKeyFile peerName); @@ -59,22 +62,35 @@ fi ''; - generatePeerPsks = nodePeerName: - map (peerName: let - pskFile = escapeShellArg ("./" + peerPresharedKeyFile nodePeerName peerName); - in '' - if [[ ! -e ${pskFile} ]]; then - mkdir -p $(dirname ${pskFile}) - echo "Generating "${pskFile}"" - psk=$(${pkgs.wireguard-tools}/bin/wg genpsk) - ${pkgs.rage}/bin/rage -e ${masterIdentityArgs} ${extraEncryptionPubkeys} <<< "$psk" > ${pskFile} \ - || { echo "error: Failed to encrypt wireguard psk for peers ${nodePeerName} and ${peerName} on network ${wgName}!" >&2; exit 1; } - fi - '') (filter (x: x != nodePeerName) peers); + # Generates the psk for peer1 and peer2. + generatePeerPsk = { + peer1, + peer2, + }: let + pskFile = escapeShellArg ("./" + peerPresharedKeyFile peer1 peer2); + in '' + if [[ ! -e ${pskFile} ]]; then + mkdir -p $(dirname ${pskFile}) + echo "Generating "${pskFile}"" + psk=$(${pkgs.wireguard-tools}/bin/wg genpsk) + ${pkgs.rage}/bin/rage -e ${masterIdentityArgs} ${extraEncryptionPubkeys} <<< "$psk" > ${pskFile} \ + || { echo "error: Failed to encrypt wireguard psk for peers ${peer1} and ${peer2} on network ${wgName}!" >&2; exit 1; } + fi + ''; + + # This generates all psks for each combination of peers given. + # xs is a list of peers and fys a function that generates a list of peers + # for any given x. + psksForPeerCombinations = xs: fys: map generatePeerPsk (unique (concatMap (x: map (sortedPeers x) (fys x)) xs)); in ["echo ==== ${wgName} ===="] - ++ map generatePeerKeys peers - ++ concatMap generatePeerPsks nodesWithNet; + ++ map generatePeerKeys (attrNames allPeers) + # All server-nodes need a psk for each other, but not reflexive. + ++ psksForPeerCombinations associatedServerNodes (n: filter (x: x != n) associatedServerNodes) + # Each server-node need a psk for all client nodes + ++ psksForPeerCombinations associatedServerNodes (_: associatedClientNodes) + # Each server-node need a psk for all their external peers + ++ psksForPeerCombinations associatedServerNodes (n: attrNames (externalPeersForNode n)); in pkgs.writeShellScript "generate-wireguard-keys" '' set -euo pipefail diff --git a/nix/apps/show-wireguard-qr.nix b/nix/apps/show-wireguard-qr.nix index 0a5da1f..489888e 100644 --- a/nix/apps/show-wireguard-qr.nix +++ b/nix/apps/show-wireguard-qr.nix @@ -20,7 +20,7 @@ externalPeersForNet = wgName: map (peer: {inherit wgName peer;}) - (attrNames ((extraLib.wireguard wgName).externalPeers self.nodes)); + (attrNames (extraLib.wireguard wgName self.nodes).allExternalPeers); allExternalPeers = concatMap externalPeersForNet wireguardNetworks; in # TODO generate "classic" config and run qrencode diff --git a/nix/lib.nix b/nix/lib.nix index fa51204..053e697 100644 --- a/nix/lib.nix +++ b/nix/lib.nix @@ -11,6 +11,7 @@ mapAttrs' mergeAttrs nameValuePair + partition unique ; in rec { @@ -33,8 +34,8 @@ in rec { concatAttrs = foldl' mergeAttrs {}; # Wireguard related functions that are reused in several files of this flake - wireguard = wgName: rec { - _sortedPeers = peerA: peerB: + wireguard = wgName: nodes: rec { + sortedPeers = peerA: peerB: if peerA < peerB then { peer1 = peerA; @@ -53,32 +54,49 @@ in rec { peerPrivateKeySecret = peerName: "wireguard-${wgName}-priv-${peerName}"; peerPresharedKeyFile = peerA: peerB: let - inherit (_sortedPeers peerA peerB) peer1 peer2; + inherit (sortedPeers peerA peerB) peer1 peer2; in "secrets/wireguard/${wgName}/psks/${peer1}+${peer2}.age"; peerPresharedKeyPath = peerA: peerB: "${../.}/" + peerPresharedKeyFile peerA peerB; peerPresharedKeySecret = peerA: peerB: let - inherit (_sortedPeers peerA peerB) peer1 peer2; + inherit (sortedPeers peerA peerB) peer1 peer2; in "wireguard-${wgName}-psks-${peer1}+${peer2}"; # All nodes that are part of this network - associatedNodes = nodes: filter (n: builtins.hasAttr wgName nodes.${n}.config.extra.wireguard) (attrNames nodes); - nodePeers = nodes: genAttrs (associatedNodes nodes) (n: nodes.${n}.config.extra.wireguard.${wgName}.address); + associatedNodes = + filter + (n: builtins.hasAttr wgName nodes.${n}.config.extra.wireguard) + (attrNames nodes); + + # Partition nodes by whether they are servers + _associatedNodes_isServerPartition = + partition + (n: nodes.${n}.config.extra.wireguard.${wgName}.server.enable) + associatedNodes; + + associatedServerNodes = _associatedNodes_isServerPartition.right; + associatedClientNodes = _associatedNodes_isServerPartition.wrong; + + # Maps all nodes that are part of this network to their addresses + nodePeers = genAttrs associatedNodes (n: nodes.${n}.config.extra.wireguard.${wgName}.address); + + # Only peers that are defined as externalPeers on the given node. + # Prepends "external-" to their name. + externalPeersForNode = node: + mapAttrs' (p: nameValuePair "external-${p}") nodes.${node}.config.extra.wireguard.${wgName}.externalPeers; + # All peers that are defined as externalPeers on any node. # Prepends "external-" to their name. - externalPeers = nodes: - concatAttrs ( - map (n: mapAttrs' (extPeerName: nameValuePair "external-${extPeerName}") nodes.${n}.config.extra.wireguard.${wgName}.externalPeers) - (associatedNodes nodes) - ); - # Concatenation of all external peer names names without any transformations. - externalPeerNamesRaw = nodes: concatMap (n: attrNames nodes.${n}.config.extra.wireguard.${wgName}.externalPeers) (associatedNodes nodes); - # A list of all occurring addresses. - usedAddresses = nodes: let - nodesWithNet = associatedNodes nodes; - in - concatMap (n: nodes.${n}.config.extra.wireguard.${wgName}.address) nodesWithNet - ++ flatten (concatMap (n: attrValues nodes.${n}.config.extra.wireguard.${wgName}.externalPeers) nodesWithNet); + allExternalPeers = concatAttrs (map externalPeersForNode associatedNodes); - allPeers = nodes: nodePeers nodes // externalPeers nodes; + # All peers that are part of this network + allPeers = nodePeers // allExternalPeers; + + # Concatenation of all external peer names names without any transformations. + externalPeerNamesRaw = concatMap (n: attrNames nodes.${n}.config.extra.wireguard.${wgName}.externalPeers) associatedNodes; + + # A list of all occurring addresses. + usedAddresses = + concatMap (n: nodes.${n}.config.extra.wireguard.${wgName}.address) associatedNodes + ++ flatten (concatMap (n: attrValues nodes.${n}.config.extra.wireguard.${wgName}.externalPeers) associatedNodes); }; }