vpn: port ovpnd connections to use systemd-network

this should allow better integration with e.g. systemd-run, in future
This commit is contained in:
Colin 2024-01-16 03:20:40 +00:00
parent c45898f903
commit 851c15aa6d
3 changed files with 93 additions and 27 deletions

View File

@ -24,9 +24,6 @@ in
sane.ports.openFirewall = true;
sane.ports.openUpnp = true;
# view refused packets with: `sudo journalctl -k`
# networking.firewall.logRefusedPackets = true;
# these useDHCP lines are legacy from the auto-generated config. might be safe to remove now?
networking.useDHCP = false;
networking.interfaces.eth0.useDHCP = true;

View File

@ -7,6 +7,16 @@
./upnp.nix
./vpn.nix
];
systemd.network.enable = true;
networking.useNetworkd = true;
# view refused packets with: `sudo journalctl -k`
# networking.firewall.logRefusedPackets = true;
# networking.firewall.logRefusedUnicastsOnly = false;
# networking.firewall.logReversePathDrops = true;
# networking.firewall.enable = false; #< set false to debug
# the default backend is "wpa_supplicant".
# wpa_supplicant reliably picks weak APs to connect to.
# see: <https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/issues/474>

View File

@ -3,56 +3,115 @@
# to add a new OVPN VPN:
# - generate a privkey `wg genkey`
# - add this key to `sops secrets/universal.yaml`
# - upload pubkey to OVPN.com
# - upload pubkey to OVPN.com (`cat wg.priv | wg pubkey`)
# - generate config @ OVPN.com
# - copy the Address, PublicKey, Endpoint from OVPN's config
# N.B.: maximum interface name in Linux is 15 characters.
#
# debugging:
# - `journalctl -u systemd-networkd`
#
# docs:
# - wireguard (nixos): <https://nixos.wiki/wiki/WireGuard#Setting_up_WireGuard_with_systemd-networkd>
# - wireguard (arch): <https://wiki.archlinux.org/title/WireGuard>
let
def-wg-vpn = name: { endpoint, publicKey, address, dns, privateKeyFile }: {
# networking.wg-quick.interfaces."${name}" = {
# inherit address privateKeyFile dns;
# peers = [
# {
# allowedIPs = [
# "0.0.0.0/0"
# "::/0"
# ];
# inherit endpoint publicKey;
# }
# ];
# # to start: `systemctl start wg-quick-${name}`
# autostart = false;
# };
systemd.network.netdevs."${name}" = {
systemd.network.netdevs."99-${name}" = {
# see: `man 5 systemd.netdev`
netdevConfig = {
Kind = "wireguard";
Name = name;
};
wireguardConfig = {
PrivateKeyFile = privateKeyFile;
FirewallMark = 51820;
};
wireguardPeers = [{
AllowedIPs = [
"0.0.0.0/0"
"::/0"
];
Endpoint = endpoint;
PublicKey = publicKey;
wireguardPeerConfig = {
AllowedIPs = [
"0.0.0.0/0"
"::/0"
];
Endpoint = endpoint;
PublicKey = publicKey;
};
}];
};
systemd.network.networks."${name}" = {
systemd.network.networks."50-${name}" = {
# see: `man 5 systemd.network`
matchConfig.Name = name;
networkConfig.Address = address;
networkConfig.DNS = dns;
# DNSDefaultRoute: system DNS queries are sent to this link's DNS server
networkConfig.DNSDefaultRoute = true;
# Domains = ~.: system DNS queries are sent to this link's DNS server
networkConfig.Domains = "~.";
routingPolicyRules = [
{
routingPolicyRuleConfig = {
# allow all non-default-route rules in the main table to take precedence over our wireguard rules.
# this allows reaching LAN machines (and the LAN's gateway!) without traversing the VPN.
SuppressPrefixLength = 0;
Table = "main";
Priority = 10000;
};
}
{
routingPolicyRuleConfig = {
# redirect any outbound packet not yet marked over to the wireguard table, through which it will enter the wg device.
# the wg device will then route it over the tunnel, using the LAN gateway to reach the tunnel endpoint
# -- which it can route directly, thanks to the higher-precedent rule above which allows reaching the LAN (and therefore gateway) without tunneling.
# this defines an ip rule: show it with `ip rule`.
FirewallMark = 51820;
InvertRule = true;
Table = 51820;
Priority = 10001;
};
}
];
routes = [{
# ovpn.com tunnels don't use a gateway. it's as if this link peers with the entire internet.
# routeConfig.Gateway = address;
# routeConfig.GatewayOnLink = true;
routeConfig.Table = 51820; #< TODO: use name-based table, per VPN
routeConfig.Scope = "link";
routeConfig.Destination = "0.0.0.0/0";
}];
# RequiredForOnline => should `systemd-networkd-wait-online` fail if this network can't come up?
linkConfig.RequiredForOnline = false;
# ActivationPolicy = "manual" means don't auto-up this interface (default is "up")
linkConfig.ActivationPolicy = "manual";
};
};
def-ovpn = name: { endpoint, publicKey, address }: def-wg-vpn "ovpnd-${name}" {
def-ovpn = name: { endpoint, publicKey, address }: (def-wg-vpn "ovpnd-${name}" {
inherit endpoint publicKey address;
privateKeyFile = config.sops.secrets."wg/ovpnd_${name}_privkey".path;
dns = [
"46.227.67.134"
"192.165.9.158"
];
}) // {
sops.secrets."wg/ovpnd_${name}_privkey" = {
# needs to be readable by systemd-network or else it says "Ignoring network device" and doesn't expose it to networkctl.
owner = "systemd-network";
};
};
in lib.mkMerge [
{
networking.firewall.extraCommands = with pkgs; ''
# wireguard packet marking. without this, rpfilter drops responses from a wireguard VPN
# because the "reverse path check" fails (i.e. it thinks a response to the packet would go out via a different interface than what the wireguard packet arrived at).
# debug with e.g. `iptables --list -v -n -t mangle`
# - and `networking.firewall.logReversePathDrops = true;`, `networking.firewall.logRefusedPackets = true;`
# - and `journalctl -k` to see dropped packets
#
# note that wg-quick also adds a rule to reject non-local traffic from all interfaces EXCEPT the tunnel.
# that may protect against actors trying to probe us: actors we connect to via wireguard who send their response packets (speculatively) to our plaintext IP to see if we accept them.
# but that's fairly low concern, and firewalling by the gateway/NAT helps protect against that already.
${iptables}/bin/iptables -t mangle -I PREROUTING 1 -j CONNMARK --restore-mark
${iptables}/bin/iptables -A POSTROUTING -t mangle -m mark --mark 51820 -j CONNMARK --save-mark
'';
}
(def-ovpn "us" {
endpoint = "vpn31.prd.losangeles.ovpn.com:9929";
publicKey = "VW6bEWMOlOneta1bf6YFE25N/oMGh1E1UFBCfyggd0k=";