network/roles/firewall/templates/nftables.nft.j2
Timotej Lazar 6840838978 firewall: ensure wireguard egress traffic uses the anycast source IP
Before we relied on the IP being first in the interfaces file, which
is less than optimal. Now we use nftables to ensure the correct source
IP is set only for the (fwmarked) wireguard traffic.

Also remove iface hints from interfaces configuration as they are not
needed with ifupdown-ng.
2025-07-18 18:35:36 +02:00

187 lines
5.8 KiB
Django/Jinja

#!/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(', ') }} }
}
# convenience port set definitions
set ad-ports { # https://learn.microsoft.com/en-us/troubleshoot/windows-server/active-directory/config-firewall-for-ad-domains-and-trusts
type inet_proto . inet_service
flags interval
elements = {
tcp . 53,
tcp . 88,
tcp . 135,
tcp . 139,
tcp . 389,
tcp . 445,
tcp . 464,
tcp . 636,
tcp . 3268-3269,
#tcp . 3389, # RDP
tcp . 5000-5100,
tcp . 5985,
tcp . 5986,
tcp . 9389,
tcp . 22222-22224,
tcp . 49152-65535,
udp . 53,
udp . 88,
udp . 135,
udp . 137, # netbios, maybe can do without
udp . 138, # netbios, maybe can do without
udp . 389,
udp . 464,
udp . 3269
}
}
set ldap-ports {
type inet_proto . inet_service
flags interval
elements = {
tcp . 88,
tcp . 389,
tcp . 636,
tcp . 3268,
tcp . 3269,
udp . 88,
udp . 389
}
}
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 %}
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"
udp dport 51820 accept \
comment "Accept WireGuard from anywhere"
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"
ip protocol icmp icmp type {
echo-request, echo-reply, destination-unreachable,
parameter-problem, time-exceeded,
} accept \
comment "Accept ICMPv4"
ip6 nexthdr icmpv6 icmpv6 type {
echo-request, echo-reply, destination-unreachable,
packet-too-big, parameter-problem, time-exceeded,
} accept \
comment "Accept ICMPv6"
include "/etc/nftables.d/forward.nft*"
}
chain output {
type filter hook output priority 0; policy accept
}
}
table inet wireguard {
chain input {
type filter hook prerouting priority raw; policy accept
udp dport 51820 notrack \
comment "Disable connection tracking for wireguard"
}
chain output {
type route hook output priority raw; policy accept
meta mark 51820 meta nfproto ipv4 ip saddr set {{ wg_ip | ipaddr('address') }} notrack \
comment "Disable connection tracking and set anycast source IP for wireguard"
}
}
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"
}
}