# Router firewall/NAT policy (nftables). Trust model: # mgmt VLAN -> trusted: router services, all VLANs, WAN # other VLANs -> DNS/DHCP on the router + WAN (if allowWan); no inter-VLAN # WAN (ppp0) -> nothing inbound beyond established/related # mesh -> admin SSH + metrics scrapes (same trust boundary as the fleet) { config, lib, ... }: let cfg = config.cnx.router; mesh = import ../mesh-hosts.nix { inherit config lib; }; vlanIfs = lib.mapAttrsToList (name: _: "vlan-${name}") cfg.vlans; wanVlanIfs = lib.mapAttrsToList (name: _: "vlan-${name}") ( lib.filterAttrs (_: vlan: vlan.allowWan) cfg.vlans ); nonMgmtIfs = lib.filter (i: i != "vlan-mgmt") vlanIfs; ifSet = ifs: "{ ${lib.concatStringsSep ", " (map (i: "\"${i}\"") ifs)} }"; in { config = lib.mkIf cfg.enable { networking.nftables.enable = true; # SSH reachable only from the mgmt VLAN (trusted) and the mesh — never # from the WAN or the other VLANs. services.openssh.openFirewall = false; networking.firewall = { enable = true; filterForward = true; trustedInterfaces = [ "vlan-mgmt" ]; # Non-mgmt VLANs may only talk to the router's DNS and DHCP. interfaces = lib.genAttrs nonMgmtIfs (_: { allowedTCPPorts = [ 53 ]; allowedUDPPorts = [ 53 67 ]; }); extraInputRules = '' ip6 saddr ${mesh.subnet} tcp dport 22 accept comment "admin ssh over the mesh" ip6 saddr ${mesh.subnet} tcp dport 4000 accept comment "blocky metrics scrape from control" ''; extraForwardRules = '' tcp flags syn tcp option maxseg size set rt mtu comment "MSS clamp for PPPoE mtu 1492" iifname "vlan-mgmt" accept comment "mgmt reaches all VLANs and the WAN" iifname ${ifSet wanVlanIfs} oifname "ppp0" accept comment "LAN to internet" ''; }; networking.nat = { enable = true; externalInterface = "ppp0"; internalInterfaces = vlanIfs; }; }; }