-
Notifications
You must be signed in to change notification settings - Fork 0
/
nftables-host.nft
178 lines (164 loc) · 8.91 KB
/
nftables-host.nft
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
#!/usr/sbin/nft --file
#### This -*-nftables-*- ruleset is my /etc/nftables.conf for new hosts.
#### Ref. http://jengelh.medozas.de/documents/Perfect_Ruleset.pdf
#### Ref. https://wiki.nftables.org/
####
#### GOTCHA: This is written for nft 0.9.1.
#### Several options have changed, even since 0.9.0!
####
#### GOTCHA: "nft --file" does not flush by default.
#### That is, it acts like "iptables-restore --noflush".
####
#### GOTCHA: If your ruleset does "flush; add; flush; add",
#### it will actually do "flush; flush; add; add".
#### That is, all the flushes move to the top.
####
#### This makes it impossible to do my old trick of
#### having the ruleset start with a "deny all" policy,
#### which was useful to make sure that if the firewall failed,
#### the OS would lock down the whole system.
#### (See "iptab" for notes about that.)
####
#### To achieve that under nft, you instead need to patch
#### nftables.service to have
#### OnFailure=nftables-denyall.service, and then write that
#### unit to load a SEPARATE, DIFFERENT nftables file that
#### blocks everything. And even then, it won't behave quite
#### the same.
####
#### This also means it is no longer safe to refer to
#### hostnames in this ruleset, safe in the knowledge that
#### they can only be resolved via local /etc/hosts. Because
#### the "deny all" ruleset can't be prepended here, we CANNOT
#### be sure the real ruleset will only resolve hostnames via
#### local sources -- so you might add ones that are only in
#### DNS, and then have the firewall fail to load during early
#### boot -- leaving you with a "working" host, with no
#### firewall!
####
#### GOTCHA: "list ruleset" here will print the ruleset BEFORE it goes through the kernel;
#### "nft list ruleset" later will print the ruleset AFTER it goes through the kernel.
#### The difference is usually like "iptables -p tcp" getting an implied "-m tcp".
#### However, differences MIGHT indicate a bug! Watch out!
####
#### GOTCHA: "table filter" and "chain INPUT" or "chain input" just
#### conventions and have NO MEANING WHATSOEVER. The actual
#### meaning comes from the "type filter hook input priority
#### filter" line.
####
#### NOTE: Only create a chain if you use it.
#### An empty chain is slightly slower than no chain at all.
#### e.g. most hosts don't need an output chain.
####
#### NOTE: iptables ALWAYS counts how many packets/bytes hit every chain and rule.
#### nftables makes this OPT IN, e.g. change "accept" to "counter accept".
#### iptables-save -c would print "[pkts:bytes] -A ...".
#### nftables list ruleset will print "... counter packgets 12 bytes 34 ...".
####
#### Since counters are useful during debugging but not production,
#### I have left them out of this example.
####
#### NOTE: "table x" is implicitly "table ip x", which is IPv4 only.
#### If you want dual-stack, say "table inet x".
####
#### NOTE: "iif lo" is resolved at ruleset load time into an interface
#### NUMBER inside the kernel; whereas "iifname lo" remains a
#### string. This means that:
####
#### * "iifname" is ~10 times slower than "iif" for every
#### packet considered (strcmp versus ==).
####
#### * If you load a ruleset with "iif foo" before foo exists,
#### the load will fail, LEAVING YOU UNPROTECTED!
####
#### * If you load a ruleset with "iif foo" and then foo is
#### removed and readded (e.g. ppp0 for a flaky ADSL link),
#### what happens?
####
#### * Rule of thumb: always use "iifname" (not "iif").
# NOTE: this will remove *ALL* tables --- including tables set up by
# other things (e.g. sshguard)!
#
# In theory you can flush just your own rules, e.g.
#
# flush table inet my_filter
#
# FIXME: I tried that, and I got locked out of SSH!
# What it did was remove all the rules, but NOT the chains, so
# the default-deny policy dropped EVERYTHING!!!
## NOTE: we add+delete each table (not "flush ruleset"), because
## otherwise we would wipe out sshguard table.
#flush ruleset
add table inet my_filter # idempotent
delete table inet my_filter # not idempotent
table inet my_filter {
chain my_input {
type filter hook input priority filter
policy drop
# Typically 99%+ of packets are part of an already-established flow.
# Allow those first, so we're a fast, stateful firewall.
# After this only "ct state new" (or "ct state untracked") will remain.
# FIXME: is a vmap here better (more efficient) than two separate rules?
# NOTE: {established or related: accept} does not match correctly!
ct state vmap { established: accept, related: accept, invalid: drop }
# Loopback traffic is needed for e.g. NFS RPC, and for debugging.
iiftype loopback accept
# Allow *some* kinds of IPv4/ICMP and IPv6/ICMPv6.
icmp type echo-request accept
icmpv6 type { echo-request, nd-neighbor-solicit, nd-router-advert, nd-neighbor-advert } accept
# "ct state" doesn't recognize MDNS/DNS-SD & LLMNR return packets, because
# the query daddr (224.0.0.251) doesn't match the reponse saddr (e.g. 192.168.0.2).
# As a stopgap to FIX PRINTING (cups-browsed) accept (some) unsolicited multicast packets.
# https://en.wikipedia.org/wiki/Multicast_DNS#Packet_structure
# FIXME: I still do not fully understand this, but it was enough to fix printing at work.
# FIXME: can we combine "ip daddr" and "ip6 daddr" on the same line?
ip daddr 224.0.0.251 udp dport mdns accept comment "MDNS/DNS-SD (e.g. printer discovery)"
ip6 daddr ff02::fb udp dport mdns accept comment "MDNS/DNS-SD (e.g. printer discovery)"
# FIXME: is this needed to get "Connected to: <switch name>:<switch port>" in "networkctl stats --all"?
# I seem to get that WITHOUT explicitly adding this, so leaving as log-and-continue for now.
# https://en.wikipedia.org/wiki/Link-Local_Multicast_Name_Resolution
ip daddr 224.0.0.252 ether daddr 01:00:5E:00:00:FC udp dport 5355 log prefix "FIXME: allow this LLMNR packet? "
ip6 daddr ff02::1:3 ether daddr 33:33:00:01:00:03 udp dport 5355 log prefix "FIXME: allow this LLMNR packet? "
# DHCPv6 has the same problem. Example request & response:
# fe80::892d:3b35:6914:6a9f → ff02::1:2 DHCPv6 136 Solicit XID: 0xccffdd CID: 0004f596f2f14208a98737a353e097ba81fe
# fe80::c256:27ff:fe2f:d1e2 → fe80::892d:3b35:6914:6a9f DHCPv6 206 Reply XID: 0xccffdd CID: 0004f596f2f14208a98737a353e097ba81fe IAA: 2403:5804:c6::add IAA: fd2e:a1a5:b5e0::add
# For now, log-and-accept iff the response is link-local.
ip6 saddr fe80::/64 ip6 daddr fe80::/64 udp dport dhcpv6-client counter log level debug prefix "non-RELATED/ESTABLISHED DHCPv6-client " accept
# YOUR RULES HERE.
# NOTE: service names resolve via nss (/etc/hosts) only in nft 0.9.1+!
##FOR "router" EXAMPLE### NOTE: a single rule CAN match "allow 53/tcp and 53/udp", but it's UGLY, so we don't.
##FOR "router" EXAMPLE### NOTE: I assume you used systemd (networkd or udev) to rename "enp0s0f0" to "lan".
##FOR "router" EXAMPLE### NOTE: "iif foo" must exist at ruleset load time.
##FOR "router" EXAMPLE### If your ruleset starts BEFORE udev and/or systemd-networkd are READY=1,
##FOR "router" EXAMPLE### consider using 'iifname lan' instead of "iif lan".
tcp dport ssh accept
tcp dport { http, https } accept
##FOR "router" EXAMPLE##iif enp11s0 tcp dport domain accept
##FOR "router" EXAMPLE##iif enp11s0 udp dport { domain, ntp, bootps } accept
# Finally, politely reject all other attempts.
# Omit to use the default policy ("policy drop", above) instead.
reject
}
# A host can't route unless you explicitly enable it, e.g.:
#
# sysctl net/ipv4/ip_forward=1
# sysctl net/ipv6/conf/all/forwarding=1
#
# We create a "deny all" inet filter forward chain anyway, as
# defense-in-depth against someone enabling routing ACCIDENTALLY.
chain my_forward {
type filter hook forward priority filter
policy drop
}
# We want output to be "allow all", so we don't even create a chain.
#chain my_output {
# type filter hook output priority filter
# policy accept
#}
}
# This is here to aid debugging.
# Note that its output WILL NOT MATCH a later "nft list ruleset".
# Also, it is buggy, e.g. the ICMPv6_RFC4890_policy it prints has gibberish in v0.9.1.
# UPDATE: in nftables=0.9.8-3 it sometimes core dumps! https://bugs.debian.org/982576
# Therefore comment out for now.
#list ruleset