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).*