From 27434455e5e538d811c0117365a23d01cff45af8 Mon Sep 17 00:00:00 2001 From: Hideki Saito Date: Sun, 27 Jun 2021 12:27:45 +0900 Subject: [PATCH] Add new firewalld_info module to ansible.posix collection * fixes #98 Signed-off-by: Hideki Saito --- .../214-add_firewalld_info_module.yml | 3 + plugins/modules/firewalld_info.py | 391 ++++++++++++++++++ .../targets/firewalld_info/aliases | 5 + .../targets/firewalld_info/tasks/main.yml | 52 +++ .../tasks/run_tests_in_started.yml | 32 ++ .../tasks/run_tests_in_stopped.yml | 40 ++ 6 files changed, 523 insertions(+) create mode 100644 changelogs/fragments/214-add_firewalld_info_module.yml create mode 100644 plugins/modules/firewalld_info.py create mode 100644 tests/integration/targets/firewalld_info/aliases create mode 100644 tests/integration/targets/firewalld_info/tasks/main.yml create mode 100644 tests/integration/targets/firewalld_info/tasks/run_tests_in_started.yml create mode 100644 tests/integration/targets/firewalld_info/tasks/run_tests_in_stopped.yml diff --git a/changelogs/fragments/214-add_firewalld_info_module.yml b/changelogs/fragments/214-add_firewalld_info_module.yml new file mode 100644 index 0000000..a1bfccc --- /dev/null +++ b/changelogs/fragments/214-add_firewalld_info_module.yml @@ -0,0 +1,3 @@ +--- +trivial: + - firewalld_info - add ``firewalld_info`` module to ``ansible.posix`` collection (https://github.com/ansible-collections/ansible.posix/issues/98) diff --git a/plugins/modules/firewalld_info.py b/plugins/modules/firewalld_info.py new file mode 100644 index 0000000..68caa1f --- /dev/null +++ b/plugins/modules/firewalld_info.py @@ -0,0 +1,391 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2021, Hideki Saito +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = r''' +--- +module: firewalld_info +short_description: Gather information about firewalld +description: + - This module gathers information about firewalld rules. +options: + active_zones: + description: Gather information about active zones. + type: bool + default: no + zones: + description: + - Gather information about specific zones. + - If only works if C(active_zones) is set to C(false). + required: false + type: list + elements: str +requirements: + - firewalld >= 0.2.11 + - python-firewall + - python-dbus +author: + - Hideki Saito (@saito-hideki) +''' + +EXAMPLES = r''' +- name: Gather information about active zones + ansible.posix.firewalld_info: + active_zones: yes + +- name: Gather information about specific zones + ansible.posix.firewalld_info: + zones: + - public + - external + - internal +''' + +RETURN = r''' +active_zones: + description: + - Gather active zones only if turn it C(true). + returned: success + type: bool + sample: false +collected_zones: + description: + - A list of collected zones. + returned: success + type: list + sample: [external, internal] +undefined_zones: + description: + - A list of undefined zones in C(zones) option. + - C(undefined_zones) will be ignored for gathering process. + returned: success + type: list + sample: [foo, bar] +firewalld_info: + description: + - Returns various information about firewalld configuration. + returned: success + type: complex + contains: + version: + description: + - The version information of firewalld. + returned: success + type: str + sample: 0.8.2 + default_zones: + description: + - The zone name of default zone. + returned: success + type: str + sample: public + zones: + description: + - A dict of zones to gather information. + returned: success + type: complex + contains: + zone: + description: + - The zone name registered in firewalld. + returned: success + type: complex + sample: external + contains: + target: + description: + - A list of services in the zone. + returned: success + type: str + sample: ACCEPT + icmp_block_inversion: + description: + - The ICMP block inversion to block + all ICMP requests. + returned: success + type: bool + sample: false + interfaces: + description: + - A list of network interfaces. + returned: success + type: list + sample: + - 'eth0' + - 'eth1' + sources: + description: + - A list of source network address. + returned: success + type: list + sample: + - '172.16.30.0/24' + - '172.16.31.0/24' + services: + description: + - A list of network services. + returned: success + type: list + sample: + - 'dhcp' + - 'dns' + - 'ssh' + ports: + description: + - A list of network port with protocol. + returned: success + type: list + sample: + - - "22" + - "tcp" + - - "80" + - "tcp" + protocols: + description: + - A list of network protocol. + returned: success + type: list + sample: + - "icmp" + - "ipv6-icmp" + forward: + description: + - The network interface forwarding. + - This parameter supports on python-firewall + 0.9.0(or later) and is not collected in earlier + versions. + returned: success + type: bool + sample: false + masquerade: + description: + - The network interface masquerading. + returned: success + type: bool + sample: false + forward_ports: + description: + - A list of forwarding port pair with protocol. + returned: success + type: list + sample: + - "icmp" + - "ipv6-icmp" + source_ports: + description: + - A list of network source port with protocol. + returned: success + type: list + sample: + - - "30000" + - "tcp" + - - "30001" + - "tcp" + icmp_blocks: + description: + - A list of blocking icmp protocol. + returned: success + type: list + sample: + - "echo-request" + rich_rules: + description: + - A list of rich language rule. + returned: success + type: list + sample: + - "rule protocol value=\"icmp\" reject" + - "rule priority=\"32767\" reject" +''' + +from ansible.module_utils.basic import AnsibleModule, missing_required_lib +from ansible.module_utils._text import to_native +from distutils.version import StrictVersion + + +try: + import dbus + HAS_DBUS = True +except ImportError: + HAS_DBUS = False + +try: + import firewall.client as fw_client + import firewall.config as fw_config + HAS_FIREWALLD = True +except ImportError: + HAS_FIREWALLD = False + + +def get_version(): + return fw_config.VERSION + + +def get_active_zones(client): + return client.getActiveZones().keys() + + +def get_all_zones(client): + return client.getZones() + + +def get_default_zone(client): + return client.getDefaultZone() + + +def get_zone_settings(client, zone): + return client.getZoneSettings(zone) + + +def get_zone_target(zone_settings): + return zone_settings.getTarget() + + +def get_zone_icmp_block_inversion(zone_settings): + return zone_settings.getIcmpBlockInversion() + + +def get_zone_interfaces(zone_settings): + return zone_settings.getInterfaces() + + +def get_zone_sources(zone_settings): + return zone_settings.getSources() + + +def get_zone_services(zone_settings): + return zone_settings.getServices() + + +def get_zone_ports(zone_settings): + return zone_settings.getPorts() + + +def get_zone_protocols(zone_settings): + return zone_settings.getProtocols() + + +# This function supports python-firewall 0.9.0(or later). +def get_zone_forward(zone_settings): + return zone_settings.getForward() + + +def get_zone_masquerade(zone_settings): + return zone_settings.getMasquerade() + + +def get_zone_forward_ports(zone_settings): + return zone_settings.getForwardPorts() + + +def get_zone_source_ports(zone_settings): + return zone_settings.getSourcePorts() + + +def get_zone_icmp_blocks(zone_settings): + return zone_settings.getIcmpBlocks() + + +def get_zone_rich_rules(zone_settings): + return zone_settings.getRichRules() + + +def main(): + module_args = dict( + active_zones=dict(required=False, type='bool', default=False), + zones=dict(required=False, type='list', elements='str'), + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + firewalld_info = dict() + result = dict( + changed=False, + active_zones=module.params['active_zones'], + collected_zones=list(), + undefined_zones=list(), + warnings=list(), + ) + + # Exit with failure message if requirements modules are not installed. + if not HAS_DBUS: + module.fail_json(msg=missing_required_lib('python-dbus')) + if not HAS_FIREWALLD: + module.fail_json(msg=missing_required_lib('python-firewall')) + + # If you want to show warning messages in the task running process, + # you can append the message to the 'warn' list. + warn = list() + + try: + client = fw_client.FirewallClient() + + # Gather general information of firewalld. + firewalld_info['version'] = get_version() + firewalld_info['default_zone'] = get_default_zone(client) + + # Gather information for zones. + zones_info = dict() + collect_zones = list() + ignore_zones = list() + if module.params['active_zones']: + collect_zones = get_active_zones(client) + elif module.params['zones']: + all_zones = get_all_zones(client) + specified_zones = module.params['zones'] + collect_zones = list(set(specified_zones) & set(all_zones)) + ignore_zones = list(set(specified_zones) - set(collect_zones)) + warn.append( + 'Please note: zone:(%s) have been ignored in the gathering process.' % ','.join(ignore_zones)) + else: + collect_zones = get_all_zones(client) + + for zone in collect_zones: + # Gather settings for each zone based on the output of + # 'firewall-cmd --info-zone=' command. + zone_info = dict() + zone_settings = get_zone_settings(client, zone) + zone_info['target'] = get_zone_target(zone_settings) + zone_info['icmp_block_inversion'] = get_zone_icmp_block_inversion(zone_settings) + zone_info['interfaces'] = get_zone_interfaces(zone_settings) + zone_info['sources'] = get_zone_sources(zone_settings) + zone_info['services'] = get_zone_services(zone_settings) + zone_info['ports'] = get_zone_ports(zone_settings) + zone_info['protocols'] = get_zone_protocols(zone_settings) + zone_info['masquerade'] = get_zone_masquerade(zone_settings) + zone_info['forward_ports'] = get_zone_forward_ports(zone_settings) + zone_info['source_ports'] = get_zone_source_ports(zone_settings) + zone_info['icmp_blocks'] = get_zone_icmp_blocks(zone_settings) + zone_info['rich_rules'] = get_zone_rich_rules(zone_settings) + + # The 'forward' parameter supports on python-firewall 0.9.0(or later). + if StrictVersion(firewalld_info['version']) >= StrictVersion('0.9.0'): + zone_info['forward'] = get_zone_forward(zone_settings) + + zones_info[zone] = zone_info + firewalld_info['zones'] = zones_info + except AttributeError as e: + module.fail_json(msg=('firewalld probably not be running, Or the following method ' + 'is not supported with your python-firewall version. (Error: %s)') % to_native(e)) + except dbus.exceptions.DBusException as e: + module.fail_json(msg=('Unable to gather firewalld settings.' + ' You may need to run as the root user or' + ' use become. (Error: %s)' % to_native(e))) + + result['collected_zones'] = collect_zones + result['undefined_zones'] = ignore_zones + result['firewalld_info'] = firewalld_info + result['warnings'] = warn + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/tests/integration/targets/firewalld_info/aliases b/tests/integration/targets/firewalld_info/aliases new file mode 100644 index 0000000..dba2910 --- /dev/null +++ b/tests/integration/targets/firewalld_info/aliases @@ -0,0 +1,5 @@ +destructive +shippable/posix/group3 +skip/aix +skip/freebsd +skip/osx diff --git a/tests/integration/targets/firewalld_info/tasks/main.yml b/tests/integration/targets/firewalld_info/tasks/main.yml new file mode 100644 index 0000000..bce6b54 --- /dev/null +++ b/tests/integration/targets/firewalld_info/tasks/main.yml @@ -0,0 +1,52 @@ +# Test playbook for the firewalld_info module +# (c) 2021, Hideki Saito +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# This test is based on the integration test playbook for firewalld module. +- name: Run firewalld tests + block: + - name: Ensure firewalld is installed + package: + name: firewalld + state: present + # This doesn't work for CentOS 6 because firewalld doesn't exist in CentOS6 + + - name: Check to make sure the firewalld python module is available. + shell: "{{ansible_python.executable}} -c 'import firewall'" + register: check_output_firewall + ignore_errors: true + + - name: Check to make sure the dbus python module is available. + shell: "{{ansible_python.executable}} -c 'import dbus'" + register: check_output_dbus + ignore_errors: true + + - name: Test Online Operations + block: + - name: start firewalld + service: + name: firewalld + state: started + + - import_tasks: run_tests_in_started.yml + when: + - check_output_firewall.rc == 0 + - check_output_dbus.rc == 0 + + - name: Test Offline Operations + block: + - name: stop firewalld + service: + name: firewalld + state: stopped + + - import_tasks: run_tests_in_stopped.yml + when: + - check_output_firewall.rc == 0 + - check_output_dbus.rc == 0 + + when: + - ansible_facts.os_family == "RedHat" and ansible_facts.distribution_major_version is version('7', '>=') + - not (ansible_distribution == "Ubuntu" and ansible_distribution_version is version('14.04', '==')) + # Firewalld package on OpenSUSE (15+) require Python 3, so we skip on OpenSUSE running py2 on these newer distros + - not (ansible_os_family == "Suse" and ansible_distribution_major_version|int != 42 and ansible_python.version.major != 3) diff --git a/tests/integration/targets/firewalld_info/tasks/run_tests_in_started.yml b/tests/integration/targets/firewalld_info/tasks/run_tests_in_started.yml new file mode 100644 index 0000000..5024884 --- /dev/null +++ b/tests/integration/targets/firewalld_info/tasks/run_tests_in_started.yml @@ -0,0 +1,32 @@ +# Test playbook for the firewalld_info module +# (c) 2021, Hideki Saito +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Ensure firewalld_info without options + firewalld_info: + register: result + +- name: Assert collected_zones and undefined_zones + assert: + that: + - 'result.collected_zones and not result.undefined_zones' + +- name: Ensure firewalld_info with active_zones + firewalld_info: + active_zones: yes + register: result + +- name: Assert turn active_zones true + assert: + that: + +- name: Ensure firewalld_zones with zone list + firewalld_info: + zones: + - public + - invalid_zone + register: result + +- name: Assert specified zones + assert: + that: diff --git a/tests/integration/targets/firewalld_info/tasks/run_tests_in_stopped.yml b/tests/integration/targets/firewalld_info/tasks/run_tests_in_stopped.yml new file mode 100644 index 0000000..2ad4c0c --- /dev/null +++ b/tests/integration/targets/firewalld_info/tasks/run_tests_in_stopped.yml @@ -0,0 +1,40 @@ +# Test playbook for the firewalld_info module +# (c) 2021, Hideki Saito +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: Ensure firewalld_info without options + firewalld_info: + register: result + ignore_errors: yes + +- name: Assert firewalld_info fails if firewalld is not running. + assert: + that: + - result.failed + - "'firewalld probably not be running,' in result.msg" + +- name: Ensure firewalld_info with active_zones + firewalld_info: + active_zones: yes + register: result + ignore_errors: yes + +- name: Assert firewalld_info with active_zones fails if firewalld is not running. + assert: + that: + - result.failed + - "'firewalld probably not be running,' in result.msg" + +- name: Ensure firewalld_zones with zone list + firewalld_info: + zones: + - public + - invalid_zone + register: result + ignore_errors: yes + +- name: Assert firewalld_info with zones list fails if firewalld is not running. + assert: + that: + - result.failed + - "'firewalld probably not be running,' in result.msg"