ansible_qemu_ceph_xcat_test.../cluster/roles/firewalld/tasks/main.yml

493 lines
15 KiB
YAML

# Copyright 2022 OCF Ltd. 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.
# -*- coding: utf-8 -*-
# vim: ft=yaml
---
######## inherit custom variables from inventory/host_vars/firewalld.yml
- name: merge custom vars
block:
- name: set role variable sources
set_fact:
role_info:
role_defaults_file: "{{ role_path }}/defaults/main.yml"
role_override_file: "{{ ansible_inventory_sources[0] | dirname }}/group_vars/{{ role_name }}.yml"
vars_return: "placeholder"
- set_fact:
source_role: "{{ role_name }}"
- name: run merge_vars role
include_role:
name: "merge_vars"
vars:
a_config_file: "{{ role_info['role_defaults_file'] }}"
b_config_file: "{{ role_info['role_override_file'] }}"
calling_role: "{{ source_role }}"
- name: merge custom vars to vars[]
set_fact:
{ "{{ entry }}": "{{ role_info['vars_return'][entry] }}" }
loop: "{{ role_info['vars_return'] | list }}"
loop_control:
loop_var: entry
when:
- not role_info['vars_return'] == 'placeholder'
delegate_to: localhost
######## setup packages
- name: update package facts
ansible.builtin.package_facts:
manager: auto
strategy: all
when: ansible_facts['packages'] is not defined
- name: install firewalld packages
block:
- name: install firewalld
ansible.builtin.package:
name:
- firewalld
- ipset
- nftables
state: latest
- name: Install python-firewall
package:
name: python-firewall
state: present
when:
- ansible_facts['os_family'] == 'RedHat' and ansible_facts['distribution_major_version'] == '7'
- name: Install python3-firewall
package:
name: python3-firewall
state: present
when:
- ansible_facts['os_family'] == 'RedHat' and ansible_facts['distribution_major_version'] == '8'
when:
- vars['firewalld']['enable'] | bool
- ansible_facts['packages']['firewalld'] is not defined or
ansible_facts['packages']['ipset'] is not defined or
ansible_facts['packages']['nftables'] is not defined
- name: update service facts
ansible.builtin.service_facts:
######## disable firewall
- name: disable firewalld
ansible.builtin.systemd:
name: firewalld
enabled: no
state: stopped
when:
- ansible_facts['services']['firewalld.service'] is not defined
- not vars['firewalld']['enable'] | bool
######## render firewalld config file
- name: update INI entries in firewalld config
ini_file:
path: "{{ firewalld['firewalld_conf_file'] }}"
no_extra_spaces: true
# write to root of document not under a section
section: null
option: "{{ entry.key }}"
value: "{{ entry.value }}"
loop: "{{ firewalld['firewalld_conf'] | dict2items }}"
loop_control:
loop_var: entry
notify: reload_firewalld
when:
- firewalld['enable'] | bool
######## map services to zones and networks
# map host 'xcat_groups' (hostvars[ansible_hostname]) to services 'xcat_groups' (vars['firewalld']['firewalld_services'] list item ['xcat_groups'])
# determine if the service (firewall rule) is applicable to the host
- name: map services to zones
block:
- name: find firewalld services to be applied to each xcat_groups that this host is a member of
set_fact:
target_services: "{{ target_services | default([]) + [service] }}"
when: xcat_group in hostvars[ansible_hostname]['xcat_groups']
with_subelements:
- "{{ firewalld['firewalld_services'] }}"
- xcat_groups
- skip_missing: True
loop_control:
loop_var: entry
vars:
xcat_group: "{{ entry.1 }}"
service: "{{ entry.0 }}"
# - debug:
# msg:
# - "{{ target_services }}"
- name: remove duplicate service entries where host in multiple xcat_groups
set_fact:
target_services: "{{ target_services | unique }}"
when:
- firewalld['enable'] | bool
######## configure ipsets
- name: configure ipsets
block:
- name: list existing ipsets in /etc/firewalld/ipsets
find:
paths: "/etc/firewalld/ipsets/"
patterns: "*.xml"
recurse: no
file_type: file
register: ipsets_files_all
- name: exclude ipsets managed by ansible
set_fact:
ipsets_files: "{{ ipsets_files | default([]) + [file_path] }}"
loop: "{{ ipsets_files_all['files'] }}"
loop_control:
loop_var: entry
vars:
file_path: "{{ entry['path'] }}"
file_name: "{{ entry['path'].split('/')[-1].split('.')[0] }}"
when:
- ipsets_files_all['files'] | length >0
- file_name not in firewalld['firewalld_ipsets']
- file_name not in vars['steel']['xcat_networks'] | list
- name: disable ipsets not managed by ansible
copy:
remote_src: yes
src: "{{ file_path }}"
dest: "{{ new_file_path }}"
loop: "{{ ipsets_files }}"
loop_control:
loop_var: entry
vars:
file_path: "{{ entry }}"
new_file_path: "{{ entry.split('.')[0] }}.ansible_disabled"
register: ipsets_disabled
notify: reload_firewalld
when:
- ipsets_files is defined
- ipsets_files | length >0
- file:
path: "{{ file_path }}"
state: absent
loop: "{{ ipsets_files }}"
loop_control:
loop_var: entry
vars:
file_path: "{{ entry }}"
when:
- not ipsets_disabled['skipped'] | bool
- name: generate ipsets from steel['xcat_networks']
set_fact:
generated_ipsets: "{{ generated_ipsets | default({}) | combine({ 'firewalld_ipsets': { network_name: { 'short': network_name, 'description': description, 'type': 'hash:ip', 'targets': [network_cidr] } } }, recursive=True) }}"
# generated_ipsets: "{{ generated_ipsets | default({}) | combine({ 'firewalld_ipsets': { network_name: { 'short': network_name, 'description': description, 'type': 'hash:ip', 'options': { 'maclem': [65536], 'timeout': [300], 'hashsize': [1024] }, 'targets': [network_cidr] } } }, recursive=True) }}" # example with additional options
loop: "{{ steel['xcat_networks'] | dict2items }}"
loop_control:
loop_var: entry
vars:
network_name: "{{ entry.key }}"
network_range: "{{ entry.value['network'] }}"
network_mask: "{{ entry.value['netmask'] }}"
network_cidr: "{{ network_range }}/{{ (network_range + '/' + network_mask) | ansible.utils.ipaddr('prefix') }}"
description: "{{ network_name }} ipset"
# required where we have provided custom ipsets
- name: merge generated generate ipsets
set_fact:
firewalld: "{{ firewalld | default({}) | combine( generated_ipsets, recursive=True) }}"
when:
- generated_ipsets is defined
- name: render firewalld ipsets
template:
src: "{{ role_path }}/templates/ipset_template.xml.j2"
dest: /etc/firewalld/ipsets/{{ entry }}.xml
loop: "{{ firewalld['firewalld_ipsets'] | list }}"
loop_control:
loop_var: entry
vars:
short: "{{ firewalld['firewalld_ipsets'][entry]['short'] }}"
description: "{{ firewalld['firewalld_ipsets'][entry]['description'] }}"
type: "{{ firewalld['firewalld_ipsets'][entry]['type'] }}"
options: "{{ firewalld['firewalld_ipsets'][entry]['options'] }}"
targets: "{{ firewalld['firewalld_ipsets'][entry]['targets'] }}"
notify: reload_firewalld
when:
- firewalld['firewalld_ipsets'] is defined
when:
- firewalld['enable'] | bool
######## configure services
- name: configure services
block:
- name: list existing services in /etc/firewalld/services
find:
paths: "/etc/firewalld/services/"
patterns: "*.xml"
recurse: no
file_type: file
register: services_files_all
- name: exclude services managed by ansible
set_fact:
services_files: "{{ services_files | default([]) + [file_path] }}"
loop: "{{ services_files_all['files'] }}"
loop_control:
loop_var: entry
vars:
file_path: "{{ entry['path'] }}"
file_name: "{{ entry['path'].split('/')[-1].split('.')[0] }}"
when:
- services_files_all['files'] | length >0
- file_name not in firewalld['firewalld_services'] | map(attribute='name')
# - debug:
# msg:
# - "{{ services_files }}"
- name: disable services not managed by ansible
copy:
remote_src: yes
src: "{{ file_path }}"
dest: "{{ new_file_path }}"
loop: "{{ services_files }}"
loop_control:
loop_var: entry
vars:
file_path: "{{ entry }}"
new_file_path: "{{ entry.split('.')[0] }}.ansible_disabled"
register: services_disabled
notify: reload_firewalld
when:
- services_files is defined
- services_files | length >0
# - debug:
# msg:
# - "{{ services_disabled }}"
- file:
path: "{{ file_path }}"
state: absent
loop: "{{ services_files }}"
loop_control:
loop_var: entry
vars:
file_path: "{{ entry }}"
when:
- not services_disabled['skipped'] | bool
- name: render firewalld services
template:
src: "{{ role_path }}/templates/service_template.xml.j2"
dest: /etc/firewalld/services/{{ name }}.xml
loop: "{{ target_services }}"
loop_control:
loop_var: entry
vars:
name: "{{ entry['name'] }}"
short: "{{ entry['short'] }}"
description: "{{ entry['description'] }}"
port: "{{ entry['port'] }}"
notify: reload_firewalld
when:
- firewalld['firewalld_services'] is defined
- firewalld['firewalld_services'] | length >0
when:
- firewalld['enable'] | bool
######## configure zones
- name: configure zones
block:
# there are no preset zone names, zones are dynamically generated from top level source inventory/networks.yml
# to create a custom zone
# - a custom firewalld_services entry with an (arbritrary) xcat_networks list item will generate a new zone
# - a custom firewalld_ipsets entry named the same as the custom services entry will be required to control ingress
#
# - name: generate all zone names from xcat_networks entry in 'firewalld_merged['firewalld_services']'
- name: generate all zone names from xcat_networks entry in 'firewalld['firewalld_services']'
set_fact:
zone_list: "{{ zone_list | default([]) + zone }}"
loop: "{{ target_services }}"
loop_control:
loop_var: entry
vars:
zone: "{{ entry['xcat_networks'] }}"
- name: filter on unique zones from services
set_fact:
zone_list: "{{ zone_list | unique }}"
# this is the pivotal task in the playbook to ensure the zones dictionary is in the format accepted for the jinja2 loops in the zone_template.xml.j2
# loop unique zones, match all services bound to the zone using xcat_networks, get a list of service names and format into a list of dicts each with the same key 'name:', render zones template in compatible format for jinja
#
- name: create zones dictionary
set_fact:
firewalld_zones: "{{ firewalld_zones | default([]) + ([{ 'name': zone_name, 'short': zone_name, 'description': zone_description, 'source': [{ 'ipset': zone_name }], 'service': service_trim }] ) }}"
# firewalld_zones: "{{ firewalld_zones | default([]) + ([{ 'name': zone_name, 'short': zone_name, 'description': zone_description, 'source': [{ 'ipset': zone_name }], 'service': [{ 'name': 'ssh' }, { 'name': 'ftp' }] }] ) }}" # format required
loop: "{{ zone_list }}"
loop_control:
loop_var: entry
vars:
zone_name: "{{ entry }}"
zone_description: "{{ entry }} zone"
# use mapping to return list of services
service: "{{ target_services | selectattr('xcat_networks', 'search', entry) | map(attribute='name') }}"
#
# inline jinja to create a list of dicts for the services used in this zone
service_format: >-
{% set results = [] %}
{% for svc in service|default([]) %}
{% set sub_results = {} %}
{% set _ = sub_results.update({"name": svc}) %}
{% set _ = results.append(sub_results) %}
{% endfor -%}
{{results}}
# trim whitespaces to allow ansible to interperet as list item in the firewalld_zones dict
service_trim: "{{ service_format | trim }}"
# - name: add pre-defined zones from firewalld_merged['firewalld_zones']
- name: add pre-defined zones from firewalld['firewalld_zones']
set_fact:
firewalld_zones: "{{ firewalld_zones | default([]) + [entry] }}"
loop: "{{ firewalld['firewalld_zones'] }}"
loop_control:
loop_var: entry
when:
- firewalld['firewalld_zones'] is defined
- firewalld['firewalld_zones'] | length >0
# - debug:
# msg:
# - "{{ firewalld_zones }}"
- name: list existing zones in /etc/firewalld/zones
find:
paths: "/etc/firewalld/zones/"
patterns: "*.xml"
recurse: no
file_type: file
register: zones_files_all
- name: exclude zones managed by ansible
set_fact:
zone_files: "{{ zone_files | default([]) + [file_path] }}"
loop: "{{ zones_files_all['files'] }}"
loop_control:
loop_var: entry
vars:
file_path: "{{ entry['path'] }}"
file_name: "{{ entry['path'].split('/')[-1].split('.')[0] }}"
when:
- zones_files_all['files'] | length >0
- file_name not in firewalld_zones | map(attribute='name')
# - debug:
# msg:
# - "{{ zone_files }}"
- name: disable zones not managed by ansible
copy:
remote_src: yes
src: "{{ file_path }}"
dest: "{{ new_file_path }}"
loop: "{{ zone_files }}"
loop_control:
loop_var: entry
vars:
file_path: "{{ entry }}"
new_file_path: "{{ entry.split('.')[0] }}.ansible_disabled"
register: zones_disabled
notify: reload_firewalld
when:
- zone_files is defined
- zone_files | length >0
# - debug:
# msg:
# - "{{ zones_disabled }}"
- file:
path: "{{ file_path }}"
state: absent
loop: "{{ zone_files }}"
loop_control:
loop_var: entry
vars:
file_path: "{{ entry }}"
when:
- not zones_disabled['skipped'] | bool
- name: render firewalld zones
template:
src: "{{ role_path }}/templates/zone_template.xml.j2"
dest: /etc/firewalld/zones/{{ name }}.xml
loop: "{{ firewalld_zones | list }}"
loop_control:
loop_var: entry
vars:
name: "{{ entry['name'] }}"
short: "{{ entry['short'] }}"
description: "{{ entry['description'] }}"
service: "{{ entry['service'] }}"
ipset: "{{ entry['name'] }}"
notify: reload_firewalld
when:
- firewalld_zones is defined
- firewalld_zones | length >0
when:
- firewalld['enable'] | bool
######## start firewalld
#
# handler starts/reloads/enables firewalld service
# - name: Flush handlers
# meta: flush_handlers
# - name: Start and enable firewalld
# ansible.builtin.systemd:
# name: firewalld.service
# state: restarted
# # daemon_reload: yes
# enabled: yes
# when:
# - ansible_facts['services']['firewalld.service'] is defined
# - firewalld['enable'] | bool