From 8dca3a7974366b9113ee1b54a8f80f2964cac6fa Mon Sep 17 00:00:00 2001 From: FileMagic Date: Fri, 7 Aug 2026 00:39:33 -0400 Subject: [PATCH] configuration.nix [13-inch-thin-cannon]: add WireGuard kill-switch, static DNS over tunnel, loose rpfilter --- 13-inch-thin-cannon/configuration.nix | 55 +++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/13-inch-thin-cannon/configuration.nix b/13-inch-thin-cannon/configuration.nix index 4fc3fd1..b55075a 100644 --- a/13-inch-thin-cannon/configuration.nix +++ b/13-inch-thin-cannon/configuration.nix @@ -36,6 +36,61 @@ networking.hostName = "13-inch-thin-cannon"; networking.networkmanager.enable = true; + + # NetworkManager adopts wg0 (state "disconnected") and, on restart, flushes + # the address/peer config that the wireguard-wg0 systemd unit applied + # externally. That is why the tunnel keeps dying after `nixos-rebuild + # switch`. Mark wg0 unmanaged so NM leaves it alone entirely. + networking.networkmanager.unmanaged = ["interface-name:wg0"]; + + # DNS over the tunnel. NM's DHCP hands us a LAN resolver (e.g. + # 10.203.12.148) that sits on the physical subnet, so its direct route + # beats the wg0 default route and the kill-switch drops it. Making + # 1.1.1.1 win via insertNameservers or openresolv is racy (NM/interface + # records reorder or clobber resolv.conf), so instead: disable openresolv + # and NM DNS entirely and write a static resolv.conf from + # networking.nameservers. All queries then egress via wg0 under the + # kill-switch. Trade-off: per-network DHCP DNS is never used (LAN + # hostnames won't resolve), acceptable on a roaming, VPN-only device. + networking.networkmanager.dns = "none"; + networking.resolvconf.enable = false; + networking.nameservers = [ + "1.1.1.1" + "2606:4700:4700::1111" + ]; + environment.etc."resolv.conf".text = + lib.concatMapStrings + (ns: "nameserver ${ns}\n") + config.networking.nameservers; + + # WireGuard kill-switch: no internet unless traffic goes through wg0. + # Only wg0, the WireGuard endpoint, DHCP, and loopback may leave the box. + # If wg0 goes down, all other egress is dropped (no leak via the physical + # interface's default route). See wireguard.nix for the tunnel itself. + networking.nftables.enable = true; + + # Strict reverse-path filtering breaks under policy routing: the WG server's + # UDP replies (from 185.159.158.226) fail the "reply must leave the same + # interface it arrived on" test because the unmarked reverse-path lookup + # resolves via table 51820 -> wg0. Loose mode (only require the source to be + # reachable via *any* route) lets WG replies through while the input chain + # still drops spoofed/unauthorized new connections. + networking.firewall.checkReversePath = "loose"; + networking.nftables.tables.killswitch = { + family = "inet"; + content = '' + chain output { + type filter hook output priority 100; policy drop; + oifname "lo" accept + oifname "wg0" accept + ip daddr 185.159.158.226 udp dport 51820 accept # WireGuard endpoint + udp sport 68 udp dport 67 accept # DHCPv4 (renewal) + udp sport 546 udp dport 547 accept # DHCPv6 (SLAAC) + ct state established,related accept + } + ''; + }; + time.timeZone = "America/New_York"; # Select internationalisation properties. -- 2.51.2