Use Boto3 for ec2_group Fixes #23507 (#25340)

* Use Boto3 for ec2_group

Currently boto doesn't support ipv6. To support ipv6 in ec2_group, we need boto3.
boto3 has significant API changes, which caused more re-factoring for ec2_group module.
Added additional integration test to test_ec2_group role.

* Follow the standard for boto3 ansible

Fixed imports. Use boto3 ansible exception with camel_dict_to_snake_dict.
Refactored the call to authorize/revoke  ingress and egress.

* Removed dependancy with module ipaddress

Added new parameter called cidr_ipv6 for specifying
ipv6 addresses inline with how boto3 handles ipv6 addresses.

* Updated integration test

* Added ipv6 integration test for ec2_group

* Set purge_rules to false for integration test

* Fixed import statements

Added example for ipv6.
Removed defining HAS_BOTO3 variable and import HAS_BOTO3 from ec2.
Cleaned up import statements.

* Fixed exception handling

* Add IAM permissions for ec2_group tests

Missing AuthorizeSecurityGroupEgress necessary for latest tests

* Wrapped botocore import in try/except block

Import just botocore to be more similar to other modules
This commit is contained in:
sramakr 2017-07-16 22:03:31 -04:00 committed by Will Thames
parent 879acf378d
commit b980a5c02a
3 changed files with 370 additions and 156 deletions

View file

