From c4ff0545f11d3fb3ed619ce794f1e06e946bb253 Mon Sep 17 00:00:00 2001 From: Zhanibek Adilbekov Date: Fri, 1 Aug 2025 14:07:07 +0500 Subject: [PATCH] Firewalld: Add functionality to set source_port --- plugins/modules/firewalld.py | 87 +++++++++++++- .../targets/firewalld/tasks/run_all_tests.yml | 4 + .../tasks/source_port_test_cases.yml | 107 ++++++++++++++++++ .../firewalld/tasks/source_test_cases.yml | 2 +- 4 files changed, 196 insertions(+), 4 deletions(-) create mode 100644 tests/integration/targets/firewalld/tasks/source_port_test_cases.yml diff --git a/plugins/modules/firewalld.py b/plugins/modules/firewalld.py index 6dd26aa8da..765a7232b5 100644 --- a/plugins/modules/firewalld.py +++ b/plugins/modules/firewalld.py @@ -28,6 +28,11 @@ - Name of a port or port range to add/remove to/from firewalld. - Must be in the form PORT/PROTOCOL or PORT-PORT/PROTOCOL for port ranges. type: str + source_port: + description: + - Name of a source port or port range to add/remove to/from firewalld. + - Must be in the form PORT/PROTOCOL or PORT-PORT/PROTOCOL for port ranges. + type: str port_forward: description: - Port and protocol to forward using firewalld. @@ -185,6 +190,13 @@ permanent: true state: enabled +- name: Permit traffic in home zone from port 20561/udp + ansible.posix.firewalld: + source_port: 20561/udp + zone: home + permanent: true + state: enabled + - name: Permit traffic in dmz zone on http service ansible.posix.firewalld: zone: dmz @@ -552,6 +564,43 @@ def set_disabled_permanent(self, port, protocol, timeout): self.update_fw_settings(fw_zone, fw_settings) +class SourcePortTransaction(FirewallTransaction): + """ + SourcePortTransaction + """ + + def __init__(self, module, action_args=None, zone=None, desired_state=None, permanent=False, immediate=False): + super(SourcePortTransaction, self).__init__( + module, action_args=action_args, desired_state=desired_state, zone=zone, permanent=permanent, immediate=immediate + ) + + def get_enabled_immediate(self, port, protocol, timeout): + if self.fw_offline: + dummy, fw_settings = self.get_fw_zone_settings() + return fw_settings.querySourcePort(port=port, protocol=protocol) + return self.fw.querySourcePort(zone=self.zone, port=port, protocol=protocol) + + def get_enabled_permanent(self, port, protocol, timeout): + dummy, fw_settings = self.get_fw_zone_settings() + return fw_settings.querySourcePort(port=port, protocol=protocol) + + def set_enabled_immediate(self, port, protocol, timeout): + self.fw.addSourcePort(zone=self.zone, port=port, protocol=protocol, timeout=timeout) + + def set_enabled_permanent(self, port, protocol, timeout): + fw_zone, fw_settings = self.get_fw_zone_settings() + fw_settings.addSourcePort(port=port, protocol=protocol) + self.update_fw_settings(fw_zone, fw_settings) + + def set_disabled_immediate(self, port, protocol, timeout): + self.fw.removeSourcePort(zone=self.zone, port=port, protocol=protocol) + + def set_disabled_permanent(self, port, protocol, timeout): + fw_zone, fw_settings = self.get_fw_zone_settings() + fw_settings.removeSourcePort(port=port, protocol=protocol) + self.update_fw_settings(fw_zone, fw_settings) + + class InterfaceTransaction(FirewallTransaction): """ InterfaceTransaction @@ -879,6 +928,7 @@ def main(): service=dict(type='str'), protocol=dict(type='str'), port=dict(type='str'), + source_port=dict(type='str'), port_forward=dict(type='list', elements='dict'), rich_rule=dict(type='str'), zone=dict(type='str'), @@ -900,8 +950,8 @@ def main(): source=('permanent',), ), mutually_exclusive=[ - ['icmp_block', 'icmp_block_inversion', 'service', 'protocol', 'port', 'port_forward', 'rich_rule', - 'interface', 'forward', 'masquerade', 'source', 'target'] + ['icmp_block', 'icmp_block_inversion', 'service', 'protocol', 'port', 'source_port', 'port_forward', + 'rich_rule', 'interface', 'forward', 'masquerade', 'source', 'target'] ], ) @@ -957,6 +1007,17 @@ def main(): else: port_protocol = None + source_port = None + if module.params['source_port'] is not None: + if '/' in module.params['source_port']: + source_port, source_port_protocol = module.params['source_port'].strip().split('/') + else: + source_port_protocol = None + if not source_port_protocol: + module.fail_json(msg='improper source_port format (missing protocol?)') + else: + source_port_protocol = None + port_forward_toaddr = '' port_forward = None if module.params['port_forward'] is not None: @@ -973,7 +1034,7 @@ def main(): port_forward_toaddr = port_forward['toaddr'] modification = False - if any([icmp_block, icmp_block_inversion, service, protocol, port, port_forward, rich_rule, + if any([icmp_block, icmp_block_inversion, service, protocol, port, source_port, port_forward, rich_rule, interface, forward, masquerade, source, target]): modification = True if modification and desired_state in ['absent', 'present'] and target is None: @@ -1079,6 +1140,26 @@ def main(): ) ) + if source_port is not None: + + transaction = SourcePortTransaction( + module, + action_args=(source_port, source_port_protocol, timeout), + zone=zone, + desired_state=desired_state, + permanent=permanent, + immediate=immediate, + ) + + changed, transaction_msgs = transaction.run() + msgs = msgs + transaction_msgs + if changed is True: + msgs.append( + "Changed source_port %s to %s" % ( + "%s/%s" % (source_port, source_port_protocol), desired_state + ) + ) + if port_forward is not None: transaction = ForwardPortTransaction( module, diff --git a/tests/integration/targets/firewalld/tasks/run_all_tests.yml b/tests/integration/targets/firewalld/tasks/run_all_tests.yml index fa8c34456e..64ac1f60cc 100644 --- a/tests/integration/targets/firewalld/tasks/run_all_tests.yml +++ b/tests/integration/targets/firewalld/tasks/run_all_tests.yml @@ -21,6 +21,10 @@ - name: Include port test cases for firewalld module ansible.builtin.include_tasks: port_test_cases.yml +# firewalld source_port operation test cases +- name: Include source_port test cases for firewalld module + ansible.builtin.include_tasks: source_port_test_cases.yml + # firewalld source operation test cases - name: Include source test cases for firewalld module ansible.builtin.include_tasks: source_test_cases.yml diff --git a/tests/integration/targets/firewalld/tasks/source_port_test_cases.yml b/tests/integration/targets/firewalld/tasks/source_port_test_cases.yml new file mode 100644 index 0000000000..a1b499e41e --- /dev/null +++ b/tests/integration/targets/firewalld/tasks/source_port_test_cases.yml @@ -0,0 +1,107 @@ +--- +# Test playbook for the firewalld module - source_port operations + +- name: Firewalld source_port range test permanent enabled + ansible.posix.firewalld: + source_port: 5500-6850/tcp + permanent: true + state: enabled + register: result + +- name: Assert firewalld source_port range test permanent enabled worked + ansible.builtin.assert: + that: + - result is changed + +- name: Firewalld source_port range test permanent enabled rerun (verify not changed) + ansible.posix.firewalld: + source_port: 5500-6850/tcp + permanent: true + state: enabled + register: result + +- name: Assert firewalld source_port range test permanent enabled rerun worked (verify not changed) + ansible.builtin.assert: + that: + - result is not changed + +- name: Firewalld source_port test permanent enabled + ansible.posix.firewalld: + source_port: 6900/tcp + permanent: true + state: enabled + register: result + +- name: Assert firewalld source_port test permanent enabled worked + ansible.builtin.assert: + that: + - result is changed + +- name: Firewalld source_port test permanent enabled + ansible.posix.firewalld: + source_port: 6900/tcp + permanent: true + state: enabled + register: result + +- name: Assert firewalld source_port test permanent enabled worked + ansible.builtin.assert: + that: + - result is not changed + +- name: Firewalld source_port test disabled + ansible.posix.firewalld: + source_port: "{{ item }}" + permanent: true + state: disabled + loop: + - 6900/tcp + - 5500-6850/tcp + +- name: Firewalld source_port test permanent enabled + ansible.posix.firewalld: + source_port: 8081/tcp + permanent: true + state: enabled + register: result + +- name: Assert firewalld source_port test permanent enabled worked + ansible.builtin.assert: + that: + - result is changed + +- name: Firewalld source_port test permanent enabled rerun (verify not changed) + ansible.posix.firewalld: + source_port: 8081/tcp + permanent: true + state: enabled + register: result + +- name: Assert firewalld source_port test permanent enabled rerun worked (verify not changed) + ansible.builtin.assert: + that: + - result is not changed + +- name: Firewalld source_port test permanent disabled + ansible.posix.firewalld: + source_port: 8081/tcp + permanent: true + state: disabled + register: result + +- name: Assert firewalld source_port test permanent disabled worked + ansible.builtin.assert: + that: + - result is changed + +- name: Firewalld source_port test permanent disabled rerun (verify not changed) + ansible.posix.firewalld: + source_port: 8081/tcp + permanent: true + state: disabled + register: result + +- name: Assert firewalld source_port test permanent disabled rerun worked (verify not changed) + ansible.builtin.assert: + that: + - result is not changed diff --git a/tests/integration/targets/firewalld/tasks/source_test_cases.yml b/tests/integration/targets/firewalld/tasks/source_test_cases.yml index 8b76521d94..66d2f45515 100644 --- a/tests/integration/targets/firewalld/tasks/source_test_cases.yml +++ b/tests/integration/targets/firewalld/tasks/source_test_cases.yml @@ -85,4 +85,4 @@ - result is not changed - > result.msg == 'parameters are mutually exclusive: - icmp_block|icmp_block_inversion|service|protocol|port|port_forward|rich_rule|interface|forward|masquerade|source|target' + icmp_block|icmp_block_inversion|service|protocol|port|source_port|port_forward|rich_rule|interface|forward|masquerade|source|target'