diff --git a/changelogs/fragments/firewalld_zone_target.yml b/changelogs/fragments/firewalld_zone_target.yml new file mode 100644 index 0000000000..39f90a49fd --- /dev/null +++ b/changelogs/fragments/firewalld_zone_target.yml @@ -0,0 +1,2 @@ +minor_changes: + - firewalld - new feature, can now set ``target`` for a ``zone`` (https://github.com/ansible-collections/community.general/pull/526). diff --git a/plugins/modules/system/firewalld.py b/plugins/modules/system/firewalld.py index 73666ef773..bb5c0f896c 100644 --- a/plugins/modules/system/firewalld.py +++ b/plugins/modules/system/firewalld.py @@ -83,6 +83,13 @@ options: description: - Whether to run this module even when firewalld is offline. type: bool + target: + description: + - firewalld Zone target + - If state is set to C(absent), this will reset the target to default + choices: [ default, ACCEPT, DROP, REJECT ] + type: str + version_added: 0.2.0 notes: - Not tested on any Debian based system. - Requires the python2 bindings of firewalld, which may not be installed by default. @@ -161,6 +168,12 @@ EXAMPLES = r''' permanent: yes icmp_block: echo-request +- firewalld: + zone: internal + state: present + permanent: yes + target: ACCEPT + - name: Redirect port 443 to 8443 with Rich Rule firewalld: rich_rule: rule family=ipv4 forward-port port=443 protocol=tcp to-port=8443 @@ -568,6 +581,53 @@ class SourceTransaction(FirewallTransaction): self.update_fw_settings(fw_zone, fw_settings) +class ZoneTargetTransaction(FirewallTransaction): + """ + ZoneTargetTransaction + """ + + def __init__(self, module, action_args=None, zone=None, desired_state=None, + permanent=True, immediate=False, enabled_values=None, disabled_values=None): + super(ZoneTargetTransaction, self).__init__( + module, action_args=action_args, desired_state=desired_state, zone=zone, + permanent=permanent, immediate=immediate, + enabled_values=enabled_values or ["present", "enabled"], + disabled_values=disabled_values or ["absent", "disabled"]) + + self.enabled_msg = "Set zone %s target to %s" % \ + (self.zone, action_args[0]) + + self.disabled_msg = "Reset zone %s target to default" % \ + (self.zone) + + self.tx_not_permanent_error_msg = "Zone operations must be permanent. " \ + "Make sure you didn't set the 'permanent' flag to 'false' or the 'immediate' flag to 'true'." + + def get_enabled_immediate(self, target): + self.module.fail_json(msg=self.tx_not_permanent_error_msg) + + def get_enabled_permanent(self, target): + fw_zone, fw_settings = self.get_fw_zone_settings() + current_target = fw_settings.getTarget() + return (current_target == target) + + def set_enabled_immediate(self, target): + self.module.fail_json(msg=self.tx_not_permanent_error_msg) + + def set_enabled_permanent(self, target): + fw_zone, fw_settings = self.get_fw_zone_settings() + fw_settings.setTarget(target) + self.update_fw_settings(fw_zone, fw_settings) + + def set_disabled_immediate(self, target): + self.module.fail_json(msg=self.tx_not_permanent_error_msg) + + def set_disabled_permanent(self, target): + fw_zone, fw_settings = self.get_fw_zone_settings() + fw_settings.setTarget("default") + self.update_fw_settings(fw_zone, fw_settings) + + class ZoneTransaction(FirewallTransaction): """ ZoneTransaction @@ -633,10 +693,12 @@ def main(): interface=dict(type='str'), masquerade=dict(type='str'), offline=dict(type='bool'), + target=dict(type='str', required=False, choices=['default', 'ACCEPT', 'DROP', 'REJECT']), ), supports_check_mode=True, required_by=dict( interface=('zone',), + target=('zone',), source=('permanent',), ), ) @@ -668,6 +730,7 @@ def main(): rich_rule = module.params['rich_rule'] source = module.params['source'] zone = module.params['zone'] + target = module.params['target'] if module.params['port'] is not None: if '/' in module.params['port']: @@ -696,12 +759,14 @@ def main(): modification_count += 1 if source is not None: modification_count += 1 + if target is not None: + modification_count += 1 if modification_count > 1: module.fail_json( msg='can only operate on port, service, rich_rule, masquerade, icmp_block, icmp_block_inversion, interface or source at once' ) - elif modification_count > 0 and desired_state in ['absent', 'present']: + elif (modification_count > 0) and (desired_state in ['absent', 'present']) and (target is None): module.fail_json( msg='absent and present state can only be used in zone level operations' ) @@ -832,6 +897,20 @@ def main(): changed, transaction_msgs = transaction.run() msgs = msgs + transaction_msgs + if target is not None: + + transaction = ZoneTargetTransaction( + module, + action_args=(target,), + zone=zone, + desired_state=desired_state, + permanent=permanent, + immediate=immediate, + ) + + changed, transaction_msgs = transaction.run() + msgs = msgs + transaction_msgs + ''' If there are no changes within the zone we are operating on the zone itself ''' if modification_count == 0 and desired_state in ['absent', 'present']: diff --git a/tests/integration/targets/firewalld/tasks/run_all_tests.yml b/tests/integration/targets/firewalld/tasks/run_all_tests.yml index d463ef206f..79c0ca7180 100644 --- a/tests/integration/targets/firewalld/tasks/run_all_tests.yml +++ b/tests/integration/targets/firewalld/tasks/run_all_tests.yml @@ -33,3 +33,6 @@ # firewalld source operation test cases - import_tasks: source_test_cases.yml + +# firewalld zone target operation test cases +- import_tasks: zone_target_test_cases.yml diff --git a/tests/integration/targets/firewalld/tasks/zone_target_test_cases.yml b/tests/integration/targets/firewalld/tasks/zone_target_test_cases.yml new file mode 100644 index 0000000000..cbcafedb6a --- /dev/null +++ b/tests/integration/targets/firewalld/tasks/zone_target_test_cases.yml @@ -0,0 +1,69 @@ +# Test playbook for the firewalld module - source operations +# (c) 2020, Adam Miller + +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +- name: firewalld dmz zone target DROP + firewalld: + zone: dmz + permanent: True + state: present + target: DROP + register: result + +- name: assert firewalld dmz zone target DROP present worked + assert: + that: + - result is changed + +- name: firewalld dmz zone target DROP rerun (verify not changed) + firewalld: + zone: dmz + permanent: True + state: present + target: DROP + register: result + +- name: assert firewalld dmz zone target DROP present worked (verify not changed) + assert: + that: + - result is not changed + +- name: firewalld dmz zone target DROP absent + firewalld: + zone: dmz + permanent: True + state: absent + target: DROP + register: result + +- name: assert firewalld dmz zone target DROP absent worked + assert: + that: + - result is changed + +- name: firewalld dmz zone target DROP rerun (verify not changed) + firewalld: + zone: dmz + permanent: True + state: absent + target: DROP + register: result + +- name: assert firewalld dmz zone target DROP present worked (verify not changed) + assert: + that: + - result is not changed