Bruno BELANYI
f79fcd020b
This makes use of my newly written adblocking DNS service, it does assume that the server would have both wireguard and DNS enabled. I would also like to move to using my ip-related library functions, however it does not support IPv6 and is unlikely to be easily added... But I am not sure that I *need* IPv6 support for my use-case. Finally, I find this module a bit too heavy, it could be improved by having specific 'server' and 'client' roles, instead of implicit roles depending on whether an external IP exists.
166 lines
5.4 KiB
Nix
166 lines
5.4 KiB
Nix
# A simple, in-kernel VPN service
|
|
#
|
|
# Strongly inspired by [1].
|
|
# [1]: https://github.com/delroth/infra.delroth.net/blob/master/roles/wireguard-peer.nix
|
|
{ config, lib, pkgs, ... }:
|
|
let
|
|
cfg = config.my.services.wireguard;
|
|
hostName = config.networking.hostName;
|
|
|
|
peers = config.my.secrets.wireguard.peers;
|
|
thisPeer = peers."${hostName}";
|
|
thisPeerIsServer = thisPeer ? externalIp;
|
|
# Only connect to clients from server, and only connect to server from clients
|
|
otherPeers =
|
|
let
|
|
allOthers = lib.filterAttrs (name: _: name != hostName) peers;
|
|
shouldConnectToPeer = _: peer: thisPeerIsServer != (peer ? externalIp);
|
|
in
|
|
lib.filterAttrs shouldConnectToPeer allOthers;
|
|
|
|
extIface = config.my.networking.externalInterface;
|
|
in
|
|
{
|
|
options.my.services.wireguard = with lib; {
|
|
enable = mkEnableOption "Wireguard VPN service";
|
|
|
|
iface = mkOption {
|
|
type = types.str;
|
|
default = "wg";
|
|
example = "wg0";
|
|
description = "Name of the interface to configure";
|
|
};
|
|
|
|
port = mkOption {
|
|
type = types.port;
|
|
default = 51820;
|
|
example = 55555;
|
|
description = "Port to configure for Wireguard";
|
|
};
|
|
|
|
dns = {
|
|
useInternal = my.mkDisableOption ''
|
|
Use internal DNS servers from wireguard 'server'
|
|
'';
|
|
|
|
additionalServers = mkOption {
|
|
type = with types; listOf str;
|
|
default = [
|
|
"1.0.0.1"
|
|
"1.1.1.1"
|
|
];
|
|
example = [
|
|
"8.8.4.4"
|
|
"8.8.8.8"
|
|
];
|
|
description = "Which DNS servers to use in addition to adblock ones";
|
|
};
|
|
};
|
|
|
|
net = {
|
|
# FIXME: use new ip library to handle this more cleanly
|
|
v4 = {
|
|
subnet = mkOption {
|
|
type = types.str;
|
|
default = "10.0.0";
|
|
example = "10.100.0";
|
|
description = "Which prefix to use for internal IPs";
|
|
};
|
|
mask = mkOption {
|
|
type = types.int;
|
|
default = 24;
|
|
example = 28;
|
|
description = "The CIDR mask to use on internal IPs";
|
|
};
|
|
};
|
|
# FIXME: extend library for IPv6
|
|
v6 = {
|
|
subnet = mkOption {
|
|
type = types.str;
|
|
default = "fd42:42:42";
|
|
example = "fdc9:281f:04d7:9ee9";
|
|
description = "Which prefix to use for internal IPs";
|
|
};
|
|
mask = mkOption {
|
|
type = types.int;
|
|
default = 64;
|
|
example = 68;
|
|
description = "The CIDR mask to use on internal IPs";
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
config.networking = lib.mkIf cfg.enable {
|
|
wg-quick.interfaces."${cfg.iface}" = {
|
|
listenPort = cfg.port;
|
|
address = with cfg.net; with lib; [
|
|
"${v4.subnet}.${toString thisPeer.clientNum}/${toString v4.mask}"
|
|
"${v6.subnet}::${toString thisPeer.clientNum}/${toHexString v6.mask}"
|
|
];
|
|
# Insecure, I don't care
|
|
privateKey = thisPeer.privateKey;
|
|
|
|
peers = lib.mapAttrsToList
|
|
(_: peer: {
|
|
inherit (peer) publicKey;
|
|
} // lib.optionalAttrs thisPeerIsServer {
|
|
# Only forward from server to clients
|
|
allowedIPs = with cfg.net; [
|
|
"${v4.subnet}.${toString peer.clientNum}/32"
|
|
"${v6.subnet}::${toString peer.clientNum}/128"
|
|
];
|
|
} // lib.optionalAttrs (peer ? externalIp) {
|
|
# Known addresses
|
|
endpoint = "${peer.externalIp}:${toString cfg.port}";
|
|
} // lib.optionalAttrs (!thisPeerIsServer) {
|
|
# Forward all traffic to server
|
|
allowedIPs = with cfg.net; [
|
|
"0.0.0.0/0"
|
|
"::/0"
|
|
];
|
|
# Roaming clients need to keep NAT-ing active
|
|
persistentKeepalive = 10;
|
|
})
|
|
otherPeers;
|
|
} // lib.optionalAttrs thisPeerIsServer {
|
|
# Setup forwarding on server
|
|
postUp = with cfg.net; ''
|
|
${pkgs.iptables}/bin/iptables -A FORWARD -i ${cfg.iface} -j ACCEPT
|
|
${pkgs.iptables}/bin/iptables -t nat -A POSTROUTING -s ${v4.subnet}.1/${toString v4.mask} -o ${extIface} -j MASQUERADE
|
|
${pkgs.iptables}/bin/ip6tables -A FORWARD -i ${cfg.iface} -j ACCEPT
|
|
${pkgs.iptables}/bin/ip6tables -t nat -A POSTROUTING -s ${v6.subnet}::1/${toString v6.mask} -o ${extIface} -j MASQUERADE
|
|
'';
|
|
preDown = with cfg.net; ''
|
|
${pkgs.iptables}/bin/iptables -D FORWARD -i ${cfg.iface} -j ACCEPT
|
|
${pkgs.iptables}/bin/iptables -t nat -D POSTROUTING -s ${v4.subnet}.1/${toString v4.mask} -o ${extIface} -j MASQUERADE
|
|
${pkgs.iptables}/bin/ip6tables -D FORWARD -i ${cfg.iface} -j ACCEPT
|
|
${pkgs.iptables}/bin/ip6tables -t nat -D POSTROUTING -s ${v6.subnet}::1/${toString v6.mask} -o ${extIface} -j MASQUERADE
|
|
'';
|
|
} // lib.optionalAttrs (!thisPeerIsServer) {
|
|
# Use DNS servers hosted on wireguard servers
|
|
dns =
|
|
let
|
|
isServer = _: peer: peer ? externalIp;
|
|
toInternalIps = peer: [
|
|
"${cfg.net.v4.subnet}.${toString peer.clientNum}"
|
|
"${cfg.net.v6.subnet}::${toString peer.clientNum}"
|
|
];
|
|
servers = lib.filterAttrs isServer otherPeers;
|
|
internalIps = lib.flatten
|
|
(lib.mapAttrsToList (_: peer: toInternalIps peer) servers);
|
|
internalServers = lib.optionals cfg.dns.useInternal internalIps;
|
|
in
|
|
internalServers ++ cfg.dns.additionalServers;
|
|
};
|
|
|
|
nat = lib.optionalAttrs thisPeerIsServer {
|
|
enable = true;
|
|
externalInterface = extIface;
|
|
internalInterfaces = [ cfg.iface ];
|
|
};
|
|
|
|
firewall.allowedUDPPorts = [ cfg.port ];
|
|
};
|
|
}
|