wireguard nix config
nix wireguard
I had a problem, I wanted to ensure that I could set up wireguard p2p connections between all my server hosts without having to manually configure each connection. It should be simple:
- Pick a link subnet
- Generate all wireguard keypairs
- Make some servers listen and some servers connect
- Known public ip for listen servers
hosts.toml
I generated a simple toml file containing hosts, like this
[ampere]
ip = "<public ip>"
bird = "<loopback>"
cost = 256
wg_pubkey = "<public key>"
wg_remotes = ["burrito"]
[burrito]
bird = "<loopback>"
wg_pubkey = "<public key>"
The idea is simple, the file contains hosts, which remotes should connect to the hosts that are listen servers.
wireguard.yaml
This file contains secrets in the form wireguard/hostName/{public,private}
. Used to ensure hosts have their private keys.
wireguard:
hosts:
ampere:
private: <private>
public: <public>
burrito:
private: <private>
public: <public>
wireguard.nix
So for the final nix config, this is the result. Functional and performant.
{ lib, pkgs, config, ... }: with lib; with builtins;
let
cfg = config.ronnvall.wireguard;
hostName = config.networking.hostName;
hostsCfg = fromTOML (readFile ./hosts.toml);
startPort = 51830;
prefix = "10.200.101.";
wgServers = (filter (x: (hasAttr "wg_remotes" hostsCfg."${x}")) (attrNames hostsCfg));
wgRemotes = flatten (map (x: hostsCfg."${x}".wg_remotes) wgServers);
wgConnections = flatten (map (x: map (y: { server = x; peer = y; remote_ip = hostsCfg."${x}".ip; }) hostsCfg."${x}".wg_remotes) wgServers);
wgSubnets = map (i: { listenPort = (startPort + i); server_ip = "${prefix}${toString i}"; peer_ip = "${prefix}${toString (i+1)}"; }) (map (x: x * 2) (range 0 (length wgConnections)));
wgConnectionsFull = map (x: (elemAt wgConnections x) // (elemAt wgSubnets x)) (range 0 ((length wgConnections) - 1));
isWireguardServer = (elem hostName wgServers);
isWireguardRemote = (elem hostName wgRemotes);
wgConnectionsHost = filter (x: elem hostName [ x.server x.peer ]) wgConnectionsFull;
listenPorts = map (x: (toString x.listenPort)) (filter (x: elem hostName [ x.server ]) wgConnectionsHost);
in
{
options.ronnvall.wireguard = {
enable = mkOption { default = (isWireguardServer || isWireguardRemote); };
};
config = mkIf cfg.enable {
sops.secrets."wireguard/hosts/${hostName}/private".sopsFile = ./wireguard.yaml;
ronnvall.bird.ospfInterfaces."wg-*".cost = 1024;
ronnvall.nftables.allowedUDPPorts = listenPorts;
ronnvall.nftables.allowedInterfaces = attrNames config.networking.wireguard.interfaces;
networking.wireguard.interfaces = listToAttrs
(map
(x:
if hostName == x.server then {
name = "wg-${x.peer}";
value = {
listenPort = x.listenPort;
allowedIPsAsRoutes = false;
ips = [ "${x.server_ip}/31" ];
privateKeyFile = config.sops.secrets."wireguard/hosts/${hostName}/private".path;
peers = [{
publicKey = hostsCfg."${x.peer}".wg_pubkey;
allowedIPs = [ "0.0.0.0/0" ];
persistentKeepalive = 10;
}];
};
} else {
name = "wg-${x.server}";
value = {
allowedIPsAsRoutes = false;
ips = [ "${x.peer_ip}/31" ];
privateKeyFile = config.sops.secrets."wireguard/hosts/${hostName}/private".path;
peers = [{
publicKey = hostsCfg."${x.server}".wg_pubkey;
allowedIPs = [ "0.0.0.0/0" ];
endpoint = "${x.remote_ip}:${toString x.listenPort}";
persistentKeepalive = 10;
}];
};
}
)
wgConnectionsHost);
};
}