Jiff Slater
🤔 About
✍️ Contact
30 Jul 2021
These articles have been archived. You may find them useful but I am no longer offering support for them. Check out my latest articles on plkt.io.
nftables rules for LXD
15 November 2019

I recently had a lot of difficultly working out how to get ufw (the uncomplicated firewall) to work with LXD Linux containers.

Because of this and me not having a lot of time to dig deep into the tool, I decided to revisit some of my old iptables configs and amend them to work with LXD. In the process of doing this, I found that Debian Buster actually defaults to using the new nftables filtering mechanism built on top of netfilter. In this guide, we’ll configure a basic firewall with nftables and move over the legacy iptables rules automatically created by LXD.


Some disclaimers

I do want to note that these rules work for me but are not really any guarantee against being attacked, hacked, monitored, MITMed, or |insert-bad-thing-here|. For me they act as reasonable defense against basic attacks. For a really important web facing box I’d recommend placing a CDN in front of it and routing traffic through the CDN or alternatively using something like Argo Tunnel and only allowing connections through Argo.

A bit of reading

Here are the notes I reviewed before authoring this post. I think they serve as good background for iptables and can be skipped at your own peril :). Even though we’re using nftables, understanding the legacy framework will be needed to understand your existing rules.

Looking at the manpage for iptables we have five built-in tables and each of these contain some chains. These notes are pulled directly from man iptables but I felt serve as a good overview of some of these tables.

For command line invocation, we’ll be using the following. The long name is appended to the command line with double dash (–) and the single letter is the abbreviated call with a single dash. <> are required parameters [] are optional.

Stateful firewall


More reading: https://wiki.archlinux.org/index.php/Simple_stateful_firewall#Setting_up_a_NAT_gateway

Saving an undo state

We’ll begin by exporting the current state of the firewall and reimport it should we break things. For most people, there won’t be any firewall rules to export. In that case, we’ll simply drop the existing rules as we create them if there are errors.

If you’re logged into a remote box, I recommend you copy the reset rules into a script and schedule it to run in 10 minutes so you don’t get logged out :).

Inspect existing rules

Note you’ll need to also inspect the other tables listed above by appending -t <table name> here. Options are nat, mangle, security, and raw. You’ll likely only have legacy rules.

iptables-legacy -L

If any rules appear from the above commands, then it’s best to export them to a file using the instructions below.

Drop existing rules

I wouldn’t recommend doing this but you can also start from a clean slate. Don’t forget to do this for every table.

iptables -F
iptables-legacy -F

Scheduling the import

To schedule the import you may either use crontab or create a systemd timer. I’d recommend an even simpler method: launch a separate tmux or screen instance, execute the below, and detach from the instance. You may need to create multiple windows for each invocation. It’s a hacky but workable solution.


[create a new window]

# sleep 600 && iptables-legacy-restore /root/iptables-legacy.rules

[create a new window]

# sleep 700 && iptables-nft-restore /root/iptables-nft.rules

Make the rules persistent

We’ll install the iptables persistent package so these are applied during each reboot.

# apt install iptables-persistent

Creating the firewall rules

We’ll perform keep the changes pretty simple.

iptables --table mangle --append PREROUTING --match conntrack --ctstate INVALID --jump DROP
iptables --table mangle --append PREROUTING --match conntrack --ctstate ESTABLISHED,RELATED --jump ACCEPT
iptables --table mangle --append PREROUTING --protocol TCP ! --syn --match conntrack --ctstate NEW --jump DROP
iptables --table mangle --append PREROUTING --source --jump DROP 
iptables --table mangle --append PREROUTING --source --jump DROP 
iptables --table mangle --append PREROUTING --source ! --in-interface lo --jump DROP
iptables --table mangle --append PREROUTING --source --jump DROP 
iptables --table mangle --append PREROUTING --source --jump DROP 
iptables --table mangle --append PREROUTING --source --jump DROP 
iptables --table mangle --append PREROUTING --source --jump DROP 
iptables --table mangle --append PREROUTING --source --jump DROP 
iptables --table mangle --append PREROUTING --source -j DROP 
iptables --table mangle --append INPUT --match limit --limit 5/min --jump LOG --log-prefix "iptables denied: " --log-level 7

You can see that equivalent nft rules are created with nft list ruleset. TBD is to move that NAT rules created by lxdbr0 over. Perhaps just creating a normal bridge and referencing it in the LXD configuration is enough.