This repository has been archived on 2024-02-16. You can view files and clone it, but cannot push or open issues or pull requests.
sysconfig/roles/nftables/templates/nftables.j2

179 lines
7.1 KiB
Django/Jinja

{% set all_public_network_interfaces = ( ansible_interfaces | select('match', '^(eth|wlan|(en|wl|ww)[osxpP])[0-9]+') | list ) -%}
#!/usr/sbin/nft -f
# NOTE:
# Dynamic blacklisting:
# - https://wiki.nftables.org/wiki-nftables/index.php/Meters
# - https://wiki.archlinux.org/title/Nftables#Dynamic_blackhole
# - https://unix.stackexchange.com/questions/581964/create-dynamic-blacklist-with-nftables
#
# Early broken packages dropping in netdev:
# - https://wiki.gentoo.org/wiki/Nftables#Family_netdev_and_ingress_hook
# - https://serverfault.com/questions/772195/drop-fragmented-packets-in-nftables
#
# udev's network interface naming scheme:
# - https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/configuring_and_managing_networking/consistent-network-interface-device-naming_configuring-and-managing-networking
# - https://systemd.io/PREDICTABLE_INTERFACE_NAMES/
#
# UDP is stateless, but the kernel can still mark a few conntrack states on the packets:
# - https://blog.cloudflare.com/everything-you-ever-wanted-to-know-about-udp-sockets-but-were-afraid-to-ask-part-1/
# - https://www.rigacci.org/wiki/lib/exe/fetch.php/doc/appunti/linux/sa/iptables/conntrack.html
# - https://www.frozentux.net/iptables-tutorial/chunkyhtml/x1555.html
# - https://github.com/torvalds/linux/blob/master/net/ipv4/udp.c
# Clear all prior state
flush ruleset
table netdev filter {
chain ingress {
# Hook priority list: https://wiki.nftables.org/wiki-nftables/index.php/Netfilter_hooks
# The priority needs to be lower than -400 (NF_IP_PRI_CONNTRACK_DEFRAG) to see fragments
type filter hook ingress devices = { {{ all_public_network_interfaces | join(', ') }} } priority -500; policy accept;
# Drop all fragments.
ip frag-off & 0x1fff != 0 drop
# Drop XMAS packets.
tcp flags & (fin|syn|rst|psh|ack|urg) == fin|syn|rst|psh|ack|urg drop
# Drop NULL packets.
tcp flags & (fin|syn|rst|psh|ack|urg) == 0x0 drop
# Drop uncommon MSS values.
tcp flags syn tcp option maxseg size 1-535 drop
}
}
# Basic IPv4/IPv6 stateful firewall for server/workstation.
table inet filter {
set blackhole { type ipv4_addr; flags dynamic, timeout; size 65535; }
set blackhole6 { type ipv6_addr; flags dynamic, timeout; size 65535; }
# NOTE: connlimit shouldn't be used with timeout flag (same goes for "update" set statement)
set connlimit { type ipv4_addr; flags dynamic; size 65535; }
set connlimit6 { type ipv6_addr; flags dynamic; size 65535; }
chain input {
type filter hook input priority 0; policy drop;
iif lo accept \
comment "Accept any localhost traffic"
ct state { established, related } accept \
comment "Accept traffic originated from us"
ct state invalid drop \
comment "Drop invalid connections"
tcp flags & (fin|syn|rst|ack) != syn ct state new drop \
comment "Drop non-SYN packets"
tcp dport 113 reject with icmpx type port-unreachable \
comment "Reject AUTH to make it fail fast"
iif != lo ip daddr 127.0.0.1/8 drop \
comment "Block spoofing as localhost (IPv4)"
iif != lo ip6 daddr ::1/128 drop \
comment "Block spoofing as localhost (IPv6)"
udp dport mdns ip daddr 224.0.0.251 accept \
comment "Accept mDNS"
udp dport mdns ip6 daddr ff02::fb accept \
comment "Accept mDNS"
jump input_dhcp_client
jump input_icmp
# Blacklisting should be done before stateful accept rules
ip saddr @blackhole counter drop
ip6 saddr @blackhole6 counter drop
# Drop future attempts on opened ports if there are already 3 established connections
{% if opened_ports.tcp is sequence and opened_ports.tcp | length > 0 %}
tcp dport { {{ opened_ports.tcp | join(', ') }} } ct state new \
add @connlimit { ip saddr ct count over 3 } drop
tcp dport { {{ opened_ports.tcp | join(', ') }} } ct state new \
add @connlimit6 { ip6 saddr ct count over 3 } drop
{% endif %}
{% if opened_ports.udp is sequence and opened_ports.udp | length > 0 %}
udp dport { {{ opened_ports.udp | join(', ') }} } ct state new \
add @connlimit { ip saddr ct count over 3 } drop
udp dport { {{ opened_ports.udp | join(', ') }} } ct state new \
add @connlimit6 { ip6 saddr ct count over 3 } drop
{% endif %}
# Allow opened ports but also dynamically add them to the blacklist
{% if opened_ports.tcp is sequence and opened_ports.tcp | length > 0 %}
tcp dport { {{ opened_ports.tcp | join(', ') }} } ct state new \
add @blackhole { ip saddr timeout 60s limit rate 10/second } accept
tcp dport { {{ opened_ports.tcp | join(', ') }} } ct state new \
add @blackhole6 { ip6 saddr timeout 60s limit rate 10/second } accept
{% endif %}
{% if opened_ports.udp is sequence and opened_ports.udp | length > 0 %}
udp dport { {{ opened_ports.udp | join(', ') }} } ct state new \
add @blackhole { ip saddr timeout 60s limit rate 10/second } accept
udp dport { {{ opened_ports.udp | join(', ') }} } ct state new \
add @blackhole6 { ip6 saddr timeout 60s limit rate 10/second } accept
{% endif %}
}
chain forward {
type filter hook forward priority 0; policy drop;
}
chain output {
type filter hook output priority 0; policy accept;
}
chain input_dhcp_client {
udp sport 67 udp dport 68 accept \
comment "Accept DHCP client input traffic"
ip6 saddr fe80::/10 udp sport 547 udp dport 546 accept \
comment "Accept DHCPv6 replies from IPv6 link-local addresses"
}
chain input_icmp {
ip protocol igmp accept \
comment "Accept IGMP"
ip protocol icmp icmp type {
echo-reply, # type 0
destination-unreachable, # type 3
echo-request, # type 8
time-exceeded, # type 11
parameter-problem, # type 12
} limit rate 10/second burst 4 packets accept \
comment "Accept ICMP"
icmpv6 type {
destination-unreachable, # type 1
packet-too-big, # type 2
time-exceeded, # type 3
parameter-problem, # type 4
echo-request, # type 128
echo-reply, # type 129
} limit rate 10/second burst 4 packets accept \
comment "Accept basic IPv6 functionality"
icmpv6 type {
nd-router-solicit, # type 133
nd-router-advert, # type 134
nd-neighbor-solicit, # type 135
nd-neighbor-advert, # type 136
} ip6 hoplimit 255 accept \
comment "Allow IPv6 SLAAC"
icmpv6 type {
mld-listener-query, # type 130
mld-listener-report, # type 131
mld-listener-reduction, # type 132
mld2-listener-report, # type 143
} ip6 saddr fe80::/10 accept \
comment "Allow IPv6 multicast listener discovery on link-local"
}
}
# The state of stateful objects saved on the nftables service stop.
include "/var/lib/nftables/*.nft"