Modify how tripleo_nftables gets its configurations

From now on, tripleo_nftables will use a directory containing rules
snippets instead of a parameter.

This will allow to push snippets from other roles during the deploy,
and then configure the firewall.

We therefore add two new modules:
- tripleo_nftables_snippet: creates files with the relevant content,
as YAML
- tripleo_nftables_from_files: gather snippets, merge the contents,
sorts the rules and pass the whole list to its output.

The tripleo_firewall role is now creating a snippet based on the
current parameter, so that we're still 100% compatible with
tripleo-heat-templates way of pushing things in.

This new usage is especially interesting for the standalone
roles/playbooks deploy, since each service role will just need to:
- ensure the destination directory exists
- push its rule snippet in there, in the tripleo_nftables format, in
  YAML
- call the "configure.yaml" from tripleo_nftables in order to get the
  rules added/processed (and, eventually, the playbook will call the
  run.yaml to apply things)

Depends-On: https://review.opendev.org/c/openstack/tripleo-ansible/+/864392
Change-Id: I38deaff740b2fcdcd7bc74ce81a2164121de11af
This commit is contained in:
Cédric Jeanneret 2022-11-07 13:21:36 +01:00 committed by Cedric Jeanneret
parent 84d7ce856a
commit f6dd406621
17 changed files with 564 additions and 135 deletions

View File

@ -0,0 +1,114 @@
#!/usr/bin/env python3
# Copyright 2021 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.
__metaclass__ = type
import os
import yaml
from ansible.module_utils.basic import AnsibleModule
ANSIBLE_METADATA = {
'metadata_version': '0.1',
'status': ['preview'],
'supported_by': 'community'
}
DOCUMENTATION = """
---
module: tripleo_nftables_from_files
author:
- Cedric Jeanneret <cjeanner@redhat.com>
version_added: '2.12'
short_description: Get yaml contents and output a single list of rules
notes: []
description:
- This action loads multiple YAML files from a specified location, and
appends the elements into a single list. This list can then be used within
tripleo_nftables in order to configure the firewall.
options:
src:
description:
- Source directory for the different files
required: True
type: str
"""
EXAMPLES = """
- name: Get nftables rules
register: tripleo_nftables_rules
tripleo_nftables_from_files:
src: /var/lib/tripleo-config/firewall
"""
RETURN = """
rules:
description: List of nftables rules built upon the files content
returned: always
type: dict
sample:
success: True
rules:
- rule_name: 000 accept related established
rule:
proto: all
state:
- RELATED
- ESTABLISHED
- rule_name: 010 accept ssh from all
rule:
proto: tcp
dport: 22
"""
class main():
"""Main method for the module
"""
result = dict(sucess=False, error="")
module = AnsibleModule(
argument_spec=yaml.safe_load(DOCUMENTATION)['options'],
supports_check_mode=False,
)
dir_src = module.params.get('src', None)
if dir_src is None:
result['error'] = 'Missing required parameter: src'
result['msg'] = result['error']
module.fail_json(**result)
if not os.path.exists(dir_src):
result['error'] = 'Missing directory on host: {}'.format(dir_src)
result['msg'] = result['error']
module.fail_json(**result)
rules = []
for r_file in os.listdir(dir_src):
with open(os.path.join(dir_src, r_file), 'r') as r_data:
try:
parsed_yaml = yaml.safe_load(r_data)
except Exception:
result['error'] = 'Unable to parse {}'.format(
os.path.join(dir_src, r_file))
result['msg'] = result['error']
module.fail_json(**result)
rules.extend(parsed_yaml)
result['rules'] = sorted(rules, key=lambda r: r['rule_name'])
result['success'] = True
module.exit_json(**result)
if __name__ == '__main__':
main()

View File

