you know it's good when you keep rewriting it
parent
e58ea8655e
commit
5a3c11f81f
@ -1,145 +0,0 @@
|
||||
{lib}: wirenix-config:
|
||||
with lib.attrsets;
|
||||
with lib.lists;
|
||||
with lib.trivial;
|
||||
with builtins;
|
||||
let
|
||||
# Math
|
||||
# extract-key :: string -> list -> attrSet
|
||||
# Example:
|
||||
# listOfSetsToSetByKey "primary" [ {primary = "foo"; secondary = 1; tertiary = "one"} {primary = "bar"; secondary = 2; tertiary = "two"} ]
|
||||
# {foo = {secondary = 1; tertiary = "one"}; bar = {secondary = 2; tertiary = "two"};}
|
||||
listOfSetsToSetByKey = key: list:
|
||||
listToAttrs (
|
||||
forEach list (elem: {
|
||||
name = elem."${key}";
|
||||
value = removeAttrs elem [ key ];
|
||||
})
|
||||
);
|
||||
# returns true if `elem` is in `list`
|
||||
inList = elem: list: any (e: e == elem) list;
|
||||
# adds colons to a string every 4 characters for IPv6 shenanigans
|
||||
add-colons = string:
|
||||
if ((stringLength string) > 4)
|
||||
then
|
||||
((substring 0 4 string) + ":" + (add-colons (substring 4 32 string)))
|
||||
else string;
|
||||
|
||||
# Peer Information
|
||||
# Helper functions for querying data about peers from the config
|
||||
peerInfo = {
|
||||
# last 20 (hardcoded atm) characters (80 bits) of the peer's IPv6 address
|
||||
ipSuffix = peerName: substring 0 20 (hashString "sha256" peerName);
|
||||
# list of subnets (as subnet attrset in wirenix config) the peer belongs to
|
||||
subnets = peer: filter (subnet: inList subnet.name peer.subnets) wirenix-config.subnets;
|
||||
# list of groups the peer connects to
|
||||
groupConnections = peer: catAttrs "group" peer.connections;
|
||||
# list of peers the peer connects to
|
||||
directConnections = peer: catAttrs "peer" peer.connections;
|
||||
# list of subnets for which the peer will try to connect to all peer in the subnet
|
||||
subnetConnections = peer: catAttrs "subnet" peer.connections;
|
||||
# gets the peer (as peer attrset in wirenix config) from a name
|
||||
peerFromName = peerName: head (filter (peer: peer.name == peerName) wirenix-config.peers);
|
||||
# gets all peers (as peer attrset in wirenix config) the are in a group
|
||||
peersInGroup = groupName: filter (peer: inList groupName peer.groups) wirenix-config.peers;
|
||||
# returns true if peer is in group
|
||||
peerIsInGroup = peer: groupName: inList peer (peerInfo.peersInGroup groupName);
|
||||
# gets all peers (as peer attrset in wirenix config) that the given peer connects to, may contain the peer itself and duplicates
|
||||
connectionsUnfiltered = peer:
|
||||
(map (peerInfo.peerFromName) (peerInfo.directConnections peer)) ++
|
||||
(concatMap (group: peerInfo.peersInGroup group) (peerInfo.groupConnections peer)) ++
|
||||
(concatMap (subnet: subnetInfo.peersInSubnet subnet) (peerInfo.subnetConnections peer));
|
||||
# gets all peers (as peer attrset in wirenix config) that the given peer connects to, will not contain the peer itself or duplicates
|
||||
connections = peer: remove peer (unique (peerInfo.connectionsUnfiltered peer));
|
||||
# returns the peer's IP when given the peer's name and subnet name
|
||||
ip = subnetName: PeerName: (add-colons ((subnetInfo.prefix subnetName) + (peerInfo.ipSuffix PeerName))) + "/80";
|
||||
# returns the endpoint for peerTo that peerFrom will connect with
|
||||
endpointMatches = peerFrom: peerTo: map (matched: removeAttrs matched [ "match" ]) (
|
||||
filter (endpoint:
|
||||
if endpoint == {} then true else
|
||||
all (id) (
|
||||
mapAttrsToList (type: value:
|
||||
if (type == "group") then
|
||||
(peerInfo.peerIsInGroup peerFrom value)
|
||||
else if (type == "peer") then
|
||||
(peerFrom.name == endpoint.match.peer)
|
||||
else if (type == "subnet") then
|
||||
(peerInfo.peerIsInSubnet peerFrom (subnetInfo.subnetFromName value))
|
||||
else throw "Unexpected type "+type+" in endpoints config."
|
||||
) endpoint
|
||||
)
|
||||
) peerTo.endpoints
|
||||
);
|
||||
endpoint = foldl' (mergeAttrs) {} endpointMatches;
|
||||
};
|
||||
# Subnet Information
|
||||
# Helper functions for querying data about subnets from the config
|
||||
subnetInfo = {
|
||||
# gets all peers (as peer attrset in wirenix config) that belong to the subnet
|
||||
peersInSubnet = subnet: filter (peer: inList subnet.name peer.subnets) wirenix-config.peers;
|
||||
# returns true if peer is in subnet
|
||||
peerIsInSubnet = subnet: peer: inList peer (subnetInfo.peersInSubnet subnet);
|
||||
# gets the subnet (as subnet attrset in wirenix config) from a name
|
||||
subnetFromName = subnetName: head (filter (subnet: subnet.name == subnetName) wirenix-config.subnets);
|
||||
# gets the first 10 characters of the IPV6 address for the subnet name
|
||||
prefix = subnetName: "fd" + (substring 0 10 (hashString "sha256" subnetName));
|
||||
};
|
||||
|
||||
# Mappers take a peer or subnet from the config and convert it
|
||||
# into a recursive attrset that is better suited for nix configs
|
||||
mappers = rec {
|
||||
|
||||
# Maps a wirenix config subnet to a recursive attrset, structure is as follows:
|
||||
# peers: a set of peers (as similar recursive attrsets), keyed by name
|
||||
# ip: the subnet ip in CIDR notation
|
||||
subnetMap = subnet: {
|
||||
name = subnet.name;
|
||||
peers = listOfSetsToSetByKey "name" (map (peerMap) (subnetInfo.peersInSubnet subnet));
|
||||
ip = (add-colons (subnetInfo.prefix subnet.name)) + "::/80";
|
||||
};
|
||||
|
||||
# Maps a wirenix config peer to a recursive attrset, structure is as follows:
|
||||
# subnets: a set of subnets (as similar recursive attrsets), keyed by name, with the following structure:
|
||||
# subnet: the subnet as described in the subnetMap function's description
|
||||
# ip: the peer's ip on the subnet (can only be one IP at the moment)
|
||||
# peers: peers (as recursive attrset) on the subnet that the current peer has connections to, keyed by name, with the following structure:
|
||||
# peer: the peer as described by this description
|
||||
# ip: the connected peers ip on the subnet (same as subnets[subnetName].peers[peerName].subnets[subnetName].ip)
|
||||
# endpoint: the endpoint to connect to, described as follows:
|
||||
# ip: the ip to connect to
|
||||
# port: the port to connect to
|
||||
# persistentKeepalive: (optional) see networking.wireguard.interfaces.<name>.*.persistentKeepalive
|
||||
# dynamicEndpointRefreshSeconds: (optional) see networking.wireguard.interfaces.<name>.*.dynamicEndpointRefreshSeconds
|
||||
# dynamicEndpointRefreshRestartSeconds: (optional) see networking.wireguard.interfaces.<name>.*.dynamicEndpointRefreshRestartSeconds
|
||||
# connections: a set of all peers (as recursive attrset) which the current peer connects to, keyed by name
|
||||
# publicKey: the peer's public key
|
||||
# privateKeyFile: the location of the peer's private key
|
||||
peerMap = peer: {
|
||||
name = peer.name;
|
||||
subnets = listOfSetsToSetByKey "name" (
|
||||
map (subnet: {
|
||||
name = subnet.name;
|
||||
subnet = removeAttrs (subnetMap subnet) [ "name" ];
|
||||
ip = peerInfo.ip subnet.name peer.name;
|
||||
peers = listOfSetsToSetByKey "name" (
|
||||
(map (peerTo: {
|
||||
name = peerTo.name;
|
||||
peer = removeAttrs (peerMap peerTo) [ "name" ];
|
||||
ip = peerInfo.ip subnet.name peerTo.name;
|
||||
endpoint = peerInfo.endpoint peer peerTo;
|
||||
}) (filter (otherPeer: inList subnet.name otherPeer.subnets) (peerInfo.connections peer)))
|
||||
);
|
||||
}) (peerInfo.subnets peer)
|
||||
);
|
||||
connections = listOfSetsToSetByKey "name" (map (peerMap) (peerInfo.connections peer));
|
||||
publicKey = peer.publicKey;
|
||||
privateKeyFile = peer.privateKeyFile;
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
peerFromName = removeAttrs (mappers.peerMap peerInfo.peerFromName) [ "name" ];
|
||||
subnetFromName = removeAttrs (mappers.subnetMap subnetInfo.subnetFromName) [ "name" ];
|
||||
peers = listOfSetsToSetByKey "name" (map (mappers.peerMap) (wirenix-config.peers));
|
||||
subnets = listOfSetsToSetByKey "name" (map (mappers.subnetMap) (wirenix-config.subnets));
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
{
|
||||
"nodes": {
|
||||
"root": {}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
{lib, ...}: v1_acl:
|
||||
with lib.attrsets;
|
||||
with lib.lists;
|
||||
with lib.trivial;
|
||||
with (import ../lib.nix);
|
||||
with builtins;
|
||||
let
|
||||
/** parsePeer :: acl_peer -> ic_peer */
|
||||
parsePeer = acl_peer: {
|
||||
subnetConnections = listOfSetsToSetByKey "name" (pipeMap [subnetFromName (getSubnetConnectionAndName acl_peer)] acl_peer.subnets);
|
||||
publicKey = acl_peer.publicKey;
|
||||
privateKeyFile = acl_peer.privateKeyFile;
|
||||
} //
|
||||
(if acl_peer ? extraArgs then {extraArgs = acl_peer.extraArgs;} else {}) //
|
||||
{
|
||||
publicKey = acl_peer.publicKey;
|
||||
privateKeyFile = acl_peer.privateKeyFile;
|
||||
} //
|
||||
(if acl_peer ? groups then {groups = map groupFromName acl_peer.groups;} else {groups = [];});
|
||||
|
||||
/** parseGroup :: acl_group -> ic_group */
|
||||
parseGroup = acl_group: {
|
||||
peers = mapListOfSetsToSetByKey parsePeer (selectPeers [{type="group"; rule="is"; value="${acl_group.name}";}]);
|
||||
} // (if acl_group ? extraArgs then {extraArgs = acl_group.extraArgs;} else {});
|
||||
|
||||
/** parseSubnet :: acl_subnet -> ic_subnet */
|
||||
parseSubnet = acl_subnet: {
|
||||
peers = mapListOfSetsToSetByKey parsePeer (selectPeers [{type="subnet"; rule="is"; value="${acl_subnet.name}";}]);
|
||||
} // (if acl_subnet ? extraArgs then {extraArgs = acl_subnet.extraArgs;} else {});
|
||||
|
||||
/** getSubnetConnection :: acl_peer -> acl_subnet -> (subnetConnection // {name}) */
|
||||
getSubnetConnectionAndName = acl_peer: acl_subnet: {
|
||||
name = acl_subnet.name; # name gets removed shortly after, name is not in the actual subnetConnection object
|
||||
subnet = parseSubnet acl_subnet;
|
||||
ipAddresses = getIpAddresses acl_peer acl_subnet;
|
||||
listenPort = acl_peer.subnets."${acl_subnet.name}".listenPort;
|
||||
peerConnections = getPeerConnections acl_peer acl_subnet;
|
||||
} // (if acl_peer.subnets."${acl_subnet.name}" ? extraArgs then {extraArgs = acl_peer.subnets."${acl_subnet.name}".extraArgs;} else {});
|
||||
|
||||
/** getIpAddresses :: acl_peer -> acl_subnet -> [str] */
|
||||
getIpAddresses = acl_peer: acl_subnet:
|
||||
if (acl_peer.subnets."${acl_subnet.name}" ? ipAddresses) then (
|
||||
if (elem "auto" acl_peer.subnets."${acl_subnet.name}".ipAddresses) then (
|
||||
(remove "auto" acl_peer.subnets."${acl_subnet.name}".ipAddresses) ++ (singleton (generateIPv6Address acl_peer.name acl_subnet.name))
|
||||
) else acl_peer.subnets."${acl_subnet.name}".ipAddresses
|
||||
) else (singleton (generateIPv6Address acl_peer.name acl_subnet.name));
|
||||
|
||||
/** getPeerConnections :: acl_peer -> acl_subnet -> str -> peerConnection */
|
||||
getPeerConnections = acl_peerFrom: acl_subnet:
|
||||
let
|
||||
filterSubnets = connection: elem acl_subnet.name connection.subnets;
|
||||
filterPeer = key: acl_peer: connection: elem acl_peer.name (catAttrs "name" (selectPeers connection."${key}"));
|
||||
getConnectionsX = key: filter (connection: all (x: x connection) [filterSubnets (filterPeer key acl_peerFrom)]) v1_acl.connections;
|
||||
getConnectionsA = getConnectionsX "a";
|
||||
getConnectionsB = getConnectionsX "b";
|
||||
allPeers = unique ((concatMap (connection: selectPeers connection.b) getConnectionsA) ++ (concatMap (connection: selectPeers connection.a) getConnectionsB));
|
||||
allOtherPeers = remove acl_peerFrom allPeers;
|
||||
getExtraArgs = acl_peerTo:
|
||||
let
|
||||
connections = (filter (filterPeer "a" acl_peerTo) getConnectionsB) ++ (filter (filterPeer "b" acl_peerTo) getConnectionsA);
|
||||
extraArgsList = catAttrs "extraArgs" connections;
|
||||
in
|
||||
foldl' mergeAttrs {} extraArgsList;
|
||||
in
|
||||
listOfSetsToSetByKey "name" (map (acl_peerTo:
|
||||
{
|
||||
name = acl_peerTo.name;
|
||||
peer = parsePeer acl_peerTo;
|
||||
ipAddresses = getIpAddresses acl_peerTo acl_subnet;
|
||||
endpoint = getEndpoint acl_peerFrom acl_peerTo;
|
||||
extraArgs = getExtraArgs acl_peerTo;
|
||||
}) allOtherPeers);
|
||||
|
||||
/** getEndpoint :: acl_peer -> acl_peer -> ic_endpoint */
|
||||
getEndpoint = acl_peerFrom: acl_peerTo:
|
||||
let
|
||||
getAllEndpointMatches = filter (endpoint: elem acl_peerFrom.name (catAttrs "name" (selectPeers (if endpoint ? match then endpoint.match else [])))) acl_peerTo.endpoints;
|
||||
in
|
||||
removeAttrs (foldl' mergeAttrs {} getAllEndpointMatches) [ "match" ];
|
||||
|
||||
/** selectPeers :: [acl_filters] -> str -> [acl_peer]
|
||||
* (str -> ic_peer) means it returns an attrset of peers keyed by name, typescript syntax:
|
||||
* selectPeers(acl: acl, acl_filters: acl_filter[]): {[peerName: string]: ic_peer};
|
||||
*/
|
||||
selectPeers = acl_filters:
|
||||
if length acl_filters == 0
|
||||
then
|
||||
v1_acl.peers
|
||||
else
|
||||
foldl' intersectAttrs (selectPeersSingleFilter (head acl_filters)) (map selectPeersSingleFilter acl_filters);
|
||||
|
||||
/** selectPeersSingleFilter :: acl_filter -> [acl_peer] */
|
||||
selectPeersSingleFilter = acl_filter:
|
||||
with acl_filter;
|
||||
let
|
||||
applyRule = comparison: if rule == "is" then comparison else if rule == "not" then !comparison else throw ("Unknown filter rule " + rule);
|
||||
in
|
||||
if type == "peer" then
|
||||
(filter (acl_peer: applyRule (acl_peer.name == value)) v1_acl.peers)
|
||||
else if type == "group" then
|
||||
(filter (acl_peer: applyRule (elem value acl_peer.groups)) v1_acl.peers)
|
||||
else if type == "subnet" then
|
||||
(filter (acl_peer: applyRule (elem value (attrNames acl_peer.subnets))) v1_acl.peers)
|
||||
else throw ("Unknown filter type " + type);
|
||||
|
||||
groupFromName = groupName: findSingle
|
||||
(group: group.name == groupName)
|
||||
(throw "No group " + groupName)
|
||||
(throw "Multiply defined group " + groupName)
|
||||
v1_acl.groups;
|
||||
|
||||
subnetFromName = subnetName: findSingle
|
||||
(subnet: subnet.name == subnetName)
|
||||
(throw "No subnet " + subnetName)
|
||||
(throw "Multiply defined subnet " + subnetName)
|
||||
v1_acl.subnets;
|
||||
|
||||
|
||||
in
|
||||
{
|
||||
peers = mapListOfSetsToSetByKey parsePeer v1_acl.peers;
|
||||
subnets = mapListOfSetsToSetByKey parseSubnet v1_acl.subnets;
|
||||
groups = mapListOfSetsToSetByKey parseGroup v1_acl.groups;
|
||||
}
|
@ -0,0 +1,307 @@
|
||||
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.
|
||||
# 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
|
||||
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
|
||||
`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.
|
||||
|
||||
# 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.
|
||||
|
||||
# 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.
|
||||
|
||||
## top level acl:
|
||||
```typescript
|
||||
type ACL = {
|
||||
version?: str;
|
||||
subnets: subnet[];
|
||||
groups: group[];
|
||||
peers: peer[];
|
||||
connections: connection[];
|
||||
extraArgs?: any;
|
||||
};
|
||||
```
|
||||
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.
|
||||
|
||||
## subnet:
|
||||
```typescript
|
||||
type subnet = {
|
||||
name: str;
|
||||
endpoints?: endpoint[];
|
||||
extraArgs?: any;
|
||||
};
|
||||
```
|
||||
|
||||
## Group:
|
||||
```typescript
|
||||
type group = {
|
||||
name: str;
|
||||
endpoints?: endpoint[];
|
||||
extraArgs?: any;
|
||||
};
|
||||
```
|
||||
|
||||
## Peer:
|
||||
```typescript
|
||||
type peer = {
|
||||
name: str;
|
||||
subnets: [subnetName: str]: {
|
||||
listenPort: int;
|
||||
ipAddresses?: str[];
|
||||
extraArgs?: any;
|
||||
};
|
||||
groups?: str[];
|
||||
endpoints?: endpoint[];
|
||||
extraArgs?: any;
|
||||
};
|
||||
```
|
||||
|
||||
## Connection:
|
||||
```typescript
|
||||
type connection = {
|
||||
a: filter;
|
||||
b: filter;
|
||||
subnets: str[];
|
||||
extraArgs?: any;
|
||||
};
|
||||
```
|
||||
|
||||
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?: any;
|
||||
};
|
||||
```
|
||||
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.
|
||||
|
||||
## Filter:
|
||||
```typescript
|
||||
type filter = {
|
||||
type: ["peer" | "group" | "subnet"];
|
||||
rule: [ "is" | "not" ];
|
||||
value: str;
|
||||
}[]; // <==== Important! It's a list
|
||||
```
|
||||
|
||||
## 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`.
|
||||
|
||||
# Architecture
|
||||
WireNix consists of 4 main components:
|
||||
1. The shared ACL Configuration
|
||||
2. Parser Modules
|
||||
3. The intermediate Configuration
|
||||
4. 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).
|
||||
|
||||
## ACL
|
||||
The shared ACL configuration should describe the full network topology. It does
|
||||
not need to consist only of NixOS peers (although at the moment, other peers
|
||||
will have to be configured manually to conform to the expected settings). 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. You can then register your
|
||||
parser which takes your ACL and produces an intermediate configuration like so:
|
||||
|
||||
TODO
|
||||
|
||||
## 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 You can register your own parser
|
||||
module like so:
|
||||
|
||||
TODO
|
||||
|
||||
## 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, repeats itself often, 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.
|
||||
It can be assumed that all types mentioned are types for the intermediate
|
||||
connection and NOT the related to types in the ACL. The intermediate
|
||||
configuration has the following structure:
|
||||
|
||||
### Root Structure
|
||||
```typescript
|
||||
type intermediateConfiguration = {
|
||||
peers: {[peerName: string]: peer};
|
||||
subnets: {[subnetName: string]: subnet};
|
||||
groups: {[groupName: string]: group};
|
||||
}
|
||||
```
|
||||
|
||||
### Peer
|
||||
```typescript
|
||||
type peer = {
|
||||
subnetConnections: {[subnetName: string]: subnetConnection};
|
||||
groups: {[groupName: string]: group}
|
||||
publicKey: string;
|
||||
privateKeyFile: string;
|
||||
extraArgs?: any;
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
### Subnet
|
||||
```typescript
|
||||
type subnet = {
|
||||
peers: {[peerName: string]: peer};
|
||||
extraArgs?: any;
|
||||
};
|
||||
```
|
||||
|
||||
### Group
|
||||
```typescript
|
||||
type group = {
|
||||
peers: {[peerName: string]: peer};
|
||||
extraArgs?: any;
|
||||
};
|
||||
```
|
||||
|
||||
### Peer Connection
|
||||
```typescript
|
||||
type peerConnection = {
|
||||
peer: peer;
|
||||
ipAddresses: string[];
|
||||
endpoint: endpoint;
|
||||
extraArgs?: any;
|
||||
};
|
||||
```
|
||||
|
||||
### Subnet Connection
|
||||
```typescript
|
||||
type subnetConnection = {
|
||||
subnet: subnet;
|
||||
ipAddresses: string[];
|
||||
listenPort: int;
|
||||
peerConnections: {[peerName: string]: peerConnection};
|
||||
extraArgs?: any;
|
||||
};
|
||||
```
|
||||
|
||||
### Endpoint
|
||||
```typescript
|
||||
type endpoint = {
|
||||
ip: str;
|
||||
port: int;
|
||||
persistentKeepalive?: int;
|
||||
dynamicEndpointRefreshSeconds?: int;
|
||||
dynamicEndpointRefreshRestartSeconds?: int;
|
||||
extraArgs?: any;
|
||||
};
|
||||
```
|
||||
|
||||
Unlike the ACL, this structure is recursive, resembling an arbitrary graph.
|
||||
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:
|
||||
|
||||
TODO
|
||||
|
||||
# Integrations:
|
||||
By default, WireNix supports setting wireguard keypairs with agenix-rekey if
|
||||
the module is detected (via the existence of `config.rekey`). WireNix also
|
||||
supports networkd, network manager, and the nixos static network configuration
|
||||
(in that order of preference).
|
||||
|
||||
# Current Issues / Drawbacks
|
||||
- To keep configuration and the initial POC simpler, assigning IP addresses
|
||||
manually on subnets is not supported (yet).
|
||||
- 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
|
||||
router(s).
|
||||
- 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.
|
@ -1,120 +0,0 @@
|
||||
{
|
||||
version = "1";
|
||||
subnets = [
|
||||
{ name = "subnet.one"; defaultPort = 51820; }
|
||||
{ name = "subnet.two"; defaultPort = 51821; }
|
||||
{ name = "subnet.three"; defaultPort = 51822; }
|
||||
];
|
||||
peers = [
|
||||
{
|
||||
name = "peer.zero";
|
||||
endpoints = [
|
||||
{match = {group = "subnet two group";}; ip = "1.1.1.1"; port = 51820;}
|
||||
{match = {peer = "peer.one";}; ip = "2.2.2.2"; port = 51820;}
|
||||
{match = {}; ip = "3.3.3.3"; port = 51820;}
|
||||
];
|
||||
subnets = [
|
||||
|
||||
];
|
||||
groups = [
|
||||
|
||||
];
|
||||
connections = [
|
||||
{ group = "everyoneConnectsToMe"; }
|
||||
];
|
||||
privateKeyFile = "/not/yet";
|
||||
publicKey = "testData";
|
||||
presharedKeyFile = "testData2";
|
||||
}
|
||||
{
|
||||
name = "peer.one";
|
||||
endpoints = [
|
||||
{match = {group = "subnet two group";}; ip = "1.1.1.1"; port = 51820; persistentKeepalive = 15;}
|
||||
{match = {peer = "peer.one";}; ip = "2.2.2.2"; port = 51820;}
|
||||
{match = {}; ip = "3.3.3.3"; port = 51820;}
|
||||
];
|
||||
subnets = [
|
||||
"subnet.one"
|
||||
];
|
||||
groups = [
|
||||
|
||||
];
|
||||
connections = [
|
||||
{ group = "everyoneConnectsToMe"; }
|
||||
{ group = "subnet one group"; }
|
||||
];
|
||||
privateKeyFile = "/not/yet";
|
||||
publicKey = "testData";
|
||||
presharedKeyFile = "testData2";
|
||||
}
|
||||
{
|
||||
name = "peer.two";
|
||||
endpoints = [
|
||||
{match = {group = "subnet two group";}; ip = "1.1.1.1"; port = 51820;}
|
||||
{match = {peer = "peer.one";}; ip = "2.2.2.2"; port = 51820;}
|
||||
{match = {}; ip = "3.3.3.3"; port = 51820;}
|
||||
];
|
||||
subnets = [
|
||||
"subnet.one"
|
||||
"subnet.two"
|
||||
];
|
||||
groups = [
|
||||
"everyoneConnectsToMe"
|
||||
"subnet two group"
|
||||
];
|
||||
connections = [
|
||||
{ group = "everyoneConnectsToMe"; }
|
||||
{ group = "subnet two group"; }
|
||||
];
|
||||
privateKeyFile = "/not/yet";
|
||||
publicKey = "testData";
|
||||
presharedKeyFile = "testData2";
|
||||
}
|
||||
{
|
||||
name = "peer.three";
|
||||
endpoints = [
|
||||
{match = {group = "subnet two group";}; ip = "1.1.1.1"; port = 51820;}
|
||||
{match = {peer = "peer.one";}; ip = "2.2.2.2"; port = 51820;}
|
||||
{match = {}; ip = "3.3.3.3"; port = 51820;}
|
||||
];
|
||||
subnets = [
|
||||
"subnet.one"
|
||||
"subnet.two"
|
||||
"subnet.three"
|
||||
];
|
||||
groups = [
|
||||
"everyoneConnectsToMe"
|
||||
"subnet two group"
|
||||
];
|
||||
connections = [
|
||||
{ group = "everyoneConnectsToMe"; }
|
||||
{ group = "subnet two group"; }
|
||||
];
|
||||
privateKeyFile = "/not/yet";
|
||||
publicKey = "testData";
|
||||
presharedKeyFile = "testData2";
|
||||
}
|
||||
{
|
||||
name = "peer.four";
|
||||
endpoints = [
|
||||
{match = {group = "subnet two group";}; ip = "1.1.1.1"; port = 51820;}
|
||||
{match = {peer = "peer.one";}; ip = "2.2.2.2"; port = 51820;}
|
||||
{match = {}; ip = "3.3.3.3"; port = 51820;}
|
||||
];
|
||||
subnets = [
|
||||
"subnet.three"
|
||||
"subnet.one"
|
||||
];
|
||||
groups = [
|
||||
|
||||
];
|
||||
connections = [
|
||||
{ group = "everyoneConnectsToMe"; }
|
||||
{ peer = "peer.one"; }
|
||||
];
|
||||
privateKeyFile = "/not/yet";
|
||||
publicKey = "testData";
|
||||
presharedKeyFile = "testData2";
|
||||
}
|
||||
];
|
||||
}
|
Loading…
Reference in New Issue