From 656c1aba3d49d96b3711b7204559ae4ad84b80ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Jeanneret?= Date: Wed, 11 May 2022 14:04:42 +0200 Subject: [PATCH] Supports nftables instead of iptables This patch introduces a new role (tripleo_nftables) as well as a new tripleo_firewall_engine parameter, allowing to switch from iptables to nftables. All of tripleo rules are pushed in the "inet" family, in a dedicated chains therein. It allows to avoid rule duplication between IPv6 and IPv4, while ensuring we don't break the compatibility layer for iptables-nft - that tool is checking only the "ip" family, while ip6tables-nft is checking the "ip6" one. This means some changes are needed in the doc, when it comes to listing the existing rules. Also, please note some tools such as neutron are still heavily using the iptables family, as well as some part of podman apparently. Change-Id: Ia43b58f304d8ef41b80820c3c98696650eb362e1 --- doc/source/roles/role-tripleo_nftables.rst | 6 + .../roles/tripleo_firewall/defaults/main.yml | 2 + .../molecule/nftables/converge.yml | 22 +++ .../molecule/nftables/molecule.yml | 27 ++++ .../molecule/nftables/prepare.yml | 21 +++ .../tripleo_firewall/tasks/iptables.yaml | 98 +++++++++++++ .../roles/tripleo_firewall/tasks/main.yml | 90 ++---------- .../roles/tripleo_nftables/defaults/main.yml | 59 ++++++++ .../roles/tripleo_nftables/files/iptables.nft | 83 +++++++++++ .../roles/tripleo_nftables/meta/main.yml | 43 ++++++ .../molecule/default/converge.yml | 21 +++ .../molecule/default/molecule.yml | 28 ++++ .../molecule/default/prepare.yml | 27 ++++ .../roles/tripleo_nftables/tasks/main.yml | 137 ++++++++++++++++++ .../roles/tripleo_nftables/tasks/service.yaml | 33 +++++ .../tripleo_nftables/templates/chains.j2 | 8 + .../tripleo_nftables/templates/destination.j2 | 6 + .../roles/tripleo_nftables/templates/dport.j2 | 15 ++ .../tripleo_nftables/templates/flush-chain.j2 | 7 + .../tripleo_nftables/templates/interface.j2 | 3 + .../tripleo_nftables/templates/ipversion.j2 | 4 + .../tripleo_nftables/templates/jump-chain.j2 | 31 ++++ .../roles/tripleo_nftables/templates/jump.j2 | 9 ++ .../roles/tripleo_nftables/templates/limit.j2 | 3 + .../tripleo_nftables/templates/limit_burst.j2 | 3 + .../tripleo_nftables/templates/protocol.j2 | 6 + .../tripleo_nftables/templates/ruleset.j2 | 30 ++++ .../tripleo_nftables/templates/source.j2 | 6 + .../roles/tripleo_nftables/templates/sport.j2 | 3 + .../roles/tripleo_nftables/templates/state.j2 | 7 + zuul.d/molecule.yaml | 10 ++ 31 files changed, 769 insertions(+), 79 deletions(-) create mode 100644 doc/source/roles/role-tripleo_nftables.rst create mode 100644 tripleo_ansible/roles/tripleo_firewall/molecule/nftables/converge.yml create mode 100644 tripleo_ansible/roles/tripleo_firewall/molecule/nftables/molecule.yml create mode 100644 tripleo_ansible/roles/tripleo_firewall/molecule/nftables/prepare.yml create mode 100644 tripleo_ansible/roles/tripleo_firewall/tasks/iptables.yaml create mode 100644 tripleo_ansible/roles/tripleo_nftables/defaults/main.yml create mode 100644 tripleo_ansible/roles/tripleo_nftables/files/iptables.nft create mode 100644 tripleo_ansible/roles/tripleo_nftables/meta/main.yml create mode 100644 tripleo_ansible/roles/tripleo_nftables/molecule/default/converge.yml create mode 100644 tripleo_ansible/roles/tripleo_nftables/molecule/default/molecule.yml create mode 100644 tripleo_ansible/roles/tripleo_nftables/molecule/default/prepare.yml create mode 100644 tripleo_ansible/roles/tripleo_nftables/tasks/main.yml create mode 100644 tripleo_ansible/roles/tripleo_nftables/tasks/service.yaml create mode 100644 tripleo_ansible/roles/tripleo_nftables/templates/chains.j2 create mode 100644 tripleo_ansible/roles/tripleo_nftables/templates/destination.j2 create mode 100644 tripleo_ansible/roles/tripleo_nftables/templates/dport.j2 create mode 100644 tripleo_ansible/roles/tripleo_nftables/templates/flush-chain.j2 create mode 100644 tripleo_ansible/roles/tripleo_nftables/templates/interface.j2 create mode 100644 tripleo_ansible/roles/tripleo_nftables/templates/ipversion.j2 create mode 100644 tripleo_ansible/roles/tripleo_nftables/templates/jump-chain.j2 create mode 100644 tripleo_ansible/roles/tripleo_nftables/templates/jump.j2 create mode 100644 tripleo_ansible/roles/tripleo_nftables/templates/limit.j2 create mode 100644 tripleo_ansible/roles/tripleo_nftables/templates/limit_burst.j2 create mode 100644 tripleo_ansible/roles/tripleo_nftables/templates/protocol.j2 create mode 100644 tripleo_ansible/roles/tripleo_nftables/templates/ruleset.j2 create mode 100644 tripleo_ansible/roles/tripleo_nftables/templates/source.j2 create mode 100644 tripleo_ansible/roles/tripleo_nftables/templates/sport.j2 create mode 100644 tripleo_ansible/roles/tripleo_nftables/templates/state.j2 diff --git a/doc/source/roles/role-tripleo_nftables.rst b/doc/source/roles/role-tripleo_nftables.rst new file mode 100644 index 000000000..5afbc8da9 --- /dev/null +++ b/doc/source/roles/role-tripleo_nftables.rst @@ -0,0 +1,6 @@ +======================= +Role - tripleo_nftables +======================= + +.. ansibleautoplugin:: + :role: tripleo_ansible/roles/tripleo_nftables diff --git a/tripleo_ansible/roles/tripleo_firewall/defaults/main.yml b/tripleo_ansible/roles/tripleo_firewall/defaults/main.yml index 4bd58d4f1..03df25a11 100644 --- a/tripleo_ansible/roles/tripleo_firewall/defaults/main.yml +++ b/tripleo_ansible/roles/tripleo_firewall/defaults/main.yml @@ -29,6 +29,8 @@ # dport: 22 # extras: # ensure: 'absent' +tripleo_firewall_engine: 'iptables' + tripleo_firewall_rules: {} tripleo_firewall_frontend_enabled: false diff --git a/tripleo_ansible/roles/tripleo_firewall/molecule/nftables/converge.yml b/tripleo_ansible/roles/tripleo_firewall/molecule/nftables/converge.yml new file mode 100644 index 000000000..9e3e93442 --- /dev/null +++ b/tripleo_ansible/roles/tripleo_firewall/molecule/nftables/converge.yml @@ -0,0 +1,22 @@ +--- +# Copyright 2019 Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +- name: Converge + hosts: all + roles: + - role: "tripleo_firewall" + tripleo_firewall_engine: 'nftables' diff --git a/tripleo_ansible/roles/tripleo_firewall/molecule/nftables/molecule.yml b/tripleo_ansible/roles/tripleo_firewall/molecule/nftables/molecule.yml new file mode 100644 index 000000000..8cb5361ef --- /dev/null +++ b/tripleo_ansible/roles/tripleo_firewall/molecule/nftables/molecule.yml @@ -0,0 +1,27 @@ +--- +provisioner: + name: ansible + config_options: + defaults: + fact_caching: jsonfile + fact_caching_connection: /tmp/molecule/facts + inventory: + hosts: + all: + hosts: + instance: + ansible_host: localhost + log: true + env: + ANSIBLE_STDOUT_CALLBACK: yaml + ANSIBLE_ROLES_PATH: "${ANSIBLE_ROLES_PATH}:${HOME}/zuul-jobs/roles" + +scenario: + name: nftables + test_sequence: + - prepare + - converge + - check + +verifier: + name: testinfra diff --git a/tripleo_ansible/roles/tripleo_firewall/molecule/nftables/prepare.yml b/tripleo_ansible/roles/tripleo_firewall/molecule/nftables/prepare.yml new file mode 100644 index 000000000..ef85c3128 --- /dev/null +++ b/tripleo_ansible/roles/tripleo_firewall/molecule/nftables/prepare.yml @@ -0,0 +1,21 @@ +--- +# Copyright 2019 Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +- name: Prepare + hosts: all + roles: + - role: test_deps diff --git a/tripleo_ansible/roles/tripleo_firewall/tasks/iptables.yaml b/tripleo_ansible/roles/tripleo_firewall/tasks/iptables.yaml new file mode 100644 index 000000000..4cfb7daad --- /dev/null +++ b/tripleo_ansible/roles/tripleo_firewall/tasks/iptables.yaml @@ -0,0 +1,98 @@ +--- +# Copyright 2019 Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +# "tripleo_firewall" will search for and load any operating system variable file +- name: Firewall add block + become: true + block: + - name: Ensure firewall is installed + package: + name: "{{ tripleo_firewall_packages }}" + state: present + + - name: Create empty ruleset in /etc/sysconfig/iptables and /etc/sysconfig/ip6tables + become: true + ignore_errors: "{{ (((ansible_facts['os_family'] | lower) ~ '-' ~ ansible_facts['distribution_major_version']) == 'redhat-7') | bool }}" + copy: + dest: "{{ item }}" + content: "# empty ruleset created by deployed-server bootstrap" + loop: + - /etc/sysconfig/iptables + - /etc/sysconfig/ip6tables + + - name: Ensure firewall is enabled/started + systemd: + name: iptables + state: started + enabled: true + + - name: Manage firewall rules + tripleo_iptables: + tripleo_rules: "{{ firewall_rules_sorted }}" + +- name: Firewall save block + become: true + block: + - name: Save firewall rules ipv4 + command: /usr/libexec/iptables/iptables.init save + + - name: Save firewall rules ipv6 + command: /usr/libexec/iptables/ip6tables.init save + + - name: Enable iptables service (and do a daemon-reload systemd) + systemd: + daemon_reload: true + enabled: true + name: "{{ item }}" + state: started + loop: + - iptables.service + - ip6tables.service + + - name: Enable tripleo-iptables service (and do a daemon-reload systemd) + systemd: + daemon_reload: true + enabled: true + name: "{{ item }}" + state: started + loop: + - tripleo-iptables.service + - tripleo-ip6tables.service + failed_when: false + + - name: Stop and disable firewalld + systemd: + enabled: false + name: "firewalld.service" + state: stopped + failed_when: false + + - name: Find non-persistent rules + command: egrep -l 'comment.*(neutron-|ironic-inspector)' /etc/sysconfig/iptables* /etc/sysconfig/ip6tables* + failed_when: false + changed_when: false + register: neutron_rules + + - name: Remove non-persistent line(s) + lineinfile: + path: "{{ item }}" + state: absent + regexp: '^((?!.*comment)(?=.*(ironic-inspector|neutron-)))' + when: + - not ansible_check_mode|bool + - item.find('v=' ~ '^/') == -1 + loop: "{{ neutron_rules.stdout_lines }}" diff --git a/tripleo_ansible/roles/tripleo_firewall/tasks/main.yml b/tripleo_ansible/roles/tripleo_firewall/tasks/main.yml index 8e7c731ac..a7766d029 100644 --- a/tripleo_ansible/roles/tripleo_firewall/tasks/main.yml +++ b/tripleo_ansible/roles/tripleo_firewall/tasks/main.yml @@ -87,83 +87,15 @@ list }}" -- name: Firewall add block - become: true - block: - - name: Ensure firewall is installed - package: - name: "{{ tripleo_firewall_packages }}" - state: present +- name: Manage rules via iptables + when: + - tripleo_firewall_engine == 'iptables' + include_tasks: iptables.yaml - - name: Create empty ruleset in /etc/sysconfig/iptables and /etc/sysconfig/ip6tables - become: true - ignore_errors: "{{ (((ansible_facts['os_family'] | lower) ~ '-' ~ ansible_facts['distribution_major_version']) == 'redhat-7') | bool }}" - copy: - dest: "{{ item }}" - content: "# empty ruleset created by deployed-server bootstrap" - loop: - - /etc/sysconfig/iptables - - /etc/sysconfig/ip6tables - - - name: Ensure firewall is enabled/started - systemd: - name: iptables - state: started - enabled: true - - - name: Manage firewall rules - tripleo_iptables: - tripleo_rules: "{{ firewall_rules_sorted }}" - -- name: Firewall save block - become: true - block: - - name: Save firewall rules ipv4 - command: /usr/libexec/iptables/iptables.init save - - - name: Save firewall rules ipv6 - command: /usr/libexec/iptables/ip6tables.init save - - - name: Enable iptables service (and do a daemon-reload systemd) - systemd: - daemon_reload: true - enabled: true - name: "{{ item }}" - state: started - loop: - - iptables.service - - ip6tables.service - - - name: Enable tripleo-iptables service (and do a daemon-reload systemd) - systemd: - daemon_reload: true - enabled: true - name: "{{ item }}" - state: started - loop: - - tripleo-iptables.service - - tripleo-ip6tables.service - failed_when: false - - - name: Stop and disable firewalld - systemd: - enabled: false - name: "firewalld.service" - state: stopped - failed_when: false - - - name: Find non-persistent rules - command: egrep -l 'comment.*(neutron-|ironic-inspector)' /etc/sysconfig/iptables* /etc/sysconfig/ip6tables* - failed_when: false - changed_when: false - register: neutron_rules - - - name: Remove non-persistent line(s) - lineinfile: - path: "{{ item }}" - state: absent - regexp: '^((?!.*comment)(?=.*(ironic-inspector|neutron-)))' - when: - - not ansible_check_mode|bool - - item.find('v=' ~ '^/') == -1 - loop: "{{ neutron_rules.stdout_lines }}" +- name: Manage rules via nftables + when: + - tripleo_firewall_engine == 'nftables' + vars: + tripleo_nftables_rules: "{{ firewall_rules_sorted | sort(attribute='rule_name') |list }}" + include_role: + name: tripleo_nftables diff --git a/tripleo_ansible/roles/tripleo_nftables/defaults/main.yml b/tripleo_ansible/roles/tripleo_nftables/defaults/main.yml new file mode 100644 index 000000000..dc4addb5f --- /dev/null +++ b/tripleo_ansible/roles/tripleo_nftables/defaults/main.yml @@ -0,0 +1,59 @@ +--- +# Copyright 2022 Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +# All variables intended for modification should be placed in this file. + +# All variables within this role should have a prefix of "tripleo_nftables_" + +# Example rule definition +tripleo_nftables_rules: + - rule: + proto: all + state: + - RELATED + - ESTABLISHED + rule_name: 000 accept related established rules + - rule: + ipversion: ipv4 + proto: icmp + rule_name: 001 accept all icmp + - rule: + ipversion: ipv6 + proto: ipv6-icmp + rule_name: 001 accept all ipv6-icmp + - rule: + interface: lo + proto: all + rule_name: 002 accept all to lo interface + - rule: + destination: fe80::/64 + dport: 546 + ipversion: ipv6 + proto: udp + state: + - NEW + rule_name: 004 accept ipv6 dhcpv6 + - rule: + jump: LOG + limit: 20/min + limit_burst: 15 + proto: all + rule_name: 998 log all + - rule: + action: drop + proto: all + rule_name: 999 drop all diff --git a/tripleo_ansible/roles/tripleo_nftables/files/iptables.nft b/tripleo_ansible/roles/tripleo_nftables/files/iptables.nft new file mode 100644 index 000000000..13fd10c37 --- /dev/null +++ b/tripleo_ansible/roles/tripleo_nftables/files/iptables.nft @@ -0,0 +1,83 @@ +#!/usr/sbin/nft -f +# +# Managed by tripleo-ansible/tripleo_nftables +# +# Ensure we get the iptables layout to make the whole thing 100% compatible, +# even if some other tools are still relying on iptables-nft compatibility +# wrapper + +# We will push our tripleo rules in the inet table - it avoids rule duplication +# and allows to keep good compatibility with the iptables-nft layer +table inet filter { + chain INPUT { + type filter hook input priority filter; policy accept; + } + chain FORWARD { + type filter hook forward priority filter; policy accept; + } + chain OUTPUT { + type filter hook output priority filter; policy accept; + } +} +table inet raw { + chain PREROUTING { + type filter hook prerouting priority raw; policy accept; + } + chain OUTPUT { + type filter hook output priority raw; policy accept; + } +} +# Compatibility tables and chains for iptables-nft +table ip filter { + chain INPUT { + type filter hook input priority filter; policy accept; + } + chain FORWARD { + type filter hook forward priority filter; policy accept; + } + chain OUTPUT { + type filter hook output priority filter; policy accept; + } +} +table ip raw { + chain PREROUTING { + type filter hook prerouting priority raw; policy accept; + } + chain OUTPUT { + type filter hook output priority raw; policy accept; + } +} +table ip nat { + chain PREROUTING { + type nat hook prerouting priority dstnat; policy accept; + } + chain INPUT { + type nat hook input priority 100; policy accept; + } + chain OUTPUT { + type nat hook output priority -100; policy accept; + } + chain POSTROUTING { + type nat hook postrouting priority srcnat; policy accept; + } +} + +table ip6 raw { + chain PREROUTING { + type filter hook prerouting priority raw; policy accept; + } + chain OUTPUT { + type filter hook output priority raw; policy accept; + } +} +table ip6 filter { + chain INPUT { + type filter hook input priority filter; policy accept; + } + chain FORWARD { + type filter hook forward priority filter; policy accept; + } + chain OUTPUT { + type filter hook output priority filter; policy accept; + } +} diff --git a/tripleo_ansible/roles/tripleo_nftables/meta/main.yml b/tripleo_ansible/roles/tripleo_nftables/meta/main.yml new file mode 100644 index 000000000..4611c235c --- /dev/null +++ b/tripleo_ansible/roles/tripleo_nftables/meta/main.yml @@ -0,0 +1,43 @@ +--- +# Copyright 2020 Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +galaxy_info: + author: OpenStack + description: TripleO OpenStack Role -- tripleo_nftables + company: Red Hat + license: Apache-2.0 + min_ansible_version: 2.7 + namespace: openstack + # + # Provide a list of supported platforms, and for each platform a list of versions. + # If you don't wish to enumerate all versions for a particular platform, use 'all'. + # To view available platforms and versions (or releases), visit: + # https://galaxy.ansible.com/api/v1/platforms/ + # + platforms: + - name: CentOS + versions: + - 8 + - 9 + + galaxy_tags: + - tripleo + + +# List your role dependencies here, one per line. Be sure to remove the '[]' above, +# if you add dependencies to this list. +dependencies: [] diff --git a/tripleo_ansible/roles/tripleo_nftables/molecule/default/converge.yml b/tripleo_ansible/roles/tripleo_nftables/molecule/default/converge.yml new file mode 100644 index 000000000..565bd7c33 --- /dev/null +++ b/tripleo_ansible/roles/tripleo_nftables/molecule/default/converge.yml @@ -0,0 +1,21 @@ +--- +# Copyright 2020 Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +- name: Converge + hosts: all + roles: + - role: "tripleo_nftables" diff --git a/tripleo_ansible/roles/tripleo_nftables/molecule/default/molecule.yml b/tripleo_ansible/roles/tripleo_nftables/molecule/default/molecule.yml new file mode 100644 index 000000000..3609fb64a --- /dev/null +++ b/tripleo_ansible/roles/tripleo_nftables/molecule/default/molecule.yml @@ -0,0 +1,28 @@ +--- +log: true + +provisioner: + name: ansible + config_options: + defaults: + fact_caching: jsonfile + fact_caching_connection: /tmp/molecule/facts + inventory: + hosts: + all: + hosts: + instance: + ansible_host: localhost + log: true + env: + ANSIBLE_STDOUT_CALLBACK: yaml + ANSIBLE_ROLES_PATH: "${ANSIBLE_ROLES_PATH}:${HOME}/zuul-jobs/roles" + +scenario: + test_sequence: + - prepare + - converge + - check + +verifier: + name: testinfra diff --git a/tripleo_ansible/roles/tripleo_nftables/molecule/default/prepare.yml b/tripleo_ansible/roles/tripleo_nftables/molecule/default/prepare.yml new file mode 100644 index 000000000..f87826c3a --- /dev/null +++ b/tripleo_ansible/roles/tripleo_nftables/molecule/default/prepare.yml @@ -0,0 +1,27 @@ +--- +# Copyright 2020 Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +- name: Prepare + hosts: all + tasks: + - name: test_deps + include_role: + name: test_deps + - name: Install nftables + become: true + package: + name: nftables diff --git a/tripleo_ansible/roles/tripleo_nftables/tasks/main.yml b/tripleo_ansible/roles/tripleo_nftables/tasks/main.yml new file mode 100644 index 000000000..63d209b72 --- /dev/null +++ b/tripleo_ansible/roles/tripleo_nftables/tasks/main.yml @@ -0,0 +1,137 @@ +--- +# Copyright 2022 Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +# systemctl will return 0 if enabled, 3 if disabled +- name: Get nftables service state + ansible.builtin.command: systemctl status nftables + register: nftables_status + failed_when: nftables_status.rc not in [0, 3] + +- name: Swith firewall management to nftables if needed + when: + - nftables_status.rc == 3 + include_tasks: service.yaml + +- name: IPtables compatibility layout + become: true + block: + - name: Push initial, empty ruleset + register: init_nftables + copy: + dest: /etc/nftables/iptables.nft + src: iptables.nft + + - name: Load empty ruleset if needed + when: + - init_nftables is changed + ansible.builtin.command: nft -f /etc/nftables/iptables.nft + +# Get current nft rules in JSON format, with our iptables compat content. +- name: Get current nftables content + become: true + ansible.builtin.command: nft -j list ruleset + register: nft_current_rules + +- name: nftables files generation + become: true + block: + # Create a dedicated file for jumps - makes easier to manage afterward. + # That one will be loaded upon boot only. + - name: Generate chain jumps + ignore_errors: "{{ ansible_check_mode|bool }}" + vars: + current_nft: "{{ nft_current_rules }}" + nft_is_update: false + template: + dest: /etc/nftables/tripleo-jumps.nft + src: jump-chain.j2 + + # Create a special "update chain jumps" file, adding just the MISSING + # jumps in the main, default chains. This will avoid useless duplication + # upon update/day-2 operation, since we cannot really flush INPUT and other + # default chains. + - name: Generate chain jumps + ignore_errors: "{{ ansible_check_mode|bool }}" + vars: + current_nft: "{{ nft_current_rules }}" + nft_is_update: true + template: + dest: /etc/nftables/tripleo-update-jumps.nft + src: jump-chain.j2 + + # Note: we do NOT include this one for boot, since chains are + # already empty! + - name: Generate nft flushes + register: nft_flushes + template: + dest: /etc/nftables/tripleo-flushes.nft + src: flush-chain.j2 + + - name: Generate nft tripleo chains + register: nft_chains + template: + dest: /etc/nftables/tripleo-chains.nft + src: chains.j2 + + - name: Generate nft ruleset in static file + register: nft_ruleset + template: + dest: /etc/nftables/tripleo-rules.nft + src: ruleset.j2 + +# We cannot use the "validate" parameter from the "template" module, since +# we don't load the chains before. So let's validate now, with all the things. +# Remember, the "iptables" compat layout is already loaded at this point. +- name: Validate all of the generated content before loading + ansible.builtin.shell: >- + cat /etc/nftables/tripleo-chains.nft + /etc/nftables/tripleo-flushes.nft + /etc/nftables/tripleo-rules.nft + /etc/nftables/tripleo-update-jumps.nft + /etc/nftables/tripleo-jumps.nft | nft -c -f - + +# Order is important here. +# Please keep that in mind in case you want to add some new ruleset in their +# dedicated file! +- name: Ensure we load our different nft rulesets on boot + become: true + ansible.builtin.blockinfile: + path: /etc/sysconfig/nftables.conf + backup: false + validate: nft -c -f %s + block: | + include "/etc/nftables/iptables.nft" + include "/etc/nftables/tripleo-chains.nft" + include "/etc/nftables/tripleo-rules.nft" + include "/etc/nftables/tripleo-jumps.nft" + +- name: Create our custom chains + become: true + ansible.builtin.command: nft -f /etc/nftables/tripleo-chains.nft + +# Here, we make different call in order to avoid jumps duplication. +# In both cases, we flush the custom chains. Doing things like that ensures +# we run all, from the flush to the rule creation, in a single transaction. +# This prevents accidental lock-outs. +- name: Reload custom nftables ruleset WITH jumps + become: true + ansible.builtin.shell: >- + cat /etc/nftables/tripleo-flushes.nft + /etc/nftables/tripleo-rules.nft + /etc/nftables/tripleo-update-jumps.nft | nft -f - + when: + - nft_ruleset is changed diff --git a/tripleo_ansible/roles/tripleo_nftables/tasks/service.yaml b/tripleo_ansible/roles/tripleo_nftables/tasks/service.yaml new file mode 100644 index 000000000..2494fbeff --- /dev/null +++ b/tripleo_ansible/roles/tripleo_nftables/tasks/service.yaml @@ -0,0 +1,33 @@ +--- +# Copyright 2022 Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +# systemctl will return 0 if success, 1 if unit not found +- name: Ensure legacy iptables services are off + become: true + ansible.builtin.command: "systemctl disable --now {{ item }}" + register: ipt_service + failed_when: + - ipt_service.rc not in [0, 1] + loop: + - iptables.service + - ip6tables.service + +- name: Ensure nftables service is enabled and running + become: true + ansible.builtin.systemd: + name: nftables + state: started + enabled: true diff --git a/tripleo_ansible/roles/tripleo_nftables/templates/chains.j2 b/tripleo_ansible/roles/tripleo_nftables/templates/chains.j2 new file mode 100644 index 000000000..9f2d1230c --- /dev/null +++ b/tripleo_ansible/roles/tripleo_nftables/templates/chains.j2 @@ -0,0 +1,8 @@ +# Create chain if needed +{%- for ruleset in tripleo_nftables_rules %} +{% set rule=ruleset['rule'] %} +{%- if 'extras' not in rule or rule['extras'].get('ensure', 'present') in ['enabled', 'present'] %} +# Create chain TRIPLEO_{{ rule.get('chain', 'INPUT') }} in {{rule.get('table', 'filter') }} table +add chain inet {{ rule.get('table', 'filter') }} TRIPLEO_{{ rule.get('chain', 'INPUT') }} +{% endif %} +{% endfor %} diff --git a/tripleo_ansible/roles/tripleo_nftables/templates/destination.j2 b/tripleo_ansible/roles/tripleo_nftables/templates/destination.j2 new file mode 100644 index 000000000..955e2bf9c --- /dev/null +++ b/tripleo_ansible/roles/tripleo_nftables/templates/destination.j2 @@ -0,0 +1,6 @@ +{%- if 'destination' in rule %} +{%- if 'ipversion' in rule and rule['ipversion'] == 'ipv6' %} + ip6 +{%- endif %} + daddr {{ rule['destination'] }} +{%- endif %} diff --git a/tripleo_ansible/roles/tripleo_nftables/templates/dport.j2 b/tripleo_ansible/roles/tripleo_nftables/templates/dport.j2 new file mode 100644 index 000000000..1fd7cf487 --- /dev/null +++ b/tripleo_ansible/roles/tripleo_nftables/templates/dport.j2 @@ -0,0 +1,15 @@ +{%- if 'dport' in rule %} +{%- if rule['dport'] is iterable %} + {{ rule.get('proto', false)|ternary('', 'tcp ') }}dport { {{ rule['dport'] |join(',') }} } +{%- else %} + {{ rule.get('proto', false)|ternary('', 'tcp ') }}dport {{ rule['dport'] }} +{%- endif %} +{%- endif %} +{#- This is for legacy things - Really, use dport... #} +{%- if 'port' in rule %} +{%- if rule['port'] is iterable %} + {{ rule.get('proto', false)|ternary('', 'tcp ') }}dport { {{ rule['port'] |join(',') }} } +{%- else %} + {{ rule.get('proto', false)|ternary('', 'tcp ') }}dport {{ rule['port'] }} +{%- endif %} +{%- endif %} diff --git a/tripleo_ansible/roles/tripleo_nftables/templates/flush-chain.j2 b/tripleo_ansible/roles/tripleo_nftables/templates/flush-chain.j2 new file mode 100644 index 000000000..8fa849e1d --- /dev/null +++ b/tripleo_ansible/roles/tripleo_nftables/templates/flush-chain.j2 @@ -0,0 +1,7 @@ +# Managed by tripleo-ansible/tripleo_nftables +{%- for ruleset in tripleo_nftables_rules %} +{% set rule=ruleset['rule'] %} +{% if 'extras' not in rule or rule['extras'].get('ensure', 'present') in ['enabled', 'present'] %} +flush chain inet {{ rule.get('table', 'filter') }} TRIPLEO_{{ rule.get('chain', 'INPUT') }} +{% endif %} +{% endfor %} diff --git a/tripleo_ansible/roles/tripleo_nftables/templates/interface.j2 b/tripleo_ansible/roles/tripleo_nftables/templates/interface.j2 new file mode 100644 index 000000000..b44111214 --- /dev/null +++ b/tripleo_ansible/roles/tripleo_nftables/templates/interface.j2 @@ -0,0 +1,3 @@ +{%- if 'interface' in rule %} + iifname {{ rule['interface'] }} +{%- endif %} diff --git a/tripleo_ansible/roles/tripleo_nftables/templates/ipversion.j2 b/tripleo_ansible/roles/tripleo_nftables/templates/ipversion.j2 new file mode 100644 index 000000000..a709b0382 --- /dev/null +++ b/tripleo_ansible/roles/tripleo_nftables/templates/ipversion.j2 @@ -0,0 +1,4 @@ +{# We force everything into the "inet" family so that we cover both + ip and ip6. This also ensures proper compat with iptables-nft. +#} + inet diff --git a/tripleo_ansible/roles/tripleo_nftables/templates/jump-chain.j2 b/tripleo_ansible/roles/tripleo_nftables/templates/jump-chain.j2 new file mode 100644 index 000000000..19ac6d2e6 --- /dev/null +++ b/tripleo_ansible/roles/tripleo_nftables/templates/jump-chain.j2 @@ -0,0 +1,31 @@ +# Managed by tripleo-ansible/tripleo_nftables +{% set chains = namespace(chains=[]) %} +{% if nft_is_update|default(false)|bool %} + +{# Add missing jumps only (usually during day-2 operations) #} +{% set existing = (current_nft['stdout']|from_json)['nftables']|map(attribute='rule', default={})|list %} +{% for ruleset in tripleo_nftables_rules %} +{% set rule=ruleset['rule'] %} +{% set query="[? table==`"~rule.get('table', 'filter')~"` && family==`inet` && chain==`"~rule.get('chain', 'INPUT')~"`].expr[*].jump.target" %} +{% set chain_key = rule.get('chain', 'INPUT') ~ rule.get('table', 'filter') %} +{% if chain_key not in chains.chains %} +{% if 'TRIPLEO_'~rule.get('chain', 'INPUT') not in (existing|json_query(query)|flatten) %} +insert rule inet {{ rule.get('table', 'filter') }} {{ rule.get('chain', 'INPUT') }} position 0 jump TRIPLEO_{{ rule.get('chain', 'INPUT') }} +{% endif %} +{% set _ = chains.chains.append(chain_key) %} +{% endif %} +{% endfor %} +{% else %} + +{# Insert all jumps to custom chains (usually during boot) #} +{% for ruleset in tripleo_nftables_rules %} +{% set rule=ruleset['rule'] %} +{% if 'extras' not in rule or rule['extras'].get('ensure', 'present') in ['enabled', 'present'] %} +{% set chain_key = rule.get('chain', 'INPUT') ~ rule.get('table', 'filter') %} +{% if chain_key not in chains.chains %} +insert rule inet {{ rule.get('table', 'filter') }} {{ rule.get('chain', 'INPUT') }} position 0 jump TRIPLEO_{{ rule.get('chain', 'INPUT') }} +{% set _ = chains.chains.append(chain_key) %} +{% endif %} +{% endif %} +{% endfor %} +{% endif %} diff --git a/tripleo_ansible/roles/tripleo_nftables/templates/jump.j2 b/tripleo_ansible/roles/tripleo_nftables/templates/jump.j2 new file mode 100644 index 000000000..e8db7671c --- /dev/null +++ b/tripleo_ansible/roles/tripleo_nftables/templates/jump.j2 @@ -0,0 +1,9 @@ +{%- if 'jump' in rule %} +{% if (rule['jump']|lower) not in ['accept', 'drop', 'log', 'masquerade', 'notrack', 'return'] %} + jump {{ rule['jump'] }} +{%- else %} + {{ rule['jump']|lower }} +{%- endif %} +{%- else %} + accept +{%- endif %} diff --git a/tripleo_ansible/roles/tripleo_nftables/templates/limit.j2 b/tripleo_ansible/roles/tripleo_nftables/templates/limit.j2 new file mode 100644 index 000000000..2ccfe1b4f --- /dev/null +++ b/tripleo_ansible/roles/tripleo_nftables/templates/limit.j2 @@ -0,0 +1,3 @@ +{%- if 'limit' in rule %} + limit rate {{ rule['limit']|regex_replace('([0-9]+)/min', '\\1/minute') }} +{%- endif %} diff --git a/tripleo_ansible/roles/tripleo_nftables/templates/limit_burst.j2 b/tripleo_ansible/roles/tripleo_nftables/templates/limit_burst.j2 new file mode 100644 index 000000000..1c8b51104 --- /dev/null +++ b/tripleo_ansible/roles/tripleo_nftables/templates/limit_burst.j2 @@ -0,0 +1,3 @@ +{%- if 'limit_burst' in rule %} + burst {{ rule['limit_burst'] }} packets +{%- endif %} diff --git a/tripleo_ansible/roles/tripleo_nftables/templates/protocol.j2 b/tripleo_ansible/roles/tripleo_nftables/templates/protocol.j2 new file mode 100644 index 000000000..066e521c3 --- /dev/null +++ b/tripleo_ansible/roles/tripleo_nftables/templates/protocol.j2 @@ -0,0 +1,6 @@ +{%- if 'proto' in rule and (rule['proto']|lower) != 'all' %} +{%- if (rule['proto']|lower) not in ['tcp', 'udp'] %} + meta l4proto +{%- endif %} + {{ rule['proto']|lower }} +{%- endif %} diff --git a/tripleo_ansible/roles/tripleo_nftables/templates/ruleset.j2 b/tripleo_ansible/roles/tripleo_nftables/templates/ruleset.j2 new file mode 100644 index 000000000..6bcfa5a75 --- /dev/null +++ b/tripleo_ansible/roles/tripleo_nftables/templates/ruleset.j2 @@ -0,0 +1,30 @@ +#!/usr/sbin/nft -f +# Managed by tripleo-ansible/tripleo_nftables +# Valid starting nft-0.9.8 + +{%- for ruleset in tripleo_nftables_rules %} +{% set rule=ruleset['rule'] %} +{% set name=ruleset['rule_name'] %} +{%- if 'extras' not in rule or rule['extras'].get('ensure', 'present') in ['enabled', 'present'] %} +# {{ rule.get('rule_name', name) }} {{ rule }} +add rule +{%- include 'templates/ipversion.j2' %} + {{ rule.get('table', 'filter') }} TRIPLEO_{{ rule.get('chain', 'INPUT') }} +{%- include 'templates/interface.j2' %} +{%- include 'templates/source.j2' %} +{%- include 'templates/sport.j2' %} +{%- include 'templates/destination.j2' %} +{%- include 'templates/protocol.j2' %} +{%- include 'templates/dport.j2' %} +{%- include 'templates/state.j2' %} +{%- include 'templates/limit.j2' %} +{%- include 'templates/limit_burst.j2' %} + counter +{%- include 'templates/jump.j2' %} + comment "{{rule.get('rule_name', name) }}" + +{% endif %} +{% endfor %} + +# Lock down INPUT chains +add chain inet filter INPUT { policy drop; } diff --git a/tripleo_ansible/roles/tripleo_nftables/templates/source.j2 b/tripleo_ansible/roles/tripleo_nftables/templates/source.j2 new file mode 100644 index 000000000..77114e060 --- /dev/null +++ b/tripleo_ansible/roles/tripleo_nftables/templates/source.j2 @@ -0,0 +1,6 @@ +{%- if 'source' in rule %} +{%- if 'ipversion' not in rule %} + ip +{%- endif %} + saddr {{ rule['source'] }} +{%- endif %} diff --git a/tripleo_ansible/roles/tripleo_nftables/templates/sport.j2 b/tripleo_ansible/roles/tripleo_nftables/templates/sport.j2 new file mode 100644 index 000000000..d2d40d409 --- /dev/null +++ b/tripleo_ansible/roles/tripleo_nftables/templates/sport.j2 @@ -0,0 +1,3 @@ +{%- if 'sport' in rule %} + {{ rule.get('proto', false)|ternary('', 'tcp ') }}sport {{ rule['sport'] }} +{%- endif %} diff --git a/tripleo_ansible/roles/tripleo_nftables/templates/state.j2 b/tripleo_ansible/roles/tripleo_nftables/templates/state.j2 new file mode 100644 index 000000000..307c533fc --- /dev/null +++ b/tripleo_ansible/roles/tripleo_nftables/templates/state.j2 @@ -0,0 +1,7 @@ +{%- if 'state' in rule %} +{%- if rule['state']|length > 0 %} + ct state {{ rule['state'] |join(',')|lower }} +{%- endif %} +{%- else %} + ct state new +{%- endif %} diff --git a/zuul.d/molecule.yaml b/zuul.d/molecule.yaml index a99a6e90b..a772457c6 100644 --- a/zuul.d/molecule.yaml +++ b/zuul.d/molecule.yaml @@ -44,6 +44,7 @@ - tripleo-ansible-centos-stream-molecule-tripleo_multipathd - tripleo-ansible-centos-stream-molecule-tripleo_mysql_client - tripleo-ansible-centos-stream-molecule-tripleo_network_config + - tripleo-ansible-centos-stream-molecule-tripleo_nftables - tripleo-ansible-centos-stream-molecule-tripleo_nodes_validation - tripleo-ansible-centos-stream-molecule-tripleo_nova_image_cache - tripleo-ansible-centos-stream-molecule-tripleo_nvdimm @@ -111,6 +112,7 @@ - tripleo-ansible-centos-stream-molecule-tripleo_multipathd - tripleo-ansible-centos-stream-molecule-tripleo_mysql_client - tripleo-ansible-centos-stream-molecule-tripleo_network_config + - tripleo-ansible-centos-stream-molecule-tripleo_nftables - tripleo-ansible-centos-stream-molecule-tripleo_nodes_validation - tripleo-ansible-centos-stream-molecule-tripleo_nova_image_cache - tripleo-ansible-centos-stream-molecule-tripleo_nvdimm @@ -176,6 +178,7 @@ - tripleo-ansible-centos-stream-molecule-tripleo_multipathd - tripleo-ansible-centos-stream-molecule-tripleo_mysql_client - tripleo-ansible-centos-stream-molecule-tripleo_network_config + - tripleo-ansible-centos-stream-molecule-tripleo_nftables - tripleo-ansible-centos-stream-molecule-tripleo_nodes_validation - tripleo-ansible-centos-stream-molecule-tripleo_nova_image_cache - tripleo-ansible-centos-stream-molecule-tripleo_nvdimm @@ -507,6 +510,13 @@ parent: tripleo-ansible-centos-stream-base vars: tripleo_role_name: tripleo_network_config +- job: + files: + - ^tripleo_ansible/roles/tripleo_nftables/(?!meta).* + name: tripleo-ansible-centos-stream-molecule-tripleo_nftables + parent: tripleo-ansible-centos-stream-base + vars: + tripleo_role_name: tripleo_nftables - job: files: - ^tripleo_ansible/roles/tripleo_nodes_validation/(?!meta).*