From 2843241ff10addec337a2163e4b85e12807e6ebe Mon Sep 17 00:00:00 2001 From: Paul Duncan Date: Wed, 20 Oct 2021 04:52:30 -0400 Subject: add content/posts/2021-10-20-nftables-sitrep.md and content/articles/nftables-examples.md --- content/articles/nftables-examples.md | 505 ++++++++++++++++++++++++++++ content/posts/2021-10-20-nftables-sitrep.md | 36 ++ 2 files changed, 541 insertions(+) create mode 100644 content/articles/nftables-examples.md create mode 100644 content/posts/2021-10-20-nftables-sitrep.md (limited to 'content') 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" diff --git a/content/posts/2021-10-20-nftables-sitrep.md b/content/posts/2021-10-20-nftables-sitrep.md new file mode 100644 index 0000000..17b421c --- /dev/null +++ b/content/posts/2021-10-20-nftables-sitrep.md @@ -0,0 +1,36 @@ +--- +slug: nftables-sitrep +title: "Nftables Sitrep" +date: "2021-10-20T01:39:11-04:00" +--- +In April I decided to switch the firewalls for my laptop and a couple of +servers from [`iptables`][iptables] to [`nft` and nftables][nft]. + +After several months of use I can report that the experience has been +positive. + +Pros: +* Simple declarative configuration file. No more hacky shell scripts. +* Atomic (all or nothing) ruleset changes. +* Faster ruleset changes. +* Built-in [JSON][] support. + +Cons: +* Occasionally finicky parser. +* Remapping IP ranges can be more verbose than [iptables][]. + +The detauls are a bit long for a blog post (even for me!), so they are +available as a [separate "Nftables Examples" article][nftables-examples] +instead. + + +[iptables]: https://en.wikipedia.org/wiki/Iptables + "iptables command-line Linux firewall tool" +[nft]: https://en.wikipedia.org/wiki/Nftables + "nft command-line tool and nftables Linux firewall subsystem" +[wireguard]: https://wireguard.com/ + "Wireguard VPN" +[json]: https://json.org/ + "JavaScript Object Notation" +[nftables-examples]: {{< relref "/articles/nftables-examples.md" >}} + "Example nftable laptop and server configs" -- cgit v1.2.3