aboutsummaryrefslogtreecommitdiff
path: root/content/articles
diff options
context:
space:
mode:
Diffstat (limited to 'content/articles')
-rw-r--r--content/articles/nftables-examples.md505
1 files changed, 505 insertions, 0 deletions
diff --git a/content/articles/nftables-examples.md b/content/articles/nftables-examples.md
new file mode 100644
index 0000000..8309daf
--- /dev/null
+++ b/content/articles/nftables-examples.md
@@ -0,0 +1,505 @@
+---
+slug: "nftables-examples"
+title: "Nftables Examples"
+date: "2021-10-20"
+
+# show table of contents
+toc: true
+---
+## Overview
+
+This article contains setup tips and complete [nftables][nft]
+configurations for a couple of my systems.
+
+The official [nftables][nft] documentation is available on the [nftables
+wiki][].
+
+## Initial Setup
+
+First, I add `/etc/nftables-reset.conf`, which is documented in the
+[Reset Script][reset-script] section below.
+
+On remote machines I also add this commented entry to the root
+[`crontab`][cron]:
+
+```cron
+# enable when fiddling with firewall or else...
+# */10 * * * * /sbin/nft -f /etc/nftables-reset.conf
+```
+
+This entry resets the firewall rules every 10 minutes, so if (when) I
+make a mistake I am only locked out for a few minutes.
+
+When making firewall changes on a remote machine I do the following:
+
+1. Uncomment the [`crontab`][cron] entry.
+2. Make the firewall changes.
+3. Verify that the firewall changes are working as expected.
+4. Comment the [`crontab`][cron] entry out again.
+
+I learned this trick the hard way after a handful of hilarious blunders
+with [`iptables`][iptables] in the past.
+
+It's not strictly necessary because modern hosting providers typically
+offer web shell access, but I still do it out of habit.
+
+## Server Firewall
+
+Here is a `nftables.conf` for a server with only `https` and
+[Wireguard][] exposed to the outside world. There is also a disabled
+rule to enable external [SSH][], but I haven't used it yet because I
+always [SSH][ssh] to the system through the [Wireguard VPN][wireguard].
+
+```bash
+#!/sbin/nft -f
+
+#
+# nftables.conf: nftables config for server firewall
+#
+# input chain
+# -----------
+# * accept all traffic related to established connections
+# * accept all traffic on loopback iface and wireguard iface
+# * accept icmp, https, and wireguard traffic on external iface
+# * drop and count any other input traffic
+#
+# forward chain
+# -------------
+# * accept all forwarded traffic (TODO: lock this down more)
+#
+# output chain
+# ------------
+# * accept and count all output traffic (TODO: lock this down more)
+#
+# Commands
+# --------
+# * `nft list counters`: to show counter values
+# * `nft list ruleset`: list rules
+# * `nft monitor`: monitor traces
+# * `nft monitor trace | grep 'output packet'`: monitor out traffic
+# * `nft -f /etc/nftables-reset.conf`: disable filters
+#
+# Notes
+# -----
+# * See commented "log" line below to log dropped input headers
+# * Used to need to enable non-wg http for certbot, but that isn't
+# necessary now because of `certbot-dns-linode`
+#
+
+# clear rules
+flush ruleset
+
+table inet filter {
+ # declare named counters
+ counter drop_ct_invalid {}
+ counter accept_ct_rel {}
+ counter drop_loop_v4 {}
+ counter drop_loop_v6 {}
+ counter accept_icmp_v4 {}
+ counter accept_icmp_v6 {}
+ # counter accept_ssh {}
+ counter accept_https {}
+ counter accept_wg {}
+ counter drop_input {}
+ counter accept_output {}
+
+ chain input {
+ # input policy: drop
+ type filter hook input priority 0; policy drop;
+
+ # connection tracker
+ ct state invalid counter name drop_ct_invalid drop \
+ comment "drop ct invalid"
+ ct state {established, related} counter name accept_ct_rel accept \
+ comment "accept ct established, related"
+
+ # accept all loopback/wg0 traffic
+ iif lo accept comment "accept iif lo"
+ iif wg0 accept comment "accept iif wg0"
+
+ # drop loopback traffic on non-loopback interfaces
+ iif != lo ip daddr 127.0.0.1/8 counter name drop_loop_v4 drop \
+ comment "drop invalid loopback traffic"
+ iif != lo ip6 daddr ::1/128 counter name drop_loop_v6 drop \
+ comment "drop invalid loopback traffic"
+
+ # accept icmp
+ ip protocol icmp counter name accept_icmp_v4 accept \
+ comment "accept icmp v4"
+ ip6 nexthdr icmpv6 counter name accept_icmp_v6 accept \
+ comment "accept icmp v6"
+
+ # accept external ssh (disabled)
+ # tcp dport 22 counter name accept_ssh accept comment "accept ssh"
+
+ # accept external https and wireguard
+ tcp dport 443 counter name accept_https accept comment "accept https"
+ udp dport 51818 counter name accept_wg accept comment "accept wg"
+
+ # count/log remaining (disabled because of log spam)
+ # counter name drop_input log prefix "DROP " comment "drop input"
+
+ # count remaining (no logging)
+ counter name drop_input comment "drop input"
+ }
+
+ # accept all forwarding (TODO: lock this down more)
+ chain forward {
+ # forward policy: accept
+ type filter hook forward priority 0; policy accept;
+ }
+
+ # count/accept all output (TODO: lock this down more)
+ chain output {
+ # output policy: accept
+ type filter hook output priority 0; policy accept;
+ counter name accept_output comment "accept output"
+ }
+}
+```
+
+## Laptop Firewall
+
+The `nftables.conf` for my personal laptop is below. It's a more
+elaborate than the server configuration from the previous section,
+because I am dealing with:
+
+* A pair of extremely chatty [Chromecasts][chromecast]
+* Three [Wireguard][] [VPNs][vpn]
+* [Syncthing][]
+* [UPNP][] broadcasts
+* [PCP][]
+* [MDNS][]
+* Traffic from local [VMs][vm]
+* [tracing][] support (disabled by default)
+* `DROP` logging (I monitor this via a `journalctl -f` on a virtual
+ desktop)
+
+The [Syncthing][] and [Chromecast][] rules are examples of:
+
+* IP sets,
+* port number sets, and
+* port number ranges
+
+```bash
+#!/sbin/nft -f
+
+#
+# nftables.conf: laptop firewall
+#
+# * input chain:
+# input chain
+# -----------
+# * accept traffic related to established connections
+# * accept traffic on loopback and wireguard interfaces
+# * accept icmp, igmp, ssh, dhcp, mdns, syncthing, and chromecast traffic
+# * drop, count, and trace any other inbound traffic
+#
+# forward chain
+# -------------
+# * accept and count all forwarded traffic
+#
+# output chain
+# ------------
+# * policy accept, until I refine the rules more
+# * accept common traffic, and count monitor all output traffic
+# * optional trace (disabled)
+#
+# Commands
+# --------
+# * `nft list ruleset`: list rules
+# * `nft list counters`: to show counter values
+# * `nft monitor`: monitor traces
+# * `nft monitor trace | grep 'output packet'`: monitor out traffic
+# * `nft -f /etc/nftables-reset.conf`: disable filters
+#
+# Notes
+# -----
+# * See "log" line in input chain to log dropped input
+#
+
+# clear rules
+flush ruleset
+
+table inet filter {
+ # declare named counters
+ counter drop_in_ct_invalid {}
+ counter accept_in_ct_rel {}
+ counter drop_loop_v4 {}
+ counter drop_loop_v6 {}
+ counter accept_icmp_v4 {}
+ counter accept_icmp_v6 {}
+ counter accept_ssh {}
+ counter drop_input {}
+ counter accept_forward {}
+ counter drop_out_ct_invalid {}
+ counter accept_out_ct_rel {}
+ counter accept_output {}
+
+ # chromecast IPs (chatty in the ephemeral udp port range)
+ # src: https://blog.g3rt.nl/allow-google-chromecast-host-firewall-iptables.html
+ define CHROMECASTS = {
+ 192.168.1.153,
+ 192.168.1.168
+ }
+
+ chain input {
+ # input policy: drop
+ type filter hook input priority 0; policy drop;
+
+ # connection tracker
+ ct state invalid counter name drop_in_ct_invalid drop \
+ comment "drop ct invalid"
+ ct state {established, related} counter name accept_in_ct_rel accept \
+ comment "accept ct established, related"
+
+ # accept all loopback/wg/virbr* traffic
+ # NOTE: "iifname" is slower than "iif", but it allows name globbing
+ # and i want to match on all 3 wireguard ifaces
+ iif lo accept comment "accept iif lo"
+ iifname "wg*" accept comment "accept iif wg*"
+ iifname "virbr*" accept comment "accept iif virbr*"
+
+ # drop loopback traffic on non-loopback interfaces
+ iif != lo ip daddr 127.0.0.1/8 counter name drop_loop_v4 drop \
+ comment "drop invalid loopback traffic"
+ iif != lo ip6 daddr ::1/128 counter name drop_loop_v6 drop \
+ comment "drop invalid loopback traffic"
+
+ # accept icmp
+ ip protocol icmp counter name accept_icmp_v4 accept \
+ comment "accept icmp v4"
+ ip6 nexthdr icmpv6 counter name accept_icmp_v6 accept \
+ comment "accept icmp v6"
+
+ # accept igmp
+ ip daddr 224.0.0.1 accept comment "accept igmp"
+
+ # accept ssh, dhcp, upnp, pcp, and mdns
+ tcp dport 22 counter name accept_ssh accept comment "accept ssh"
+ udp dport 68 counter accept comment "accept dhcp"
+ udp sport 1900 counter accept comment "accept upnp"
+ udp dport 5131 counter accept comment "accept pcp"
+ udp dport 5353 counter accept comment "accept mdns"
+
+ # accept syncthing
+ # src: https://docs.syncthing.net/users/firewall.html
+ tcp dport 22000 counter accept comment "accept syncthing"
+ udp dport {21027, 22000} counter accept comment "accept syncthing"
+
+ # accept chromecast traffic
+ ip saddr $CHROMECASTS udp dport 32768-65535 \
+ accept comment "accept chromecast"
+
+ # drop netgear usb control center/readyshare traffic
+ # (src: https://www.speedguide.net/port.php?port=7423)
+ udp dport 7423 drop comment "drop netgear readyshare"
+
+ # log drops
+ log prefix "DROP "
+
+ # count/monitor drops
+ counter name drop_input meta nftrace set 1 comment "drop input"
+ }
+
+ # accept all forwarding
+ # (TODO: lock this down more)
+ chain forward {
+ # forward policy: accept
+ type filter hook forward priority 0; policy accept;
+ counter name accept_forward comment "accept forward"
+ }
+
+ # output chain
+ # (TODO: the output policy is ACCEPT until I can spend some more
+ # time locking this down)
+ chain output {
+ # output policy: accept
+ type filter hook output priority 0; policy accept;
+
+ # connection tracker
+ ct state invalid counter name drop_out_ct_invalid drop \
+ comment "drop ct invalid"
+ ct state {established, related} counter name accept_out_ct_rel accept \
+ comment "accept ct established, related"
+
+ # accept all loopback/wg/virbr0 traffic
+ # NOTE: "iifname" is slower than "iif", but it allows name globbing
+ # and i want to match on all 3 wireguard ifaces
+ oif lo accept comment "accept oif lo"
+ oifname "wg*" accept comment "accept oif wg*"
+ oifname "virbr*" accept comment "accept oif virbr*"
+
+ # drop loopback traffic on non-loopback interfaces
+ oif != lo ip daddr 127.0.0.1/8 counter name drop_loop_v4 drop \
+ comment "drop invalid loopback traffic"
+ oif != lo ip6 daddr ::1/128 counter name drop_loop_v6 drop \
+ comment "drop invalid loopback traffic"
+
+ # accept icmp
+ ip protocol icmp counter accept comment "accept icmp v4"
+ ip6 nexthdr icmpv6 counter accept comment "accept icmp v6"
+
+ # accept igmp
+ ip daddr 224.0.0.0/24 accept comment "accept igmp"
+
+ # accept out ssh, http, https, dns, dhcp, https (udp), upnp, pcp, and mdns
+ tcp dport {22, 80, 443} counter accept
+ udp dport {53, 67, 443, 1900, 5131, 5353} counter accept
+
+ # accept syncthing
+ # src: https://docs.syncthing.net/users/firewall.html
+ tcp sport 22000 counter accept comment "accept syncthing"
+ tcp dport 22000 counter accept comment "accept syncthing"
+ udp sport {21027, 22000} counter accept comment "accept syncthing"
+ udp dport {21027, 22000} counter accept comment "accept syncthing"
+
+ # accept chromecast traffic
+ ip saddr $CHROMECASTS udp dport 32768-65535 \
+ accept comment "accept chromecast"
+
+ # accept outbound wireguard traffic
+ # FIXME: should probably lock down dest addrs
+ udp dport 51818-51820 accept comment "accept wireguard"
+
+ # monitor output accepts
+ #
+ # Enable this and then monitor outbound traffic like so:
+ # nft monitor trace | grep 'output packet'
+ meta nftrace set 1 comment "trace output"
+
+ # count accepts (no trace)
+ counter name accept_output comment "accept output"
+ }
+}
+```
+
+
+## Reset Script
+
+I always have a file which clears the firewall rules named
+`/etc/nftables-reset.conf` and looks like this:
+
+```bash
+#!/usr/sbin/nft -f
+
+#
+# nftables-reset.conf: clear nft rules
+#
+# (what did you do this time?)
+#
+
+flush ruleset
+
+table inet filter {
+ chain input {
+ type filter hook input priority 0; policy accept;
+ }
+
+ chain forward {
+ type filter hook forward priority 0;
+ }
+
+ chain output {
+ type filter hook output priority 0;
+ }
+}
+```
+
+The path and file name are arbitrary, but I always put it in the same
+file so I can use the following command to clear the firewall rules
+on any machine I am working on:
+
+```bash
+nft -f /etc/nftables-reset.conf
+```
+
+## Bonus: DNATs and Maps
+
+If you have a [VPN][] to your house and your home network uses a common
+reserved subnet (`192.168.0.0/24`, `192.168.1.0/24`, `192.168.100.0/24`,
+etc), then you can use [DNATting][dnat] and [maps][] to make everything
+play nicely together.
+
+In my case, I [DNAT][dnat] all incoming traffic from the [Wireguard][]
+interface that is destined for `192.168.2.0/24` to `192.168.1.0/24` and
+masquerade the return traffic using [nftables][nft].
+
+Here is the `nftables.conf`:
+
+```bash
+#!/usr/sbin/nft -f
+
+flush ruleset
+
+table ip nat {
+ # dnat 192.168.2.0/24 to 192.168.1.0/24 map
+ map dnats {
+ type ipv4_addr: ipv4_addr;
+
+ elements = {
+ # deliberately remapping 2.100 to 1.1 here, the reason isn't
+ # relevant but it's an example of doing arbitrary DNATs with an
+ # nftables map
+ 192.168.2.100: 192.168.1.1,
+
+ 192.168.2.2: 192.168.1.2, # nfs
+ 192.168.2.3: 192.168.1.3, # user
+ 192.168.2.4: 192.168.1.4, # hive
+ # ... snipped for brevity ...
+ 192.168.2.253: 192.168.1.253,
+ 192.168.2.254: 192.168.1.254
+ }
+ }
+
+ # dnat incoming wg0 traffic to 192.168.1.0/24
+ chain prerouting {
+ type nat hook prerouting priority 0;
+ policy accept;
+ iifname "wg0" ip daddr 192.168.2.0/24 dnat ip daddr map @dnats;
+ }
+
+ # masquerade 192.168.3.0/24 traffic
+ # (so return traffic has routable src IP)
+ chain postrouting {
+ type nat hook postrouting priority 100;
+ policy accept;
+ oifname "eth0" ip saddr 192.168.3.0/24 masquerade
+ }
+}
+```
+
+[nft]: https://en.wikipedia.org/wiki/Nftables
+ "nft command-line tool and nftables Linux firewall subsystem"
+[vm]: https://en.wikipedia.org/wiki/System_virtual_machine
+ "Virtual machine"
+[cron]: https://en.wikipedia.org/wiki/Cron
+ "Unix periodic job scheduler"
+[ssh]: https://en.wikipedia.org/wiki/Secure_Shell
+ "Secure Shell"
+[wireguard]: https://wireguard.com/
+ "Wireguard VPN"
+[vpn]: https://en.wikipedia.org/wiki/Virtual_private_network
+ "Virtual Private Network"
+[reset-script]: #reset-script
+ "Escape hatch for my mistakes"
+[iptables]: https://en.wikipedia.org/wiki/Iptables
+ "iptables command-line Linux firewall tool"
+[chromecast]: https://en.wikipedia.org/wiki/Chromecast
+ "Great way to play annoying YouTube videos on your TV"
+[upnp]: https://en.wikipedia.org/wiki/Universal_Plug_and_Play
+ "Universal Plug and Play"
+[pcp]: https://en.wikipedia.org/wiki/Port_Control_Protocol
+ "Port Control Protocol"
+[mdns]: https://en.wikipedia.org/wiki/Multicast_DNS
+ "Multicast Domain Name Service"
+[syncthing]: https://syncthing.net/
+ "Continuous file synchronization program"
+[tracing]: https://wiki.nftables.org/wiki-nftables/index.php/Ruleset_debug/tracing
+ "nft packet tracing"
+[dnat]: https://en.wikipedia.org/wiki/Network_address_translation
+ "Destination Network Address Translation"
+[maps]: https://wiki.nftables.org/wiki-nftables/index.php/Maps
+ "nftables maps"
+[nftables wiki]: https://wiki.nftables.org/
+ "nftables wiki"