ansible.nftables/README.md

608 lines
24 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Nftables
1. [Overview](#overview)
1. [Role Variables](#role-variables)
* [OS Specific Variables](#os-specific-variables)
* [Rules Dictionaries](#rules-dictionaries)
1. [Examples](#examples)
* [With playbooks](#with-playbooks)
* [With group_vars and host_vars](#with-group_vars-and-host_vars)
1. [Configuration](#configuration)
* [Fail2ban integration](#fail2ban-integration)
1. [Development](#development)
1. [License](#license)
1. [Author Information](#author-information)
## Overview
A role to manage Nftables rules and packages.
Highly inspired by [Mike Gleason firewall role][mikegleasonjr firewall github]
(3 levels of rules definition and template), thanks! I hope i haven't
complexify his philosophy… (I'm pretty sure, i now did complexify it :D) ^^
## Role Variables
* **nft_enabled**: Enable or disable support for Nftables [default: `true`].
* **nft_pkg_state**: State of new `nftables` package(s) [default: `present`].
* **nft_old_pkg_list**: The list of useless packages to remove (such as Iptables,…) [default: `iptables`].
* **nft_old_pkg_state**: State of old package(s) [default: `absent`].
* **nft_old_pkg_manage**: If old package(s) should be managed with this role [default: `true`].
* **nft_conf_dir_path**: Directory to store the differents Nftables configuration files [default: `/etc/nftables.d`].
* **nft_main_conf_path**: Main configuration file loaded by systemd unit [default: `/etc/nftables.conf`].
* **nft_main_conf_content**: Template used to generate the previous main configuration file [default: `etc/nftables.conf.j2`].
* **nft_input_conf_path**: Input configuration file include in main configuration file [default: `{{ nft_conf_dir_path }}/filter-input.nft`].
* **nft_input_conf_content**: Template used to generate the previous input configuration file [default: `etc/nftables.d/filter-input.nft.j2`].
* **nft_output_conf_path**: Output configuration file include in main configuration file [default: `{{ nft_conf_dir_path }}/filter-output.nft`].
* **nft_output_conf_content**: Template used to generate the previous output configuration file [default: `etc/nftables.d/filter-output.nft.j2`].
* **nft_forward_conf_path**: forward configuration file include in main configuration file [default: `{{ nft_conf_dir_path }}/filter-forward.nft`].
* **nft_forward_conf_content**: Template used to generate the previous forward configuration file [default: `etc/nftables.d/filter-forward.nft.j2`].
* **nft_define_conf_path**: Vars definition file include in main configuration file [default: `{{ nft_conf_dir_path }}/defines.nft`].
* **nft_define_conf_content**: Template used to generate the previous vars definition file [default: `etc/nftables.d/defines.nft.j2`].
* **nft_sets_conf_path**: Sets and maps definition file include in main configuration file [default: `{{ nft_conf_dir_path }}/sets.nft`].
* **nft_sets_conf_content**: Template used to generate the previous sets and maps definition file [default: `etc/nftables.d/sets.nft.j2`].
* **nft_global_default_rules**: Set default rules for `global` chain. Other chains will jump to `global` before apply their specific rules.
* **nft_global_rules**: You can add `global` rules or override those defined by **nft_global_default_rules** for all hosts.
* **nft_global_group_rules**: You can add `global` rules or override those defined by **nft_global_default_rules** and **nft_global_rules** for a group.
* **nft_global_host_rules**: Hosts can also add or override all previours rules.
* **nft__custom_content**: Custom content (tables, include,…) to add in Nftables configuration [default: `''`].
* **nft_input_default_rules**: Set default rules for `input` chain.
* **nft_input_rules**: You can add `input` rules or override those defined by **nft_input_default_rules** for all hosts.
* **nft_input_group_rules**: You can add `input` rules or override those defined by **nft_input_default_rules** and **nft_input_rules** for a group.
* **nft_input_host_rules:**: Hosts can also add or override all previous `input` rules.
* **nft_output_default_rules**: Set default rules for `output` chain.
* **nft_output_rules**: You can add `output` rules or override those defined by **nft_output_default_rules** for all hosts.
* **nft_output_group_rules**: You can add `output` rules or override those defined by **nft_output_default_rules** and **nft_output_rules** for a group.
* **nft_output_host_rules**: Hosts can also add or override all previous `output` rules.
* **nft_forward_default_rules**: Set default rules for `forward` chain.
* **nft_forward_rules**: You can add `forward` rules or override those defined by **nft_forward_default_rules** for all hosts.
* **nft_forward_group_rules**: You can add `forward` rules or override those defined by **nft_forward_default_rules** and **nft_forward_rules** for a group.
* **nft_forward_host_rules**: Hosts can also add or override all previous `forward` rules.
* **nft__forward_table_manage**: If the forward table should be managed [default: `False`].
* **nft__nat_table_manage**: If the nat table should be managed [default: `False`].
* **nft__nat_default_prerouting_rules**: Set default rules for `prerouting` chain of **nat** table.
* **nft__nat_prerouting_rules**: Set rules for `prerouting` chain of **nat** table for all hosts in the Ansible inventory.
* **nft__nat_group_prerouting_rules**: Set rules for `prerouting` chain of **nat** table for hosts in specific Ansible inventory group.
* **nft__nat_host_prerouting_rules**: Set rules for `prerouting` chain of **nat** table for specific hosts the Ansible inventory.
* **nft__nat_prerouting_conf_path**: Prerouting configuration file include in the main configuration [default: `{{ nft_conf_dir_path }}/nat-prerouting.nft`].
* **nft__nat_prerouting_conf_content**: Template used to generate the previous prerouting configuration file [default: `etc/nftables.d/nat-prerouting.nft.j2`].
* **nft__nat_default_postrouting_rules**: Set default rules for `postrouting` chain of **nat** table.
* **nft__nat_postrouting_rules**: Set rules for `postrouting` chain of **nat** table for all hosts in the Ansible inventory.
* **nft__nat_group_postrouting_rules**: Set rules for `postrouting` chain of **nat** table for hosts in specific Ansible inventory group.
* **nft__nat_host_postrouting_rules**: Set rules for `postrouting` chain of **nat** table for specific hosts the Ansible inventory.
* **nft__nat_postrouting_conf_path**: postrouting configuration file include in the main configuration [default: `{{ nft_conf_dir_path }}/nat-postrouting.nft`].
* **nft__nat_postrouting_conf_content**: Template used to generate the previous postrouting configuration file [default: `etc/nftables.d/nat-postrouting.nft.j2`].
* **nft_define_default**: Set default vars available in all rules.
* **nft_define**: You can add vars or override those defined by **nft_define_default** for all hosts.
* **nft_define_group**: You can add vars or override those defined by **nft_define_default** and **nft_define** for a group.
* **nft_define_host**: You can add or override all previous vars.
* **nft_service_manage**: If `nftables` service should be managed with this role [default: `true`].
* **nft_service_name**: `nftables` service name [default: `nftables`].
* **nft_service_enabled**: Set `nftables` service available at startup [default: `true`].
* **nft__service_protect**: If systemd unit should protect system and home [default: `true`].
* **nft_merged_groups** : If variables from the hosts Ansible groups should be merged [default : `false`].
* **nft_merged_groups_dir** : The dictionary where the nftables group rules, named like the Ansible groups, are located in [default : `vars/`].
* **nft_debug** : Toggle more verbose output on/off. [default: 'false'].
### OS Specific Variables
Please see default value by Operating System file in [vars][vars directory] directory.
* **nft_pkg_list**: The list of package(s) to provide `nftables`.
* **nft__bin_location** : Path to `nftables` executable. [default : `/usr/sbin/nft`]
### Rules Dictionaries
Each type of rules dictionaries will be merged and rules will be applied in the alphabetical order of the keys (the reason to use 000 to 999 as prefix). So:
* **nft_*_default_rules**: Define default rules for all nodes. You can define it in `group_vars/all`.
* **nft_*_rules**: Can add rules and override those defined by **nft_*_default_rules**. You can define it in `group_vars/all`.
* **nft_*_group_rules**: Can add rules and override those defined by **nft_*_default_rules** and **nft_*_rules**. You can define it in `group_vars/webservers`.
* If 'nft_merged_groups' is set to true, multiple group rules from the ansible groups will also be merged together.
* **nft_*_host_rules**: Can add rules and override those define by **nft_*_default_rules**, **nft_*_group_rules** and **nft_*_rules**. You can define it in `host_vars/www.local.domain`.
`defaults/main.yml`:
``` yml
# rules
nft_global_default_rules:
005 state management:
- ct state established,related accept
- ct state invalid drop
nft_global_rules: {}
nft_merged_groups: false
nft_merged_groups_dir: vars/
nft_global_group_rules: {}
nft_global_host_rules: {}
nft_input_default_rules:
000 policy:
- type filter hook input priority 0; policy drop;
005 global:
- jump global
010 drop unwanted:
- ip daddr @blackhole counter drop
015 localhost:
- iif lo accept
210 input tcp accepted:
- tcp dport @in_tcp_accept ct state new accept
nft_input_rules: {}
nft_input_group_rules: {}
nft_input_host_rules: {}
nft_output_default_rules:
000 policy:
- type filter hook output priority 0; policy drop;
005 global:
- jump global
015 localhost:
- oif lo accept
050 icmp:
- ip protocol icmp accept
- ip6 nexthdr icmpv6 counter accept
200 output udp accepted:
- udp dport @out_udp_accept ct state new accept
210 output tcp accepted:
- tcp dport @out_tcp_accept ct state new accept
nft_output_rules: {}
nft_output_group_rules: {}
nft_output_host_rules: {}
# define nft vars
nft_define_default:
broadcast and multicast:
desc: 'broadcast and multicast'
name: badcast_addr
value: '{ 255.255.255.255, 224.0.0.1, 224.0.0.251 }'
input tcp accepted:
name: in_tcp_accept
value: '{ ssh }'
output tcp accepted:
name: out_tcp_accept
value: '{ http, https, hkp }'
output udp accepted:
name: out_udp_accept
value: '{ bootps, domain, ntp }'
nft_define: {}
nft_define_group: {}
nft_define_host: {}
# sets and maps
nft_set_default:
blackhole:
- type ipv4_addr;
- elements = $badcast_addr
in_tcp_accept:
- type inet_service; flags interval;
- elements = $in_tcp_accept
out_tcp_accept:
- type inet_service; flags interval;
- elements = $out_tcp_accept
out_udp_accept:
- type inet_service; flags interval;
- elements = $out_udp_accept
nft_set: {}
nft_set_group: {}
nft_set_host: {}
```
Those default will generate the following configuration:
```
#!/usr/sbin/nft -f
# Ansible managed
# clean
flush ruleset
include "/etc/nftables.d/defines.nft"
table inet filter {
chain global {
# 000 state management
ct state established,related accept
ct state invalid drop
}
include "/etc/nftables.d/sets.nft"
include "/etc/nftables.d/filter-input.nft"
include "/etc/nftables.d/filter-output.nft"
}
```
And you can get all rules and definitons by displaying the ruleset on the host: `$ nft list ruleset`:
```
table inet filter {
set blackhole {
type ipv4_addr
elements = { 255.255.255.255, 224.0.0.1, 224.0.0.251}
}
set out_tcp_accept {
type inet_service
flags interval
elements = { http, https, hkp}
}
set out_udp_accept {
type inet_service
flags interval
elements = { domain, bootps, ntp}
}
chain global {
ct state established,related accept
ct state invalid drop
}
chain input {
type filter hook input priority 0; policy drop;
jump global
ip daddr @blackhole counter packets 0 bytes 0 drop
iif "lo" accept
tcp dport @in_tcp_accept ct state new accept
}
chain output {
type filter hook output priority 0; policy drop;
jump global
oif "lo" accept
ip protocol icmp accept
udp dport @out_udp_accept ct state new accept
tcp dport @out_tcp_accept ct state new accept
}
}
```
## Examples
### With playbooks
<details>
<summary>Manage Nftables with defaults vars (<i>click to expand</i>)</summary>
<!-- have to be followed by an empty line! -->
``` yml
- hosts: serverXYZ
roles:
- role: ipr-cnrs.nftables
```
</details>
<details>
<summary>Add a new <b>simple</b> filter rule for incoming traffic (eg. 1 port for UDP/torrent) (<i>click to expand</i>)</summary>
``` yml
- hosts: serverXYZ
vars:
nft_input_rules:
400 input torrent accepted:
- udp dport 6881 ct state new accept
roles:
- role: ipr-cnrs.nftables
```
* **nft_input_group_rules** or **nft_input_host_rules** variables can
also be used.
* The weight (`400`) allow to order all merged rules (from
<b>nft_input_*rules</b> dictionaries).
* The text following the weight (`input torrent accepted`) is a small
description that will be added as a comment in **nft_input_conf_path** file
on the remote host.
</details>
<details>
<summary>Add a new <b>multi-ports</b> filter rule for incoming traffic (eg. TCP/http, https, http-alt,…) (<i>click to expand</i>)</summary>
``` yml
- hosts: serverXYZ
vars:
nft_input_rules:
400 input http accepted:
- tcp dport { 80, 443, 8080-8082 } ct state new accept
roles:
- role: ipr-cnrs.nftables
```
* **nft_input_group_rules** or **nft_input_host_rules** variables can
also be used.
* The weight (`400`) allow to order all merged rules (from
<b>nft_input_*rules</b> dictionaries).
* The text following the weight (`input http accepted`) is a small
description that will be added as a comment in **nft_input_conf_path** file
on the remote host.
* In this case, brackets are useful and define a anonymous set. For a single
element (port, IP address,…), brackets are overkill and the singleton
definition is enought.
</details>
<details>
<summary>Add a new rule with a variable (<i>click to expand</i>)</summary>
Nftables variables can be useful if you define somes generic rules for all hosts
with such variables (called with **$**) and override variable's value for some
groups or hosts.
``` yml
- hosts: serverXYZ
vars:
- nft_define_group:
input http accepted:
desc: HTTP and HTTPS
name: in_http_accept
value: '{ 80, 443 }'
nft_input_group_rules:
400 input http accepted:
- tcp dport $in_http_accept ct state new accept
roles:
- role: ipr-cnrs.nftables
```
1. Add a new variable with **define** for HTTP ports.
1. Add a new rule for incoming traffic and use the previous defined variable.
1. Result of `nft list ruleset` on the remote host will be :
``` bash
table inet filter {
chain input {
tcp dport { http, https } ct state new accept
}
}
```
* No mention of `$in_http_accept` variable.
* **nft_define** or **nft_define_host** variables can also be used.
* **nft_input_rules** or **nft_input_host_rules** variables can also be used.
* The weight (`400`) allow to order all merged rules (from
<b>nft_input_*rules</b> dictionaries).
* The text following the weight (`input http accepted`) is a small description
that will be added as a comment in **nft_input_conf_path** file on
the remote host.
</details>
<details>
<summary>Add a new rule with a named set (<i>click to expand</i>)</summary>
Quite similar to Nftables variables, **named set** can be useful if you define
somes generic rules and sets (eg. for all hosts) and override only the set in
some case (eg. for a group or some hosts).
In addition to variables, it is possible to add content to named sets on the
fly from the host without completely rewrite the rule.
``` yml
- hosts: serverXYZ
vars:
nft_set_group:
in_udp_accept:
- type inet_service; flags interval;
- elements = { 6881-6887, 6889 }
nft_input_group_rules:
200 input udp accepted:
- udp dport @in_udp_accept ct state new accept
roles:
- role: ipr-cnrs.nftables
```
1. Add a new named set with <b>nft_set_group</b> dictionary (eg. for torrent ports).
1. Add a new rule for incoming traffic and use the previous defined set.
1. On the remote host, if you try to add a port to this set : `nft add element inet filter in_udp_accept \{ 6999 \}`
1. Result of `nft list ruleset` on the remote host will now be :
``` bash
table inet filter {
set in_udp_accept {
type inet_service
flags interval
elements = { 6881-6887, 6889, 6999 }
}
chain input {
udp dport @in_udp_accept ct state new accept
}
}
```
* **nft_set** or **nft_set_host** variables can also be used.
* **nft_input_rules** or **nft_input_host_rules** variables can also be used.
* The weight (`200`) allow to order all merged rules (from
<b>nft_input_*rules</b> dictionaries).
* The text following the weight (`input upd accepted`) is a small description
that will be added as a comment in **nft_input_conf_path** file on
the remote host.
</details>
<details>
<summary>Override a default rule with 2 new rules (<i>click to expand</i>)</summary>
``` yml
- hosts: serverXYZ
vars:
nft_input_host_rules:
050 icmp:⏎
- ip protocol icmp ip saddr != 192.168.0.0/24 counter drop⏎
- ip protocol icmp icmp type echo-request ip length <= 84 counter limit rate 10/minute accept
roles:
- role: ipr-cnrs.nftables
```
1. Get rule description from `defaults/main.yml` file (eg. `050 icmp`).
1. Drop any ICMP request that doesn't come from 192.168.0.0 network.
1. Ensure the request is less or equal to 84 bytes and set up a limit
to 10 requests per minute.
* **nft_input_rules** or **nft_input_group_rules** variables can also be used.
* The weight (`050`) allow to order all merged rules (from
<b>nft_input_*rules</b> dictionaries).
* The text following the weight (`icmp`) is a small description that
will be added as a comment in **nft_input_conf_path** file on
the remote host.
</details>
<details>
<summary>Override some of the default defined sets (<i>click to expand</i>)</summary>
``` yml
- hosts: serverXYZ
vars:
- nft_define:
input tcp accepted:
desc: Custom SSH port and torrent
name: in_tcp_accept
value: '{ 2201, 6881 }'
roles:
- role: ipr-cnrs.nftables
```
1. Get item name (eg. `input tcp accepted`) and variable name
(eg. `in_tcp_accept`) from `defaults/main.yml` file.
1. Set a new value (eg. `'{ 2201, 6881 }'`).
1. You can add a `desc` attribute that will be set as a comment in
**nft_input_conf_path** file on the remote host.
* **nft_define_group** or **nft_define_host** variables can also be used.
</details>
<details>
<summary>Override all default rules (eg. for outgoing traffic) (<i>click to expand</i>)</summary>
If the default rules are too permissive, if you already override most of them,…
In some case, i guess, it can be interesting to redefine the default variable :
``` yml
- hosts: serverXYZ
vars:
nft_output_default_rules:
000 policy:
- type filter hook output priority 0; policy drop;
005 state management:
- ct state established,related accept
- ct state invalid drop
015 localhost:
- oif lo accept
050 my rule for XXX hosts and services:
- tcp dport 2000 ip saddr { xxx.xxx.xxx.xxx, yyy.yyy.yyy.yyy } ct state new accept
250 reset-ssh: # allow the host to reset SSH connections to avoid 10 min delay from Ansible controller
- tcp sport ssh tcp flags { rst, psh | ack } counter accept
roles:
- role: ipr-cnrs.nftables
```
* At least, don't forget to :
1. set a default `policy`.
1. manage already `established` state.
1. accept `rst, psh | ack` flags for **ssh** to avoid a 10 minutes delay at
the first run of this Nftables role (see #1).
* Then add your own rules with the wanted weight to order all merged rules (from
<b>nft_output_*rules</b> dictionaries) and descriptions.
</details>
<details>
<summary>Remove a default rule (<i>click to expand</i>)</summary>
``` yml
- hosts: serverXYZ
vars:
nft_output_host_rules:
210 output tcp accepted:
-
roles:
- role: ipr-cnrs.nftables
```
1. Get rule description from `defaults/main.yml` file (`210 output tcp accepted`).
1. The default policy for outgoing traffic (**drop**) will now be applied to
ports defined in **out_tcp_accept** variable. Be sure of what you are doing.
1. The rule will no longer be present in `nft list ruleset` result, just a
comment will remain (`210 output tcp accepted`) in **nft_output_conf_path**
file on the remote host.
* **nft_output_rules** or **nft_output_group_rules** variables can also be used.
* The weight (`210`) allow to order all merged rules (from
<b>nft_output_*rules</b> dictionaries).
</details>
### With group_vars and host_vars
<details>
<summary>Use default rules and allow, for <b>first_group</b>, incoming ICMP and count both ICMP and default policy (<b>drop</b>) packets (<i>click to expand</i>)</summary>
`group_vars/first_group`:
``` yaml
nft_input_group_rules:
020 icmp:
- ip protocol icmp icmp type echo-request ip length <= 84 counter limit rate 1/minute accept
999 count policy packet:
- counter
```
</details>
<details>
<summary>Use merged group rules from multiple ansible groups (<i>click to expand</i>)</summary>
1. Enable to merge group's variables :
``` yml
- hosts: serverXYZ
vars:
nft_merged_groups: true
nft_merged_groups_dir: vars/
roles:
- role: ipr-cnrs.nftables
```
1. Put extra rules inside the "vars" folder named after your ansible groups for serverXYZ :
* `vars/first_group`:
``` yaml
nft_input_group_rules:
020 icmp:
- ip protocol icmp icmp type echo-request ip length <= 84 counter limit rate 1/minute accept
999 count policy packet:
- counter
```
* `vars/second_group`:
``` yaml
nft_input_group_rules:
021 LAN:
- iif eth0 accept
```
1. These rulesets, from the two groups, will be merged if the host is a member
of these groups.
</details>
## Configuration
This role will:
* Install `nftables` on the system.
* Enable `nftables` service by default at startup.
* Generate a default configuration file which include all following files and
loaded by systemd unit.
* Generate input and output rules files include called by the main configuration file.
* Generate vars in a file and sets and maps in another file.
* (re)Start `nftables` service at first run.
* Reload `nftables` service at next runs to avoid to let the host without firewall
rules due to invalid syntax.
### Fail2ban integration
Before Debian Bullseye, systemd unit for Fail2ban doesn't come with a decent
integration with Nftables.
So this role will create override file for `fail2ban` unit, even if it's not
(yet) available on the host, in order to :
* Start `fail2ban` unit after `nftables`.
* Restart `fail2ban` unit when `nftables` unit restart.
## Development
This source code comes from our [Gitea instance][nftables source] and the
[Github repo][nftables github] exist just to be able to send the role to Ansible
Galaxy…
But feel free to send issue/PR here :)
Thanks to this [hook][gogs to github hook], Github automatically got updates from our [Gitea instance][nftables source] :)
## License
[WTFPL][wtfpl website]
## Author Information
Jérémy Gardais
* Source: [on IPR's Gitea][nftables source]
* [IPR][ipr website] (Institut de Physique de Rennes)
[gogs to github hook]: https://stackoverflow.com/a/21998477
[nftables source]: https://git.ipr.univ-rennes1.fr/cellinfo/ansible.nftables
[nftables github]: https://github.com/ipr-cnrs/nftables
[wtfpl website]: http://www.wtfpl.net/about/
[ipr website]: https://ipr.univ-rennes1.fr/
[mikegleasonjr firewall github]: https://github.com/mikegleasonjr/ansible-role-firewall