luminare.infra_edge · v0.2.1
0009. One nftables ruleset with a drop-in directory instead of a firewall per role
- Status: accepted
- Date: 2026-10-21
Context
Several roles open ports on the same host: a web server, a database for a few application
hosts, application ports, metrics exporters. Each role should only open its own port, nothing
may be open by default, and roles from other collections must be able to add rules without
knowing the others. Debian and Ubuntu both ship nftables.service, which loads
/etc/nftables.conf.
Considered options
baseowns/etc/nftables.confwith policy drop; other roles add files to/etc/nftables.d/, included by the last line.- Each role creates its own nftables table and chains.
- ufw with one rule per role.
Decision
Option 1. /etc/nftables.conf starts with flush ruleset, defines table inet filter with the
chains input (policy drop; loopback, established and related connections, ICMP and ICMPv6,
DHCPv6 replies to the client port 546 from link-local addresses, and SSH per base_ssh_allow_mode
accepted), forward (policy drop) and output (policy accept), and
its last line is include "/etc/nftables.d/*.nft". A role adds /etc/nftables.d/<NN>-<role>.nft
with add rule inet filter input … accept lines only (20 web server, 30 database, 40 to 49
application ports, 50 metrics exporters), writes it only when /etc/nftables.d exists or it is
told to, checks the whole ruleset with nft -c -f /etc/nftables.conf and restarts nftables.
Because the input policy is drop, a drop-in never needs a drop rule. base removes ufw so that
only one tool manages the rules.
Option 2 spreads the default policy over several tables, where a drop in one table is not undone by an accept in another, so the result depends on every role. Option 3 hides the ruleset behind another tool and has no include mechanism for files of other roles.
Consequences
- Good: one file shows the default policy,
nft -cchecks it together with every drop-in, and removing a role’s file and restarting nftables closes its port again. - Good: the Molecule scenario of
basechecks the ruleset without a drop-in, with a valid one and with a broken one (which must fail the check). - Bad:
flush rulesetalso removes rules that other software (for example Docker) adds, so such hosts keepbase_firewall_enabled: false. - Bad: containers cannot prove that the loaded ruleset blocks traffic; that check needs a virtual machine (listed in the README limitations).
docs/adr/0009-nftables-ruleset-with-drop-in-directory.md on GitHub
Licensed under Apache-2.0. Source: luminare-dev/ansible-collection-infra_edge at v0.2.1.