From ea48c316cca6489e2caaf8d8f19aa2cc68ee5c39 Mon Sep 17 00:00:00 2001 From: oddlama Date: Tue, 11 Apr 2023 01:27:58 +0200 Subject: [PATCH] feat: add preliminary wireguard module --- README.md | 3 +- flake.nix | 7 +- hosts/ward/net.nix | 56 +++-------- modules/wireguard.nix | 172 +++++++++++++++++++++++++++++++++ nix/apps/draw-graph.nix | 3 +- nix/apps/show-wireguard-qr.nix | 12 +++ nix/generate-node.nix | 1 + nix/home-manager.nix | 46 --------- 8 files changed, 207 insertions(+), 93 deletions(-) create mode 100644 modules/wireguard.nix create mode 100644 nix/apps/show-wireguard-qr.nix delete mode 100644 nix/home-manager.nix diff --git a/README.md b/README.md index 811a2c4..2993955 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ This is my personal nix config. - `secrets.nix.age` Repository-wide local secrets. Decrypted on import via `builtins.extraBuiltins.rageImportEncrypted`. - `host.pub` This host's public key. Used for agenix rekeying. - `default.nix` The actual system definition. Follow the imports from there to see what it entails. - - `meta.nix` Determines the type and architecture of this system, and some other optional meta information. Used e.g. by `nix/colmena.nix` to know which hosts are NixOS and which are home-manger only. + - `meta.nix` Determines the type and architecture of this system, and some other optional meta information. Used e.g. by `nix/colmena.nix` to know which hosts are real NixOS hosts, and which are VMs or some other type. - `fs.nix` Filesystem setup. - `net.nix` Networking setup. - `nom/` - My laptop and main development machine @@ -36,7 +36,6 @@ This is my personal nix config. - `colmena.nix` Setup for distributed deployment using colmena (actually defines all NixOS hosts) - `dev-shell.nix` Environment setup for `nix develop` for using this flake - `extra-builtins.nix` Extra builtins via nix-plugins to support transparent repository-wide secrets - - `home-manager.nix` Definition of home-manager only hosts (not used currently) - `hosts.nix` Wrapper that extracts all defined hosts from `hosts/` - `rage-decrypt.sh` Auxiliary script for repository-wide secrets - `secrets.nix` Helper to access repository-wide secrets, used by colmena.nix diff --git a/flake.nix b/flake.nix index 1123a9d..00f6cdb 100644 --- a/flake.nix +++ b/flake.nix @@ -62,10 +62,11 @@ hosts = import ./nix/hosts.nix inputs; colmena = import ./nix/colmena.nix inputs; - homeConfigurations = import ./nix/home-manager.nix inputs; - microVms = import ./nix/microvms.nix inputs; + colmenaNodes = ((colmena.lib.makeHive self.colmena).introspect (x: x)).nodes; + microvmNodes = import ./nix/microvms.nix inputs; - inherit ((colmena.lib.makeHive self.colmena).introspect (x: x)) nodes; + # All nixos based hosts collected together + nodes = self.colmenaNodes // self.microvmNodes; } // flake-utils.lib.eachDefaultSystem (system: rec { pkgs = import nixpkgs { diff --git a/hosts/ward/net.nix b/hosts/ward/net.nix index e2cc253..a7a2801 100644 --- a/hosts/ward/net.nix +++ b/hosts/ward/net.nix @@ -1,7 +1,8 @@ -{nodeSecrets, ...}: let - wgName = "wg-vms"; - wgPort = 51820; -in { +{ + lib, + nodeSecrets, + ... +}: { networking.hostId = "49ce3b71"; systemd.network.networks = { @@ -21,41 +22,14 @@ in { }; }; - #systemd.network.netdevs."20-${wgName}" = { - # netdevConfig = { - # Kind = "wireguard"; - # Name = "${wgName}"; - # Description = "Wireguard network ${wgName}"; - # }; - # wireguardConfig = { - # PrivateKeyFile = wireguardPrivateKey wgName nodeMeta.name; - # ListenPort = wgPort; - # }; - # wireguardPeers = [ - # { - # wireguardPeerConfig = { - # PublicKey = wireguardPublicKey wgName nodeMeta.name;; - # PresharedKey = wireguardPresharedKey wgName nodeMeta.name;; - # AllowedIPs = [ "10.66.66.10/32" ]; - # PersistentKeepalive = 25; - # }; - # } - # { - # wireguardPeerConfig = { - # AllowedIPs = [ "10.66.66.100/32" ]; - # PersistentKeepalive = 25; - # }; - # } - # ]; - #}; - #networks."20-${wgName}" = { - # matchConfig.Name = wgName; - # networkConfig = { - # Address = "10.66.66.1/24"; - # IPForward = "ipv4"; - # }; - #}; - - #extra.wireguard.servers.home = { - #}; + imports = [../../modules/wireguard.nix]; + extra.wireguard.networks.vms = { + address = ["10.0.0.1/24"]; + listen = true; + listenPort = 51822; + openFirewall = true; + externalPeers = { + test = ["10.0.0.91/32"]; + }; + }; } diff --git a/modules/wireguard.nix b/modules/wireguard.nix new file mode 100644 index 0000000..5546a71 --- /dev/null +++ b/modules/wireguard.nix @@ -0,0 +1,172 @@ +{ + config, + lib, + pkgs, + nodes, + nodeName, + ... +}: let + inherit + (lib) + attrNames + concatMapAttrs + filter + foldl' + genAttrs + mapAttrsToList + mapAttrs' + mdDoc + mkIf + mkOption + nameValuePair + recursiveUpdate + types + ; + + cfg = config.extra.wireguard; + + peerPublicKey = wgName: peerName: builtins.readFile (../secrets/wireguard + "/${wgName}/${peerName}.pub"); + peerPrivateKeyFile = wgName: peerName: ../secrets/wireguard + "/${wgName}/${peerName}.priv.age"; + peerPrivateKeySecret = wgName: peerName: "wireguard-${wgName}-${peerName}.priv"; + + peerPresharedKeyFile = wgName: peerA: peerB: let + sortedPeers = + if peerA < peerB + then { + peer1 = peerA; + peer2 = peerB; + } + else { + peer1 = peerB; + peer2 = peerA; + }; + inherit (sortedPeers) peer1 peer2; + in + ../secrets/wireguard + "/${wgName}/${peer1}-${peer2}.psk.age"; + + peerPresharedKeySecret = wgName: peerA: peerB: let + sortedPeers = + if peerA < peerB + then { + peer1 = peerA; + peer2 = peerB; + } + else { + peer1 = peerB; + peer2 = peerA; + }; + inherit (sortedPeers) peer1 peer2; + in "wireguard-${wgName}-${peer1}-${peer2}.psk"; + + peerDefinition = wgName: peerName: peerAllowedIPs: { + wireguardPeerConfig = { + PublicKey = peerPublicKey wgName peerName; + PresharedKeyFile = config.rekey.secrets.${peerPresharedKeySecret wgName nodeName peerName}.path; + AllowedIPs = peerAllowedIPs; + + # TODO only if we are the ones listening + PersistentKeepalive = 25; + }; + }; + + configForNetwork = wgName: wg: let + # All peers that are other nodes + nodePeerNames = filter (n: n != nodeName && builtins.hasAttr wgName nodes.${n}.config.extra.wireguard.networks) (attrNames nodes); + nodePeers = genAttrs nodePeerNames (n: nodes.${n}.config.extra.wireguard.networks.${wgName}.address); + # All peers that are defined as externalPeers on any node. Also prepends "external-" to their name. + externalPeers = foldl' recursiveUpdate {} (map (n: mapAttrs' (extPeerName: nameValuePair "external-${extPeerName}") nodes.${n}.config.extra.wireguard.networks.${wgName}.externalPeers) (attrNames nodes)); + + peers = nodePeers // externalPeers; + in { + rekey.secrets = + foldl' recursiveUpdate { + ${peerPrivateKeySecret wgName nodeName}.file = peerPrivateKeyFile wgName nodeName; + } (map (peerName: { + ${peerPresharedKeySecret wgName nodeName peerName}.file = peerPresharedKeyFile wgName nodeName peerName; + }) (attrNames peers)); + + systemd.network = { + netdevs."${wg.priority}-${wgName}" = { + netdevConfig = { + Kind = "wireguard"; + Name = "${wgName}"; + Description = "Wireguard network ${wgName}"; + }; + wireguardConfig = { + PrivateKeyFile = config.rekey.secrets.${peerPrivateKeySecret wgName nodeName}.path; + ListenPort = wg.listenPort; + }; + wireguardPeers = mapAttrsToList (peerDefinition wgName) peers; + }; + + networks."${wg.priority}-${wgName}" = { + matchConfig.Name = wgName; + networkConfig.Address = wg.address; + }; + }; + }; +in { + options = { + networks = mkOption { + default = {}; + description = ""; + type = types.attrsOf (types.submodule { + options = { + address = mkOption { + type = types.listOf types.str; + description = mdDoc '' + The addresses to configure for this interface. Will automatically be added + as this peer's allowed addresses to all other peers. + ''; + }; + + listen = mkOption { + type = types.bool; + default = false; + description = mdDoc '' + Enables listening for incoming wireguard connections. + This also causes all other peers to include this as an endpoint in their configuration. + ''; + }; + + listenPort = mkOption { + default = 51820; + type = types.int; + description = mdDoc "The port to listen on, if {option}`listen` is `true`."; + }; + + priority = mkOption { + default = "20"; + type = types.str; + description = mdDoc "The order priority used when creating systemd netdev and network files."; + }; + + openFirewall = mkOption { + default = false; + type = types.bool; + description = mdDoc "Whether to open the firewall for the specified `listenPort`, if {option}`listen` is `true`."; + }; + + externalPeers = mkOption { + type = types.attrsOf (types.listOf types.str); + 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. + ''; + }; + }; + }); + }; + }; + + config = mkIf (cfg.networks != {}) ({ + # TODO assert that at least one peer has listen true + # TODO assert that no external peers are specified twice in different node configs + #assertions = []; + + networking.firewall.allowedUDPPorts = mkIf (cfg.listen && cfg.openFirewall) [cfg.listenPort]; + } + // foldl' recursiveUpdate {} (map configForNetwork cfg.networks)); +} diff --git a/nix/apps/draw-graph.nix b/nix/apps/draw-graph.nix index a53cf03..eb501e0 100644 --- a/nix/apps/draw-graph.nix +++ b/nix/apps/draw-graph.nix @@ -28,8 +28,9 @@ ${filterMapAttrsToLines (_: v: v.matchConfig ? MACAddress) renderNic node.config.systemd.network.networks} } ''; + # TODO vms graph = '' - ${mapAttrsToLines renderNode self.nodes} + ${mapAttrsToLines renderNode self.colmenaNodes} ''; in pkgs.writeShellScript "draw-graph" '' diff --git a/nix/apps/show-wireguard-qr.nix b/nix/apps/show-wireguard-qr.nix new file mode 100644 index 0000000..4b2d41f --- /dev/null +++ b/nix/apps/show-wireguard-qr.nix @@ -0,0 +1,12 @@ +{ + self, + pkgs, + ... +}: let + inherit (pkgs.lib) escapeShellArg; +in + # TODO fzf selection of all external peers pls + pkgs.writeShellScript "generate-wireguard-keys" '' + set -euo pipefail + echo TODO + '' diff --git a/nix/generate-node.nix b/nix/generate-node.nix index 87ec74c..899711e 100644 --- a/nix/generate-node.nix +++ b/nix/generate-node.nix @@ -23,6 +23,7 @@ in inherit inputs; inherit nodeName; inherit nodeMeta; + inherit (self) nodes; secrets = self.secrets.content; nodeSecrets = self.secrets.content.nodes.${nodeName}; nixos-hardware = nixos-hardware.nixosModules; diff --git a/nix/home-manager.nix b/nix/home-manager.nix deleted file mode 100644 index 3afa106..0000000 --- a/nix/home-manager.nix +++ /dev/null @@ -1,46 +0,0 @@ -{ - self, - home-manager, - nixpkgs, - templates, - ... -}: -with nixpkgs.lib; let - homeManagerHosts = filterAttrs (_: x: x.type == "homeManager") self.hosts; - moduleForHost = hostName: {homeDirectory, ...}: { - config, - pkgs, - ... - }: { - imports = [(../hosts + "/${hostName}")]; - nix.registry = { - nixpkgs.flake = nixpkgs; - p.flake = nixpkgs; - pkgs.flake = nixpkgs; - templates.flake = templates; - }; - - home = { - inherit homeDirectory; - sessionVariables.NIX_PATH = concatStringsSep ":" [ - "nixpkgs=${config.xdg.dataHome}/nixpkgs" - ]; - }; - - xdg = { - dataFile = { - nixpkgs.source = nixpkgs; - }; - configFile."nix/nix.conf".text = '' - flake-registry = ${config.xdg.configHome}/nix/registry.json - ''; - }; - }; - - genConfiguration = hostName: {system, ...} @ attrs: - home-manager.lib.homeManagerConfiguration { - pkgs = self.pkgs.${system}; - modules = [(moduleForHost hostName attrs)]; - }; -in - mapAttrs genConfiguration hostManagerHosts