diff --git a/changelogs/fragments/320_firewalld_intra_zone_forwarding.yml b/changelogs/fragments/320_firewalld_intra_zone_forwarding.yml new file mode 100644 index 0000000..b707262 --- /dev/null +++ b/changelogs/fragments/320_firewalld_intra_zone_forwarding.yml @@ -0,0 +1,4 @@ +--- +minor_changes: +- firewalld - Added parameter ``forward`` to support enabling/disabling intra-zone + forwarding. diff --git a/docs/ansible.posix.firewalld_module.rst b/docs/ansible.posix.firewalld_module.rst index ea58ff2..aa457bd 100644 --- a/docs/ansible.posix.firewalld_module.rst +++ b/docs/ansible.posix.firewalld_module.rst @@ -118,6 +118,21 @@ Parameters
The masquerade setting you would like to enable/disable to/from zones within firewalld.
+ + +
+ forward + +
+ string +
+ + + + +
Whether intra zone forwarding should be enabled/disabled for a zone in firewalld.
+ +
@@ -456,6 +471,12 @@ Examples permanent: yes zone: dmz + - ansible.posix.firewalld: + forward: yes + state: enabled + permanent: yes + zone: dmz + - ansible.posix.firewalld: zone: custom state: present diff --git a/plugins/module_utils/firewalld.py b/plugins/module_utils/firewalld.py index 6a76c32..3c7c4d6 100644 --- a/plugins/module_utils/firewalld.py +++ b/plugins/module_utils/firewalld.py @@ -127,9 +127,16 @@ class FirewallTransaction(object): def get_fw_zone_settings(self): if self.fw_offline: fw_zone = self.fw.config.get_zone(self.zone) - fw_settings = FirewallClientZoneSettings( - list(self.fw.config.get_zone_config(fw_zone)) - ) + + # If firewalld version is 0.9.0 or higher retrieve the configuration + # using the get_zone_config_dict call, otherwise the returned value + # for the 'forward' field is always zero. + if LooseVersion(FW_VERSION) >= LooseVersion("0.9.0"): + fw_settings = FirewallClientZoneSettings(self.fw.config.get_zone_config_dict(fw_zone)) + else: + fw_settings = FirewallClientZoneSettings( + list(self.fw.config.get_zone_config(fw_zone)) + ) else: fw_zone = self.fw.config().getZoneByName(self.zone) fw_settings = fw_zone.getSettings() diff --git a/plugins/modules/firewalld.py b/plugins/modules/firewalld.py index 52a2a5a..fe8d32f 100644 --- a/plugins/modules/firewalld.py +++ b/plugins/modules/firewalld.py @@ -106,6 +106,11 @@ options: description: - The masquerade setting you would like to enable/disable to/from zones within firewalld. type: str + forward: + description: + - Whether intra zone forwarding should be enabled/disabled for a zone in firewalld. + This parameter supports on python-firewall 0.9.0 or later. + type: bool offline: description: - Whether to run this module even when firewalld is offline. @@ -183,6 +188,12 @@ EXAMPLES = r''' permanent: true zone: dmz +- ansible.posix.firewalld: + forward: yes + state: enabled + permanent: yes + zone: custom + - ansible.posix.firewalld: zone: custom state: present @@ -218,10 +229,12 @@ EXAMPLES = r''' from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.parsing.convert_bool import boolean from ansible_collections.ansible.posix.plugins.module_utils.firewalld import FirewallTransaction, fw_offline +from ansible_collections.ansible.posix.plugins.module_utils.version import StrictVersion try: from firewall.client import Rich_Rule from firewall.client import FirewallClientZoneSettings + from firewall.config import VERSION as FIREWALLD_VERSION except ImportError: # The import errors are handled via FirewallTransaction, don't need to # duplicate that here @@ -386,6 +399,49 @@ class MasqueradeTransaction(FirewallTransaction): self.update_fw_settings(fw_zone, fw_settings) +class ForwardTransaction(FirewallTransaction): + """ + ForwardTransaction + """ + + def __init__(self, module, action_args=None, zone=None, desired_state=None, permanent=False, immediate=False): + super(ForwardTransaction, self).__init__( + module, action_args=action_args, desired_state=desired_state, zone=zone, permanent=permanent, immediate=immediate + ) + + self.enabled_msg = "Enabled intra zone forwarding on zone %s" % self.zone + self.disabled_msg = "Disabled intra zone forwarding on zone %s" % self.zone + + def get_enabled_immediate(self): + if self.fw.queryForward(self.zone) is True: + return True + else: + return False + + def get_enabled_permanent(self): + fw_zone, fw_settings = self.get_fw_zone_settings() + if fw_settings.getForward() is True: + return True + else: + return False + + def set_enabled_immediate(self): + self.fw.addForward(self.zone) + + def set_enabled_permanent(self): + fw_zone, fw_settings = self.get_fw_zone_settings() + fw_settings.setForward(True) + self.update_fw_settings(fw_zone, fw_settings) + + def set_disabled_immediate(self): + self.fw.removeForward(self.zone) + + def set_disabled_permanent(self): + fw_zone, fw_settings = self.get_fw_zone_settings() + fw_settings.setForward(False) + self.update_fw_settings(fw_zone, fw_settings) + + class PortTransaction(FirewallTransaction): """ PortTransaction @@ -759,6 +815,7 @@ def main(): timeout=dict(type='int', default=0), interface=dict(type='str'), masquerade=dict(type='str'), + forward=dict(type='bool'), offline=dict(type='bool'), target=dict(type='str', choices=['default', 'ACCEPT', 'DROP', '%%REJECT%%']), ), @@ -770,7 +827,7 @@ def main(): ), mutually_exclusive=[ ['icmp_block', 'icmp_block_inversion', 'service', 'port', 'port_forward', 'rich_rule', - 'interface', 'masquerade', 'source', 'target'] + 'interface', 'masquerade', 'forward', 'source', 'target'] ], ) @@ -780,6 +837,7 @@ def main(): timeout = module.params['timeout'] interface = module.params['interface'] masquerade = module.params['masquerade'] + forward = module.params['forward'] # Sanity checks FirewallTransaction.sanity_check(module) @@ -830,7 +888,7 @@ def main(): modification = False if any([icmp_block, icmp_block_inversion, service, port, port_forward, rich_rule, - interface, masquerade, source, target]): + interface, masquerade, forward, source, target]): modification = True if modification and desired_state in ['absent', 'present'] and target is None: module.fail_json( @@ -1002,6 +1060,23 @@ def main(): 'The type of the option will be changed from string to boolean in a future release. ' 'To avoid unexpected behavior, please change the value to boolean.' % masquerade) + if forward is not None: + + if StrictVersion(FIREWALLD_VERSION) < StrictVersion('0.9.0'): + module.fail_json(msg='Intra zone forwarding requires firewalld>=0.9.0. Current version is {0}.'.format(FIREWALLD_VERSION)) + + transaction = ForwardTransaction( + module, + action_args=(), + zone=zone, + desired_state=desired_state, + permanent=permanent, + immediate=immediate, + ) + + changed, transaction_msgs = transaction.run() + msgs = msgs + transaction_msgs + if target is not None: transaction = ZoneTargetTransaction( diff --git a/tests/integration/targets/firewalld/tasks/forward_test_cases.yml b/tests/integration/targets/firewalld/tasks/forward_test_cases.yml new file mode 100644 index 0000000..c8b8d62 --- /dev/null +++ b/tests/integration/targets/firewalld/tasks/forward_test_cases.yml @@ -0,0 +1,86 @@ +# Test playbook for the firewalld module - forward operations +# (c) 2017, Adam Miller +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +- name: query firewalld version + package_facts: + +- name: run tests if intra zone forwarding is supported + block: + + # Starting with firewalld 1.0.0 intra-zone forwarding is enabled by default. + # Ensure it is disabled before starting our tests. + - name: ensure forwarding starts disabled + firewalld: + forward: yes + permanent: true + state: disabled + + - name: firewalld forward test permanent enabled + firewalld: + forward: yes + permanent: true + state: enabled + register: result + + - name: assert firewalld forward test permanent enabled worked + assert: + that: + - result is changed + + - name: firewalld forward test permanent enabled rerun (verify not changed) + firewalld: + forward: yes + permanent: true + state: enabled + register: result + + - name: assert firewalld forward test permanent enabled rerun worked (verify not changed) + assert: + that: + - result is not changed + + - name: firewalld forward test permanent disabled + firewalld: + forward: no + permanent: true + state: disabled + register: result + + - name: assert firewalld forward test permanent disabled worked + assert: + that: + - result is changed + + - name: firewalld forward test permanent disabled rerun (verify not changed) + firewalld: + forward: no + permanent: true + state: disabled + register: result + + - name: assert firewalld forward test permanent disabled rerun worked (verify not changed) + assert: + that: + - result is not changed + + when: ansible_facts.packages.firewalld[0].version is version('0.9.0', '>=') + +- name: run tests if intra zone forwarding is not supported + block: + + - name: try to enable intra zone forwarding + firewalld: + forward: yes + permanent: yes + state: enabled + ignore_errors: yes + register: result + + - name: assert unsupported firewalld version + assert: + that: + - result is failed + - "'Intra zone forwarding requires firewalld>=0.9.0. Current version is' in result.msg" + + when: ansible_facts.packages.firewalld[0].version is version('0.9.0', '<') \ No newline at end of file diff --git a/tests/integration/targets/firewalld/tasks/run_all_tests.yml b/tests/integration/targets/firewalld/tasks/run_all_tests.yml index 5027c1c..27bcac8 100644 --- a/tests/integration/targets/firewalld/tasks/run_all_tests.yml +++ b/tests/integration/targets/firewalld/tasks/run_all_tests.yml @@ -24,3 +24,6 @@ # firewalld port forwarding operation test cases - include_tasks: port_forward_test_cases.yml + +# firewalld zone forwarding operation test cases +- include_tasks: forward_test_cases.yml diff --git a/tests/integration/targets/firewalld/tasks/source_test_cases.yml b/tests/integration/targets/firewalld/tasks/source_test_cases.yml index 172a47e..9766075 100644 --- a/tests/integration/targets/firewalld/tasks/source_test_cases.yml +++ b/tests/integration/targets/firewalld/tasks/source_test_cases.yml @@ -82,4 +82,4 @@ assert: that: - result is not changed - - "result.msg == 'parameters are mutually exclusive: icmp_block|icmp_block_inversion|service|port|port_forward|rich_rule|interface|masquerade|source|target'" + - "result.msg == 'parameters are mutually exclusive: icmp_block|icmp_block_inversion|service|port|port_forward|rich_rule|interface|masquerade|forward|source|target'"