#!/usr/sbin/nft -f {% set ifaces_fabric = interfaces | selectattr('name', 'match', '^lan') | map(attribute='name') %} flush ruleset table inet filter { include "/etc/nftables.d/interfaces.nft" include "/etc/nftables.d/networks.nft" include "/etc/nftables.d/sets.nft*" set link { type iface_index elements = { {{ ifaces_fabric | product(['2', '4']) | map('join', '.') | join(', ') }} } } chain input { type filter hook input priority 0; policy drop ct state vmap { established : accept, related : accept, invalid : drop } \ comment "Accept established streams and drop invalid connections" iif lo accept \ comment "Accept any localhost traffic" iif mgmt tcp dport ssh accept \ comment "Accept SSH from management VRF" # allow SSH connections from firewall master’s IPs {% for iface in hostvars[master].interfaces %} {% for address in iface.ip_addresses | selectattr('family.value', '==', 4) %} tcp dport ssh {{ 'ip' if address.family.value == 4 else 'ip6' }} saddr {{ address.address | ipaddr('address') }} accept {% for nat_address in address.nat_outside %} tcp dport ssh ip saddr {{ nat_address.address | ipaddr('address') }} accept {% endfor %} {% endfor %} {% endfor %} iif @link tcp dport bgp ip6 saddr fe80::/10 accept \ comment "Accept link-local BGP on fabric links" iif @link udp dport 3784 ip6 saddr fe80::/10 accept \ comment "Accept link-local BFD on fabric links" iif @outside udp dport 51820 accept \ comment "Accept WireGuard from outside" iif {{ iface_sync }} ip6 saddr fe80::/10 udp dport 3780 accept \ comment "Accept connection tracking sync data" tcp dport auth reject with icmpx type port-unreachable \ comment "Reject AUTH to make it fail fast" # ICMPv4 ip protocol icmp icmp type { echo-request, echo-reply, destination-unreachable, parameter-problem, time-exceeded, } accept \ comment "Accept ICMP" # ICMPv6 ip6 nexthdr icmpv6 icmpv6 type { echo-request, echo-reply, destination-unreachable, packet-too-big, parameter-problem, time-exceeded, } accept \ comment "Accept basic IPv6 functionality" ip6 nexthdr icmpv6 icmpv6 type { nd-neighbor-solicit, nd-neighbor-advert, nd-router-solicit, nd-router-advert, } ip6 hoplimit 255 accept \ comment "Allow IPv6 neighbor discovery" } chain forward { type filter hook forward priority filter; policy drop ct state { established, related } accept \ comment "Forward all established and related traffic" ct status dnat accept \ comment "Forward DNAT traffic for servers and suchlike" # Forward IPv4 to/from VPN users in the same network. {% for vlan in vlans %} iif @inside ip saddr @{{ vlan.name }} ip daddr @{{ vlan.name }} accept {% endfor %} # Forward IPv6 to/from VPN users in the same network. {% for vlan in vlans %} iif @inside ip6 saddr @{{ vlan.name }}/6 ip6 daddr @{{ vlan.name }}/6 accept {% endfor %} include "/etc/nftables.d/forward.nft*" } chain output { type filter hook output priority 0; policy accept } } table ip nat { include "/etc/nftables.d/interfaces.nft" include "/etc/nftables.d/networks.nft" include "/etc/nftables.d/sets.nft*" include "/etc/nftables.d/netmap.nft*" # Ensure these maps exist even if empty. map netmap-in { type ipv4_addr : interval ipv4_addr; flags interval; } map netmap-out { type ipv4_addr : interval ipv4_addr; flags interval; } chain postrouting { type nat hook postrouting priority srcnat iif @inside oif @outside snat ip prefix to ip saddr map @netmap-out \ comment "Static source NAT for 1:1 mapped addresses" include "/etc/nftables.d/nat.nft*" } chain prerouting { type nat hook prerouting priority dstnat dnat ip prefix to ip daddr map @netmap-in \ comment "Static destination NAT for 1:1 mapped addresses" } }