Coverage for /Users/jingyu.wy/git/ansible-module-apsarastack/src/ansible_collections/alibaba/apsarastack/plugins/modules/ali_security_group.py: 34%
147 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-12 18:03 +0800
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-12 18:03 +0800
1#!/usr/bin/python
2# -*- coding: utf-8 -*-
4# Copyright (c) 2017-present Alibaba Group Holding Limited. He Guimin <heguimin36@163.com.com>
5# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
6#
7# This file is part of Ansible
8#
9# Ansible is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# Ansible is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with Ansible. If not, see http://www.gnu.org/licenses/.
22from __future__ import (absolute_import, division, print_function)
24__metaclass__ = type
26ANSIBLE_METADATA = {'metadata_version': '1.1',
27 'status': ['preview'],
28 'supported_by': 'community'}
30DOCUMENTATION = '''
31---
32module: ali_security_group
33short_description: Manage Alibaba Cloud Security Group and its rules.
34description:
35 - Create and Delete Security Group, Modify its description and add/remove rules.
36options:
37 state:
38 description:
39 - Create, delete a security group
40 default: 'present'
41 choices: ['present', 'absent']
42 type: str
43 name:
44 description:
45 - Name of the security group, which is a string of 2 to 128 Chinese or English characters. It must begin with an
46 uppercase/lowercase letter or a Chinese character and can contain numerals, "_", "." or "-".
47 It cannot begin with http:// or https://.
48 This is used in combination with C(vpc_id) to determine if a Securty Group already exists.
49 required: True
50 aliases: ['group_name']
51 type: str
52 description:
53 description:
54 - Description of the security group, which is a string of 2 to 256 characters.
55 - It cannot begin with http:// or https://.
56 type: str
57 vpc_id:
58 description:
59 - ID of the VPC to create the group in. This is used in conjunction with the C(name) to ensure idempotence.
60 type: str
61 rules:
62 description:
63 - List of hash/dictionaries firewall inbound rules to enforce in this group (see example). If none are supplied,
64 no inbound rules will be enabled. Each rule has several keys and refer to
65 https://www.alibabacloud.com/help/doc-detail/25554.htm. Each key should be format as under_score.
66 At present, the valid keys including "ip_protocol", "port_range", "source_port_range", "nic_type", "policy",
67 "dest_cidr_ip", "source_cidr_ip", "source_group_id", "source_group_owner_account", "source_group_owner_id",
68 "priority" and "description".
69 type: list
70 elements: dict
71 rules_egress:
72 description:
73 - List of hash/dictionaries firewall outbound rules to enforce in this group (see example). If none are supplied,
74 no outbound rules will be enabled. Each rule has several keys and refer to
75 https://www.alibabacloud.com/help/doc-detail/25560.htm. Each key should be format as under_score.
76 At present, the valid keys including "ip_protocol", "port_range", "source_port_range", "nic_type", "policy",
77 "dest_cidr_ip", "source_cidr_ip", "dest_group_id", "dest_group_owner_account", "dest_group_owner_id",
78 "priority" and "description".
79 type: list
80 elements: dict
81 purge_rules:
82 description:
83 - Purge existing rules on security group that are not found in rules
84 default: True
85 type: bool
86 purge_rules_egress:
87 description:
88 - Purge existing rules_egress on security group that are not found in rules_egress
89 default: True
90 type: bool
91 security_group_id:
92 description:
93 - Security group ID.
94 aliases: ['id']
95 type: str
96 tags:
97 description:
98 - A hash/dictionaries of security group tags. C({"key":"value"})
99 aliases: ["group_tags"]
100 type: dict
101 multi_ok:
102 description:
103 - By default the module will not create another Security Group if there is another Security Group
104 with the same I(name) or I(vpc_id). Specify this as true if you want duplicate Security Groups created.
105 There will be conflict when I(multi_ok=True) and I(recent=True).
106 default: False
107 type: bool
108 recent:
109 description:
110 - By default the module will not choose the recent one if there is another Security Group with
111 the same I(name) or I(vpc_id). Specify this as true if you want to target the recent Security Group.
112 There will be conflict when I(multi_ok=True) and I(recent=True).
113 default: False
114 type: bool
115requirements:
116 - "python >= 3.6"
117 - "footmark >= 1.13.0"
118extends_documentation_fragment:
119 - apsarastack
120author:
121 - "He Guimin (@xiaozhu36)"
122'''
124EXAMPLES = '''
125# Note: These examples do not set authentication details, see the Alibaba Cloud Guide for details.
126- name: Create a new security group
127 ali_security_group:
128 name: 'AliyunSG'
129 vpc_id: 'vpc-123csecd'
131- name: Authorize security group
132 ali_security_group:
133 name: 'AliyunSG'
134 vpc_id: 'vpc-123csecd'
135 rules:
136 - ip_protocol: tcp
137 port_range: 1/122
138 source_cidr_ip: '10.159.6.18/12'
139 rules_egress:
140 - ip_protocol: icmp
141 port_range: -1/-1
142 source_cidr_ip: 10.0.0.0/10
143 dest_group_id: 'sg-ce33rdsfe'
144 priority: 1
146- name: Delete security grp
147 ali_security_group:
148 name: 'AliyunSG'
149 vpc_id: 'vpc-123csecd'
150 state: absent
151'''
153RETURN = '''
154group:
155 description: Dictionary of security group values
156 returned: always
157 type: complex
158 contains:
159 description:
160 description: The Security Group description.
161 returned: always
162 type: str
163 sample: "my ansible group"
164 group_name:
165 description: Security group name
166 sample: "my-ansible-group"
167 type: str
168 returned: always
169 group_id:
170 description: Security group id
171 sample: sg-abcd1234
172 type: str
173 returned: always
174 id:
175 description: Alias of "group_id".
176 sample: sg-abcd1234
177 type: str
178 returned: always
179 inner_access_policy:
180 description: Whether can access each other in one security group.
181 sample: True
182 type: bool
183 returned: always
184 tags:
185 description: Tags associated with the security group
186 type: dict
187 sample:
188 - Name: My Security Group
189 From: Ansible
190 type: dict
191 returned: always
192 returned: always
193 vpc_id:
194 description: ID of VPC to which the security group belongs
195 sample: vpc-abcd1234
196 type: str
197 returned: always
198 permissions:
199 description: Inbound rules associated with the security group.
200 sample:
201 - create_time: "2018-06-28T08:45:58Z"
202 description: "None"
203 dest_cidr_ip: "None"
204 dest_group_id: "None"
205 dest_group_name: "None"
206 dest_group_owner_account: "None"
207 direction: "ingress"
208 ip_protocol: "TCP"
209 nic_type: "intranet"
210 policy: "Accept"
211 port_range: "22/22"
212 priority: 1
213 source_cidr_ip: "0.0.0.0/0"
214 source_group_id: "None"
215 source_group_name: "None"
216 source_group_owner_account: "None"
217 type: list
218 returned: always
219 permissions_egress:
220 description: Outbound rules associated with the security group.
221 sample:
222 - create_time: "2018-06-28T08:45:59Z"
223 description: "NOne"
224 dest_cidr_ip: "192.168.0.54/32"
225 dest_group_id: "None"
226 dest_group_name: "None"
227 dest_group_owner_account: "None"
228 direction: "egress"
229 ip_protocol: "TCP"
230 nic_type: "intranet"
231 policy: "Accept"
232 port_range: "80/80"
233 priority: 1
234 source_cidr_ip: "None"
235 source_group_id: "None"
236 source_group_name: "None"
237 source_group_owner_account: "None"
238 type: list
239 returned: always
240'''
242import time
243from ansible.module_utils.basic import AnsibleModule
244from ansible_collections.alibaba.apsarastack.plugins.module_utils.apsarastack_common import common_argument_spec
245from ansible_collections.alibaba.apsarastack.plugins.module_utils.apsarastack_connections import ecs_connect
248try:
249 from footmark.exception import ECSResponseError
251 HAS_FOOTMARK = True
252except ImportError:
253 HAS_FOOTMARK = False
256VALID_INGRESS_PARAMS = ["ip_protocol", "port_range", "source_port_range", "nic_type", "policy",
257 "dest_cidr_ip", "source_cidr_ip", "priority", "description",
258 "source_group_id", "source_group_owner_account", "source_group_owner_id"]
259VALID_EGRESS_PARAMS = ["ip_protocol", "port_range", "source_port_range", "nic_type", "policy",
260 "dest_cidr_ip", "source_cidr_ip", "priority", "description",
261 "dest_group_id", "dest_group_owner_account", "dest_group_owner_id"]
264def validate_group_rule_keys(module, rule, direction):
266 if not isinstance(rule, dict):
267 module.fail_json(msg='Invalid rule parameter type [{0}].'.format(type(rule)))
269 VALID_PARAMS = VALID_INGRESS_PARAMS
270 if direction == "egress":
271 VALID_PARAMS = VALID_EGRESS_PARAMS
273 for k in rule:
274 if k not in VALID_PARAMS:
275 module.fail_json(msg="Invalid rule parameter '{0}' for rule: {1}. Supported parametes include: {2}".format(k, rule, VALID_PARAMS))
277 if 'ip_protocol' not in rule:
278 module.fail_json(msg='ip_protocol is required.')
279 if 'port_range' not in rule:
280 module.fail_json(msg='port_range is required.')
281 # The system response will return upper protocol
282 rule['ip_protocol'] = str(rule['ip_protocol']).upper()
285def purge_rules(module, group, existing_rule, rules, direction):
287 if not isinstance(existing_rule, dict):
288 module.fail_json(msg='Invalid existing rule type [{0}].'.format(type(existing_rule)))
290 if not isinstance(rules, list):
291 module.fail_json(msg='Invalid rules type [{0}]. The specified rules should be a list.'.format(type(rules)))
293 VALID_PARAMS = VALID_INGRESS_PARAMS
294 if direction == "egress":
295 VALID_PARAMS = VALID_EGRESS_PARAMS
297 # Find the rules which is not in the specified rules
298 find = False
299 for rule in rules:
300 for key in VALID_PARAMS:
301 if not rule.get(key):
302 continue
303 if existing_rule.get(key) != rule.get(key):
304 find = False
305 break
306 find = True
307 if find:
308 break
309 # If it is not found, there will not purge anythind
310 if not find:
311 return group.revoke(existing_rule, direction)
312 return False
315def group_exists(conn, module, vpc_id, name, security_group_id, multi, recent):
316 """Returns None or a security group object depending on the existence of a security group.
317 When supplied with a vpc_id and Name, it will check them to determine if it is a match
318 otherwise it will assume the Security Group does not exist and thus return None.
319 """
320 if multi:
321 return None
322 matching_groups = []
323 filters = {}
324 if vpc_id:
325 filters['vpc_id'] = vpc_id
326 if security_group_id:
327 filters['security_group_id'] = security_group_id
328 try:
329 for g in conn.describe_security_groups(**filters):
330 if name and g.security_group_name != name:
331 continue
332 matching_groups.append(g.get())
333 except Exception as e:
334 module.fail_json(msg="Failed to describe Security Groups: {0}".format(e))
336 if len(matching_groups) == 1:
337 return matching_groups[0]
338 elif len(matching_groups) > 1:
339 if recent:
340 return matching_groups[-1]
341 module.fail_json(msg='Currently there are {0} Security Groups that have the same name and '
342 'vpc id you specified. If you would like to create anyway '
343 'please pass True to the multi_ok param. Or, please pass True to the recent '
344 'param to choose the recent one.'.format(len(matching_groups)))
345 return None
348def main():
349 argument_spec = common_argument_spec()
350 argument_spec.update(dict(
351 state=dict(default='present', type='str', choices=['present', 'absent']),
352 name=dict(type='str', required=True, aliases=['group_name']),
353 description=dict(type='str'),
354 vpc_id=dict(type='str'),
355 security_group_id=dict(type='str', aliases=['id', 'group_id']),
356 tags=dict(type='dict', aliases=['group_tags']),
357 rules=dict(type='list', elements='dict'),
358 rules_egress=dict(type='list', elements='dict'),
359 purge_rules=dict(type='bool', default=True),
360 purge_rules_egress=dict(type='bool', default=True),
361 multi_ok=dict(type='bool', default=False),
362 recent=dict(type='bool', default=False)
363 ))
365 module = AnsibleModule(argument_spec=argument_spec)
367 if HAS_FOOTMARK is False:
368 module.fail_json(msg='footmark is required for the module ali_security_group.')
369 ecs = ecs_connect(module)
370 state = module.params['state']
371 security_group_id = module.params['security_group_id']
372 group_name = module.params['name']
373 if str(group_name).startswith('http://') or str(group_name).startswith('https://'):
374 module.fail_json(msg='Name can not start with http:// or https://')
375 description = module.params['description']
376 if str(description).startswith('http://') or str(description).startswith('https://'):
377 module.fail_json(msg='description can not start with http:// or https://')
378 multi = module.params['multi_ok']
379 recent = module.params['recent']
381 if multi and recent:
382 module.fail_json(msg='multi_ok and recent can not be True at the same time.')
384 changed = False
386 group = group_exists(ecs, module, module.params['vpc_id'], group_name, security_group_id, multi, recent)
388 if state == 'absent':
389 if not group:
390 module.exit_json(changed=changed, group={})
391 try:
392 module.exit_json(changed=group.delete(), group={})
393 except ECSResponseError as e:
394 module.fail_json(msg="Deleting security group {0} is failed. Error: {1}".format(group.id, e))
396 if not group:
397 try:
398 params = module.params
399 params['security_group_name'] = group_name
400 params['client_token'] = "Ansible-Apsarastack-%s-%s" % (hash(str(module.params)), str(time.time()))
401 group = ecs.create_security_group(**params)
402 except ECSResponseError as e:
403 module.fail_json(changed=changed, msg='Creating a security group is failed. Error: {0}'.format(e))
405 if not description:
406 description = group.description
407 if group.modify(name=group_name, description=description):
408 changed = True
410 # validating rules if provided
411 ingress_rules = module.params['rules']
412 if ingress_rules:
413 direction = 'ingress'
414 for rule in ingress_rules:
415 validate_group_rule_keys(module, rule, direction)
416 if module.params['purge_rules']:
417 for existing in group.permissions:
418 if existing['direction'] != direction:
419 continue
420 if purge_rules(module, group, existing, ingress_rules, direction):
421 changed = True
422 for rule in ingress_rules:
423 if group.authorize(rule, direction):
424 changed = True
426 egress_rules = module.params['rules_egress']
427 if egress_rules:
428 direction = 'egress'
429 for rule in egress_rules:
430 validate_group_rule_keys(module, rule, direction)
431 if module.params['purge_rules_egress']:
432 for existing in group.permissions:
433 if existing['direction'] != direction:
434 continue
435 if purge_rules(module, group, existing, egress_rules, direction):
436 changed = True
437 for rule in egress_rules:
438 if group.authorize(rule, direction):
439 changed = True
441 module.exit_json(changed=changed, group=group.get().read())
444if __name__ == '__main__':
445 main()