lib/ansible/modules/cloud/alicloud/_alicloud_security_group.py (499 lines of code) (raw):

#!/usr/bin/python # Copyright (c) 2017-present Alibaba Group Holding Limited. He Guimin <heguimin36@163.com.com> # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # # 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 http://www.gnu.org/licenses/. ANSIBLE_METADATA = {'metadata_version': '1.1', 'status': ['deprecated'], 'supported_by': 'community'} DOCUMENTATION = ''' --- module: alicloud_security_group version_added: "1.0.9" short_description: Create, Query or Delete Security Group. description: - Create and Delete Security Group, and it contains security group rules management. deprecated: removed_in: "1.5.0" why: Alibaba Cloud module name prefix "ali" will be more concise. alternative: Use M(ali_security_group) instead. options: state: description: - Create, delete a security group default: 'present' choices: ['present', 'absent'] group_name: description: - Name of the security group, which is a string of 2 to 128 Chinese or English characters. It must begin with an uppercase/lowercase letter or a Chinese character and can contain numerals, "_", "." or "-". It cannot begin with http:// or https://. aliases: ['name'] description: description: - Description of the security group, which is a string of 2 to 256 characters. - It cannot begin with http:// or https://. vpc_id: description: - ID of the VPC to which the security group belongs. rules: description: - List of hash/dictionaries firewall inbound rules to enforce in this group. suboptions: ip_protocol: description: - IP protocol required: true choices: ["tcp", "udp", "icmp", "gre", "all"] aliases: ['proto'] port_range: description: - The range of port numbers. Tcp and udp's valid range is 1 to 65535, and other protocol's valid value is -1/-1. required: true source_group_id: description: - The source security group id. aliases: ['group_id'] source_group_owner_id: description: - The source security group owner id. aliases: ['group_owner_id'] source_cidr_ip: description: - The source IP address range aliases: ['cidr_ip'] policy: description: - Authorization policy default: "accept" choices: ["accept", "drop"] priority: description: - Authorization policy priority default: 1 choices: ["1~100"] nic_type: description: - Network type default: "internet" choices: ["internet", "intranet"] rules_egress: description: - List of hash/dictionaries firewall outbound rules to enforce in this group. Keys allowed are:ip_protocol, port_range, dest_group_id, dest_group_owner_id, dest_cidr_ip, policy, priority,nic_type. And these keys's attribution same as rules keys. suboptions: ip_protocol: description: - IP protocol required: true choices: ["tcp", "udp", "icmp", "gre", "all"] aliases: ['proto'] port_range: description: - The range of port numbers. Tcp and udp's valid range is 1 to 65535, and other protocol's valid value is "-1/-1". required: true dest_group_id: description: - The destination security group id. aliases: ['group_id'] dest_group_owner_id: description: - The destination security group owner id. aliases: ['group_owner_id'] dest_cidr_ip: description: - The destination IP address range aliases: ['cidr_ip'] policy: description: - Authorization policy default: "accept" choices: ["accept", "drop"] priority: description: - Authorization policy priority default: 1 choices: ["1~100"] nic_type: description: - Network type default: "internet" choices: ["internet", "intranet"] group_id: description: - Security group ID. It is required when deleting or querying security group or performing rules authorization. requirements: - "python >= 2.6" - "footmark >= 1.1.16" extends_documentation_fragment: - alicloud author: - "He Guimin (@xiaozhu36)" ''' EXAMPLES = ''' # # Provisioning new Security Group # # Basic provisioning example to create security group - name: create security group hosts: localhost connection: local vars: alicloud_access_key: xxxxxxxxxx alicloud_secret_key: xxxxxxxxxx alicloud_region: cn-shenzhen tasks: - name: create security grp alicloud_security_group: alicloud_access_key: '{{ alicloud_access_key }}' alicloud_secret_key: '{{ alicloud_secret_key }}' alicloud_region: '{{ alicloud_region }}' group_name: 'AliyunSG' # Basic provisioning example authorize security group - name: authorize security grp hosts: localhost connection: local vars: alicloud_access_key: xxxxxxxxxx alicloud_secret_key: xxxxxxxxxx alicloud_region: cn-shenzhen tasks: - name: authorize security group alicloud_security_group: alicloud_access_key: '{{ alicloud_access_key }}' alicloud_secret_key: '{{ alicloud_secret_key }}' group_id: xxxxxxxxxx alicloud_region: '{{ alicloud_region }}' rules: - ip_protocol: tcp port_range: 1/122 source_cidr_ip: '10.159.6.18/12' rules_egress: - proto: all port_range: -1/-1 dest_group_id: xxxxxxxxxx nic_type: intranet # Provisioning example create and authorize security group - name: create and authorize security group hosts: localhost connection: local vars: alicloud_access_key: xxxxxxxxxx alicloud_secret_key: xxxxxxxxxx alicloud_region: cn-shenzhen tasks: - name: create and authorize security grp alicloud_security_group: alicloud_access_key: '{{ alicloud_access_key }}' alicloud_secret_key: '{{ alicloud_secret_key }}' group_name: 'AliyunSG' description: 'an example ECS group' alicloud_region: '{{ alicloud_region }}' rules: - ip_protocol: tcp port_range: 1/122 source_cidr_ip: '10.159.6.18/12' priority: 10 policy: drop nic_type: intranet rules_egress: - proto: all port_range: -1/-1 dest_group_id: xxxxxxxxxx group_owner_id: xxxxxxxxxx priority: 10 policy: accept nic_type: intranet # Provisioning example to delete security group - name: delete security grp hosts: localhost connection: local vars: alicloud_access_key: xxxxxxxxxx alicloud_secret_key: xxxxxxxxxx alicloud_region: us-west-1 group_ids: - xxxxxxxxxx state: absent tasks: - name: delete security grp alicloud_security_group: alicloud_access_key: '{{ alicloud_access_key }}' alicloud_secret_key: '{{ alicloud_secret_key }}' alicloud_region: '{{ alicloud_region }}' group_ids: '{{ group_ids }}' state: '{{ state }}' ''' RETURN = ''' group_id: description: Deprecated from version 1.3.1 and replaced by 'id'. returned: type: sample: id: description: ID of the security group. returned: when present type: string sample: "sd-safhi3gsv" name: description: Name of the security group. returned: when present type: string sample: "new-group" group: description: Details about the security group that was created returned: when present type: dict sample: { "description": "travis-ansible-instance", "id": "sg-2ze1hhyn7tac4p85gh13", "name": "travis-ansible-instance", "region_id": "cn-beijing", "rules": [ { "create_time": "2017-06-19T02:43:29Z", "description": "", "dest_cidr_ip": "", "dest_group_id": "", "dest_group_name": "", "dest_group_owner_account": "", "direction": "ingress", "ip_protocol": "TCP", "nic_type": "internet", "policy": "Accept", "port_range": "80/86", "priority": 1, "source_cidr_ip": "192.168.0.54/32", "source_group_id": "", "source_group_name": "", "source_group_owner_account": "" }, { "create_time": "2017-06-19T02:43:30Z", "description": "", "dest_cidr_ip": "47.89.23.33/32", "dest_group_id": "", "dest_group_name": "", "dest_group_owner_account": "", "direction": "egress", "ip_protocol": "TCP", "nic_type": "internet", "policy": "Accept", "port_range": "8080/8085", "priority": 1, "source_cidr_ip": "", "source_group_id": "", "source_group_name": "", "source_group_owner_account": "" } ], "tags": {}, "vpc_id": "" } vpc_id: description: ID of the VPC to which the security group belongs returned: when present type: string sample: "vpc-snif3g3iv" ''' import time from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.alicloud_ecs import ecs_argument_spec, ecs_connect try: from footmark.exception import ECSResponseError HAS_FOOTMARK = True except ImportError: HAS_FOOTMARK = False def authorize_security_group(module, ecs, group_id, inbound_rules, outbound_rules): """ authorize security group in ecs :param module: Ansible module object :param ecs: authenticated ecs connection object :param group_id: Security Group Id for authorization :param inbound_rules: Inbound rules for authorization :param outbound_rules: Outbound rules for authorization :param add_to_fail_result: message to add to failed message at the beginning, if two tasks are performed :return: Returns changed state, security group id and custom message. """ try: changed = False inbound_failed_rules, outbound_failed_rules, result = ecs.authorize_security_group( group_id, inbound_rules, outbound_rules) if 'error' in (''.join(str(result))).lower(): module.fail_json(changed=changed, msg="Authorizing SecurityGroup is failed, error: %s ; group_id: %s ; " "failed inbound rules: %s ; failed outbound rules: %s." % (str(result), group_id, inbound_failed_rules, outbound_failed_rules)) changed = True except ECSResponseError as e: module.fail_json(msg='Unable to authorize security group, error: {0}'.format(e)) return changed def validate_format_sg_rules(module, inbound_rules=None, outbound_rules=None): """ Validate and format security group for inbound and outbound rules :param module: Ansible module object :param inbound_rules: Inbound rules for authorization to validate and format :param outbound_rules: Outbound rules for authorization to validate and format :return: """ # aliases for rule ip_protocol_aliases = ('ip_protocol', 'proto') inbound_cidr_ip_aliases = ('source_cidr_ip', 'cidr_ip') outbound_cidr_ip_aliases = ('dest_cidr_ip', 'cidr_ip') inbound_group_id_aliases = ('source_group_id', 'group_id') outbound_group_id_aliases = ('dest_group_id', 'group_id') inbound_group_owner_aliases = ('source_group_owner_id', 'group_owner_id') outbound_group_owner_aliases = ('dest_group_owner_id', 'group_owner_id') cidr_ip_aliases = { "inbound": inbound_cidr_ip_aliases, "outbound": outbound_cidr_ip_aliases, } group_id_aliases = { "inbound": inbound_group_id_aliases, "outbound": outbound_group_id_aliases, } group_owner_aliases = { "inbound": inbound_group_owner_aliases, "outbound": outbound_group_owner_aliases, } COMMON_VALID_PARAMS = ('proto', 'ip_protocol', 'cidr_ip', 'group_id', 'group_owner_id', 'nic_type', 'policy', 'priority', 'port_range') INBOUND_VALID_PARAMS = ('source_cidr_ip', 'source_group_id', 'source_group_owner_id') OUTBOUND_VALID_PARAMS = ('dest_cidr_ip', 'dest_group_id', 'dest_group_owner_id') rule_types = [] rule_choice = { "inbound": inbound_rules, "outbound": outbound_rules, } valid_params = { "inbound": INBOUND_VALID_PARAMS, "outbound": OUTBOUND_VALID_PARAMS, } if inbound_rules: rule_types.append('inbound') if outbound_rules: rule_types.append('outbound') for rule_type in rule_types: rules = rule_choice.get(rule_type) total_rules = 0 if rules: total_rules = len(rules) if total_rules != 0: for rule in rules: if not isinstance(rule, dict): module.fail_json(msg='Invalid rule parameter type [%s].' % type(rule)) for k in rule: if k not in COMMON_VALID_PARAMS and k not in valid_params.get(rule_type): module.fail_json(msg='Invalid rule parameter \'{}\''.format(k)) ip_protocol = get_alias_value(rule, ip_protocol_aliases) if ip_protocol is None: module.fail_json(msg="Ip Protocol required for rule authorization") port_range = get_alias_value(rule, ['port_range']) if port_range is None: module.fail_json(msg="Port range is required for rule authorization") # verifying whether group_id is provided and cidr_ip is not, so nic_type should be set to intranet cidr_ip = get_alias_value(rule, cidr_ip_aliases.get(rule_type)) if cidr_ip is None: if get_alias_value(rule, group_id_aliases.get(rule_type)) is not None: if 'nic_type' in rule: if not rule['nic_type'] == "intranet": module.fail_json(msg="In mutual security group authorization (namely, " "GroupId is specified, while CidrIp is not specified), " "you must specify the nic_type as intranet") else: module.fail_json(msg="In mutual security group authorization (namely, " "GroupId is specified, while CidrIp is not specified), " "you must specify the nic_type as intranet") # format rules to return for authorization formatted_rule = {} formatted_rule['ip_protocol'] = ip_protocol formatted_rule['port_range'] = port_range if cidr_ip: formatted_rule['cidr_ip'] = cidr_ip group_id = get_alias_value(rule, group_id_aliases.get(rule_type)) if group_id: formatted_rule['group_id'] = group_id group_owner_id = get_alias_value(rule, group_owner_aliases.get(rule_type)) if group_owner_id: formatted_rule['group_owner_id'] = group_owner_id if 'nic_type' in rule: if rule['nic_type']: formatted_rule['nic_type'] = rule['nic_type'] if 'policy' in rule: if rule['policy']: formatted_rule['policy'] = rule['policy'] if 'priority' in rule: if rule['priority']: formatted_rule['priority'] = rule['priority'] rule.clear() rule.update(formatted_rule) def get_alias_value(dictionary, aliases): """ Get alias or key value from a dictionary :param dictionary: a dictionary to check in for keys/aliases :param aliases: list of aliases to find in dictionary to retrieve value :return: returns value of found alias else None """ if (dictionary and aliases) is not None: for alias in aliases: if alias in dictionary: return dictionary[alias] return None else: return None def get_group_basic(group): """ Parse security group basic information. returns it as a dictionary """ return {'id': group.id, 'name': group.name, 'vpc_id': group.vpc_id} def get_group_detail(group): """ Parse security group detail information. returns it as a dictionary """ return {'id': group.id, 'name': group.name, 'description': group.description, 'region_id': group.region_id, 'tags': group.tags, 'vpc_id': group.vpc_id, 'rules': group.rules} def main(): argument_spec = ecs_argument_spec() argument_spec.update(dict( state=dict(default='present', type='str', choices=['present', 'absent']), group_name=dict(type='str', required=False, aliases=['name']), description=dict(type='str', required=False), vpc_id=dict(type='str'), group_tags=dict(type='list', aliases=['tags']), rules=dict(type='list'), rules_egress=dict(type='list'), group_id=dict(type='str') )) module = AnsibleModule(argument_spec=argument_spec) if HAS_FOOTMARK is False: module.fail_json(msg='footmark required for the module alicloud_security_group.') ecs = ecs_connect(module) state = module.params['state'] group_name = module.params['group_name'] description = module.params['description'] vpc_id = module.params['vpc_id'] group_id = module.params['group_id'] group_tags = module.params['group_tags'] changed = False group = None try: if group_id: security_groups = ecs.get_all_security_groups(group_ids=[group_id], vpc_id=vpc_id, name=group_name) else: security_groups = ecs.get_all_security_groups(vpc_id=vpc_id, name=group_name) except ECSResponseError as e: module.fail_json(msg='Error in get_all_security_groups: %s' % str(e)) group_ids = [] if security_groups and len(security_groups) > 0: if group_id or len(security_groups) == 1: group = security_groups[0] else: for cur in security_groups: group_ids.append(cur.id) module.fail_json(msg="There are several security group in our record based on name {0} or vpc {1}: {2}. " "Please specified one using 'id' and try again.".format(group_name, vpc_id, group_ids)) if state == 'absent': if not group: module.fail_json(changed=changed, msg="Please specify a security group by using 'group_id' or 'group_name' " "and 'vpc_id' before deleting a security group.") try: module.exit_json(changed=group.delete()) except ECSResponseError as e: module.fail_json(msg="Deleting security group {0} is failed. Error: {1}".format(group.id, e)) if not group: try: client_token = "Ansible-Alicloud-%s-%s" % (hash(str(module.params)), str(time.time())) group = ecs.create_security_group(group_name=group_name, description=description, vpc_id=vpc_id, group_tags=group_tags, client_token=client_token) changed = True except ECSResponseError as e: module.fail_json(changed=changed, msg='Creating a security group is failed. Error: {0}'.format(e)) # validating rules if provided total_rules_count = 0 inbound_rules = module.params['rules'] if inbound_rules: total_rules_count = len(inbound_rules) outbound_rules = module.params['rules_egress'] if outbound_rules: total_rules_count += len(outbound_rules) if total_rules_count > 100: module.fail_json(msg='more than 100 rules for authorization are not allowed') validate_format_sg_rules(module, inbound_rules, outbound_rules) if inbound_rules or outbound_rules: changed = authorize_security_group(module, ecs, group_id=group.id, inbound_rules=inbound_rules, outbound_rules=outbound_rules) module.exit_json(changed=changed, group_id=group.id, group=get_group_detail(group), vpc_id=group.vpc_id) if __name__ == '__main__': main()