@ -0,0 +1,136 @@
#!/usr/bin/env python3
# Copyright 2021 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.
__metaclass__ = type
import hashlib
import os
import yaml
from ansible.module_utils.basic import AnsibleModule
ANSIBLE_METADATA = {
'metadata_version': '0.1',
'status': ['preview'],
'supported_by': 'community'
}
DOCUMENTATION = """
---
module: tripleo_nftables_snippet
author:
- Cedric Jeanneret <cjeanner@redhat.com>
version_added: '2.12'
short_description: Create rule snippets in selected configuration directory
notes: []
description:
- This module validate and write the YAML in specified location/file, while
ensuring the filename is unique in the location.
options:
dest:
description:
- Destination absolute path, with filename
required: True
type: str
content:
description:
- List of rule dicts in valid YAML
required: False
type: str
state:
description:
- State of the snippet, either present or absent
type: str
default: present
"""
EXAMPLES = """
- name: Inject snippet for CI
tripleo_nftables_snippet:
dest: /var/lib/tripleo-config/firewall/ci-rules.yaml
content: |
- rule_name: 010 Allow SSH from everywhere
rule:
proto: tcp
dport: 22
- rule_name: Allow console stream from everywhere
rule:
proto: tcp
dport: 19885
state: []
"""
RETURN = """
"""
class main():
"""Main method for the module
"""
result = dict(sucess=False, error="", changed=False)
module = AnsibleModule(
argument_spec=yaml.safe_load(DOCUMENTATION)['options'],
supports_check_mode=False,
)
dest = module.params.get('dest', None)
content = module.params.get('content', None)
state = module.params.get('state', 'present')
if dest is None:
result['error'] = 'Missing required parameter: dest'
result['msg'] = result['error']
module.fail_json(**result)
if not os.path.isabs(dest):
result['error'] = '"dest" parameter must be an absolute path'
result['msg'] = result['error']
module.fail_json(**result)
if state == 'present' and content is None:
result['error'] = 'Missing required parameter: content'
result['msg'] = result['error']
module.fail_json(**result)
if not os.path.exists(os.path.dirname(dest)):
result['error'] = 'Destination directory does not exist'
result['msg'] = ("Directory {} doesn't exist, please create it "
"before trying to push files in there").format(
os.path.dirname(dest))
module.fail_json(**result)
if state == 'present':
try:
parsed_yaml = yaml.safe_load(content)
except Exception:
result['error'] = "Content doesn't look like a valid YAML."
result['msg'] = result['error']
module.fail_json(**result)
with open(dest, 'w') as f_output:
yaml.dump(parsed_yaml, f_output)
result['changed'] = True
else:
if os.path.exists(dest):
try:
os.remove(dest)
result['changed'] = True
except Exception:
result['error'] = "Unable to remove {}".format(dest)
result['msg'] = result['error']
module.fail_json(**result)
result['success'] = True
module.exit_json(**result)
if __name__ == '__main__':
main()

View File

@ -45,37 +45,3 @@ tripleo_firewall_edge_frontend_enabled: false
tripleo_firewall_edge_frontend_rules: {}
tripleo_firewall_edge_ssl_frontend_rules: {}
tripleo_firewall_default_rules:
'000 accept related established rules':
proto: all
state:
- RELATED
- ESTABLISHED
'001 accept all icmp':
ipversion: ipv4
proto: icmp
'001 accept all ipv6-icmp':
ipversion: ipv6
proto: ipv6-icmp
state: []
'002 accept all to lo interface':
proto: all
interface: lo
state: []
'004 accept ipv6 dhcpv6':
ipversion: ipv6
dport: 546
proto: udp
state:
- NEW
destination: 'fe80::/64'
'999 log all':
proto: all
jump: LOG
limit: 20/min
limit_burst: 15
nft_level: 'warn'
nft_flags: 'all'
nft_prefix: 'DROPPING: '
state: []

View File

@ -77,18 +77,29 @@
- name: Set rule fact
set_fact:
firewall_rules_sorted: "{{
tripleo_firewall_default_rules |
combine(tripleo_firewall_rules) |
tripleo_firewall_rules |
combine(tripleo_firewall_frontend_rules_real) |
combine(masquerade_rules|from_yaml) |
dict2items(key_name='rule_name', value_name='rule') |
sort(attribute='rule_name') |
reverse |
list
}}"
- name: Ensures rule snippets directory exists
become: true
file:
path: /var/lib/tripleo-config/firewall
state: directory
owner: root
group: root
mode: 0750
- name: Output rule snippet
become: true
tripleo_nftables_snippet:
dest: /var/lib/tripleo-config/firewall/tripleo-generated-rules.yaml
content: "{{ firewall_rules_sorted | to_nice_yaml }}"
- name: Manage rules via nftables
vars:
tripleo_nftables_rules: "{{ firewall_rules_sorted | sort(attribute='rule_name') |list }}"
include_role:
name: tripleo_nftables

View File

@ -18,44 +18,4 @@
# 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
state: []
rule_name: 001 accept all ipv6-icmp
- rule:
interface: lo
proto: all
state: []
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
level: 'warn'
flags: 'all'
prefix: 'DROPPING: '
state: []
rule_name: 999 log all
tripleo_nftables_src: /var/lib/tripleo-config/firewall

View File

