multiple instance and multiple server support for ezwg

This commit is contained in:
notgne2 2022-01-16 18:11:20 -07:00
parent 358d8f7825
commit 26a8e34a59
No known key found for this signature in database
GPG Key ID: BB661E172B42A7F8

View File

@ -2,102 +2,148 @@
with lib; with lib;
let let
cfg = config.services.ezwg; cfg = config.services.ezwg;
in
{
options.services.ezwg = {
enable = mkEnableOption "Enable simple Wireguard connection";
peerNameReplacement = lib.replaceChars [ "/" "-" " " "+" "=" ] [
"-"
"\\x2d"
"\\x20"
"\\x2b"
"\\x3d"
];
ranges = serverIPs:
let
generateRangesScript =
builtins.toFile "exclusionary-wildcard-ranges-generator.py" ''
import ipaddress
serverNetworks = [${map (ip: "ip_network('${ip}/32')") serverIPs}]
ranges = [ipaddress.ip_network('0.0.0.0/0')]
for serverNetwork in serverNetworks:
ranges = map(lambda r: list(r.address_exclude(serverNetwork)), ranges)
print(':'.join(ranges))
'';
rangesOutput = pkgs.runCommandNoCC "exclusionary-wildcard-ranges" { } ''
${pkgs.python3}/bin/python3 ${generateRangesScript} > $out
'';
in
lib.splitString ":" (builtins.readFile "${rangesOutput}");
subnet = vlanIP: vlanSize:
let
generateSubnetScript =
builtins.toFile "subnet-without-host-bits-generator.py" ''
import ipaddress
n1 = ipaddress.ip_network('${vlanIP}/${toString vlanSize}', False)
print(n1, end="")
'';
subnetOutput = pkgs.runCommandNoCC "subnet-without-host-bits" { } ''
${pkgs.python3}/bin/python3 ${generateSubnetScript} > $out
'';
in
builtins.readFile "${subnetOutput}";
serverOpts.options = {
ip = mkOption {
type = types.str;
description = "The IP of the wg server";
};
port = mkOption {
type = types.int;
default = 51820;
description = "The port of the wg server";
};
publicKey = mkOption {
type = types.str;
description = "The public key of the wg server";
};
};
instanceOpts.options = {
servers = mkOption {
description = "Configuration of servers to connect to";
default = { };
type = with types; listOf (submodule serverOpts);
};
autoStart = mkOption {
type = types.bool;
default = true;
description = "Start this wireguard tunnel by default";
};
proxy = mkOption { proxy = mkOption {
type = types.bool; type = types.bool;
default = true; default = true;
description = "Route all your traffic through this connection"; description = "Route all your traffic through this connection";
}; };
vlanSize = mkOption {
lanSize = mkOption {
type = types.int; type = types.int;
default = 24; default = 24;
description = "Size of your VLAN (only relevant if proxy is false)"; description = "Size of your VLAN";
}; };
serverIP = mkOption {
type = types.str;
description = "The IP of the wg server";
};
excludeIP = mkOption {
type = types.str;
default = cfg.serverIP;
description = "The IP to _not_ route through the proxy, you normally want this to be the same as `serverIP` when not tunneling";
};
serverPort = mkOption {
type = types.int;
default = 51820;
description = "The port of the wg server";
};
serverKey = mkOption {
type = types.str;
description = "The public key of the wg server";
};
privateKeyFile = mkOption { privateKeyFile = mkOption {
type = types.str; type = types.str;
description = "Private wg key"; description = "Private wg key";
}; };
vlanIP = mkOption { vlanIP = mkOption {
type = types.str; type = types.str;
description = "The IP to use on the wg VLAN"; description = "The IP to use on the wg VLAN";
}; };
}; };
in
config = mkIf cfg.enable { {
networking.firewall.checkReversePath = false; options.services.ezwg = {
networking.wireguard.interfaces.wg0 = enable = mkEnableOption "Enable simple Wireguard connection";
let instances = mkOption {
generateRangesScript = description = "Configuration of instances of Wireguard";
builtins.toFile "exclusionary-wildcard-ranges-generator.py" '' default = { };
import ipaddress type = with types; attrsOf (submodule instanceOpts);
n1 = ipaddress.ip_network('0.0.0.0/0') };
n2 = ipaddress.ip_network('${cfg.excludeIP}/32')
print(':'.join(list(map(lambda x: str(x), list(n1.address_exclude(n2))))), end="")
'';
rangesOutput =
pkgs.runCommandNoCC "exclusionary-wildcard-ranges"
{ } ''
${pkgs.python3}/bin/python3 ${generateRangesScript} > $out
'';
generateSubnetScript =
builtins.toFile "subnet-without-host-bits-generator.py" ''
import ipaddress
n1 = ipaddress.ip_network('${cfg.vlanIP}/${toString cfg.lanSize}', False)
print(n1, end="")
'';
subnetOutput =
pkgs.runCommandNoCC "subnet-without-host-bits"
{ } ''
${pkgs.python3}/bin/python3 ${generateSubnetScript} > $out
'';
ranges = lib.splitString ":" (builtins.readFile "${rangesOutput}");
subnet = builtins.readFile "${subnetOutput}";
in
{
ips = [ "${cfg.vlanIP}/32" ];
privateKeyFile = cfg.privateKeyFile;
peers = [
{
publicKey = cfg.serverKey;
allowedIPs = if cfg.proxy then ranges else [ subnet ];
endpoint = "${cfg.serverIP}:${toString cfg.serverPort}";
persistentKeepalive = 25;
}
];
};
}; };
config = mkIf cfg.enable
{
networking.firewall.checkReversePath = false;
systemd.paths = mapAttrs'
(instName: inst: {
name = "wireguard-${instName}";
value = if inst.autoStart then { } else { wantedBy = mkForce [ ]; };
})
cfg.instances;
systemd.services = lib.listToAttrs (flatten
(mapAttrsToList
(instName: inst: [
{
name = "wireguard-${instName}";
value = if inst.autoStart then { } else { wantedBy = mkForce [ ]; };
}
] ++ map
(server: {
name = "wireguard-${instName}-peer${peerNameReplacement server.publicKey}";
value = if inst.autoStart then { } else { wantedBy = mkForce [ ]; };
})
inst.servers)
cfg.instances));
networking.wireguard.interfaces = mapAttrs
(instName: inst:
let
allowedIPs = if inst.proxy then ranges (map (s: s.ip) inst.servers) else [ (subnet inst.vlanIP inst.vlanSize) ];
in
{
ips = [ "${inst.vlanIP}/${toString inst.vlanSize}" ];
privateKeyFile = inst.privateKeyFile;
peers = map
(server: {
inherit allowedIPs;
publicKey = server.publicKey;
endpoint = "${server.ip}:${toString server.port}";
persistentKeepalive = 25;
})
inst.servers;
})
cfg.instances;
};
} }