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.
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
# 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;
|
||||
};
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user