@ -35,6 +35,7 @@
"Effect": "Allow",
"Action": [
"ec2:AuthorizeSecurityGroupIngress",
"ec2:AuthorizeSecurityGroupEgress",
"ec2:CreateTags",
"ec2:DeleteRouteTable",
"ec2:DeleteSecurityGroup",

View file

@ -19,12 +19,12 @@ ANSIBLE_METADATA = {'metadata_version': '1.0',
'status': ['stableinterface'],
'supported_by': 'curated'}
DOCUMENTATION = '''
---
module: ec2_group
author: "Andrew de Quincey (@adq)"
version_added: "1.3"
requirements: [ boto3 ]
short_description: maintain an ec2 VPC security group.
description:
- maintains ec2 security groups. This module has a dependency on python-boto >= 2.5
@ -143,6 +143,7 @@ EXAMPLES = '''
from_port: 80
to_port: 80
cidr_ip: 0.0.0.0/0
cidr_ipv6: 64:ff9b::/96
group_name: example-other
# description to use if example-other needs to be created
group_desc: other example EC2 group
@ -178,6 +179,9 @@ EXAMPLES = '''
cidr_ip:
- 172.16.1.0/24
- 172.16.17.0/24
cidr_ipv6:
- 2607:F8B0::/32
- 64:ff9b::/96
group_id:
- sg-edcd9784
@ -191,17 +195,17 @@ import json
import re
import time
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ec2 import ec2_connect, ec2_argument_spec
from ansible.module_utils.ec2 import boto3_conn
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 camel_dict_to_snake_dict
from ansible.module_utils.ec2 import HAS_BOTO3
import traceback
try:
import boto.ec2
from boto.ec2.securitygroup import SecurityGroup
from boto.exception import BotoServerError
HAS_BOTO = True
import botocore
except ImportError:
HAS_BOTO = False
import traceback
pass # caught by imported HAS_BOTO3
def deduplicate_rules_args(rules):
@ -212,35 +216,33 @@ def deduplicate_rules_args(rules):
def make_rule_key(prefix, rule, group_id, cidr_ip):
"""Creates a unique key for an individual group rule"""
if isinstance(rule, dict):
if 'proto' in rule:
proto, from_port, to_port = [rule.get(x, None) for x in ('proto', 'from_port', 'to_port')]
# fix for 11177
if proto not in ['icmp', 'tcp', 'udp'] and from_port == -1 and to_port == -1:
from_port = 'none'
to_port = 'none'
else: # isinstance boto.ec2.securitygroup.IPPermissions
proto, from_port, to_port = [getattr(rule, x, None) for x in ('ip_protocol', 'from_port', 'to_port')]
elif 'IpProtocol' in rule:
proto, from_port, to_port = [rule.get(x, None) for x in ('IpProtocol', 'FromPort', 'ToPort')]
if proto not in ['icmp', 'tcp', 'udp'] and from_port == -1 and to_port == -1:
from_port = 'none'
to_port = 'none'
key = "%s-%s-%s-%s-%s-%s" % (prefix, proto, from_port, to_port, group_id, cidr_ip)
return key.lower().replace('-none', '-None')
def addRulesToLookup(rules, prefix, rules_dict):
for rule in rules:
for grant in rule.grants:
rules_dict[make_rule_key(prefix, rule, grant.group_id, grant.cidr_ip)] = (rule, grant)
def add_rules_to_loopkup(ipPermissions, group_id, prefix, dict):
for rule in ipPermissions:
for groupGrant in rule.get('UserIdGroupPairs'):
dict[make_rule_key(prefix, rule, group_id, groupGrant.get('GroupId'))] = (rule, groupGrant)
for ipv4Grants in rule.get('IpRanges'):
dict[make_rule_key(prefix, rule, group_id, ipv4Grants.get('CidrIp'))] = (rule, ipv4Grants)
for ipv6Grants in rule.get('Ipv6Ranges'):
dict[make_rule_key(prefix, rule, group_id, ipv6Grants.get('CidrIpv6'))] = (rule, ipv6Grants)
def validate_rule(module, rule):
VALID_PARAMS = ('cidr_ip',
VALID_PARAMS = ('cidr_ip', 'cidr_ipv6',
'group_id', 'group_name', 'group_desc',
'proto', 'from_port', 'to_port')
if not isinstance(rule, dict):
module.fail_json(msg='Invalid rule parameter type [%s].' % type(rule))
for k in rule:
if k not in VALID_PARAMS:
module.fail_json(msg='Invalid rule parameter \'{}\''.format(k))
@ -249,6 +251,12 @@ def validate_rule(module, rule):
module.fail_json(msg='Specify group_id OR cidr_ip, not both')
elif 'group_name' in rule and 'cidr_ip' in rule:
module.fail_json(msg='Specify group_name OR cidr_ip, not both')
elif 'group_id' in rule and 'cidr_ipv6' in rule:
module.fail_json(msg="Specify group_id OR cidr_ipv6, not both")
elif 'group_name' in rule and 'cidr_ipv6' in rule:
module.fail_json(msg="Specify group_name OR cidr_ipv6, not both")
elif 'cidr_ip' in rule and 'cidr_ipv6' in rule:
module.fail_json(msg="Specify cidr_ip OR cidr_ipv6, not both")
elif 'group_id' in rule and 'group_name' in rule:
module.fail_json(msg='Specify group_id OR group_name, not both')
@ -270,17 +278,25 @@ def get_target_from_rule(module, ec2, rule, name, group, groups, vpc_id):
group_id = None
group_name = None
ip = None
ipv6 = None
target_group_created = False
if 'group_id' in rule and 'cidr_ip' in rule:
module.fail_json(msg="Specify group_id OR cidr_ip, not both")
elif 'group_name' in rule and 'cidr_ip' in rule:
module.fail_json(msg="Specify group_name OR cidr_ip, not both")
elif 'group_id' in rule and 'cidr_ipv6' in rule:
module.fail_json(msg="Specify group_id OR cidr_ipv6, not both")
elif 'group_name' in rule and 'cidr_ipv6' in rule:
module.fail_json(msg="Specify group_name OR cidr_ipv6, not both")
elif 'group_id' in rule and 'group_name' in rule:
module.fail_json(msg="Specify group_id OR group_name, not both")
elif rule.get('group_id') and re.match(FOREIGN_SECURITY_GROUP_REGEX, rule['group_id']):
elif 'cidr_ip' in rule and 'cidr_ipv6' in rule:
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']):
# 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()
group_instance = SecurityGroup(owner_id=owner_id, name=group_name, id=group_id)
group_instance = SecurityGroup(owner_id=owner_id, group_name=group_name, id=group_id)
groups[group_id] = group_instance
groups[group_name] = group_instance
elif 'group_id' in rule:
@ -295,7 +311,8 @@ def get_target_from_rule(module, ec2, rule, name, group, groups, vpc_id):
group_id = groups[group_name].id
else:
if not rule.get('group_desc', '').strip():
module.fail_json(msg="group %s will be automatically created by rule %s and no description was provided" % (group_name, rule))
module.fail_json(msg="group %s will be automatically created by rule %s and "
"no description was provided" % (group_name, rule))
if not module.check_mode:
auto_group = ec2.create_security_group(group_name, rule['group_desc'], vpc_id=vpc_id)
group_id = auto_group.id
@ -304,8 +321,10 @@ def get_target_from_rule(module, ec2, rule, name, group, groups, vpc_id):
target_group_created = True
elif 'cidr_ip' in rule:
ip = rule['cidr_ip']
elif 'cidr_ipv6' in rule:
ipv6 = rule['cidr_ipv6']
return group_id, ip, target_group_created
return group_id, ip, ipv6, target_group_created
def ports_expand(ports):
@ -351,7 +370,7 @@ def rules_expand_ports(rules):
def rule_expand_source(rule, source_type):
# takes a rule dict and returns a list of expanded rule dicts for specified source_type
sources = rule[source_type] if isinstance(rule[source_type], list) else [rule[source_type]]
source_types_all = ('cidr_ip', 'group_id', 'group_name')
source_types_all = ('cidr_ip', 'cidr_ipv6', 'group_id', 'group_name')
rule_expanded = []
for source in sources:
@ -366,7 +385,7 @@ def rule_expand_source(rule, source_type):
def rule_expand_sources(rule):
# takes a rule dict and returns a list of expanded rule discts
source_types = (stype for stype in ('cidr_ip', 'group_id', 'group_name') if stype in rule)
source_types = (stype for stype in ('cidr_ip', 'cidr_ipv6', 'group_id', 'group_name') if stype in rule)
return [r for stype in source_types
for r in rule_expand_source(rule, stype)]
@ -381,6 +400,79 @@ def rules_expand_sources(rules):
for rule in rule_expand_sources(rule_complex)]
def authorize_ip(type, changed, client, group, groupRules,
ip, ip_permission, module, rule, ethertype):
# If rule already exists, don't later delete it
for thisip in ip:
rule_id = make_rule_key(type, rule, group.id, thisip)
if rule_id in groupRules:
del groupRules[rule_id]
else:
if not module.check_mode:
ip_permission = serialize_ip_grant(rule, thisip, ethertype)
if ip_permission:
try:
if type == "in":
client.authorize_security_group_ingress(GroupId=group.group_id,
IpPermissions=[ip_permission])
elif type == "out":
client.authorize_security_group_egress(GroupId=group.group_id,
IpPermissions=[ip_permission])
except botocore.exceptions.ClientError as e:
module.fail_json(msg="Unable to authorize %s for ip %s security group '%s' - %s" %
(type, thisip, group.group_name, e),
exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
changed = True
return changed, ip_permission
def serialize_group_grant(group_id, rule):
permission = {'IpProtocol': rule['proto'],
'FromPort': rule['from_port'],
'ToPort': rule['to_port'],
'UserIdGroupPairs': [{'GroupId': group_id}]}
return permission
def serialize_revoke(grant, rule):
permission = dict()
fromPort = rule['FromPort'] if 'FromPort' in rule else None
toPort = rule['ToPort'] if 'ToPort' in rule else None
if 'GroupId' in grant:
permission = {'IpProtocol': rule['IpProtocol'],
'FromPort': fromPort,
'ToPort': toPort,
'UserIdGroupPairs': [{'GroupId': grant['GroupId'], 'UserId': grant['UserId']}]
}
elif 'CidrIp' in grant:
permission = {'IpProtocol': rule['IpProtocol'],
'FromPort': fromPort,
'ToPort': toPort,
'IpRanges': [grant]
}
elif 'CidrIpv6' in grant:
permission = {'IpProtocol': rule['IpProtocol'],
'FromPort': fromPort,
'ToPort': toPort,
'Ipv6Ranges': [grant]
}
if rule['IpProtocol'] in ('all', '-1', -1):
del permission['FromPort']
del permission['ToPort']
return permission
def serialize_ip_grant(rule, thisip, ethertype):
permission = {'IpProtocol': rule['proto'],
'FromPort': rule['from_port'],
'ToPort': rule['to_port']}
if ethertype == "ipv4":
permission.update({'IpRanges': [{'CidrIp': thisip}]})
elif ethertype == "ipv6":
permission.update({'Ipv6Ranges': [{'CidrIpv6': thisip}]})
return permission
def main():
argument_spec = ec2_argument_spec()
argument_spec.update(dict(
@ -392,8 +484,7 @@ def main():
rules_egress=dict(type='list'),
state=dict(default='present', type='str', choices=['present', 'absent']),
purge_rules=dict(default=True, required=False, type='bool'),
purge_rules_egress=dict(default=True, required=False, type='bool'),
purge_rules_egress=dict(default=True, required=False, type='bool')
)
)
module = AnsibleModule(
@ -403,8 +494,8 @@ def main():
required_if=[['state', 'present', ['name']]],
)
if not HAS_BOTO:
module.fail_json(msg='boto required for this module')
if not HAS_BOTO3:
module.fail_json(msg='boto3 required for this module')
name = module.params['name']
group_id = module.params['group_id']
@ -420,32 +511,43 @@ def main():
module.fail_json(msg='Must provide description when state is present.')
changed = False
ec2 = ec2_connect(module)
# find the group if present
region, ec2_url, aws_connect_params = get_aws_connection_info(module, boto3=True)
if not region:
module.fail_json(msg="The AWS region must be specified as an "
"environment variable or in the AWS credentials "
"profile.")
client, ec2 = boto3_conn(module, conn_type='both', resource='ec2', endpoint=ec2_url, region=region, **aws_connect_params)
group = None
groups = {}
groups = dict()
security_groups = []
# do get all security groups
# find if the group is present
try:
security_groups = ec2.get_all_security_groups()
except BotoServerError as e:
module.fail_json(msg="Error in get_all_security_groups: %s" % e.message, exception=traceback.format_exc())
response = client.describe_security_groups()
if 'SecurityGroups' in response:
security_groups = response.get('SecurityGroups')
except botocore.exceptions.NoCredentialsError as e:
module.fail_json(msg="Error in describe_security_groups: %s" % "Unable to locate credentials", exception=traceback.format_exc())
except botocore.exceptions.ClientError as e:
module.fail_json(msg="Error in describe_security_groups: %s" % e, exception=traceback.format_exc(),
**camel_dict_to_snake_dict(e.response))
for curGroup in security_groups:
groups[curGroup.id] = curGroup
if curGroup.name in groups:
for sg in security_groups:
curGroup = ec2.SecurityGroup(sg['GroupId'])
groups[curGroup.id] = ec2.SecurityGroup(curGroup.id)
groupName = curGroup.group_name
if groupName in groups:
# Prioritise groups from the current VPC
if vpc_id is None or curGroup.vpc_id == vpc_id:
groups[curGroup.name] = curGroup
groups[groupName] = curGroup
else:
groups[curGroup.name] = curGroup
groups[groupName] = curGroup
if group_id:
if curGroup.id == group_id:
group = curGroup
else:
if curGroup.name == name and (vpc_id is None or curGroup.vpc_id == vpc_id):
if groupName == name and (vpc_id is None or curGroup.vpc_id == vpc_id):
group = curGroup
# Ensure requested group is absent
@ -455,8 +557,9 @@ def main():
try:
if not module.check_mode:
group.delete()
except BotoServerError as e:
module.fail_json(msg="Unable to delete security group '%s' - %s" % (group, e.message), exception=traceback.format_exc())
except botocore.exceptions.ClientError as 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))
else:
group = None
changed = True
@ -469,40 +572,41 @@ def main():
if group:
# existing group
if group.description != description:
module.fail_json(msg="Group description does not match existing group. ec2_group does not support this case.")
module.fail_json(
msg="Group description does not match existing group. ec2_group does not support this case.")
# if the group doesn't exist, create it now
else:
# no match found, create it
if not module.check_mode:
group = ec2.create_security_group(name, description, vpc_id=vpc_id)
group = client.create_security_group(GroupName=name, Description=description)
groupId = group.get('GroupId')
# When a group is created, an egress_rule ALLOW ALL
# to 0.0.0.0/0 is added automatically but it's not
# reflected in the object returned by the AWS API
# 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
while len(ec2.get_all_security_groups(filters={'group_id': group.id})) == 0:
while len(client.describe_security_groups(GroupIds=[groupId])
['SecurityGroups'][0]['IpPermissionsEgress']) == 0:
time.sleep(0.1)
group = ec2.get_all_security_groups(group_ids=(group.id,))[0]
group = ec2.SecurityGroup(groupId)
changed = True
else:
module.fail_json(msg="Unsupported state requested: %s" % state)
# create a lookup for all existing rules on the group
if group:
# Manage ingress rules
groupRules = {}
addRulesToLookup(group.rules, 'in', groupRules)
add_rules_to_loopkup(group.ip_permissions, group.id, 'in', groupRules)
# Now, go through all provided rules and ensure they are there.
if rules is not None:
ip_permission = []
for rule in rules:
validate_rule(module, rule)
group_id, ip, target_group_created = get_target_from_rule(module, ec2, rule, name, group, groups, vpc_id)
group_id, ip, ipv6, target_group_created = get_target_from_rule(module, ec2, rule, name,
group, groups, vpc_id)
if target_group_created:
changed = True
@ -511,49 +615,63 @@ def main():
rule['from_port'] = None
rule['to_port'] = None
# Convert ip to list we can iterate over
if not isinstance(ip, list):
ip = [ip]
# If rule already exists, don't later delete it
for thisip in ip:
ruleId = make_rule_key('in', rule, group_id, thisip)
if ruleId not in groupRules:
grantGroup = None
if group_id:
grantGroup = groups[group_id]
if not module.check_mode:
group.authorize(rule['proto'], rule['from_port'], rule['to_port'], thisip, grantGroup)
changed = True
if group_id:
rule_id = make_rule_key('in', rule, group.id, group_id)
if rule_id in groupRules:
del groupRules[rule_id]
else:
del groupRules[ruleId]
if not module.check_mode:
ip_permission = serialize_group_grant(group_id, rule)
if ip_permission:
ips = ip_permission
if vpc_id:
[useridpair.update({'VpcId': vpc_id}) for useridpair in
ip_permission.get('UserIdGroupPairs')]
try:
client.authorize_security_group_ingress(GroupId=group.group_id, IpPermissions=[ips])
except botocore.exceptions.ClientError as e:
module.fail_json(
msg="Unable to authorize ingress for group %s security group '%s' - %s" %
(group_id, group.group_name, e),
exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
changed = True
elif ip:
# Convert ip to list we can iterate over
if ip and not isinstance(ip, list):
ip = [ip]
changed, ip_permission = authorize_ip("in", changed, client, group, groupRules, ip, ip_permission,
module, rule, "ipv4")
elif ipv6:
# Convert ip to list we can iterate over
if not isinstance(ipv6, list):
ipv6 = [ipv6]
# If rule already exists, don't later delete it
changed, ip_permission = authorize_ip("in", changed, client, group, groupRules, ipv6, ip_permission,
module, rule, "ipv6")
# Finally, remove anything left in the groupRules -- these will be defunct rules
if purge_rules:
for (rule, grant) in groupRules.values():
grantGroup = None
if grant.group_id:
if grant.owner_id != group.owner_id:
# this is a foreign Security Group. Since you can't fetch it you must create an instance of it
group_instance = SecurityGroup(owner_id=grant.owner_id, name=grant.name, id=grant.group_id)
groups[grant.group_id] = group_instance
groups[grant.name] = group_instance
grantGroup = groups[grant.group_id]
ip_permission = serialize_revoke(grant, rule)
if not module.check_mode:
group.revoke(rule.ip_protocol, rule.from_port, rule.to_port, grant.cidr_ip, grantGroup)
try:
client.revoke_security_group_ingress(GroupId=group.group_id, IpPermissions=[ip_permission])
except botocore.exceptions.ClientError as e:
module.fail_json(
msg="Unable to revoke ingress for security group '%s' - %s" %
(group.group_name, e),
exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
changed = True
# Manage egress rules
groupRules = {}
addRulesToLookup(group.rules_egress, 'out', groupRules)
add_rules_to_loopkup(group.ip_permissions_egress, group.id, 'out', groupRules)
# Now, go through all provided rules and ensure they are there.
if rules_egress is not None:
for rule in rules_egress:
validate_rule(module, rule)
group_id, ip, target_group_created = get_target_from_rule(module, ec2, rule, name, group, groups, vpc_id)
group_id, ip, ipv6, target_group_created = get_target_from_rule(module, ec2, rule, name,
group, groups, vpc_id)
if target_group_created:
changed = True
@ -562,45 +680,58 @@ def main():
rule['from_port'] = None
rule['to_port'] = None
# Convert ip to list we can iterate over
if not isinstance(ip, list):
ip = [ip]
# If rule already exists, don't later delete it
for thisip in ip:
ruleId = make_rule_key('out', rule, group_id, thisip)
if ruleId in groupRules:
del groupRules[ruleId]
# Otherwise, add new rule
if group_id:
rule_id = make_rule_key('out', rule, group.id, group_id)
if rule_id in groupRules:
del groupRules[rule_id]
else:
grantGroup = None
if group_id:
grantGroup = groups[group_id].id
if not module.check_mode:
ec2.authorize_security_group_egress(
group_id=group.id,
ip_protocol=rule['proto'],
from_port=rule['from_port'],
to_port=rule['to_port'],
src_group_id=grantGroup,
cidr_ip=thisip)
ip_permission = serialize_group_grant(group_id, rule)
if ip_permission:
ips = ip_permission
if vpc_id:
[useridpair.update({'VpcId': vpc_id}) for useridpair in
ip_permission.get('UserIdGroupPairs')]
try:
client.authorize_security_group_egress(GroupId=group.group_id, IpPermissions=[ips])
except botocore.exceptions.ClientError as e:
module.fail_json(
msg="Unable to authorize egress for group %s security group '%s' - %s" %
(group_id, group.group_name, e),
exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
changed = True
elif ip:
# Convert ip to list we can iterate over
if not isinstance(ip, list):
ip = [ip]
changed, ip_permission = authorize_ip("out", changed, client, group, groupRules, ip,
ip_permission, module, rule, "ipv4")
elif ipv6:
# Convert ip to list we can iterate over
if not isinstance(ipv6, list):
ipv6 = [ipv6]
# If rule already exists, don't later delete it
changed, ip_permission = authorize_ip("out", changed, client, group, groupRules, ipv6,
ip_permission, module, rule, "ipv6")
else:
# when no egress rules are specified,
# we add in a default allow all out rule, which was the
# default behavior before egress rules were added
default_egress_rule = 'out--1-None-None-None-0.0.0.0/0'
default_egress_rule = 'out--1-None-None-' + group.id + '-0.0.0.0/0'
if default_egress_rule not in groupRules:
if not module.check_mode:
ec2.authorize_security_group_egress(
group_id=group.id,
ip_protocol=-1,
from_port=None,
to_port=None,
src_group_id=None,
cidr_ip='0.0.0.0/0'
)
ip_permission = [{'IpProtocol': '-1',
'IpRanges': [{'CidrIp': '0.0.0.0/0'}]
}
]
try:
client.authorize_security_group_egress(GroupId=group.group_id, IpPermissions=ip_permission)
except botocore.exceptions.ClientError as e:
module.fail_json(msg="Unable to authorize egress for ip %s security group '%s' - %s" %
('0.0.0.0/0',
group.group_name,
e),
exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
changed = True
else:
# make sure the default egress rule is not removed
@ -609,24 +740,24 @@ def main():
# Finally, remove anything left in the groupRules -- these will be defunct rules
if purge_rules_egress:
for (rule, grant) in groupRules.values():
grantGroup = None
if grant.group_id:
grantGroup = groups[grant.group_id].id
if not module.check_mode:
ec2.revoke_security_group_egress(
group_id=group.id,
ip_protocol=rule.ip_protocol,
from_port=rule.from_port,
to_port=rule.to_port,
src_group_id=grantGroup,
cidr_ip=grant.cidr_ip)
changed = True
# we shouldn't be revoking 0.0.0.0 egress
if grant != '0.0.0.0/0':
ip_permission = serialize_revoke(grant, rule)
if not module.check_mode:
try:
client.revoke_security_group_egress(GroupId=group.group_id, IpPermissions=[ip_permission])
except botocore.exceptions.ClientError as e:
module.fail_json(msg="Unable to revoke egress for ip %s security group '%s' - %s" %
(grant,
group.group_name,
e),
exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
changed = True
if group:
module.exit_json(changed=changed, group_id=group.id)
else:
module.exit_json(changed=changed, group_id=None)
if __name__ == '__main__':
main()

View file

@ -63,21 +63,6 @@
- 'result.failed'
- 'result.msg == "Must provide description when state is present."'
# ============================================================
- name: test invalid region parameter
ec2_group:
name='{{ec2_group_name}}'
description='{{ec2_group_description}}'
region='asdf querty 1234'
register: result
ignore_errors: true
- name: assert invalid region parameter
assert:
that:
- 'result.failed'
- 'result.msg.startswith("Region asdf querty 1234 does not seem to be available for aws module boto.ec2. If the region definitely exists, you may need to upgrade boto or extend with endpoints_path")'
# ============================================================
- name: test valid region parameter
ec2_group:
@ -91,7 +76,7 @@
assert:
that:
- 'result.failed'
- 'result.msg.startswith("No handler was ready to authenticate.")'
- '"Unable to locate credentials" in result.msg'
# ============================================================
- name: test environment variable EC2_REGION
@ -107,7 +92,7 @@
assert:
that:
- 'result.failed'
- 'result.msg.startswith("No handler was ready to authenticate.")'
- '"Unable to locate credentials" in result.msg'
# ============================================================
- name: test invalid ec2_url parameter
@ -123,7 +108,7 @@
assert:
that:
- 'result.failed'
- 'result.msg.startswith("No handler was ready to authenticate.")'
- 'result.msg.startswith("The AWS region must be specified as an environment variable or in the AWS credentials profile")'
# ============================================================
- name: test valid ec2_url parameter
@ -139,7 +124,7 @@
assert:
that:
- 'result.failed'
- 'result.msg.startswith("No handler was ready to authenticate.")'
- 'result.msg.startswith("The AWS region must be specified as an environment variable or in the AWS credentials profile")'
# ============================================================
- name: test credentials from environment
@ -157,7 +142,7 @@
assert:
that:
- 'result.failed'
- '"Error in get_all_security_groups: AWS was not able to validate the provided access credentials" in result.msg'
- '"validate the provided access credentials" in result.msg'
# ============================================================
- name: test credential parameters
@ -174,7 +159,7 @@
assert:
that:
- 'result.failed'
- '"Error in get_all_security_groups: AWS was not able to validate the provided access credentials" in result.msg'
- '"validate the provided access credentials" in result.msg'
# ============================================================
- name: test state=absent
@ -243,6 +228,103 @@
- 'not result.changed'
- 'result.group_id.startswith("sg-")'
# ============================================================
- name: test state=present for ipv6 (expected changed=true)
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
rules:
- proto: "tcp"
from_port: 8182
to_port: 8182
cidr_ipv6: "64:ff9b::/96"
register: result
- name: assert state=present (expected changed=true)
assert:
that:
- 'result.changed'
- 'result.group_id.startswith("sg-")'
# ============================================================
- name: test rules_egress state=present for ipv6 (expected changed=true)
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
rules:
- proto: "tcp"
from_port: 8182
to_port: 8182
cidr_ipv6: "64:ff9b::/96"
rules_egress:
- proto: "tcp"
from_port: 8181
to_port: 8181
cidr_ipv6: "64:ff9b::/96"
register: result
- name: assert state=present (expected changed=true)
assert:
that:
- 'result.changed'
- 'result.group_id.startswith("sg-")'
# ============================================================
- name: test state=present for ipv4 (expected changed=true)
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
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.group_id.startswith("sg-")'
# ============================================================
- name: add same rule to the existing group (expected changed=false)
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
rules:
- proto: "tcp"
from_port: 8182
to_port: 8182
cidr_ip: "1.1.1.1/32"
register: result
- name: assert state=present (expected changed=false)
assert:
that:
- 'not result.changed'
- 'result.group_id.startswith("sg-")'
# ============================================================
- name: test state=absent (expected changed=true)
ec2_group: