Files
cnx-network-clan/modules/router/firewall.nix
Berwn 158252323f Add site gateway role (modules/router) and gw-cnx-1
Reusable cnx.router.* module for the Topton 1U boxes replacing OPNsense:
PPPoE WAN (optionally VLAN-tagged, AIS: 10, secret credentials incl.
username), VLAN-filtering bridge, nftables NAT/firewall with MSS clamp,
Kea DHCP with per-VLAN lease time, Blocky DNS, DHCPv6-PD, CrowdSec with
the ZeroTier mesh whitelisted, optional Omada controller, ZFS disk.

Fleet baseline rides along: admins sops group is now derived for every
machine in clan.nix (secrets encrypt to it from the first vars generate)
and time sync is chrony everywhere instead of systemd-timesyncd.
2026-07-28 17:06:07 +07:00

63 lines
2.0 KiB
Nix

# 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;
};
};
}