137 lines
4.9 KiB
Nix
137 lines
4.9 KiB
Nix
{ config, lib, pkgs, ... }:
|
|
|
|
let
|
|
inherit (lib) types mkIf mkMerge mkOption mkEnableOption optionals filterAttrs mapAttrsToList;
|
|
cfg = config.birdsong.peering;
|
|
hostName = if null != cfg.hostName then cfg.hostName else config.networking.hostName;
|
|
hosts = config.birdsong.hosts;
|
|
host = hosts.${hostName};
|
|
in
|
|
{
|
|
options.birdsong.peering = {
|
|
enable = mkEnableOption "WireGuard peering with the birdsong network";
|
|
hostName = mkOption {
|
|
default = null;
|
|
description = ''
|
|
The hostname of this peer within the network. Must be listed in
|
|
{option}`birdsong.hosts`. If not set, defaults to
|
|
{option}`networking.hostName`.
|
|
'';
|
|
type = with types; nullOr str;
|
|
};
|
|
interface = mkOption {
|
|
default = "wg-birdsong";
|
|
example = "wg0";
|
|
description = "The name of the network interface to use for WireGuard.";
|
|
type = types.str;
|
|
};
|
|
openPorts = mkOption {
|
|
default = true;
|
|
description = "Whether to automatically open firewall ports.";
|
|
type = types.bool;
|
|
};
|
|
privateKeyFile = mkOption {
|
|
default = null;
|
|
description = ''
|
|
Path to the private key for this peer, as generated by
|
|
{command}`wg genkey`. Must be readable by the user "systemd-network";
|
|
systemd.netdev(5) recommends it be owned by "root:systemd-network" with
|
|
a "0640" file mode. Set exactly one of this or
|
|
{option}`birdsong.peering.privateKeyCredential`.
|
|
'';
|
|
type = with types; nullOr str;
|
|
};
|
|
privateKeyCredential = mkOption {
|
|
default = null;
|
|
description = ''
|
|
Name of a systemd credential containing a private key for this peer, as
|
|
generated by {command}`wg genkey`. Set exactly one of this or
|
|
{option}`birdsong.peering.privateKeyFile`.
|
|
|
|
To load the credential from an encrypted credential file, set
|
|
{option}`systemd.services.systemd-networkd.serviceConfig.LoadCredentialEncrypted`.
|
|
'';
|
|
type = with types; nullOr str;
|
|
};
|
|
persistentKeepalive = mkOption {
|
|
default = 0;
|
|
example = 23;
|
|
description = ''
|
|
Constantly ping each peer outside the LAN this often, in seconds, in
|
|
order to keep the WireGuard tunnel open. Set this if you are behind NAT
|
|
to keep the NAT session active, or if you have a dynamic IP to keep the
|
|
other peers aware when your IP changes. To avoid syncing, this should
|
|
ideally be a prime number that is not shared by another peer in the same
|
|
LAN. 0 (the default) disables this.
|
|
'';
|
|
type = types.int;
|
|
};
|
|
};
|
|
|
|
config = mkIf cfg.enable {
|
|
assertions = [
|
|
{
|
|
assertion = config.systemd.network.enable == true;
|
|
message = "birdsong depends on networkd. systemd.network.enable must be true";
|
|
}
|
|
{
|
|
assertion = (cfg.privateKeyCredential != null && cfg.privateKeyFile == null) || (cfg.privateKeyFile != null && cfg.privateKeyCredential == null);
|
|
message = "exactly one of birdsong.peering.privateKeyCredential or birdsong.peering.privateKeyFile must be set";
|
|
}
|
|
{
|
|
assertion = hostName != null;
|
|
message = "birdsong.peering.hostName or networking.hostName must be set";
|
|
}
|
|
];
|
|
|
|
boot.kernel.sysctl = mkIf host.isRouter {
|
|
"net.ipv4.conf.${cfg.interface}.forwarding" = true;
|
|
"net.ipv6.conf.${cfg.interface}.forwarding" = true;
|
|
};
|
|
|
|
networking.firewall.allowedUDPPorts = mkIf cfg.openPorts [ host.port ];
|
|
|
|
systemd.network = {
|
|
netdevs."30-birdsong" = {
|
|
netdevConfig = {
|
|
Name = cfg.interface;
|
|
Kind = "wireguard";
|
|
Description = "wireguard tunnel to the birdsong network";
|
|
};
|
|
|
|
wireguardConfig = mkMerge [
|
|
{
|
|
ListenPort = host.port;
|
|
}
|
|
(mkIf (cfg.privateKeyCredential != null) {
|
|
PrivateKey = "@${cfg.privateKeyCredential}";
|
|
})
|
|
(mkIf (cfg.privateKeyFile != null) {
|
|
PrivateKeyFile = cfg.privateKeyFile;
|
|
})
|
|
];
|
|
|
|
wireguardPeers =
|
|
let
|
|
canDirectPeer = host: peer: peer.subnet == "internet" || (host.subnet != "roaming" && peer.subnet == host.subnet);
|
|
in
|
|
mapAttrsToList
|
|
(name: peer: {
|
|
PublicKey = peer.wireguardKey;
|
|
AllowedIPs = [ peer.ipv4 peer.ipv6 ]
|
|
++ optionals peer.isRouter [ "10.127.0.0/16" "fd70:81ca:0f8f::/48" ];
|
|
Endpoint = mkIf (canDirectPeer host peer) "${peer.endpoint}:${toString peer.port}";
|
|
PersistentKeepalive = mkIf (peer.subnet != host.subnet) cfg.persistentKeepalive;
|
|
})
|
|
(filterAttrs (name: peer: peer != host && (canDirectPeer host peer || canDirectPeer peer host)) hosts);
|
|
};
|
|
|
|
networks."30-birdsong" = {
|
|
matchConfig.Name = cfg.interface;
|
|
networkConfig.Address = [ "${host.ipv4}/16" "${host.ipv6}/48" ]
|
|
++ optionals host.isRouter [ "10.127.0.0/16" "fd70:81ca:0f8f::/48" ];
|
|
};
|
|
};
|
|
};
|
|
}
|