493 lines
15 KiB
YAML
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 |