@ -0,0 +1,38 @@
- 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
state: []
rule_name: 001 accept all ipv6-icmp
- rule:
interface: lo
proto: all
state: []
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
level: 'warn'
flags: 'all'
prefix: 'DROPPING: '
state: []
rule_name: 999 log all

View File

@ -17,29 +17,14 @@
- name: Converge
hosts: all
become: true
vars:
tripleo_nftables_rules:
- rule_name: '000 related established'
rule:
proto: all
state:
- established
- related
- rule_name: '001 local'
rule:
proto: all
interface: lo
state: []
- rule_name: '010 testing action'
rule:
proto: tcp
dport: 1211
action: drop
roles:
- role: "tripleo_nftables"
tripleo_nftables_src: /opt/tripleo-firewall
tasks:
- name: Run role
ansible.builtin.import_role:
name: tripleo_nftables
- name: "Ensure we drop connections on TCP/1211"
become: true
lineinfile:
path: /etc/nftables/tripleo-rules.nft
line: 'add rule inet filter TRIPLEO_INPUT tcp dport { 1211 } ct state new counter drop comment "010 testing action"'

View File

@ -17,6 +17,8 @@
- name: Prepare
hosts: all
vars:
tripleo_nftables_src: /opt/tripleo-firewall
roles:
- role: test_deps
test_deps_extra_packages:
@ -24,6 +26,32 @@
- role: env_data
tasks:
- name: Cleanup nftables
import_role:
ansible.builtin.import_role:
name: tripleo_nftables
tasks_from: cleanup.yml
- name: Create snippet directory
become: true
ansible.builtin.file:
path: /opt/tripleo-firewall
state: directory
- name: Inject snippet for action
become: true
tripleo_nftables_snippet:
dest: /opt/tripleo-firewall/action.yaml
content: |
- rule_name: '000 related established'
rule:
proto: all
state:
- established
- related
- rule_name: '001 local'
rule:
proto: all
interface: lo
state: []
- rule_name: '010 testing action'
rule:
proto: tcp
dport: 1211
action: drop

View File

@ -18,23 +18,7 @@
- name: Converge
hosts: all
vars:
tripleo_nftables_rules:
- rule_name: '000 related established'
rule:
proto: all
state:
- established
- related
- rule_name: '001 local'
rule:
proto: all
interface: lo
state: []
- rule_name: '010 testing destination'
rule:
proto: tcp
destination: "fd00:fd00:fd00:2000::/64"
dport: 1211
tripleo_nftables_src: /opt/tripleo-firewall
roles:
- role: "tripleo_nftables"
tasks:

View File

@ -24,6 +24,34 @@
- role: env_data
tasks:
- name: Cleanup nftables
vars:
tripleo_nftables_src: /opt/tripleo-firewall
import_role:
name: tripleo_nftables
tasks_from: cleanup.yml
- name: Create snippet directory
become: true
ansible.builtin.file:
path: /opt/tripleo-firewall
state: directory
- name: Push snippet for destination
become: true
tripleo_nftables_snippet:
dest: /opt/tripleo-firewall/destination.yml
content: |
- rule_name: '000 related established'
rule:
proto: all
state:
- established
- related
- rule_name: '001 local'
rule:
proto: all
interface: lo
state: []
- rule_name: '010 testing destination'
rule:
proto: tcp
destination: "fd00:fd00:fd00:2000::/64"
dport: 1211

View File

@ -18,23 +18,7 @@
- name: Converge
hosts: all
vars:
tripleo_nftables_rules:
- rule_name: '000 related established'
rule:
proto: all
state:
- established
- related
- rule_name: '001 local'
rule:
proto: all
interface: lo
state: []
- rule_name: '010 testing source'
rule:
proto: tcp
source: "fd00:fd00:fd00:2000::/64"
dport: 1211
tripleo_nftables_src: /opt/tripleo-firewall
roles:
- role: "tripleo_nftables"
tasks:

View File

@ -27,3 +27,29 @@
import_role:
name: tripleo_nftables
tasks_from: cleanup.yml
- name: Create snippet directory
become: true
ansible.builtin.file:
path: /opt/tripleo-firewall
state: directory
- name: Push snippet for source
become: true
tripleo_nftables_snippet:
dest: /opt/tripleo-firewall/source.yml
content: |
- rule_name: '000 related established'
rule:
proto: all
state:
- established
- related
- rule_name: '001 local'
rule:
proto: all
interface: lo
state: []
- rule_name: '010 testing source'
rule:
proto: tcp
source: "fd00:fd00:fd00:2000::/64"
dport: 1211

View File

