From f228096f734d1194e7b71daa5f568860cdb8a8d0 Mon Sep 17 00:00:00 2001 From: Rene Moser Date: Wed, 11 Aug 2021 18:30:23 +0200 Subject: [PATCH 1/2] cs_firewall: add dest cidrs --- .../84-cs_instance_extend_ip_to_networks.yml | 2 ++ plugins/modules/cs_firewall.py | 26 +++++++++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 changelogs/fragments/84-cs_instance_extend_ip_to_networks.yml diff --git a/changelogs/fragments/84-cs_instance_extend_ip_to_networks.yml b/changelogs/fragments/84-cs_instance_extend_ip_to_networks.yml new file mode 100644 index 0000000..9f453ac --- /dev/null +++ b/changelogs/fragments/84-cs_instance_extend_ip_to_networks.yml @@ -0,0 +1,2 @@ +minor_changes: +- cs_firewall - implemented support for ``dest_cidrs`` (https://github.com/ngine-io/ansible-collection-cloudstack/issues/76). diff --git a/plugins/modules/cs_firewall.py b/plugins/modules/cs_firewall.py index fda792a..dd16176 100644 --- a/plugins/modules/cs_firewall.py +++ b/plugins/modules/cs_firewall.py @@ -49,11 +49,17 @@ cidrs: description: - List of CIDRs (full notation) to be used for firewall rule. - - Since version 2.5, it is a list of CIDR. elements: str type: list default: 0.0.0.0/0 aliases: [ cidr ] + dest_cidrs: + description: + - List of destination CIDRs (full notation) to forward traffic to if I(type=egress). + elements: str + type: list + aliases: [ dest_cidr ] + version_added: 2.2.0 start_port: description: - Start port for this rule. @@ -178,6 +184,12 @@ returned: success type: list sample: [ '0.0.0.0/0' ] +dest_cidrs: + description: CIDR list of the rule to forward traffic to. + returned: success + type: list + sample: [ '0.0.0.0/0' ] + version_added: 2.2.0 protocol: description: Protocol of the rule. returned: success @@ -224,6 +236,7 @@ def __init__(self, module): super(AnsibleCloudStackFirewall, self).__init__(module) self.returns = { 'cidrlist': 'cidr', + 'destcidrlist': 'dest_cidrs', 'startport': 'start_port', 'endport': 'end_port', 'protocol': 'protocol', @@ -237,6 +250,7 @@ def __init__(self, module): def get_firewall_rule(self): if not self.firewall_rule: cidrs = self.module.params.get('cidrs') + dest_cidrs = self.module.params.get('destcidrs') protocol = self.module.params.get('protocol') start_port = self.module.params.get('start_port') end_port = self.get_or_fallback('end_port', 'start_port') @@ -280,7 +294,7 @@ def get_firewall_rule(self): if firewall_rules: for rule in firewall_rules: - type_match = self._type_cidrs_match(rule, cidrs, egress_cidrs) + type_match = self._type_cidrs_match(rule, cidrs, egress_cidrs) and self._type_dest_cidrs_match(rule, dest_cidrs) protocol_match = ( self._tcp_udp_match(rule, protocol, start_port, end_port) or @@ -322,6 +336,12 @@ def _type_cidrs_match(self, rule, cidrs, egress_cidrs): else: return ",".join(cidrs) == rule['cidrlist'] + def _type_dest_cidrs_match(self, rule, dest_cidrs): + if dest_cidrs is not None and 'destcidrlist' in rule: + return ",".join(dest_cidrs) == rule['destcidrlist'] + else: + return True + def create_firewall_rule(self): firewall_rule = self.get_firewall_rule() if not firewall_rule: @@ -329,6 +349,7 @@ def create_firewall_rule(self): args = { 'cidrlist': self.module.params.get('cidrs'), + 'destcidrlist': self.module.params.get('dest_cidrs'), 'protocol': self.module.params.get('protocol'), 'startport': self.module.params.get('start_port'), 'endport': self.get_or_fallback('end_port', 'start_port'), @@ -393,6 +414,7 @@ def main(): ip_address=dict(), network=dict(), cidrs=dict(type='list', elements='str', default='0.0.0.0/0', aliases=['cidr']), + dest_cidrs=dict(type='list', elements='str', aliases=['dest_cidr']), protocol=dict(choices=['tcp', 'udp', 'icmp', 'all'], default='tcp'), type=dict(choices=['ingress', 'egress'], default='ingress'), icmp_type=dict(type='int'), From 785c634134edbc0a152d03a1f5d9faafa2ce643b Mon Sep 17 00:00:00 2001 From: Rene Moser Date: Fri, 23 Aug 2024 13:32:28 +0200 Subject: [PATCH 2/2] reformat in black --- plugins/modules/cs_firewall.py | 248 +++++++++++++++------------------ 1 file changed, 116 insertions(+), 132 deletions(-) diff --git a/plugins/modules/cs_firewall.py b/plugins/modules/cs_firewall.py index dd16176..e63dfe8 100644 --- a/plugins/modules/cs_firewall.py +++ b/plugins/modules/cs_firewall.py @@ -5,10 +5,11 @@ # 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 = ''' +DOCUMENTATION = """ --- module: cs_firewall short_description: Manages firewall rules on Apache CloudStack based clouds. @@ -112,9 +113,9 @@ aliases: [ tag ] extends_documentation_fragment: - ngine_io.cloudstack.cloudstack -''' +""" -EXAMPLES = ''' +EXAMPLES = """ - name: Allow inbound port 80/tcp from 1.2.3.4 to 4.3.2.1 ngine_io.cloudstack.cs_firewall: ip_address: 4.3.2.1 @@ -155,9 +156,9 @@ type: egress port: 80 cidr: 10.101.1.20 -''' +""" -RETURN = ''' +RETURN = """ --- id: description: UUID of the rule. @@ -220,14 +221,11 @@ returned: success type: str sample: my_network -''' +""" from ansible.module_utils.basic import AnsibleModule -from ..module_utils.cloudstack import ( - AnsibleCloudStack, - cs_argument_spec, - cs_required_together -) + +from ..module_utils.cloudstack import AnsibleCloudStack, cs_argument_spec, cs_required_together class AnsibleCloudStackFirewall(AnsibleCloudStack): @@ -235,71 +233,71 @@ class AnsibleCloudStackFirewall(AnsibleCloudStack): def __init__(self, module): super(AnsibleCloudStackFirewall, self).__init__(module) self.returns = { - 'cidrlist': 'cidr', - 'destcidrlist': 'dest_cidrs', - 'startport': 'start_port', - 'endport': 'end_port', - 'protocol': 'protocol', - 'ipaddress': 'ip_address', - 'icmpcode': 'icmp_code', - 'icmptype': 'icmp_type', + "cidrlist": "cidr", + "destcidrlist": "dest_cidrs", + "startport": "start_port", + "endport": "end_port", + "protocol": "protocol", + "ipaddress": "ip_address", + "icmpcode": "icmp_code", + "icmptype": "icmp_type", } self.firewall_rule = None self.network = None def get_firewall_rule(self): if not self.firewall_rule: - cidrs = self.module.params.get('cidrs') - dest_cidrs = self.module.params.get('destcidrs') - protocol = self.module.params.get('protocol') - start_port = self.module.params.get('start_port') - end_port = self.get_or_fallback('end_port', 'start_port') - icmp_code = self.module.params.get('icmp_code') - icmp_type = self.module.params.get('icmp_type') - fw_type = self.module.params.get('type') - - if protocol in ['tcp', 'udp'] and not (start_port and end_port): + cidrs = self.module.params.get("cidrs") + dest_cidrs = self.module.params.get("destcidrs") + protocol = self.module.params.get("protocol") + start_port = self.module.params.get("start_port") + end_port = self.get_or_fallback("end_port", "start_port") + icmp_code = self.module.params.get("icmp_code") + icmp_type = self.module.params.get("icmp_type") + fw_type = self.module.params.get("type") + + if protocol in ["tcp", "udp"] and not (start_port and end_port): self.module.fail_json(msg="missing required argument for protocol '%s': start_port or end_port" % protocol) - if protocol == 'icmp' and not icmp_type: + if protocol == "icmp" and not icmp_type: self.module.fail_json(msg="missing required argument for protocol 'icmp': icmp_type") - if protocol == 'all' and fw_type != 'egress': + if protocol == "all" and fw_type != "egress": self.module.fail_json(msg="protocol 'all' could only be used for type 'egress'") args = { - 'account': self.get_account('name'), - 'domainid': self.get_domain('id'), - 'projectid': self.get_project('id'), - 'fetch_list': True, + "account": self.get_account("name"), + "domainid": self.get_domain("id"), + "projectid": self.get_project("id"), + "fetch_list": True, } - if fw_type == 'egress': - args['networkid'] = self.get_network(key='id') - if not args['networkid']: + if fw_type == "egress": + args["networkid"] = self.get_network(key="id") + if not args["networkid"]: self.module.fail_json(msg="missing required argument for type egress: network") # CloudStack 4.11 use the network cidr for 0.0.0.0/0 in egress # That is why we need to replace it. - network_cidr = self.get_network(key='cidr') - egress_cidrs = [network_cidr if cidr == '0.0.0.0/0' else cidr for cidr in cidrs] + network_cidr = self.get_network(key="cidr") + egress_cidrs = [network_cidr if cidr == "0.0.0.0/0" else cidr for cidr in cidrs] - firewall_rules = self.query_api('listEgressFirewallRules', **args) + firewall_rules = self.query_api("listEgressFirewallRules", **args) else: - args['ipaddressid'] = self.get_ip_address('id') - if not args['ipaddressid']: + args["ipaddressid"] = self.get_ip_address("id") + if not args["ipaddressid"]: self.module.fail_json(msg="missing required argument for type ingress: ip_address") egress_cidrs = None - firewall_rules = self.query_api('listFirewallRules', **args) + firewall_rules = self.query_api("listFirewallRules", **args) if firewall_rules: for rule in firewall_rules: type_match = self._type_cidrs_match(rule, cidrs, egress_cidrs) and self._type_dest_cidrs_match(rule, dest_cidrs) protocol_match = ( - self._tcp_udp_match(rule, protocol, start_port, end_port) or - self._icmp_match(rule, protocol, icmp_code, icmp_type) or - self._egress_all_match(rule, protocol, fw_type) + self._tcp_udp_match(rule, protocol, start_port, end_port) + or self._icmp_match(rule, protocol, icmp_code, icmp_type) + or self._egress_all_match(rule, protocol, fw_type) ) if type_match and protocol_match: @@ -308,70 +306,56 @@ def get_firewall_rule(self): return self.firewall_rule def _tcp_udp_match(self, rule, protocol, start_port, end_port): - return ( - protocol in ['tcp', 'udp'] and - protocol == rule['protocol'] and - start_port == int(rule['startport']) and - end_port == int(rule['endport']) - ) + return protocol in ["tcp", "udp"] and protocol == rule["protocol"] and start_port == int(rule["startport"]) and end_port == int(rule["endport"]) def _egress_all_match(self, rule, protocol, fw_type): - return ( - protocol in ['all'] and - protocol == rule['protocol'] and - fw_type == 'egress' - ) + return protocol in ["all"] and protocol == rule["protocol"] and fw_type == "egress" def _icmp_match(self, rule, protocol, icmp_code, icmp_type): - return ( - protocol == 'icmp' and - protocol == rule['protocol'] and - icmp_code == rule['icmpcode'] and - icmp_type == rule['icmptype'] - ) + return protocol == "icmp" and protocol == rule["protocol"] and icmp_code == rule["icmpcode"] and icmp_type == rule["icmptype"] def _type_cidrs_match(self, rule, cidrs, egress_cidrs): if egress_cidrs is not None: - return ",".join(egress_cidrs) == rule['cidrlist'] or ",".join(cidrs) == rule['cidrlist'] + return ",".join(egress_cidrs) == rule["cidrlist"] or ",".join(cidrs) == rule["cidrlist"] else: - return ",".join(cidrs) == rule['cidrlist'] + return ",".join(cidrs) == rule["cidrlist"] def _type_dest_cidrs_match(self, rule, dest_cidrs): - if dest_cidrs is not None and 'destcidrlist' in rule: - return ",".join(dest_cidrs) == rule['destcidrlist'] + if dest_cidrs is not None and "destcidrlist" in rule: + return set(dest_cidrs) == set(rule["destcidrlist"].split(",")) else: return True def create_firewall_rule(self): firewall_rule = self.get_firewall_rule() if not firewall_rule: - self.result['changed'] = True + self.result["changed"] = True args = { - 'cidrlist': self.module.params.get('cidrs'), - 'destcidrlist': self.module.params.get('dest_cidrs'), - 'protocol': self.module.params.get('protocol'), - 'startport': self.module.params.get('start_port'), - 'endport': self.get_or_fallback('end_port', 'start_port'), - 'icmptype': self.module.params.get('icmp_type'), - 'icmpcode': self.module.params.get('icmp_code') + "cidrlist": self.module.params.get("cidrs"), + "destcidrlist": self.module.params.get("dest_cidrs"), + "protocol": self.module.params.get("protocol"), + "startport": self.module.params.get("start_port"), + "endport": self.get_or_fallback("end_port", "start_port"), + "icmptype": self.module.params.get("icmp_type"), + "icmpcode": self.module.params.get("icmp_code"), } - fw_type = self.module.params.get('type') + fw_type = self.module.params.get("type") if not self.module.check_mode: - if fw_type == 'egress': - args['networkid'] = self.get_network(key='id') - res = self.query_api('createEgressFirewallRule', **args) + if fw_type == "egress": + args["networkid"] = self.get_network(key="id") + res = self.query_api("createEgressFirewallRule", **args) else: - args['ipaddressid'] = self.get_ip_address('id') - res = self.query_api('createFirewallRule', **args) + args["ipaddressid"] = self.get_ip_address("id") + res = self.query_api("createFirewallRule", **args) - poll_async = self.module.params.get('poll_async') + poll_async = self.module.params.get("poll_async") if poll_async: - firewall_rule = self.poll_job(res, 'firewallrule') + firewall_rule = self.poll_job(res, "firewallrule") if firewall_rule: - firewall_rule = self.ensure_tags(resource=firewall_rule, resource_type='Firewallrule') + firewall_rule = self.ensure_tags(resource=firewall_rule, resource_type="Firewallrule") self.firewall_rule = firewall_rule return firewall_rule @@ -379,80 +363,80 @@ def create_firewall_rule(self): def remove_firewall_rule(self): firewall_rule = self.get_firewall_rule() if firewall_rule: - self.result['changed'] = True + self.result["changed"] = True - args = { - 'id': firewall_rule['id'] - } + args = {"id": firewall_rule["id"]} - fw_type = self.module.params.get('type') + fw_type = self.module.params.get("type") if not self.module.check_mode: - if fw_type == 'egress': - res = self.query_api('deleteEgressFirewallRule', **args) + if fw_type == "egress": + res = self.query_api("deleteEgressFirewallRule", **args) else: - res = self.query_api('deleteFirewallRule', **args) + res = self.query_api("deleteFirewallRule", **args) - poll_async = self.module.params.get('poll_async') + poll_async = self.module.params.get("poll_async") if poll_async: - self.poll_job(res, 'firewallrule') + self.poll_job(res, "firewallrule") return firewall_rule def get_result(self, firewall_rule): super(AnsibleCloudStackFirewall, self).get_result(firewall_rule) if firewall_rule: - self.result['type'] = self.module.params.get('type') - if self.result['type'] == 'egress': - self.result['network'] = self.get_network(key='displaytext') - if 'cidrlist' in firewall_rule: - self.result['cidrs'] = firewall_rule['cidrlist'].split(',') or [firewall_rule['cidrlist']] + self.result["type"] = self.module.params.get("type") + if self.result["type"] == "egress": + self.result["network"] = self.get_network(key="displaytext") + if "cidrlist" in firewall_rule: + self.result["cidrs"] = firewall_rule["cidrlist"].split(",") or [firewall_rule["cidrlist"]] return self.result def main(): argument_spec = cs_argument_spec() - argument_spec.update(dict( - ip_address=dict(), - network=dict(), - cidrs=dict(type='list', elements='str', default='0.0.0.0/0', aliases=['cidr']), - dest_cidrs=dict(type='list', elements='str', aliases=['dest_cidr']), - protocol=dict(choices=['tcp', 'udp', 'icmp', 'all'], default='tcp'), - type=dict(choices=['ingress', 'egress'], default='ingress'), - icmp_type=dict(type='int'), - icmp_code=dict(type='int'), - start_port=dict(type='int', aliases=['port']), - end_port=dict(type='int'), - state=dict(choices=['present', 'absent'], default='present'), - zone=dict(required=True), - domain=dict(), - account=dict(), - project=dict(), - poll_async=dict(type='bool', default=True), - tags=dict(type='list', elements='dict', aliases=['tag']), - )) + argument_spec.update( + dict( + ip_address=dict(), + network=dict(), + cidrs=dict(type="list", elements="str", default="0.0.0.0/0", aliases=["cidr"]), + dest_cidrs=dict(type="list", elements="str", aliases=["dest_cidr"]), + protocol=dict(choices=["tcp", "udp", "icmp", "all"], default="tcp"), + type=dict(choices=["ingress", "egress"], default="ingress"), + icmp_type=dict(type="int"), + icmp_code=dict(type="int"), + start_port=dict(type="int", aliases=["port"]), + end_port=dict(type="int"), + state=dict(choices=["present", "absent"], default="present"), + zone=dict(required=True), + domain=dict(), + account=dict(), + project=dict(), + poll_async=dict(type="bool", default=True), + tags=dict(type="list", elements="dict", aliases=["tag"]), + ) + ) required_together = cs_required_together() - required_together.extend([ - ['icmp_type', 'icmp_code'], - ]) + required_together.extend( + [ + ["icmp_type", "icmp_code"], + ] + ) module = AnsibleModule( argument_spec=argument_spec, required_together=required_together, - required_one_of=( - ['ip_address', 'network'], - ), + required_one_of=(["ip_address", "network"],), mutually_exclusive=( - ['icmp_type', 'start_port'], - ['icmp_type', 'end_port'], - ['ip_address', 'network'], + ["icmp_type", "start_port"], + ["icmp_type", "end_port"], + ["ip_address", "network"], ), - supports_check_mode=True + supports_check_mode=True, ) acs_fw = AnsibleCloudStackFirewall(module) - state = module.params.get('state') - if state in ['absent']: + state = module.params.get("state") + if state in ["absent"]: fw_rule = acs_fw.remove_firewall_rule() else: fw_rule = acs_fw.create_firewall_rule() @@ -462,5 +446,5 @@ def main(): module.exit_json(**result) -if __name__ == '__main__': +if __name__ == "__main__": main()