diff --git a/README.md b/README.md index 7dca090..263808c 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,23 @@ WireNix is a Nix Flake designed to make creation of Wireguard mesh networks easier. The simplist and most likely layout is a full mesh network, but Wirenix -is able to support arbitrary graph topologies. +can also support arbitrary graph topologies. # Reading the README -Due to Nix's typeless nature, I have opted to define all my configurations in -psuedo-typescript to make options more legible. I have chosen typescript +Due to Nix's dynamic typing, I have opted to define all my ACL configurations +in psuedo-typescript to make options more legible. I have chosen typescript because it looks somewhat like JSON and is easy to understand. Examples will -still be given in Nix EL. +still be given in Nix EL. Function signatures will still follow the traditional +Haskell-like function signatures seen throughout nix projects. -You can start by reading the `ACL Configuration` section, then reading -`Quick Start` section for how to use configure your machines. Other sections -exist to provide helpful context and advanced usage, but should not be -necessary for a working setup. +You can start by reading the [ACL Configuration](ACL Configuration) section, +then reading [Quick Start](Quick Start) section for how to configure your +machines. Other sections exist to provide helpful context and advanced usage, +but should not be necessary for a working setup. + +Wirenix assumes a flakes setup, that's what I use. Maybe it works without +flakes, maybe not. I'm not familiar enough with the non-flakes landscape +to provide support. I am open to making simple changes to make using this +project without flakes easier if anyone has suggestions or wants to submit +a patch. # ACL Configuration The ACL is a nix attrset designed to be represented in JSON for easy importing @@ -28,11 +35,11 @@ type ACL = { }; ``` -Version is used to check for config compatibility and is recommended. Not -specifying version will parse the configuration with the most recent parser -available and generate a warning. Using an older configuration version than -available will use the parser for that version and generate a warning. Using -a version newer than any parsers available will throw an error. +`Version` is used to check to find the right parser and is required. Using an +older. At the moment there is only "v1" builtin. + +`extraArgs` is explained later, and can be ignored unless you are trying to +make your own integrations. ## subnet: ```typescript @@ -61,14 +68,18 @@ type peer = { ipAddresses?: str[]; extraArgs?: any; // goes to intermediate config subnetConnection }; - publicKey: string; - privateKeyFile: string; + publicKey: str; + privateKeyFile: str; groups?: str[]; endpoints?: endpoint[]; extraArgs?: any; // goes to intermediate config peer }; ``` +`[subnetName: str]: {...}` means `subnets` is an object (attrset) with +string typed keys, and values that follows the typing of the nested object +`...`. + ## Connection: ```typescript type connection = { @@ -105,6 +116,9 @@ merged in this order: subnet -> group -> peer; with peer being the highest priority, overriding others. A good layout is to set ports in subnet, ip in peer, and leave group empty. group endpoints can be useful for specifying connection details across port forwarded NATs, however. +Note that `dynamicEndpointRefreshSeconds` and +`dynamicEndpointRefreshRestartSeconds` are ignored for connecting networkd +peers. ## Filter: ```typescript @@ -115,6 +129,20 @@ type filter = { }[]; // <==== Important! It's a list ``` +A filter is a list of filter rules. Each filter rule has the attributes +`type`, `rule` and `value`. `type` selects what to match with, `rule` +selects whether to invert the match (`"not"`) or not (`"is"`). `Value` is +the value to search for. Multiple filter rules in the filter list combine +as the intersection. For example: +```nix +[ + {type="group"; rule="is"; value="desktops"} + {type="peer"; rule="not"; value="joesdesktop"} +] +``` +This will select all peers in the `desktop` group, except the peer named +`joesdesktop`. + ## extraArgs `extraArgs` is intentionally left alone. I promise I won't ever set `extraArgs`, but any value in it will be forwarded on to the corresponding @@ -122,22 +150,58 @@ section in the intermediate configuration. Because of this, it can be used to pass data into user defined Configuration Modules. Most users can ignore `extraArgs`. +# Quick Start +1. Make your ACL according to the [ACL Configuration]](ACL Configuration) section. +You can look in the `examples/acl` folder for examples. +2. Include the module in your flake config: +```nix +... +inputs.wirenix.url = "sourcehut:~msalerno/wirenix"; +outputs = { self, nixpkgs, wirenix }: { + nixosConfigurations = { + example = nixpkgs.lib.nixosSystem rec { + system = "x86_64-linux"; + modules = [ + ./machines/example.nix + wirenix.nixosModules.default + ] + }; +}; +``` + +3. Configure wirenix in your nixosConfiguration (`./machines/example.nix` in this +case): +```nix +wirenix = { + enable = true; + peerName = "example" # defaults to hostname otherwise + configurer = "static" # defaults to "static", could also be "networkd" + keyProviders = ["acl"]; # could also be ["agenix-rekey"] or ["acl" "agenix-rekey"] + # secretsDir = ../../secrets; # only if you're using agenix-rekey + aclConfig = import ../../acl.nix; +}; +``` + +4. Profit + # Architecture -WireNix consists of 4 main components: +WireNix consists of 5 main components: 1. The shared ACL Configuration -2. Parser Modules -3. The intermediate Configuration -4. Configuration Modules +2. The Key Providers +3. Parser Modules +4. The intermediate Configuration +5. Configuration Modules The goal of splitting WireNix into modules is both for my own sanity when developing, and to make it hackable without requiring users to make their own fork. Users are able to specify their own Parser Modules, enabling them to use their own preferred ACL syntax if they desire. Users can also specify their own configuration modules, allowing them to add compatibility to for other network -stacks or to enable their own modules. Using both custom Parser and -Configuration modules enables essentially rewriting this flake however you see -fit, all without making a fork (although at that point I may question why you -don't write your own module from scratch). +stacks or to enable their own modules. It is also possible to add new key +providers. Using both custom Parser and Configuration modules enables +essentially rewriting this flake however you see fit, all without making a fork +(although at that point I may question why you don't write your own module from +scratch). ## ACL The shared ACL configuration should describe the full network topology. It does @@ -151,15 +215,30 @@ You can make your own ACL configuration format so long as you keep the Parser Modules are responsible for taking an ACL and converting it to the intermediate configuration format. Parser modules are selected by matching the ACL version field. A parser module must take an ACL and return the -corresponding Intermediate Configuration You can register your own parser +corresponding Intermediate Configuration. A parser has the following +interface: + +```typescript +type parser = (inputs: attrset, aclConfig: ACL) => intermediateConfiguration; +``` + +You can register your own parser module like so: ```nix -modules.wirenix.additionalParsers = { +wirenix.additionalParsers = { myParser = import ./my-parser.nix; } ``` +And then, in your ACL, set the version: + +```nix +... +version = "myParser"; +... +``` + ## Intermediate Configuration The Intermediate Configuration is a recursive attrset that is more suited for being used in a NixOS configuration than the ACL Configuration. @@ -176,9 +255,9 @@ configuration has the following structure: ### Root Structure ```typescript type intermediateConfiguration = { - peers: {[peerName: string]: peer}; - subnets: {[subnetName: string]: subnet}; - groups: {[groupName: string]: group}; + peers: {[peerName: str]: peer}; + subnets: {[subnetName: str]: subnet}; + groups: {[groupName: str]: group}; } ``` @@ -186,10 +265,10 @@ type intermediateConfiguration = { ```typescript type peer = { - subnetConnections: {[subnetName: string]: subnetConnection}; - groups: {[groupName: string]: group} - publicKey: string; - privateKeyFile: string; + subnetConnections: {[subnetName: str]: subnetConnection}; + groups: {[groupName: str]: group} + publicKey: str; + privateKeyFile: str; extraArgs?: any; }; ``` @@ -199,7 +278,7 @@ type peer = { ```typescript type subnet = { - peers: {[peerName: string]: peer}; + peers: {[peerName: str]: peer}; extraArgs?: any; }; ``` @@ -208,7 +287,7 @@ type subnet = { ```typescript type group = { - peers: {[peerName: string]: peer}; + peers: {[peerName: str]: peer}; extraArgs?: any; }; ``` @@ -218,7 +297,7 @@ type group = { ```typescript type peerConnection = { peer: peer; - ipAddresses: string[]; + ipAddresses: str[]; endpoint: endpoint; extraArgs?: any; }; @@ -229,9 +308,9 @@ type peerConnection = { ```typescript type subnetConnection = { subnet: subnet; - ipAddresses: string[]; + ipAddresses: str[]; listenPort: int; - peerConnections: {[peerName: string]: peerConnection}; + peerConnections: {[peerName: str]: peerConnection}; extraArgs?: any; }; ``` @@ -254,33 +333,96 @@ This graph can be traversed back and forth in circles until you run out of stack space. ## Configuration Modules -Configuration Modules take the Intermediate Configuration and produce NixOS -configurations from them. By default, there exist configuration modules for -setting up wireguard with the static network configuration, networkd, and -Network Manager. There is a fourth, "default" configuration module that -intelligently selects which module to use (with priority being networkd > -network manager > static configuration). However, you can manually override -which module is used (or use your own module) in your flake.nix file: +Configuration Modules take the Key provider list and Intermediate Configuration +to produce NixOS configurations. By default, there exist configuration modules +for setting up wireguard with the static network configuration (default) or +networkd configuration. A configurer has the following interface: + +```typescript +type configurer = (inputs: attrset, keyProviders: keyProvider[], intermediateConfig: intermediateConfiguration) => nixosConfiguration; +``` + +You can set which module is used (or use your +own module) in your flake.nix file: ```nix -modules.wirenix.configurer = "v0"; # there is no v0, this is just an example +wirenix.configurer = "networkd"; ``` or for your own module: ```nix -modules.wirenix.additionalConfigurers.myConfigurer = import ./my-parser.nix; -modules.wirenix.configurer = "myConfigurer"; +wirenix.additionalConfigurers.myConfigurer = import ./my-configurer.nix; +wirenix.configurer = "myConfigurer"; ``` -And then, in your ACL, set the version: - +## Key Providers +Configurers require a list of key providers to query for information about +wireguard key pairs. The providers in the list are queried in order, moving on +to the next provider if `null` is returned. This allows keeping key pairs +in multiple places, but most likely the key provider list will be a singleton. +Key Providers have the following stracture: + +```typescript +type keyProvider = { + config: nixosConfig; + getPeerPubKey: (otherPeerName: str) => str; + getPrivKeyFile: str; + getSubnetPSKFile: (subnetName: str) => str; +}; +``` + +You can add your own key providers like so: ```nix -... -version = "myConfigurer"; -... +wirenix.additionalKeyProviders.myKeyProvider = import ./my-key-provider.nix; +wirenix.keyProviders = ["myKeyProvider"]; ``` +# Integrations: +By default, WireNix supports setting wireguard keypairs with +[agenix-rekey](https://github.com/oddlama/agenix-rekey). +WireNix also supports networkd, network manager, and the nixos static network +configuration (default). + +Using networkd: +```nix +systemd.network.enable = true; +wirenix = { + enable = true; + configurer = "networkd" + aclConfig = import ./my-acl.nix; +}; +``` + +Using static configuration: +```nix +wirenix = { + enable = true; + configurer = "static" + aclConfig = import ./my-acl.nix; +}; +``` + +Using agenix-rekey (assuming it's already set up properly) +```nix +wirenix = { + enable = true; + keyProviders = ["agenix-rekey"]; + secretsDir = ../../secrets; + aclConfig = import ./my-acl.nix; +}; +``` + +Using the ACL's keypairs if specified, otherwise using agenix-rekey +(reverse order not possible) +```nix +wirenix = { + enable = true; + keyProviders = ["acl" "agenix-rekey"]; + secretsDir = ../../secrets; + aclConfig = import ./my-acl.nix; +}; +``` # Troubleshooting Wirenix tries to stay seperated from the inner working of your config for as @@ -294,10 +436,12 @@ $ nix-repl> :l $ nix-repl> :lf "sourcehut:~msalerno/wirenix" > Added 11 variables. +# named the wirenix lib 'wnlib' to prevent issues with nixpkgs.lib in the repl +$ nix-repl> parse = wnlib.defaultParsers.v1 {inherit lib;} -$ nix-repl> parse = wirenix.lib.defaultParsers.v1 {inherit lib;} +$ nix-repl> keyProviders = [wnlib.defaultKeyProviders.acl] -$ nix-repl> configure = wirenix.lib.defaultConfigurers.static {inherit lib;} +$ nix-repl> configure = wnlib.defaultConfigurers.static {inherit lib;} keyProviders $ nix-repl> acl = import ./examples/fullMesh/acl.nix # replace with your acl @@ -318,10 +462,10 @@ $ nix-repl> :p genPeerConfig "peer1" # printing the intermediate config with :p will cause a stack overflow # but we have a helper function for this -$ nix-repl> :p wirenix.lib.breakIntermediateRecursion intConfig +$ nix-repl> :p wnlib.breakIntermediateRecursion intConfig > { a bunch of hard to read data } # you can get a string and paste it into echo -e for pretty printing -$ nix-repl> lib.generators.toPretty {} (wirenix.lib.breakIntermediateRecursion intConfig) +$ nix-repl> lib.generators.toPretty {} (wnlib.breakIntermediateRecursion intConfig) > "even uglier result but it copy pastes well" ``` @@ -332,36 +476,6 @@ $ echo -e "paste the big text result from nix repl in here" > a nice result ``` -# Integrations: -By default, WireNix supports setting wireguard keypairs with -[agenix-rekey](https://github.com/oddlama/agenix-rekey). -WireNix also supports networkd, network manager, and the nixos static network -configuration (default). - -Using networkd: - -```nix -TODO -``` - -Using network manager: - -```nix -TODO -``` - -Using static configuration: - -```nix -TODO -``` - -Using agenix-rekey (assuming it's already set up properly) - -```nix -TODO -``` - # Current Issues / Drawbacks - WireNix does not do NAT traversal, it's up to you to forward the correct ports on your NAT device(s) and apply the correct firewall rules on your @@ -370,6 +484,15 @@ router(s). more dynamic, look into Tailscale/Headscale. - Peers cannot have multiple keys. If this is a desirable feature I may think of adding it, but I cannot think of a good reason for it. +- There's no testing infrastructure in place right now, and plenty of untested +scenarios. +- Currently this will create empty `sops` and `age` top level attributes in your +config if you don't already have them. It has to do with some terrible hackery +I did in `wire.nix` to prevent infinite recursion. If any wizards out there +want to send in a patch it would be mutch appreciated! + +# License +This project is licensed under the MPL 2.0 # Glosary ## ACL diff --git a/examples/fullMesh/acl.nix b/examples/acl/fullmesh.nix similarity index 100% rename from examples/fullMesh/acl.nix rename to examples/acl/fullmesh.nix diff --git a/examples/ring/acl.nix b/examples/acl/ring.nix similarity index 100% rename from examples/ring/acl.nix rename to examples/acl/ring.nix diff --git a/examples/spoke/acl.nix b/examples/acl/spoke.nix similarity index 100% rename from examples/spoke/acl.nix rename to examples/acl/spoke.nix diff --git a/flake.nix b/flake.nix index b672949..becc3fc 100644 --- a/flake.nix +++ b/flake.nix @@ -7,7 +7,7 @@ description = "A wireguard network creation tool"; outputs = { self, ... }: { - wirenix.lib = import ./lib.nix; + wnlib = import ./lib.nix; nixosModules.default = import ./wire.nix; }; }