From 23670913baba838aa00defda2a46c36213d58096 Mon Sep 17 00:00:00 2001 From: Yunge Zhu <37337818+yungezz@users.noreply.github.com> Date: Mon, 1 Apr 2019 16:56:16 +0800 Subject: [PATCH] support application security group in network security group (#54584) --- .../cloud/azure/azure_rm_securitygroup.py | 66 ++++++++++++++++- .../azure_rm_securitygroup/tasks/main.yml | 72 +++++++++++++++++++ 2 files changed, 137 insertions(+), 1 deletion(-) diff --git a/lib/ansible/modules/cloud/azure/azure_rm_securitygroup.py b/lib/ansible/modules/cloud/azure/azure_rm_securitygroup.py index e7abea4456c..5d2e5aeb822 100644 --- a/lib/ansible/modules/cloud/azure/azure_rm_securitygroup.py +++ b/lib/ansible/modules/cloud/azure/azure_rm_securitygroup.py @@ -95,6 +95,22 @@ options: - Default tags such as C(VirtualNetwork), C(AzureLoadBalancer) and C(Internet) can also be used. - It can accept string type or a list of string type. default: "*" + source_application_security_groups: + description: + - List of the source application security groups. + - It could be list of resource id. + - It could be list of names in same resource group. + - It could be list of dict containing resource_group and name. + - It is mutually exclusive with C(source_address_prefix) and C(source_address_prefixes). + type: list + destination_application_security_groups: + description: + - List of the destination application security groups. + - It could be list of resource id. + - It could be list of names in same resource group. + - It could be list of dict containing resource_group and name. + - It is mutually exclusive with C(destination_address_prefix) and C(destination_address_prefixes). + type: list access: description: - Whether or not to allow the traffic flow. @@ -341,6 +357,7 @@ state: try: from msrestazure.azure_exceptions import CloudError + from msrestazure.tools import is_valid_resource_id from azure.mgmt.network import NetworkManagementClient except ImportError: # This is handled in azure_rm_common @@ -373,6 +390,12 @@ def validate_rule(self, rule, rule_type=None): check_plural('source_port_range', 'source_port_ranges') check_plural('destination_port_range', 'destination_port_ranges') + # when source(destination)_application_security_groups set, remove the default value * of source(destination)_address_prefix + if rule.get('source_application_security_groups') and rule.get('source_address_prefix') == '*': + rule['source_address_prefix'] = None + if rule.get('destination_application_security_groups') and rule.get('destination_address_prefix') == '*': + rule['destination_address_prefix'] = None + def compare_rules_change(old_list, new_list, purge_list): old_list = old_list or [] @@ -425,6 +448,10 @@ def compare_rules(old_rule, rule): changed = True if set(rule.get('destination_port_ranges') or []) != set(old_rule.get('destination_port_ranges') or []): changed = True + if set(rule.get('source_application_security_groups') or []) != set(old_rule.get('source_application_security_groups') or []): + changed = True + if set(rule.get('destination_application_security_groups') or []) != set(old_rule.get('destination_application_security_groups') or []): + changed = True return changed @@ -446,6 +473,12 @@ def create_rule_instance(self, rule): destination_address_prefixes=rule.get('destination_address_prefixes', None), source_port_ranges=rule.get('source_port_ranges', None), destination_port_ranges=rule.get('destination_port_ranges', None), + source_application_security_groups=[ + self.nsg_models.ApplicationSecurityGroup(id=p) + for p in rule.get('source_application_security_groups')] if rule.get('source_application_security_groups') else None, + destination_application_security_groups=[ + self.nsg_models.ApplicationSecurityGroup(id=p) + for p in rule.get('destination_application_security_groups')] if rule.get('destination_application_security_groups') else None, access=rule.get('access', None), priority=rule.get('priority', None), direction=rule.get('direction', None), @@ -475,6 +508,9 @@ def create_rule_dict_from_obj(rule): destination_port_ranges=rule.destination_port_ranges, source_address_prefixes=rule.source_address_prefixes, destination_address_prefixes=rule.destination_address_prefixes, + source_application_security_groups=[p.id for p in rule.source_application_security_groups] if rule.source_application_security_groups else None, + destination_application_security_groups=[ + p.id for p in rule.destination_application_security_groups] if rule.destination_application_security_groups else None, access=rule.access, priority=rule.priority, direction=rule.direction, @@ -522,6 +558,8 @@ rule_spec = dict( destination_port_range=dict(type='raw', default='*'), source_address_prefix=dict(type='raw', default='*'), destination_address_prefix=dict(type='raw', default='*'), + source_application_security_groups=dict(type='list', elements='raw'), + destination_application_security_groups=dict(type='list', elements='raw'), access=dict(type='str', choices=['Allow', 'Deny'], default='Allow'), priority=dict(type='int', required=True), direction=dict(type='str', choices=['Inbound', 'Outbound'], default='Inbound') @@ -559,8 +597,14 @@ class AzureRMSecurityGroup(AzureRMModuleBase): state=dict() ) + mutually_exclusive = [["source_application_security_group", "source_address_prefix"], + ["source_application_security_group", "source_address_prefixes"], + ["destination_application_security_group", "destination_address_prefix"], + ["destination_application_security_group", "destination_address_prefixes"]] + super(AzureRMSecurityGroup, self).__init__(self.module_arg_spec, - supports_check_mode=True) + supports_check_mode=True, + mutually_exclusive=mutually_exclusive) def exec_module(self, **kwargs): # tighten up poll interval for security groups; default 30s is an eternity @@ -585,6 +629,7 @@ class AzureRMSecurityGroup(AzureRMModuleBase): validate_rule(self, rule) except Exception as exc: self.fail("Error validating rule {0} - {1}".format(rule, str(exc))) + self.convert_asg_to_id(rule) if self.default_rules: for rule in self.default_rules: @@ -592,6 +637,7 @@ class AzureRMSecurityGroup(AzureRMModuleBase): validate_rule(self, rule, 'default') except Exception as exc: self.fail("Error validating default rule {0} - {1}".format(rule, str(exc))) + self.convert_asg_to_id(rule) try: nsg = self.network_client.network_security_groups.get(self.resource_group, self.name) @@ -698,6 +744,24 @@ class AzureRMSecurityGroup(AzureRMModuleBase): raise Exception("Error deleting security group {0} - {1}".format(self.name, str(exc))) return result + def convert_asg_to_id(self, rule): + def convert_to_id(rule, key): + if rule.get(key): + ids = [] + for p in rule.get(key): + if isinstance(p, dict): + ids.append("/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Network/applicationSecurityGroups/{2}".format( + self.subscription_id, p.get('resource_group'), p.get('name'))) + elif isinstance(p, str): + if is_valid_resource_id(p): + ids.append(p) + else: + ids.append("/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Network/applicationSecurityGroups/{2}".format( + self.subscription_id, self.resource_group, p)) + rule[key] = ids + convert_to_id(rule, 'source_application_security_groups') + convert_to_id(rule, 'destination_application_security_groups') + def main(): AzureRMSecurityGroup() diff --git a/test/integration/targets/azure_rm_securitygroup/tasks/main.yml b/test/integration/targets/azure_rm_securitygroup/tasks/main.yml index 073c10bffa7..cbe0740f9e9 100644 --- a/test/integration/targets/azure_rm_securitygroup/tasks/main.yml +++ b/test/integration/targets/azure_rm_securitygroup/tasks/main.yml @@ -1,6 +1,9 @@ - name: Prepare random number set_fact: secgroupname: "sg{{ resource_group | hash('md5') | truncate(7, True, '') }}{{ 1000 | random }}" + asg_name1: "asg1{{ resource_group | hash('md5') | truncate(7, True, '') }}" + asg_name2: "asg2{{ resource_group | hash('md5') | truncate(7, True, '') }}" + sg_name1: "sgasg{{ resource_group | hash('md5') | truncate(7, True, '') }}{{ 1000 | random }}" run_once: yes @@ -213,6 +216,75 @@ - output.changed - "{{ output.state.rules | length }} == 2" +- name: Create Application security group 1 + azure_rm_applicationsecuritygroup: + resource_group: "{{ resource_group }}" + name: "{{ asg_name1 }}" + tags: + testing: testing + register: asg1 + +- name: Create Application security group 2 + azure_rm_applicationsecuritygroup: + resource_group: "{{ resource_group_secondary }}" + name: "{{ asg_name2 }}" + tags: + testing: testing + register: asg2 + +- name: Create security group with application security group + azure_rm_securitygroup: + resource_group: "{{ resource_group }}" + name: "{{ sg_name1 }}" + purge_rules: yes + rules: + - name: AsgToAsg + protocol: Tcp + source_application_security_groups: + - "{{ asg1.id }}" + destination_application_security_groups: + - resource_group: "{{ resource_group_secondary }}" + name: "{{ asg_name2 }}" + destination_port_range: 22 + access: Allow + priority: 101 + direction: Inbound + register: output + +- assert: + that: + - output.changed + +- name: Create security group with application security group - Idempotent + azure_rm_securitygroup: + resource_group: "{{ resource_group }}" + name: "{{ sg_name1 }}" + purge_rules: yes + rules: + - name: AsgToAsg + protocol: Tcp + source_application_security_groups: + - "{{ asg_name1 }}" + destination_application_security_groups: + - resource_group: "{{ resource_group_secondary }}" + name: "{{ asg_name2 }}" + destination_port_range: 22 + access: Allow + priority: 101 + direction: Inbound + register: output + +- assert: + that: + - not output.changed + + +- name: Delete security group + azure_rm_securitygroup: + resource_group: "{{ resource_group }}" + name: "{{ sg_name1 }}" + state: absent + - name: Delete all security groups azure_rm_securitygroup: resource_group: "{{ resource_group }}"