Skip to content

Repository files navigation

nftable role

Branch Status
master Build status

Variables

  • 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 legacy nft_ansible_controllers_v4 / nft_ansible_controllers_v6 lists are still merged in if set, but are deprecated in favour of this combined list.
  • nft_auto_whitelist (default false): whether to auto-whitelist all hosts in play
  • nft_ssh_ports (default [22,22222]): SSH ports to open for ansible/ssh controllers
  • nft_whitelist_rfc1918 (default false): when nft_auto_whitelist is 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 (default fw): name of the dedicated inet table this role owns. The role only ever touches table inet <nft_table_name> (i.e. table inet fw by default); every other table is left alone (see Docker / coexistence)

Test

molecule test

Usage

Base deploy

You can call this role to install nftables and deploy base rules like so:

- hosts: all
  become: true
  gather_facts: true
  roles:
    - ansible-nftables

This 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.

Custom rule

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'

Applying changes

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 element statements (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.

Docker / coexistence

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 does add table inet fw then delete table inet fw before rebuilding it — so only this role's table is swapped, atomically.
  • Membership apply only runs flush set / add element on this role's sets.
  • A systemd drop-in overrides the service's ExecStop so that even a manual systemctl stop/restart nftables removes 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.

About

Ansible role for nftables

Resources

Stars

1 star

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages