Hi all,
I have a config for several hosts that are connected using WireGuard:
{ config, self, ... }: {
services.sumo-wireguard-new = {
enable = true;
server = false;
address = "10.64.7.8";
inherit (config.sops.secrets) wireguardPrivateKey;
wireguardPublicKey = "dW7GMrn4OXvZIHtlKru/cglao7jouLpN9ZgUzyQdsRk=";
hosts = self.nixosConfigurations;
};
}
and the service definition:
{ config, lib, tools, ... }:
let
cfg = config.services.sumo-wireguard-new;
netmask = "/10";
port = 51821; # don't interfere with standard wireguard port
endpoint = "65.108.76.77:${builtins.toString port}";
network = "vpn.sustainablemotion.io";
ipRegex =
"10.(6[4-9]|[7-9][0-9]|1[0-1][0-9]|12[0-7])(.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){2}";
keyRegex = "^[A-Za-z0-9+/]{42}[AEIMQUYcgkosw480]=$";
peer = {
options = {
address = lib.mkOption {
type = with lib.types; strMatching ipRegex;
example = "10.64.0.1";
description = "IPv4 address on the SUMO Wireguard VPN.";
};
wireguardPublicKey = lib.mkOption {
type = with lib.types; strMatching keyRegex;
example = "L4msD0mEG2ctKDtaMJW2y3cs1fT2LBRVV7iVlWZ2nZc=";
description = "The public key of this host.";
};
};
};
in {
options.services.sumo-wireguard-new = {
enable = lib.mkEnableOption "SUMO Wireguard VPN configuration";
server = lib.mkOption {
type = with lib.types; bool;
example = true;
default = false;
description = "Set to true if this is the VPN server, otherwise false.";
};
wireguardPrivateKey = lib.mkOption {
type = with lib.types; attrs;
description = "Sops secret containing the private key of this host.";
};
hosts = lib.mkOption {
type = with lib.types; attrs;
default = { };
example = lib.options.literalExpression "self.nixosConfigurations";
description =
"nixosConfigurations of all systems, if this host is the VPN server all peers will be looked up, otherwise the VPN server will be looked up.";
};
extraPeers = lib.mkOption {
type = with lib.types; listOf (submodule peer);
default = [ ];
description = "Extra peers that are not managed in this configuration";
};
} // peer.options;
config = let
hosts = lib.attrsets.mapAttrsToList (systemName: systemConfig: {
inherit systemName;
inherit (systemConfig.config.services.sumo-wireguard-new) address server;
}) (lib.filterAttrs
(_: systemConfig: systemConfig.config.services.sumo-wireguard-new.enable)
cfg.hosts);
servers = lib.attrsets.mapAttrsToList (_: systemConfig: {
PublicKey =
systemConfig.config.services.sumo-wireguard-new.wireguardPublicKey;
AllowedIPs =
"${systemConfig.config.services.sumo-wireguard-new.address}${netmask}";
Endpoint = endpoint;
PersistentKeepalive = 25;
}) (lib.filterAttrs (_: systemConfig:
systemConfig.config.services.sumo-wireguard-new.enable
&& systemConfig.config.services.sumo-wireguard-new.server) cfg.hosts);
peers = lib.attrsets.mapAttrsToList (_: systemConfig: {
PublicKey =
systemConfig.config.services.sumo-wireguard-new.wireguardPublicKey;
AllowedIPs =
"${systemConfig.config.services.sumo-wireguard-new.address}/32";
}) (lib.filterAttrs (_: systemConfig:
systemConfig.config.services.sumo-wireguard-new.enable
&& !systemConfig.config.services.sumo-wireguard-new.server) cfg.hosts)
++ builtins.map (p: {
PublicKey = p.wireguardPublicKey;
AllowedIPs = "${p.address}/32";
}) cfg.extraPeers;
addresses =
builtins.map (p: builtins.elemAt (builtins.split "/" p.AllowedIPs) 0)
(servers ++ peers);
in lib.mkIf cfg.enable {
warnings = if cfg.server && peers == [ ] then
[
"This is a SUMO Wireguard server but the list of peers empty, no peers will be able to connect to this server."
]
else
[ ];
assertions = [
{
assertion = cfg.hosts != { };
message = "The hosts attrs can not be empty.";
}
{
assertion = cfg.wireguardPrivateKey ? "path";
message = "The SUMO Wireguard private key is not set.";
}
{
assertion = (builtins.length servers) == 1;
message =
"Only one host can be the VPN server and one host must be the VPN server in the SUMO Wireguard network.";
}
{
assertion = tools.list.duplicates addresses == [ ];
message = ''
Duplicate addresses found for the SUMO Wireguard network:
${lib.strings.concatLines (tools.list.duplicates addresses)}'';
}
];
boot.kernelModules = [ "wireguard" ];
systemd.network = {
netdevs = {
"10-wg-sumo" = {
netdevConfig = {
Kind = "wireguard";
Name = "wg-sumo";
};
wireguardConfig = {
PrivateKeyFile = cfg.wireguardPrivateKey.path;
ListenPort = port;
};
wireguardPeers = if cfg.server then peers else servers;
};
};
networks = {
"10-wg-sumo" = {
matchConfig.Name = "wg-sumo";
address = [ "${cfg.address}${netmask}" ];
dns = builtins.map (h: h.address) (lib.filter (h: h.server) hosts);
networkConfig = lib.mkIf cfg.server {
IPMasquerade = "ipv4";
IPv4Forwarding = true;
};
};
};
};
meta.maintainers = with lib.maintainers; [ munnik ];
}
This works fine, but I want to achieve one additional thing. When I update the wireguardPrivateKey
for a host in the secrets.yaml
file and forget to update the corresponding wireguardPublicKey
the WireGuard config is broken. The public key can be generated via the wg pubkey
command. Is it possible to call that script in the nix config so that I don’t need to specify the public key anymore? I know that calling a script is unpure, but in this case the script itself is pure.