Role to provision NFTables firewall on linux servers.
Tested:
- Debian 11
- Debian 12
# latest
ansible-galaxy role install git+https://github.com/ansibleguy/infra_nftables
# from galaxy
ansible-galaxy install ansibleguy.infra_nftables
# or to custom role-path
ansible-galaxy install ansibleguy.infra_nftables --roles-path ./roles
- NFTables: Wiki
- Check out the Example!
- Practical Use-Cases (Docker, Proxmox, Network firewall)
- Integration of Fail2Ban with NFTables
- Troubleshooting Guide
-
Need professional support using Ansible or NFTables? Contact us:
E-Mail: [email protected]
Tel: +43 720 302 5732
Language: German or English
-
You want a simple Ansible GUI?
Check-out this Ansible WebUI
Define the config as needed:
nftables:
# enable: # features must be supported by kernel
# sets: true
# nat: true
# deb11_backport: false # use debian11 backports repository to install newer version on debian 10
# bash_completion: false
_defaults: # defaults inherited by all tables and chains
table:
type: 'inet'
chain:
policy: 'drop'
type: 'filter'
priority: 0
log:
drop: true
rules:
_all: [] # rules added to all chains of all tables
incoming: [] # rules added to 'incoming' chain of all tables
tables:
example:
# type: 'inet' # ipv4 + ipv6
_defaults:
rules:
_all: [] # rules added to all chains of this table
vars:
dns_servers: ['1.1.1.1', '1.1.0.0', '8.8.8.8', '8.8.4.4']
private_ranges: ['192.168.0.0/16', '172.16.0.0/12', '10.0.0.0/8']
sets:
blacklist:
flags: ['dynamic', 'timeout']
settings:
timeout: '3m'
counters:
invalid_packages:
comment: 'Invalid'
chains:
incoming:
hook: 'input'
rules:
- sequence: 1
raw: 'ct state invalid counter name invalid_packages log prefix "DROP invalid states" drop'
- seq: 2
raw: 'ct state {established, related} counter accept comment "Allow open sessions"'
- s: 3
raw: 'iifname "lo" accept comment "Allow loopback traffic"'
- {proto: 'icmp', type: 'echo-request', limit: 'rate 10/second', comment: 'Allow icmp-ping'}
- {proto: 'icmpv6', type: 'echo-request', limit: 'rate 10/second', comment: 'Allow icmp-ping'}
- {proto: 'icmp', code: 30, limit: 'rate 10/second', comment: 'Allow icmp-traceroute'}
- {proto: 'icmpv6', limit: 'rate 10/second', comment: 'Allow necessary icmpv6-types for ipv6 to work',
type: ['nd-neighbor-solicit', 'nd-router-advert', 'nd-neighbor-advert']}
- {proto: 'udp', port: 46251, counter: 'invalid_packages'}
outgoing:
hook: 'output'
# policy: 'accept'
rules:
- {dest: '$dns_servers', proto: 'udp', port: 53}
- {dest: '$dns_servers', proto: 'tcp', port: [53, 853]}
- {proto: ['tcp', 'udp'], port: [80, 443]}
- {proto: ['icmp', 'icmpv6'], comment: 'Allow outbound icmp'}
route:
hook: 'forward'
translate:
hook: 'postrouting'
type: 'nat'
policy: 'accept'
rules:
- {'src': '$private_ranges', oif: 'eno2', masquerade: true} # dynamic outbound nat
- {'src': '$private_ranges', oif: 'eno3', snat: '192.168.0.1'} # static outbound nat
If you want to merge group- & host-rules you could do it like that:
# define the basic ruleset used by all hosts as: 'fw_rules_all'
# define service-specific rules as: 'fw_rules_group'
# define host-specific rules as: 'fw_rules_host'
- name: NFTables
become: true
hosts: all
vars:
nftables:
tables:
example:
chains: "{{ fw_rules_all |
combine(fw_rules_group|default({}), recursive=true, list_merge='append') |
combine(fw_rules_host|default({}), recursive=true, list_merge='append') }}"
pre_tasks:
- debug:
var: nftables
roles:
- ansibleguy.infra_nftables
Run the playbook:
ansible-playbook -K -D -i inventory/hosts.yml playbook.yml
There are also some useful tags available:
- config_table => only provision actual rulesets
- config
- purge
To debug errors - you can set the 'debug' variable at runtime:
ansible-playbook -K -D -i inventory/hosts.yml playbook.yml -e debug=yes
-
Package installation
- Ansible dependencies (minimal)
- NFTables
-
Configuration
-
Possibility to define
- variables on global level
- variables, sets, counters and limits on table level
- variables on chain level
-
Config will be validated before being written
-
Default config:
- Enabled features (must be supported by kernel)
- Sets
- NAT
- No rules are added by default
- tables
- table-type = inet
- chains
- chain-type = filter
- chain-policy = drop
- priority = 0
- add counter = yes
- log implicit dropy = yes
- sets
- set-type = ipv4_addr
- add counter = yes
- rules
- policy = accept (set it to 'none' if you want to explicitly remove it)
- logging drops = yes
- Enabled features (must be supported by kernel)
-
Default opt-ins:
- Purging of unmanaged config-files stored in '/etc/nftables.d/'
-
Default opt-outs:
- Installing NFTables from Debian 11 backports when running on Debian 10 (newer version)
- Adding bash-completion script for the 'nft' command
-
-
Note: Most of the role's functionality can be opted in or out.
For all available options - see the default-config located in the main defaults-file!
-
Warning: Not every setting/variable you provide will be checked for validity. Bad config might break the role!
-
Info: You can add DNS-Resolution and IP-Blocklist functionalities to NFTables using the ansibleguy.addons_nftables role!
-
Warning: Some core functionalities (NAT/Sets) might not be supported by mainstream Distribution kernels.
-
Info: Read the Hook documentation to know when and how to configure hooks and priorities!
-
Info: Rules can be provided in dictionary format as seen in the examples.
These are the available fields and aliases:
Function Keys Note Rule sequence s, id, seq, sequence The sequence-id (integer) to sort the rules inside a chain. If none is provided one will be auto-generated beginning at 1000. If a duplicate sequence id is provided the role will fail its config-check! Input interface if, iif, iifname - Output interface of, oif, oifname - Protocol proto, pr, protocol - Protocol sub-type t, type - Protocol sub-code co, code - Destination Address/Network d, dest, target, destination, 'ip daddr', d6, dest6, target6, 'ip6 daddr' - Destination Port dp, port, dport, dest_port - Source Address/Network s, src, source, 'ip saddr', s6, src6, source6, 'ip6 saddr' - Source Port sp, sport, sport, src_port - Logging / Log message l, log, 'log prefix' If set to 'True' and a 'comment' is provided, it will be used as message. Else no message will be used Traffic counter count, counter If set to 'True' a rule-specific counter will be used. Else it will use the provided pre-defined counter Traffic Limit lim, limit A limit to set for the rule, see: Anonymous Limits and Pre-defined Limits Rule action a, action If no action is provided, it will default to 'accept' Source NAT masquerading m, masque, masquerade If NAT masquerading should be used Source NAT snat, src_nat, source_nat, outbound_nat, 'snat to' - Destination NAT dnat, dest_nat, destination_nat, 'dnat to' - Redirect redir, redirect, 'redirect to' By using redirect, packets will be forwarded to local machine Rule comment c, cmt, comment - User user, uid Match only traffic originating from specific user Group group, gid Match only traffic originating from specific group Firewall-Mark mark - Priority prio, priority - Packet length len, length - Timestamp time, timestamp Match timestamp of packet reception Weekday day Match day of week (0 = Sunday to 6 = Saturday or "Monday", "tuesday" also "fri", "Sat") Time hour Match 24-hour "HH:MM:SS", with seconds optional Only one of Action, Source-NAT, Masquerading or Destination-NAT can be set for one rule!
-
Info: Special/complex rules cannot be configured using the rule-dictionary.
You can use the 'raw' key to provide any custom rule that will be added to the ruleset directly.
-
Info: You can define variables, sets, counters and limits on table-level.
- Variables are key-value pairs.
var-name: var-value var2-name: ['value1', 'value2']
- Sets have this structure:
set-name: flags: [list-of-flags] # optional settings: setting: value # optional
- Counters have this structure:
counter-name: comment: text # optional
- Limits have this structure:
limit-name: rate: 'over 1024 bytes/second burst 512 bytes' comment: text # optional
- Variables are key-value pairs.
-
Warning: If you want to add a 'count-only' rule you need to set 'action' explicitly to 'none' - else the default value 'accept' will be added!
-
Info: If any unsupported field is supplied to the rule-translation it will throw an error as this might lead to unexpected results!
-
Info: Docker might need IPTables as Package-Dependency.
See: Use-Case Docker Host