From 0221843560319e9ae39346d52e8998364bc31c24 Mon Sep 17 00:00:00 2001 From: Matthew Salerno Date: Thu, 31 Aug 2023 22:05:34 -0400 Subject: [PATCH] moved readme to wiki --- README.md | 556 +----------------------------------------------------- 1 file changed, 5 insertions(+), 551 deletions(-) diff --git a/README.md b/README.md index de24c14..449e9d0 100644 --- a/README.md +++ b/README.md @@ -1,557 +1,11 @@ -[Issue Tracker](https://todo.sr.ht/~msalerno/wirenix) -[Mailing List](https://lists.sr.ht/~msalerno/wirenix) ([As Email](mailto:~msalerno/wirenix@lists.sr.ht)) +[Issue Tracker](https://todo.sr.ht/~msalerno/wirenix) ([As Email](mailto:~msalerno/wirenix@todo.sr.ht)) +[Mailing List](https://lists.sr.ht/~msalerno/wirenix) ([As Email](mailto:~msalerno/wirenix@lists.sr.ht)) +[Wiki](https://man.sr.ht/~msalerno/wirenix) ([Wiki Repo](https://git.sr.ht/~msalerno/wirenix.wiki)) 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 -can also support arbitrary graph topologies. -# Reading the README -Due to Nix's dynamic typing, I have opted to define 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. - -You can start by reading the "ACL Configuration" section, then reading the -"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 work without flakes 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 -and potential use outside of the nix ecosystem. The vast majority of all your -wirenix configuration will end up in here, with a few exceptions noted later. - -## top level acl -```typescript -type ACL = { - version?: str; - subnets: subnet[]; - groups: group[]; - peers: peer[]; - connections: connection[]; - extraArgs?: attrset; // goes to intermediate config -}; -``` - -`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 -type subnet = { - name: str; - endpoints?: endpoint[]; - presharedKeyFile?: str; - extraArgs?: attrset; // goes to intermediate config subnet -}; -``` - -! WARNING ! If your subnet name has a ".", the wireguard connection's name will -be everything preceding the ".". This is because there is a hard limit of 15 -characters for wireguard connection names. For example, if your subnet is named -"myVPN.mydomain.com" (18 characters), the wireguard connection will be named -"myVPN". This is useful because the longer name will still be used to generate -a unique ip and in the hosts file, just not for the netdev name. This -unfortunately means that you cannot share subdomains across different domain -on the same machine. A workaround is to use dashes if your FQDN is under 15 -characters. - -## Group -```typescript -type group = { - name: str; - endpoints?: endpoint[]; - extraArgs?: attrset; // goes to intermediate config group -}; -``` - -## Peer: -```typescript -type peer = { - name: str; - subnets: [subnetName: str]: { - listenPort: int; - ipAddresses?: str[]; - extraArgs?: attrset; // goes to intermediate config subnetConnection - }; - publicKey: str; - privateKeyFile: str; - groups?: str[]; - endpoints?: endpoint[]; - extraArgs?: attrset; // goes to intermediate config peer -}; -``` - -"`[subnetName: str]: {...}`" means "`subnets`" is an attrset with -string typed keys, and values that follow the typing of the nested object -"`...`". - -## Connection -```typescript -type connection = { - a: filter; - b: filter; - subnets: str[]; - extraArgs?: attrset; // merged into intermediate config peerConnection -}; -``` - -Connections connect all peers matching filter `a` to all peers matching -filter `b`, and all peers matching filter `b` to all peers matching filter `a` -subnets filters the connection to only be made over the subnets listed. It is -recomended to use the subnets property iff the `subnet` filter is also used -(the `subnet` filter on its own will connect all shared subnets of machines in -`a` and `b`, even subnets not mentioned in the filters if they are shared). - -## Endpoint: -```typescript -type endpoint = { - match?: filter; - ip?: str; - port?: int; - persistentKeepalive?: int; - dynamicEndpointRefreshSeconds?: int; - dynamicEndpointRefreshRestartSeconds?: int; - extraArgs?: attrset; // merged to intermediate config peerConnection.endpoin -}; -``` - -Endpoints are merged in this order: First lists of endpoints are merged top to -bottom, with the bottom endpoints overriding the top ones. Then, lists are -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 -type filter = { - type: ["peer" | "group" | "subnet"]; - rule: [ "is" | "not" ]; - value: str; -}[]; // <==== 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 -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" 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 5 main components: -1. The shared ACL Configuration -2. Parser Modules -3. The intermediate Configuration -4. Configuration Modules -5. The Key Providers - - -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. 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 describes the full network topology. It does not -need to consist only of NixOS peers The details of this file are documented in -the "Top Level ACL" section. You can make your own ACL configuration format so -long as you keep the "`version`" field and set it to some unique name. - -## Parser Modules -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. A parser has the following -interface: - -```typescript -type parser = (inputs: attrset, aclConfig: ACL) => intermediateConfiguration; -``` - -You can register your own parser -module like so: - -```nix -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. Unlike the ACL, -the intermediate configuration is more verbose, easier to traverse, contains -duplicate information, and is recursive. This allows cross version -compatibility so long as the intermediate configuration doesn't change. Any -changes will likely only be the addition of optional features that do not -interfere with existing intermediate configuration use, though at this stage -there are no guarentees. -Take note while reading that certain structures may be similar to the ACL, -but they are not necessarily the same as their ACL counterparts. - -### Root Structure -```typescript -type intermediateConfiguration = { - peers: {[peerName: str]: peer}; - subnets: {[subnetName: str]: subnet}; - groups: {[groupName: str]: group}; - extraArgs?: attrset; -} -``` - -### Peer - -```typescript -type peer = { - subnetConnections: {[subnetName: str]: subnetConnection}; - publicKey: str; - privateKeyFile: str; - groups?: {[groupName: str]: group} - extraArgs?: attrset; -}; -``` - - -### Subnet - -```typescript -type subnet = { - peers: {[peerName: str]: peer}; - presharedKeyFile?: str; - extraArgs?: attrset; -}; -``` - -### Group - -```typescript -type group = { - peers: {[peerName: str]: peer}; - extraArgs?: attrset; -}; -``` - -### Subnet Connection - -```typescript -type subnetConnection = { - subnet: subnet; - ipAddresses: str[]; - listenPort: int; - peerConnections: {[peerName: str]: peerConnection}; - extraArgs?: attrset; -}; -``` - -### Peer Connection - -```typescript -type peerConnection = { - peer: peer; - ipAddresses: str[]; - endpoint: endpoint; - extraArgs?: attrset; -}; -``` - -### Endpoint - -```typescript -type endpoint = { - ip: str; - port: int; - persistentKeepalive?: int; - dynamicEndpointRefreshSeconds?: int; - dynamicEndpointRefreshRestartSeconds?: int; - extraArgs?: attrset; -}; -``` - -## Configuration Modules -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 -wirenix.configurer = "networkd"; -``` - -or for your own module: - -```nix -wirenix.additionalConfigurers.myConfigurer = import ./my-configurer.nix; -wirenix.configurer = "myConfigurer"; -``` - -## 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 -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 using either networkd or 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 -long as possible. As a result, you can do most of your troubleshooting in the -nix repl: - -```sh -$ nix repl -$ nix-repl> :l -> Added 17766 variables. - -$ 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> keyProviders = [wnlib.defaultKeyProviders.acl] - -$ nix-repl> configure = wnlib.defaultConfigurers.static {inherit lib;} keyProviders - -$ nix-repl> acl = import ./examples/fullMesh/acl.nix # replace with your acl - -# get intermediate config -$ nix-repl> intConfig = parse acl -# you can explore the structure -$ nix-repl> intConfig -> { groups = { ... }; peers = { ... }; subnets = { ... }; } -# we can also see what the generated network config would be -$ nix-repl> genPeerConfig = configure intConfig -# `configure` is only partially applied, and genPeerConfig still needs a peer name -$ nix-repl> genPeerConfig -> «lambda @ /nix/store/h8gyjv62yddarvr533vi8f2rh5w0wh1p-source/configurers/static.nix:1:33» - -# we can then inspect the result -$ nix-repl> :p genPeerConfig "peer1" -> { networking = { wireguard = { interfaces = { ... }; }; } - -# printing the intermediate config with :p will cause a stack overflow -# but we have a helper function for this -$ 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 {} (wnlib.breakIntermediateRecursion intConfig) -> "even uglier result but it copy pastes well" -``` - -In your terminal: - -```sh -$ echo -e "paste the big text result from nix repl in here" -> a nice result -``` - -# Current Issues / Drawbacks -- WireNix does not do NAT traversal, routing, DNS (although it will add to the -hosts file), or anything like that. It's up to you to forward the correct -ports, set the right firewall rules, make routing rules, do NAT traversal, etc. -- WireNix does not allow for dynamic addition of peers. If you need something -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 Nix wizards out there -want to send in a patch it would be mutch appreciated! - -# Other Bits -- The netdev, network, and interfaces options for a subnet on a client will -have the same key as your subnet name, except in the case where the name -contains a '.', in which case it will be truncated to the first stretch of text -before the '.'. Networkd files have a priority of 50. -- It's not unexpected to add your own configuration to these options. If -something breaks, reach out. -- Sourcehut is weird, if you don't know, you can submit issues on -[my wirenix tracker](https://todo.sr.ht/~msalerno/wirenix) -and read the -[git email guide](https://git-send-email.io/) -for info on submitting patches. You can also email issues to -~msalerno/wirenix@todo.sr.ht -And they'll end up on the same issue tracker. +can also support arbitrary graph topologies. Please see (the wiki)[] for up to +date info # License This project is licensed under the MPL 2.0 - -# Glosary -## ACL -Access Control List: -This is your shared configuration for the network. - -## Subnet -In Wirenix, the word subnet represents any network of connected peers. -In the implementation, subnets are keyed by their `name` property. Subnet names -define the initial 32 bits after `fd` in of an the IPv6 addresses peers -connecting to the subnet will use. Generally speaking, one subnet = one -wireguard interface for each client on the subnet. - -## Peer - In Wirenix, peer is any machine with a unique public key In the - implementation, peer names define last 80 bits of their IPv6 address. - -## Group -In Wirenix, a group is just a tag that peers can have. These are used for -matching peers and can contain arbitrary names. - -## Endpoint -In wirenix, an endpoint specifies external IP of a peer that other peers should -connect to. -In the ACL configuration, endpoints can exist on subnets, groups, and peers, -but these are just for convenience. Think of adding an endpoint to a subnet or -group as being the same as adding the endpoint to all peers in the subnet or -group. -Endpoints have filters, which can specify for which connecting clients the -endpoint will apply to. - -## Filter -In Wirenix, a filter is used to select peers by their subnets, groups, and -names. A filter is made up of filter rules, specifying multiple rules will -yield the intersection of those rules. -Note that selecting by peer name will always return a list of 1 or 0 entries, -on account of names needing to be unique. \ No newline at end of file