@ -0,0 +1,48 @@
---
# 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
vars:
tripleo_nftables_src: /opt/tripleo-firewall
roles:
- role: "tripleo_nftables"
tasks:
- name: Update snippet
become: true
tripleo_nftables_snippet:
dest: /opt/tripleo-firewall/ruleset.yml
content: |
- rule_name: '00 related established'
rule:
proto: all
state:
- related
- established
- rule_name: '01 local link'
rule:
proto: all
interface: lo
state: []
- rule_name: '02 ssh from all'
rule:
proto: tcp
dport: 22
- name: Cleanup nftables
ansible.builtin.import_role:
name: tripleo_nftables
tasks_from: cleanup.yml

View File

@ -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:
name: update_rules
test_sequence:
- prepare
- converge
verifier:
name: testinfra

View File

@ -0,0 +1,54 @@
---
# 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
roles:
- role: test_deps
test_deps_extra_packages:
- nftables
- role: env_data
tasks:
- name: Cleanup nftables
ansible.builtin.import_role:
name: tripleo_nftables
tasks_from: cleanup.yml
- name: Create snippet directory
become: true
ansible.builtin.file:
path: /opt/tripleo-firewall
state: directory
- name: Inject snippet for action
become: true
tripleo_nftables_snippet:
dest: /opt/tripleo-firewall/ruleset.yml
content: |
- rule_name: '000 related established'
rule:
proto: all
state:
- established
- related
- rule_name: '001 local'
rule:
proto: all
interface: lo
state: []
- rule_name: '002 ssh'
rule:
proto: tcp
dport: 22

View File

@ -27,3 +27,8 @@
include "/etc/nftables/tripleo-chains.nft"
include "/etc/nftables/tripleo-rules.nft"
include "/etc/nftables/tripleo-jumps.nft"
- name: Remove snippets directory
ansible.builtin.file:
path: "{{ tripleo_nftables_src }}"
state: absent

View File

@ -14,6 +14,22 @@
# License for the specific language governing permissions and limitations
# under the License.
- name: Basic config steps and basic rules
become: true
block:
- name: Create snipets directory
ansible.builtin.file:
path: "{{ tripleo_nftables_src }}"
state: directory
owner: root
group: root
mode: 0750
- name: Push default ruleset snipet
ansible.builtin.copy:
dest: "{{ tripleo_nftables_src }}/tripleo-nftables-base.yaml"
src: 00-base-rules.yaml
- name: IPtables compatibility layout
become: true
block:
@ -31,8 +47,16 @@
ansible.builtin.command: nft -j list ruleset
register: nft_current_rules
- name: Load firewall snippets
become: true
register: tripleo_nftables_rules_list
tripleo_nftables_from_files:
src: "{{ tripleo_nftables_src }}"
- name: nftables files generation
become: true
when:
- not ansible_check_mode|bool
block:
# Create a dedicated file for jumps - makes easier to manage afterward.
# That one will be loaded upon boot only.
@ -41,6 +65,7 @@
vars:
current_nft: "{{ nft_current_rules }}"
nft_is_update: false
tripleo_nftables_rules: "{{ tripleo_nftables_rules_list['rules'] }}"
ansible.builtin.template:
dest: /etc/nftables/tripleo-jumps.nft
src: jump-chain.j2
@ -54,6 +79,7 @@
vars:
current_nft: "{{ nft_current_rules }}"
nft_is_update: true
tripleo_nftables_rules: "{{ tripleo_nftables_rules_list['rules'] }}"
ansible.builtin.template:
dest: /etc/nftables/tripleo-update-jumps.nft
src: jump-chain.j2
@ -62,18 +88,24 @@
# already empty!
- name: Generate nft flushes
register: nft_flushes
vars:
tripleo_nftables_rules: "{{ tripleo_nftables_rules_list['rules'] }}"
ansible.builtin.template:
dest: /etc/nftables/tripleo-flushes.nft
src: flush-chain.j2
- name: Generate nft tripleo chains
register: nft_chains
vars:
tripleo_nftables_rules: "{{ tripleo_nftables_rules_list['rules'] }}"
ansible.builtin.template:
dest: /etc/nftables/tripleo-chains.nft
src: chains.j2
- name: Generate nft ruleset in static file
register: nft_ruleset
vars:
tripleo_nftables_rules: "{{ tripleo_nftables_rules_list['rules'] }}"
ansible.builtin.template:
dest: /etc/nftables/tripleo-rules.nft
src: ruleset.j2
@ -82,6 +114,8 @@
# 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
when:
- not ansible_check_mode|bool
ansible.builtin.shell: >-
cat /etc/nftables/tripleo-chains.nft
/etc/nftables/tripleo-flushes.nft