[cloud] fix VPC behavior for ec2_group module, improve integration tests (#27038)
* Add tests for group in a VPC * Improve ec2_group output and documentation Update ec2_group to provide full security group information Add RETURN documentation to match * Fix ec2_group creation within a VPC Ensure VPC ID gets passed when creating security group * Add test for auto creating SG * Fix ec2_group auto group creation * Add backoff to describe_security_groups Getting LimitExceeded from describe_security_groups is definitely possible (source: me) so add backoff to increase likelihood of success. To ensure that all `describe_security_group` calls are backed off, remove implicit ones that use `ec2.SecurityGroup`. From there, the decision to remove the `ec2` boto3 resource and rely on the client alone makes good sense. * Tidy up auto created security group Add resource_prefix to auto created security group and delete it in the `always` section. Use YAML argument form for all module parameters
This commit is contained in:
parent
2d734c7ea7
commit
f972994662
3 changed files with 282 additions and 116 deletions
|
@ -20,6 +20,7 @@
|
||||||
"ec2:CreateVpc",
|
"ec2:CreateVpc",
|
||||||
"ec2:DeleteKeyPair",
|
"ec2:DeleteKeyPair",
|
||||||
"ec2:DeleteNatGateway",
|
"ec2:DeleteNatGateway",
|
||||||
|
"ec2:DeleteVpc",
|
||||||
"ec2:Describe*",
|
"ec2:Describe*",
|
||||||
"ec2:DisassociateAddress",
|
"ec2:DisassociateAddress",
|
||||||
"ec2:DisassociateRouteTable",
|
"ec2:DisassociateRouteTable",
|
||||||
|
|
|
@ -191,6 +191,65 @@ EXAMPLES = '''
|
||||||
state: absent
|
state: absent
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
RETURN = '''
|
||||||
|
group_name:
|
||||||
|
description: Security group name
|
||||||
|
sample: My Security Group
|
||||||
|
type: string
|
||||||
|
returned: on create/update
|
||||||
|
group_id:
|
||||||
|
description: Security group id
|
||||||
|
sample: sg-abcd1234
|
||||||
|
type: string
|
||||||
|
returned: on create/update
|
||||||
|
description:
|
||||||
|
description: Description of security group
|
||||||
|
sample: My Security Group
|
||||||
|
type: string
|
||||||
|
returned: on create/update
|
||||||
|
tags:
|
||||||
|
description: Tags associated with the security group
|
||||||
|
sample:
|
||||||
|
Name: My Security Group
|
||||||
|
Purpose: protecting stuff
|
||||||
|
type: dict
|
||||||
|
returned: on create/update
|
||||||
|
vpc_id:
|
||||||
|
description: ID of VPC to which the security group belongs
|
||||||
|
sample: vpc-abcd1234
|
||||||
|
type: string
|
||||||
|
returned: on create/update
|
||||||
|
ip_permissions:
|
||||||
|
description: Inbound rules associated with the security group.
|
||||||
|
sample:
|
||||||
|
- from_port: 8182
|
||||||
|
ip_protocol: tcp
|
||||||
|
ip_ranges:
|
||||||
|
- cidr_ip: "1.1.1.1/32"
|
||||||
|
ipv6_ranges: []
|
||||||
|
prefix_list_ids: []
|
||||||
|
to_port: 8182
|
||||||
|
user_id_group_pairs: []
|
||||||
|
type: list
|
||||||
|
returned: on create/update
|
||||||
|
ip_permissions_egress:
|
||||||
|
description: Outbound rules associated with the security group.
|
||||||
|
sample:
|
||||||
|
- ip_protocol: -1
|
||||||
|
ip_ranges:
|
||||||
|
- cidr_ip: "0.0.0.0/0"
|
||||||
|
ipv6_ranges: []
|
||||||
|
prefix_list_ids: []
|
||||||
|
user_id_group_pairs: []
|
||||||
|
type: list
|
||||||
|
returned: on create/update
|
||||||
|
owner_id:
|
||||||
|
description: AWS Account ID of the security group
|
||||||
|
sample: 123456789012
|
||||||
|
type: int
|
||||||
|
returned: on create/update
|
||||||
|
'''
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
@ -200,6 +259,8 @@ from ansible.module_utils.ec2 import get_aws_connection_info
|
||||||
from ansible.module_utils.ec2 import ec2_argument_spec
|
from ansible.module_utils.ec2 import ec2_argument_spec
|
||||||
from ansible.module_utils.ec2 import camel_dict_to_snake_dict
|
from ansible.module_utils.ec2 import camel_dict_to_snake_dict
|
||||||
from ansible.module_utils.ec2 import HAS_BOTO3
|
from ansible.module_utils.ec2 import HAS_BOTO3
|
||||||
|
from ansible.module_utils.ec2 import boto3_tag_list_to_ansible_dict
|
||||||
|
from ansible.module_utils.ec2 import AWSRetry
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -208,6 +269,11 @@ except ImportError:
|
||||||
pass # caught by imported HAS_BOTO3
|
pass # caught by imported HAS_BOTO3
|
||||||
|
|
||||||
|
|
||||||
|
@AWSRetry.backoff(tries=5, delay=5, backoff=2.0)
|
||||||
|
def get_security_groups_with_backoff(connection, **kwargs):
|
||||||
|
return connection.describe_security_groups(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
def deduplicate_rules_args(rules):
|
def deduplicate_rules_args(rules):
|
||||||
"""Returns unique rules"""
|
"""Returns unique rules"""
|
||||||
if rules is None:
|
if rules is None:
|
||||||
|
@ -227,7 +293,7 @@ def make_rule_key(prefix, rule, group_id, cidr_ip):
|
||||||
return key.lower().replace('-none', '-None')
|
return key.lower().replace('-none', '-None')
|
||||||
|
|
||||||
|
|
||||||
def add_rules_to_loopkup(ipPermissions, group_id, prefix, dict):
|
def add_rules_to_lookup(ipPermissions, group_id, prefix, dict):
|
||||||
for rule in ipPermissions:
|
for rule in ipPermissions:
|
||||||
for groupGrant in rule.get('UserIdGroupPairs'):
|
for groupGrant in rule.get('UserIdGroupPairs'):
|
||||||
dict[make_rule_key(prefix, rule, group_id, groupGrant.get('GroupId'))] = (rule, groupGrant)
|
dict[make_rule_key(prefix, rule, group_id, groupGrant.get('GroupId'))] = (rule, groupGrant)
|
||||||
|
@ -261,7 +327,7 @@ def validate_rule(module, rule):
|
||||||
module.fail_json(msg='Specify group_id OR group_name, not both')
|
module.fail_json(msg='Specify group_id OR group_name, not both')
|
||||||
|
|
||||||
|
|
||||||
def get_target_from_rule(module, ec2, rule, name, group, groups, vpc_id):
|
def get_target_from_rule(module, client, rule, name, group, groups, vpc_id):
|
||||||
"""
|
"""
|
||||||
Returns tuple of (group_id, ip) after validating rule params.
|
Returns tuple of (group_id, ip) after validating rule params.
|
||||||
|
|
||||||
|
@ -293,10 +359,10 @@ def get_target_from_rule(module, ec2, rule, name, group, groups, vpc_id):
|
||||||
module.fail_json(msg="Specify group_id OR group_name, not both")
|
module.fail_json(msg="Specify group_id OR group_name, not both")
|
||||||
elif 'cidr_ip' in rule and 'cidr_ipv6' in rule:
|
elif 'cidr_ip' in rule and 'cidr_ipv6' in rule:
|
||||||
module.fail_json(msg="Specify cidr_ip OR cidr_ipv6, not both")
|
module.fail_json(msg="Specify cidr_ip OR cidr_ipv6, not both")
|
||||||
elif 'group_id' in rule and re.match(FOREIGN_SECURITY_GROUP_REGEX, rule['group_id']):
|
elif rule.get('group_id') and re.match(FOREIGN_SECURITY_GROUP_REGEX, rule['group_id']):
|
||||||
# this is a foreign Security Group. Since you can't fetch it you must create an instance of it
|
# this is a foreign Security Group. Since you can't fetch it you must create an instance of it
|
||||||
owner_id, group_id, group_name = re.match(FOREIGN_SECURITY_GROUP_REGEX, rule['group_id']).groups()
|
owner_id, group_id, group_name = re.match(FOREIGN_SECURITY_GROUP_REGEX, rule['group_id']).groups()
|
||||||
group_instance = ec2.SecurityGroup(owner_id=owner_id, group_name=group_name, id=group_id)
|
group_instance = dict(GroupId=group_id, GroupName=group_name)
|
||||||
groups[group_id] = group_instance
|
groups[group_id] = group_instance
|
||||||
groups[group_name] = group_instance
|
groups[group_name] = group_instance
|
||||||
elif 'group_id' in rule:
|
elif 'group_id' in rule:
|
||||||
|
@ -304,18 +370,21 @@ def get_target_from_rule(module, ec2, rule, name, group, groups, vpc_id):
|
||||||
elif 'group_name' in rule:
|
elif 'group_name' in rule:
|
||||||
group_name = rule['group_name']
|
group_name = rule['group_name']
|
||||||
if group_name == name:
|
if group_name == name:
|
||||||
group_id = group.id
|
group_id = group['GroupId']
|
||||||
groups[group_id] = group
|
groups[group_id] = group
|
||||||
groups[group_name] = group
|
groups[group_name] = group
|
||||||
elif group_name in groups and (vpc_id is None or groups[group_name].vpc_id == vpc_id):
|
elif group_name in groups and (vpc_id is None or groups[group_name]['VpcId'] == vpc_id):
|
||||||
group_id = groups[group_name].id
|
group_id = groups[group_name]['GroupId']
|
||||||
else:
|
else:
|
||||||
if not rule.get('group_desc', '').strip():
|
if not rule.get('group_desc', '').strip():
|
||||||
module.fail_json(msg="group %s will be automatically created by rule %s and "
|
module.fail_json(msg="group %s will be automatically created by rule %s and "
|
||||||
"no description was provided" % (group_name, rule))
|
"no description was provided" % (group_name, rule))
|
||||||
if not module.check_mode:
|
if not module.check_mode:
|
||||||
auto_group = ec2.create_security_group(group_name, rule['group_desc'], vpc_id=vpc_id)
|
params = dict(GroupName=group_name, Description=rule['group_desc'])
|
||||||
group_id = auto_group.id
|
if vpc_id:
|
||||||
|
params['VpcId'] = vpc_id
|
||||||
|
auto_group = client.create_security_group(**params)
|
||||||
|
group_id = auto_group['GroupId']
|
||||||
groups[group_id] = auto_group
|
groups[group_id] = auto_group
|
||||||
groups[group_name] = auto_group
|
groups[group_name] = auto_group
|
||||||
target_group_created = True
|
target_group_created = True
|
||||||
|
@ -404,7 +473,7 @@ def authorize_ip(type, changed, client, group, groupRules,
|
||||||
ip, ip_permission, module, rule, ethertype):
|
ip, ip_permission, module, rule, ethertype):
|
||||||
# If rule already exists, don't later delete it
|
# If rule already exists, don't later delete it
|
||||||
for thisip in ip:
|
for thisip in ip:
|
||||||
rule_id = make_rule_key(type, rule, group.id, thisip)
|
rule_id = make_rule_key(type, rule, group['GroupId'], thisip)
|
||||||
if rule_id in groupRules:
|
if rule_id in groupRules:
|
||||||
del groupRules[rule_id]
|
del groupRules[rule_id]
|
||||||
else:
|
else:
|
||||||
|
@ -413,14 +482,14 @@ def authorize_ip(type, changed, client, group, groupRules,
|
||||||
if ip_permission:
|
if ip_permission:
|
||||||
try:
|
try:
|
||||||
if type == "in":
|
if type == "in":
|
||||||
client.authorize_security_group_ingress(GroupId=group.group_id,
|
client.authorize_security_group_ingress(GroupId=group['GroupId'],
|
||||||
IpPermissions=[ip_permission])
|
IpPermissions=[ip_permission])
|
||||||
elif type == "out":
|
elif type == "out":
|
||||||
client.authorize_security_group_egress(GroupId=group.group_id,
|
client.authorize_security_group_egress(GroupId=group['GroupId'],
|
||||||
IpPermissions=[ip_permission])
|
IpPermissions=[ip_permission])
|
||||||
except botocore.exceptions.ClientError as e:
|
except botocore.exceptions.ClientError as e:
|
||||||
module.fail_json(msg="Unable to authorize %s for ip %s security group '%s' - %s" %
|
module.fail_json(msg="Unable to authorize %s for ip %s security group '%s' - %s" %
|
||||||
(type, thisip, group.group_name, e),
|
(type, thisip, group['GroupName'], e),
|
||||||
exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
||||||
changed = True
|
changed = True
|
||||||
return changed, ip_permission
|
return changed, ip_permission
|
||||||
|
@ -516,16 +585,15 @@ def main():
|
||||||
module.fail_json(msg="The AWS region must be specified as an "
|
module.fail_json(msg="The AWS region must be specified as an "
|
||||||
"environment variable or in the AWS credentials "
|
"environment variable or in the AWS credentials "
|
||||||
"profile.")
|
"profile.")
|
||||||
client, ec2 = boto3_conn(module, conn_type='both', resource='ec2', endpoint=ec2_url, region=region, **aws_connect_params)
|
client = boto3_conn(module, conn_type='client', resource='ec2', endpoint=ec2_url, region=region, **aws_connect_params)
|
||||||
group = None
|
group = None
|
||||||
groups = dict()
|
groups = dict()
|
||||||
security_groups = []
|
security_groups = []
|
||||||
# do get all security groups
|
# do get all security groups
|
||||||
# find if the group is present
|
# find if the group is present
|
||||||
try:
|
try:
|
||||||
response = client.describe_security_groups()
|
response = get_security_groups_with_backoff(client)
|
||||||
if 'SecurityGroups' in response:
|
security_groups = response.get('SecurityGroups', [])
|
||||||
security_groups = response.get('SecurityGroups')
|
|
||||||
except botocore.exceptions.NoCredentialsError as e:
|
except botocore.exceptions.NoCredentialsError as e:
|
||||||
module.fail_json(msg="Error in describe_security_groups: %s" % "Unable to locate credentials", exception=traceback.format_exc())
|
module.fail_json(msg="Error in describe_security_groups: %s" % "Unable to locate credentials", exception=traceback.format_exc())
|
||||||
except botocore.exceptions.ClientError as e:
|
except botocore.exceptions.ClientError as e:
|
||||||
|
@ -533,22 +601,21 @@ def main():
|
||||||
**camel_dict_to_snake_dict(e.response))
|
**camel_dict_to_snake_dict(e.response))
|
||||||
|
|
||||||
for sg in security_groups:
|
for sg in security_groups:
|
||||||
curGroup = ec2.SecurityGroup(sg['GroupId'])
|
groups[sg['GroupId']] = sg
|
||||||
groups[curGroup.id] = ec2.SecurityGroup(curGroup.id)
|
groupName = sg['GroupName']
|
||||||
groupName = curGroup.group_name
|
|
||||||
if groupName in groups:
|
if groupName in groups:
|
||||||
# Prioritise groups from the current VPC
|
# Prioritise groups from the current VPC
|
||||||
if vpc_id is None or curGroup.vpc_id == vpc_id:
|
if vpc_id is None or sg['VpcId'] == vpc_id:
|
||||||
groups[groupName] = curGroup
|
groups[groupName] = sg
|
||||||
else:
|
else:
|
||||||
groups[groupName] = curGroup
|
groups[groupName] = sg
|
||||||
|
|
||||||
if group_id:
|
if group_id:
|
||||||
if curGroup.id == group_id:
|
if sg['GroupId'] == group_id:
|
||||||
group = curGroup
|
group = sg
|
||||||
else:
|
else:
|
||||||
if groupName == name and (vpc_id is None or curGroup.vpc_id == vpc_id):
|
if groupName == name and (vpc_id is None or sg['VpcId'] == vpc_id):
|
||||||
group = curGroup
|
group = sg
|
||||||
|
|
||||||
# Ensure requested group is absent
|
# Ensure requested group is absent
|
||||||
if state == 'absent':
|
if state == 'absent':
|
||||||
|
@ -556,7 +623,7 @@ def main():
|
||||||
# found a match, delete it
|
# found a match, delete it
|
||||||
try:
|
try:
|
||||||
if not module.check_mode:
|
if not module.check_mode:
|
||||||
group.delete()
|
client.delete_security_group(GroupId=group['GroupId'])
|
||||||
except botocore.exceptions.ClientError as e:
|
except botocore.exceptions.ClientError as e:
|
||||||
module.fail_json(msg="Unable to delete security group '%s' - %s" % (group, e),
|
module.fail_json(msg="Unable to delete security group '%s' - %s" % (group, e),
|
||||||
exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
||||||
|
@ -571,7 +638,7 @@ def main():
|
||||||
elif state == 'present':
|
elif state == 'present':
|
||||||
if group:
|
if group:
|
||||||
# existing group
|
# existing group
|
||||||
if group.description != description:
|
if group['Description'] != description:
|
||||||
module.fail_json(
|
module.fail_json(
|
||||||
msg="Group description does not match existing group. ec2_group does not support this case.")
|
msg="Group description does not match existing group. ec2_group does not support this case.")
|
||||||
|
|
||||||
|
@ -579,18 +646,22 @@ def main():
|
||||||
else:
|
else:
|
||||||
# no match found, create it
|
# no match found, create it
|
||||||
if not module.check_mode:
|
if not module.check_mode:
|
||||||
group = client.create_security_group(GroupName=name, Description=description)
|
params = dict(GroupName=name, Description=description)
|
||||||
groupId = group.get('GroupId')
|
if vpc_id:
|
||||||
|
params['VpcId'] = vpc_id
|
||||||
|
group = client.create_security_group(**params)
|
||||||
# When a group is created, an egress_rule ALLOW ALL
|
# When a group is created, an egress_rule ALLOW ALL
|
||||||
# to 0.0.0.0/0 is added automatically but it's not
|
# to 0.0.0.0/0 is added automatically but it's not
|
||||||
# reflected in the object returned by the AWS API
|
# reflected in the object returned by the AWS API
|
||||||
# call. We re-read the group for getting an updated object
|
# call. We re-read the group for getting an updated object
|
||||||
# amazon sometimes takes a couple seconds to update the security group so wait till it exists
|
# amazon sometimes takes a couple seconds to update the security group so wait till it exists
|
||||||
while len(client.describe_security_groups(GroupIds=[groupId])
|
while True:
|
||||||
['SecurityGroups'][0]['IpPermissionsEgress']) == 0:
|
group = get_security_groups_with_backoff(client, GroupIds=[group['GroupId']])['SecurityGroups'][0]
|
||||||
|
if not group['IpPermissionsEgress']:
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
group = ec2.SecurityGroup(groupId)
|
|
||||||
changed = True
|
changed = True
|
||||||
else:
|
else:
|
||||||
module.fail_json(msg="Unsupported state requested: %s" % state)
|
module.fail_json(msg="Unsupported state requested: %s" % state)
|
||||||
|
@ -599,13 +670,13 @@ def main():
|
||||||
if group:
|
if group:
|
||||||
# Manage ingress rules
|
# Manage ingress rules
|
||||||
groupRules = {}
|
groupRules = {}
|
||||||
add_rules_to_loopkup(group.ip_permissions, group.id, 'in', groupRules)
|
add_rules_to_lookup(group['IpPermissions'], group['GroupId'], 'in', groupRules)
|
||||||
# Now, go through all provided rules and ensure they are there.
|
# Now, go through all provided rules and ensure they are there.
|
||||||
if rules is not None:
|
if rules is not None:
|
||||||
ip_permission = []
|
ip_permission = []
|
||||||
for rule in rules:
|
for rule in rules:
|
||||||
validate_rule(module, rule)
|
validate_rule(module, rule)
|
||||||
group_id, ip, ipv6, target_group_created = get_target_from_rule(module, ec2, rule, name,
|
group_id, ip, ipv6, target_group_created = get_target_from_rule(module, client, rule, name,
|
||||||
group, groups, vpc_id)
|
group, groups, vpc_id)
|
||||||
if target_group_created:
|
if target_group_created:
|
||||||
changed = True
|
changed = True
|
||||||
|
@ -616,7 +687,7 @@ def main():
|
||||||
rule['to_port'] = None
|
rule['to_port'] = None
|
||||||
|
|
||||||
if group_id:
|
if group_id:
|
||||||
rule_id = make_rule_key('in', rule, group.id, group_id)
|
rule_id = make_rule_key('in', rule, group['GroupId'], group_id)
|
||||||
if rule_id in groupRules:
|
if rule_id in groupRules:
|
||||||
del groupRules[rule_id]
|
del groupRules[rule_id]
|
||||||
else:
|
else:
|
||||||
|
@ -628,11 +699,11 @@ def main():
|
||||||
[useridpair.update({'VpcId': vpc_id}) for useridpair in
|
[useridpair.update({'VpcId': vpc_id}) for useridpair in
|
||||||
ip_permission.get('UserIdGroupPairs')]
|
ip_permission.get('UserIdGroupPairs')]
|
||||||
try:
|
try:
|
||||||
client.authorize_security_group_ingress(GroupId=group.group_id, IpPermissions=[ips])
|
client.authorize_security_group_ingress(GroupId=group['GroupId'], IpPermissions=[ips])
|
||||||
except botocore.exceptions.ClientError as e:
|
except botocore.exceptions.ClientError as e:
|
||||||
module.fail_json(
|
module.fail_json(
|
||||||
msg="Unable to authorize ingress for group %s security group '%s' - %s" %
|
msg="Unable to authorize ingress for group %s security group '%s' - %s" %
|
||||||
(group_id, group.group_name, e),
|
(group_id, group['GroupName'], e),
|
||||||
exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
||||||
changed = True
|
changed = True
|
||||||
elif ip:
|
elif ip:
|
||||||
|
@ -655,22 +726,22 @@ def main():
|
||||||
ip_permission = serialize_revoke(grant, rule)
|
ip_permission = serialize_revoke(grant, rule)
|
||||||
if not module.check_mode:
|
if not module.check_mode:
|
||||||
try:
|
try:
|
||||||
client.revoke_security_group_ingress(GroupId=group.group_id, IpPermissions=[ip_permission])
|
client.revoke_security_group_ingress(GroupId=group['GroupId'], IpPermissions=[ip_permission])
|
||||||
except botocore.exceptions.ClientError as e:
|
except botocore.exceptions.ClientError as e:
|
||||||
module.fail_json(
|
module.fail_json(
|
||||||
msg="Unable to revoke ingress for security group '%s' - %s" %
|
msg="Unable to revoke ingress for security group '%s' - %s" %
|
||||||
(group.group_name, e),
|
(group['GroupName'], e),
|
||||||
exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
||||||
changed = True
|
changed = True
|
||||||
|
|
||||||
# Manage egress rules
|
# Manage egress rules
|
||||||
groupRules = {}
|
groupRules = {}
|
||||||
add_rules_to_loopkup(group.ip_permissions_egress, group.id, 'out', groupRules)
|
add_rules_to_lookup(group['IpPermissionsEgress'], group['GroupId'], 'out', groupRules)
|
||||||
# Now, go through all provided rules and ensure they are there.
|
# Now, go through all provided rules and ensure they are there.
|
||||||
if rules_egress is not None:
|
if rules_egress is not None:
|
||||||
for rule in rules_egress:
|
for rule in rules_egress:
|
||||||
validate_rule(module, rule)
|
validate_rule(module, rule)
|
||||||
group_id, ip, ipv6, target_group_created = get_target_from_rule(module, ec2, rule, name,
|
group_id, ip, ipv6, target_group_created = get_target_from_rule(module, client, rule, name,
|
||||||
group, groups, vpc_id)
|
group, groups, vpc_id)
|
||||||
if target_group_created:
|
if target_group_created:
|
||||||
changed = True
|
changed = True
|
||||||
|
@ -681,7 +752,7 @@ def main():
|
||||||
rule['to_port'] = None
|
rule['to_port'] = None
|
||||||
|
|
||||||
if group_id:
|
if group_id:
|
||||||
rule_id = make_rule_key('out', rule, group.id, group_id)
|
rule_id = make_rule_key('out', rule, group['GroupId'], group_id)
|
||||||
if rule_id in groupRules:
|
if rule_id in groupRules:
|
||||||
del groupRules[rule_id]
|
del groupRules[rule_id]
|
||||||
else:
|
else:
|
||||||
|
@ -693,11 +764,11 @@ def main():
|
||||||
[useridpair.update({'VpcId': vpc_id}) for useridpair in
|
[useridpair.update({'VpcId': vpc_id}) for useridpair in
|
||||||
ip_permission.get('UserIdGroupPairs')]
|
ip_permission.get('UserIdGroupPairs')]
|
||||||
try:
|
try:
|
||||||
client.authorize_security_group_egress(GroupId=group.group_id, IpPermissions=[ips])
|
client.authorize_security_group_egress(GroupId=group['GroupId'], IpPermissions=[ips])
|
||||||
except botocore.exceptions.ClientError as e:
|
except botocore.exceptions.ClientError as e:
|
||||||
module.fail_json(
|
module.fail_json(
|
||||||
msg="Unable to authorize egress for group %s security group '%s' - %s" %
|
msg="Unable to authorize egress for group %s security group '%s' - %s" %
|
||||||
(group_id, group.group_name, e),
|
(group_id, group['GroupName'], e),
|
||||||
exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
||||||
changed = True
|
changed = True
|
||||||
elif ip:
|
elif ip:
|
||||||
|
@ -717,7 +788,7 @@ def main():
|
||||||
# when no egress rules are specified,
|
# when no egress rules are specified,
|
||||||
# we add in a default allow all out rule, which was the
|
# we add in a default allow all out rule, which was the
|
||||||
# default behavior before egress rules were added
|
# default behavior before egress rules were added
|
||||||
default_egress_rule = 'out--1-None-None-' + group.id + '-0.0.0.0/0'
|
default_egress_rule = 'out--1-None-None-' + group['GroupId'] + '-0.0.0.0/0'
|
||||||
if default_egress_rule not in groupRules:
|
if default_egress_rule not in groupRules:
|
||||||
if not module.check_mode:
|
if not module.check_mode:
|
||||||
ip_permission = [{'IpProtocol': '-1',
|
ip_permission = [{'IpProtocol': '-1',
|
||||||
|
@ -725,11 +796,11 @@ def main():
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
try:
|
try:
|
||||||
client.authorize_security_group_egress(GroupId=group.group_id, IpPermissions=ip_permission)
|
client.authorize_security_group_egress(GroupId=group['GroupId'], IpPermissions=ip_permission)
|
||||||
except botocore.exceptions.ClientError as e:
|
except botocore.exceptions.ClientError as e:
|
||||||
module.fail_json(msg="Unable to authorize egress for ip %s security group '%s' - %s" %
|
module.fail_json(msg="Unable to authorize egress for ip %s security group '%s' - %s" %
|
||||||
('0.0.0.0/0',
|
('0.0.0.0/0',
|
||||||
group.group_name,
|
group['GroupName'],
|
||||||
e),
|
e),
|
||||||
exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
||||||
changed = True
|
changed = True
|
||||||
|
@ -745,17 +816,19 @@ def main():
|
||||||
ip_permission = serialize_revoke(grant, rule)
|
ip_permission = serialize_revoke(grant, rule)
|
||||||
if not module.check_mode:
|
if not module.check_mode:
|
||||||
try:
|
try:
|
||||||
client.revoke_security_group_egress(GroupId=group.group_id, IpPermissions=[ip_permission])
|
client.revoke_security_group_egress(GroupId=group['GroupId'], IpPermissions=[ip_permission])
|
||||||
except botocore.exceptions.ClientError as e:
|
except botocore.exceptions.ClientError as e:
|
||||||
module.fail_json(msg="Unable to revoke egress for ip %s security group '%s' - %s" %
|
module.fail_json(msg="Unable to revoke egress for ip %s security group '%s' - %s" %
|
||||||
(grant,
|
(grant, group['GroupName'], e),
|
||||||
group.group_name,
|
|
||||||
e),
|
|
||||||
exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
|
||||||
changed = True
|
changed = True
|
||||||
|
|
||||||
if group:
|
if group:
|
||||||
module.exit_json(changed=changed, group_id=group.id)
|
security_group = get_security_groups_with_backoff(client, GroupIds=[group['GroupId']])['SecurityGroups'][0]
|
||||||
|
security_group = camel_dict_to_snake_dict(security_group)
|
||||||
|
security_group['tags'] = boto3_tag_list_to_ansible_dict(security_group.get('tags', {}),
|
||||||
|
tag_name_key_name='key', tag_value_key_name='value')
|
||||||
|
module.exit_json(changed=changed, **security_group)
|
||||||
else:
|
else:
|
||||||
module.exit_json(changed=changed, group_id=None)
|
module.exit_json(changed=changed, group_id=None)
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
# - EC2_REGION -> AWS_REGION
|
# - EC2_REGION -> AWS_REGION
|
||||||
#
|
#
|
||||||
|
|
||||||
# - include: ../../setup_ec2/tasks/common.yml module_name=ec2_group
|
# - include: ../../setup_ec2/tasks/common.yml module_name: ec2_group
|
||||||
|
|
||||||
- block:
|
- block:
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@
|
||||||
# ============================================================
|
# ============================================================
|
||||||
- name: test failure with only name
|
- name: test failure with only name
|
||||||
ec2_group:
|
ec2_group:
|
||||||
name='{{ec2_group_name}}'
|
name: '{{ec2_group_name}}'
|
||||||
register: result
|
register: result
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
# ============================================================
|
# ============================================================
|
||||||
- name: test failure with only description
|
- name: test failure with only description
|
||||||
ec2_group:
|
ec2_group:
|
||||||
description='{{ec2_group_description}}'
|
description: '{{ec2_group_description}}'
|
||||||
register: result
|
register: result
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
|
|
||||||
|
@ -51,9 +51,9 @@
|
||||||
# ============================================================
|
# ============================================================
|
||||||
- name: test failure with empty description (AWS API requires non-empty string desc)
|
- name: test failure with empty description (AWS API requires non-empty string desc)
|
||||||
ec2_group:
|
ec2_group:
|
||||||
name='{{ec2_group_name}}'
|
name: '{{ec2_group_name}}'
|
||||||
description=''
|
description: ''
|
||||||
region='{{ec2_region}}'
|
region: '{{ec2_region}}'
|
||||||
register: result
|
register: result
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
|
|
||||||
|
@ -66,9 +66,9 @@
|
||||||
# ============================================================
|
# ============================================================
|
||||||
- name: test valid region parameter
|
- name: test valid region parameter
|
||||||
ec2_group:
|
ec2_group:
|
||||||
name='{{ec2_group_name}}'
|
name: '{{ec2_group_name}}'
|
||||||
description='{{ec2_group_description}}'
|
description: '{{ec2_group_description}}'
|
||||||
region='{{ec2_region}}'
|
region: '{{ec2_region}}'
|
||||||
register: result
|
register: result
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
|
|
||||||
|
@ -81,8 +81,8 @@
|
||||||
# ============================================================
|
# ============================================================
|
||||||
- name: test environment variable EC2_REGION
|
- name: test environment variable EC2_REGION
|
||||||
ec2_group:
|
ec2_group:
|
||||||
name='{{ec2_group_name}}'
|
name: '{{ec2_group_name}}'
|
||||||
description='{{ec2_group_description}}'
|
description: '{{ec2_group_description}}'
|
||||||
environment:
|
environment:
|
||||||
EC2_REGION: '{{ec2_region}}'
|
EC2_REGION: '{{ec2_region}}'
|
||||||
register: result
|
register: result
|
||||||
|
@ -97,8 +97,8 @@
|
||||||
# ============================================================
|
# ============================================================
|
||||||
- name: test invalid ec2_url parameter
|
- name: test invalid ec2_url parameter
|
||||||
ec2_group:
|
ec2_group:
|
||||||
name='{{ec2_group_name}}'
|
name: '{{ec2_group_name}}'
|
||||||
description='{{ec2_group_description}}'
|
description: '{{ec2_group_description}}'
|
||||||
environment:
|
environment:
|
||||||
EC2_URL: bogus.example.com
|
EC2_URL: bogus.example.com
|
||||||
register: result
|
register: result
|
||||||
|
@ -113,8 +113,8 @@
|
||||||
# ============================================================
|
# ============================================================
|
||||||
- name: test valid ec2_url parameter
|
- name: test valid ec2_url parameter
|
||||||
ec2_group:
|
ec2_group:
|
||||||
name='{{ec2_group_name}}'
|
name: '{{ec2_group_name}}'
|
||||||
description='{{ec2_group_description}}'
|
description: '{{ec2_group_description}}'
|
||||||
environment:
|
environment:
|
||||||
EC2_URL: '{{ec2_url}}'
|
EC2_URL: '{{ec2_url}}'
|
||||||
register: result
|
register: result
|
||||||
|
@ -129,8 +129,8 @@
|
||||||
# ============================================================
|
# ============================================================
|
||||||
- name: test credentials from environment
|
- name: test credentials from environment
|
||||||
ec2_group:
|
ec2_group:
|
||||||
name='{{ec2_group_name}}'
|
name: '{{ec2_group_name}}'
|
||||||
description='{{ec2_group_description}}'
|
description: '{{ec2_group_description}}'
|
||||||
environment:
|
environment:
|
||||||
EC2_REGION: '{{ec2_region}}'
|
EC2_REGION: '{{ec2_region}}'
|
||||||
EC2_ACCESS_KEY: bogus_access_key
|
EC2_ACCESS_KEY: bogus_access_key
|
||||||
|
@ -147,11 +147,11 @@
|
||||||
# ============================================================
|
# ============================================================
|
||||||
- name: test credential parameters
|
- name: test credential parameters
|
||||||
ec2_group:
|
ec2_group:
|
||||||
name='{{ec2_group_name}}'
|
name: '{{ec2_group_name}}'
|
||||||
description='{{ec2_group_description}}'
|
description: '{{ec2_group_description}}'
|
||||||
ec2_region='{{ec2_region}}'
|
ec2_region: '{{ec2_region}}'
|
||||||
ec2_access_key='bogus_access_key'
|
ec2_access_key: 'bogus_access_key'
|
||||||
ec2_secret_key='bogus_secret_key'
|
ec2_secret_key: 'bogus_secret_key'
|
||||||
register: result
|
register: result
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
|
|
||||||
|
@ -164,25 +164,25 @@
|
||||||
# ============================================================
|
# ============================================================
|
||||||
- name: test state=absent
|
- name: test state=absent
|
||||||
ec2_group:
|
ec2_group:
|
||||||
name='{{ec2_group_name}}'
|
name: '{{ec2_group_name}}'
|
||||||
description='{{ec2_group_description}}'
|
description: '{{ec2_group_description}}'
|
||||||
ec2_region='{{ec2_region}}'
|
ec2_region: '{{ec2_region}}'
|
||||||
ec2_access_key='{{ec2_access_key}}'
|
ec2_access_key: '{{ec2_access_key}}'
|
||||||
ec2_secret_key='{{ec2_secret_key}}'
|
ec2_secret_key: '{{ec2_secret_key}}'
|
||||||
security_token='{{security_token}}'
|
security_token: '{{security_token}}'
|
||||||
state=absent
|
state: absent
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
- name: test state=present (expected changed=true)
|
- name: test state=present (expected changed=true)
|
||||||
ec2_group:
|
ec2_group:
|
||||||
name='{{ec2_group_name}}'
|
name: '{{ec2_group_name}}'
|
||||||
description='{{ec2_group_description}}'
|
description: '{{ec2_group_description}}'
|
||||||
ec2_region='{{ec2_region}}'
|
ec2_region: '{{ec2_region}}'
|
||||||
ec2_access_key='{{ec2_access_key}}'
|
ec2_access_key: '{{ec2_access_key}}'
|
||||||
ec2_secret_key='{{ec2_secret_key}}'
|
ec2_secret_key: '{{ec2_secret_key}}'
|
||||||
security_token='{{security_token}}'
|
security_token: '{{security_token}}'
|
||||||
state=present
|
state: present
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- name: assert state=present (expected changed=true)
|
- name: assert state=present (expected changed=true)
|
||||||
|
@ -194,13 +194,13 @@
|
||||||
# ============================================================
|
# ============================================================
|
||||||
- name: test state=present different description raises error
|
- name: test state=present different description raises error
|
||||||
ec2_group:
|
ec2_group:
|
||||||
name='{{ec2_group_name}}'
|
name: '{{ec2_group_name}}'
|
||||||
description='{{ec2_group_description}}CHANGED'
|
description: '{{ec2_group_description}}CHANGED'
|
||||||
ec2_region='{{ec2_region}}'
|
ec2_region: '{{ec2_region}}'
|
||||||
ec2_access_key='{{ec2_access_key}}'
|
ec2_access_key: '{{ec2_access_key}}'
|
||||||
ec2_secret_key='{{ec2_secret_key}}'
|
ec2_secret_key: '{{ec2_secret_key}}'
|
||||||
security_token='{{security_token}}'
|
security_token: '{{security_token}}'
|
||||||
state=present
|
state: present
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
|
@ -213,13 +213,13 @@
|
||||||
# ============================================================
|
# ============================================================
|
||||||
- name: test state=present (expected changed=false)
|
- name: test state=present (expected changed=false)
|
||||||
ec2_group:
|
ec2_group:
|
||||||
name='{{ec2_group_name}}'
|
name: '{{ec2_group_name}}'
|
||||||
description='{{ec2_group_description}}'
|
description: '{{ec2_group_description}}'
|
||||||
ec2_region='{{ec2_region}}'
|
ec2_region: '{{ec2_region}}'
|
||||||
ec2_access_key='{{ec2_access_key}}'
|
ec2_access_key: '{{ec2_access_key}}'
|
||||||
ec2_secret_key='{{ec2_secret_key}}'
|
ec2_secret_key: '{{ec2_secret_key}}'
|
||||||
security_token='{{security_token}}'
|
security_token: '{{security_token}}'
|
||||||
state=present
|
state: present
|
||||||
register: result
|
register: result
|
||||||
|
|
||||||
- name: assert state=present (expected changed=false)
|
- name: assert state=present (expected changed=false)
|
||||||
|
@ -325,11 +325,91 @@
|
||||||
- 'not result.changed'
|
- 'not result.changed'
|
||||||
- 'result.group_id.startswith("sg-")'
|
- 'result.group_id.startswith("sg-")'
|
||||||
|
|
||||||
|
- name: add a rule that auto creates another security group
|
||||||
|
ec2_group:
|
||||||
|
name: '{{ec2_group_name}}'
|
||||||
|
description: '{{ec2_group_description}}'
|
||||||
|
ec2_region: '{{ec2_region}}'
|
||||||
|
ec2_access_key: '{{ec2_access_key}}'
|
||||||
|
ec2_secret_key: '{{ec2_secret_key}}'
|
||||||
|
security_token: '{{security_token}}'
|
||||||
|
state: present
|
||||||
|
purge_rules: no
|
||||||
|
rules:
|
||||||
|
- proto: "tcp"
|
||||||
|
group_name: "{{ resource_prefix }} - Another security group"
|
||||||
|
group_desc: Another security group
|
||||||
|
ports: 7171
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: check that there are now two rules
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- result.changed
|
||||||
|
- result.ip_permissions|length == 2
|
||||||
|
- result.ip_permissions[0].user_id_group_pairs or
|
||||||
|
result.ip_permissions[1].user_id_group_pairs
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
- name: test state=absent (expected changed=true)
|
- name: test state=absent (expected changed=true)
|
||||||
ec2_group:
|
ec2_group:
|
||||||
name='{{ec2_group_name}}'
|
name: '{{ec2_group_name}}'
|
||||||
state=absent
|
state: absent
|
||||||
|
environment:
|
||||||
|
EC2_REGION: '{{ec2_region}}'
|
||||||
|
EC2_ACCESS_KEY: '{{ec2_access_key}}'
|
||||||
|
EC2_SECRET_KEY: '{{ec2_secret_key}}'
|
||||||
|
EC2_SECURITY_TOKEN: '{{security_token|default("")}}'
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: assert state=absent (expected changed=true)
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- 'result.changed'
|
||||||
|
- 'not result.group_id'
|
||||||
|
|
||||||
|
- name: create a VPC
|
||||||
|
ec2_vpc_net:
|
||||||
|
name: "{{ resource_prefix }}-vpc"
|
||||||
|
state: present
|
||||||
|
cidr_block: "10.232.232.128/26"
|
||||||
|
ec2_region: '{{ec2_region}}'
|
||||||
|
ec2_access_key: '{{ec2_access_key}}'
|
||||||
|
ec2_secret_key: '{{ec2_secret_key}}'
|
||||||
|
security_token: '{{security_token}}'
|
||||||
|
tags:
|
||||||
|
Name: "{{ resource_prefix }}-vpc"
|
||||||
|
Description: "Created by ansible-test"
|
||||||
|
register: vpc_result
|
||||||
|
|
||||||
|
- name: create security group in the VPC
|
||||||
|
ec2_group:
|
||||||
|
name: '{{ec2_group_name}}'
|
||||||
|
description: '{{ec2_group_description}}'
|
||||||
|
ec2_region: '{{ec2_region}}'
|
||||||
|
ec2_access_key: '{{ec2_access_key}}'
|
||||||
|
ec2_secret_key: '{{ec2_secret_key}}'
|
||||||
|
security_token: '{{security_token}}'
|
||||||
|
vpc_id: '{{ vpc_result.vpc.id }}'
|
||||||
|
state: present
|
||||||
|
rules:
|
||||||
|
- proto: "tcp"
|
||||||
|
from_port: 8182
|
||||||
|
to_port: 8182
|
||||||
|
cidr_ip: "1.1.1.1/32"
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: assert state=present (expected changed=true)
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- 'result.changed'
|
||||||
|
- 'result.vpc_id == vpc_result.vpc.id'
|
||||||
|
- 'result.group_id.startswith("sg-")'
|
||||||
|
|
||||||
|
- name: test state=absent (expected changed=true)
|
||||||
|
ec2_group:
|
||||||
|
name: '{{ec2_group_name}}'
|
||||||
|
state: absent
|
||||||
environment:
|
environment:
|
||||||
EC2_REGION: '{{ec2_region}}'
|
EC2_REGION: '{{ec2_region}}'
|
||||||
EC2_ACCESS_KEY: '{{ec2_access_key}}'
|
EC2_ACCESS_KEY: '{{ec2_access_key}}'
|
||||||
|
@ -346,19 +426,31 @@
|
||||||
always:
|
always:
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
- name: test state=absent (expected changed=false)
|
- name: tidy up security group
|
||||||
ec2_group:
|
ec2_group:
|
||||||
name='{{ec2_group_name}}'
|
name: '{{ec2_group_name}}'
|
||||||
state=absent
|
state: absent
|
||||||
environment:
|
environment:
|
||||||
EC2_REGION: '{{ec2_region}}'
|
EC2_REGION: '{{ec2_region}}'
|
||||||
EC2_ACCESS_KEY: '{{ec2_access_key}}'
|
EC2_ACCESS_KEY: '{{ec2_access_key}}'
|
||||||
EC2_SECRET_KEY: '{{ec2_secret_key}}'
|
EC2_SECRET_KEY: '{{ec2_secret_key}}'
|
||||||
EC2_SECURITY_TOKEN: '{{security_token|default("")}}'
|
EC2_SECURITY_TOKEN: '{{security_token|default("")}}'
|
||||||
register: result
|
|
||||||
|
|
||||||
- name: assert state=absent (expected changed=false)
|
- name: tidy up automatically created SG
|
||||||
assert:
|
ec2_group:
|
||||||
that:
|
name: "{{ resource_prefix }} - Another security group"
|
||||||
- 'not result.changed'
|
state: absent
|
||||||
- 'not result.group_id'
|
ec2_region: '{{ec2_region}}'
|
||||||
|
ec2_access_key: '{{ec2_access_key}}'
|
||||||
|
ec2_secret_key: '{{ec2_secret_key}}'
|
||||||
|
security_token: '{{security_token}}'
|
||||||
|
|
||||||
|
- name: tidy up VPC
|
||||||
|
ec2_vpc_net:
|
||||||
|
name: "{{ resource_prefix }}-vpc"
|
||||||
|
state: absent
|
||||||
|
cidr_block: "10.232.232.128/26"
|
||||||
|
ec2_region: '{{ec2_region}}'
|
||||||
|
ec2_access_key: '{{ec2_access_key}}'
|
||||||
|
ec2_secret_key: '{{ec2_secret_key}}'
|
||||||
|
security_token: '{{security_token}}'
|
||||||
|
|
Loading…
Reference in a new issue