2022-11-05 05:21:19 +01:00
|
|
|
{% set all_public_network_interfaces = ( ansible_interfaces | select('match', '^(eth|wlan|(en|wl|ww)[osxpP])[0-9]+') | list ) -%}
|
2022-01-14 19:46:59 +01:00
|
|
|
#!/usr/sbin/nft -f
|
2022-11-05 05:21:19 +01:00
|
|
|
|
|
|
|
# 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
|
2022-01-14 19:46:59 +01:00
|
|
|
|
|
|
|
# Clear all prior state
|
|
|
|
flush ruleset
|
|
|
|
|
2022-05-10 18:13:35 +02:00
|
|
|
table netdev filter {
|
2022-01-14 19:46:59 +01:00
|
|
|
chain ingress {
|
2022-05-10 18:13:35 +02:00
|
|
|
# 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
|
2022-11-05 05:21:19 +01:00
|
|
|
type filter hook ingress devices = { {{ all_public_network_interfaces | join(', ') }} } priority -500; policy accept;
|
2022-01-14 19:46:59 +01:00
|
|
|
|
|
|
|
# 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 {
|
2022-11-05 05:21:19 +01:00
|
|
|
set blackhole { type ipv4_addr; flags dynamic, timeout; size 65535; }
|
|
|
|
set blackhole6 { type ipv6_addr; flags dynamic, timeout; size 65535; }
|
2023-10-14 19:00:00 +02:00
|
|
|
|
2022-11-05 05:21:19 +01:00
|
|
|
# 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; }
|
|
|
|
|
2022-01-14 19:46:59 +01:00
|
|
|
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"
|
|
|
|
|
2022-04-04 08:27:06 +02:00
|
|
|
tcp flags & (fin|syn|rst|ack) != syn ct state new drop \
|
|
|
|
comment "Drop non-SYN packets"
|
|
|
|
|
2022-01-14 19:46:59 +01:00
|
|
|
tcp dport 113 reject with icmpx type port-unreachable \
|
|
|
|
comment "Reject AUTH to make it fail fast"
|
|
|
|
|
2022-04-04 08:27:06 +02:00
|
|
|
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)"
|
|
|
|
|
2024-01-19 18:00:00 +01:00
|
|
|
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
|
2022-05-10 18:13:35 +02:00
|
|
|
jump input_icmp
|
2022-11-05 05:21:19 +01:00
|
|
|
|
|
|
|
# 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
|
2022-11-14 02:57:36 +01:00
|
|
|
{% if opened_ports.tcp is sequence and opened_ports.tcp | length > 0 %}
|
2022-11-05 05:21:19 +01:00
|
|
|
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
|
2022-11-14 02:57:36 +01:00
|
|
|
{% endif %}
|
|
|
|
{% if opened_ports.udp is sequence and opened_ports.udp | length > 0 %}
|
2022-11-05 05:21:19 +01:00
|
|
|
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
|
2022-11-14 02:57:36 +01:00
|
|
|
{% endif %}
|
2022-11-05 05:21:19 +01:00
|
|
|
|
|
|
|
# Allow opened ports but also dynamically add them to the blacklist
|
2022-11-14 02:57:36 +01:00
|
|
|
{% if opened_ports.tcp is sequence and opened_ports.tcp | length > 0 %}
|
2022-11-05 05:21:19 +01:00
|
|
|
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
|
2022-11-14 02:57:36 +01:00
|
|
|
{% endif %}
|
|
|
|
{% if opened_ports.udp is sequence and opened_ports.udp | length > 0 %}
|
2022-11-05 05:21:19 +01:00
|
|
|
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
|
2022-11-14 02:57:36 +01:00
|
|
|
{% endif %}
|
2022-05-10 18:13:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
chain forward {
|
|
|
|
type filter hook forward priority 0; policy drop;
|
|
|
|
}
|
|
|
|
|
|
|
|
chain output {
|
|
|
|
type filter hook output priority 0; policy accept;
|
|
|
|
}
|
|
|
|
|
2024-01-19 18:00:00 +01:00
|
|
|
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"
|
|
|
|
}
|
|
|
|
|
2022-05-10 18:13:35 +02:00
|
|
|
chain input_icmp {
|
2024-01-19 18:00:00 +01:00
|
|
|
ip protocol igmp accept \
|
|
|
|
comment "Accept IGMP"
|
2022-01-14 19:46:59 +01:00
|
|
|
|
|
|
|
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
|
2022-11-05 05:21:19 +01:00
|
|
|
} limit rate 10/second burst 4 packets accept \
|
2022-01-14 19:46:59 +01:00
|
|
|
comment "Accept ICMP"
|
|
|
|
|
2024-01-19 18:00:00 +01:00
|
|
|
icmpv6 type {
|
2022-01-14 19:46:59 +01:00
|
|
|
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
|
2022-11-05 05:21:19 +01:00
|
|
|
} limit rate 10/second burst 4 packets accept \
|
2022-01-14 19:46:59 +01:00
|
|
|
comment "Accept basic IPv6 functionality"
|
|
|
|
|
2024-01-19 18:00:00 +01:00
|
|
|
icmpv6 type {
|
2022-01-14 19:46:59 +01:00
|
|
|
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"
|
|
|
|
|
2024-01-19 18:00:00 +01:00
|
|
|
icmpv6 type {
|
2022-01-14 19:46:59 +01:00
|
|
|
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"
|
2022-02-11 18:39:35 +01:00
|
|
|
}
|
|
|
|
}
|
2024-01-19 18:00:00 +01:00
|
|
|
|
|
|
|
# The state of stateful objects saved on the nftables service stop.
|
|
|
|
include "/var/lib/nftables/*.nft"
|