| Branch | Status |
|---|---|
| master |
nft_ansible_controllers(default[]): list of ansible/ssh controllers to open SSH for. A single mixed list of v4 and v6 addresses (or CIDRs) — the role classifies each entry by family automatically. The legacynft_ansible_controllers_v4/nft_ansible_controllers_v6lists are still merged in if set, but are deprecated in favour of this combined list.nft_auto_whitelist(defaultfalse): whether to auto-whitelist all hosts in playnft_ssh_ports(default[22,22222]): SSH ports to open for ansible/ssh controllersnft_whitelist_rfc1918(defaultfalse): whennft_auto_whitelistis set, also whitelist the RFC1918 private ranges (10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,fd00::/8)nft_table_name(defaultfw): name of the dedicatedinettable this role owns. The role only ever touchestable inet <nft_table_name>(i.e.table inet fwby default); every other table is left alone (see Docker / coexistence)
molecule testYou can call this role to install nftables and deploy base rules like so:
- hosts: all
become: true
gather_facts: true
roles:
- ansible-nftablesThis will deploy base rules, and open SSH for hosts listed in
nft_ansible_controllers.
If nft_auto_whitelist is set to true, it will also whitelist servers listed in inventory.
If you need to add a specific rule for a service, you can call the role in
"single rule mode" by setting nft_add_input_rule:
# - deploy a web server for instance
- role: ansible-nftables
nft_add_input_rule:
- type: 'dport_accept'
protocol: 'tcp'
dports: [ '80', '443' ]
saddrs: [ '0.0.0.0/0', '::/0' ]
weight: '90'
comment: "allow HTTP & HTTPS in (v4/v6)"
name: 'web_clients_accept'Address lists (controllers, auto-whitelisted "friends", and custom-rule
saddrs) are stored in nftables named sets rather than expanded inline into
rules. This lets the role update set membership without tearing down and
rebuilding the whole firewall.
Each set is rendered as two drop-in files under /etc/nftables.d/:
- a structure file declaring the empty set and the rules that reference it
(e.g.
pre/00_variables.nft,custom/<weight>_<name>.nft) - an elements file containing only
flush set+add elementstatements (e.g.pre/90_elements.nft,custom/<weight>_<name>_elements.nft)
Both are included by /etc/nftables.conf, so a reboot rebuilds everything and
membership persists.
When only set membership changes, the role applies just the elements files with
nft -f, replacing the set contents atomically. When the rules themselves change
(ports, protocol, adding/removing a rule), the role re-applies
/etc/nftables.conf, which atomically replaces its own table (table inet fw).
Both paths run automatically on change and are non-disruptive: no flush ruleset, no service restart, and other tables (e.g. Docker's) are left
untouched. Rules, chains and counters of the replaced table are rebuilt in a
single atomic transaction, and existing connections keep flowing via connection
tracking — so the firewall is live immediately after a role run.
Membership updates are applied only if the target set is already live (validated
with nft -c -f); on a fresh host, or for a brand-new set whose structure has
not been loaded yet, the element update is skipped and picked up when the table
is next (re)loaded.
Note: because changes converge on the live firewall, removing an address from a set (e.g. an SSH controller) takes effect immediately for new connections. Established connections are unaffected.
The role confines itself to a single, dedicated table: table inet fw
(configurable via nft_table_name). It never runs flush ruleset and
never restarts the nftables service. Every apply path only ever creates,
replaces or updates that one table:
- Structural apply re-applies
/etc/nftables.conf, which doesadd table inet fwthendelete table inet fwbefore rebuilding it — so only this role's table is swapped, atomically. - Membership apply only runs
flush set/add elementon this role's sets. - A systemd drop-in overrides the service's
ExecStopso that even a manualsystemctl stop/restart nftablesremoves only this role's table instead of flushing everything.
As a result, rules injected into other tables — notably Docker's
ip filter (DOCKER, DOCKER-USER, forwarding) and ip nat (published-port DNAT),
which are not present in this role's files — are never touched, and Docker does
not need to be restarted after a firewall change.
For the same reason the role does not manage a forward hook: forwarding is
left to whatever owns it (e.g. Docker). Hosts that need a default-drop FORWARD
policy should manage it separately.