def delete_network(): existing_vpcs = u.get_vpc_dict() if VPC_NAME in existing_vpcs: vpc = ec2.Vpc(existing_vpcs[VPC_NAME].id) print("Deleting VPC %s (%s) subresources:" % (VPC_NAME, vpc.id)) for subnet in vpc.subnets.all(): try: sys.stdout.write("Deleting subnet %s ... " % subnet.id) sys.stdout.write(response_type(subnet.delete()) + '\n') except Exception as e: sys.stdout.write('failed\n') util.log_error(str(e) + '\n') for gateway in vpc.internet_gateways.all(): sys.stdout.write("Deleting gateway %s ... " % gateway.id) # note: if instances are using VPC, this fails with # botocore.exceptions.ClientError: An error occurred (DependencyViolation) when calling the DetachInternetGateway operation: Network vpc-ca4abab3 has some mapped public address(es). Please unmap those public address(es) before detaching the gateway. sys.stdout.write('detached ... ' if u.is_good_response( gateway.detach_from_vpc(VpcId=vpc.id)) else ' detach_failed ') sys.stdout.write('deleted ' if u.is_good_response(gateway.delete( )) else ' delete_failed ') sys.stdout.write('\n') def desc(): return "%s (%s)" % (route_table.id, u.get_name(route_table.tags)) for route_table in vpc.route_tables.all(): sys.stdout.write(f"Deleting route table {desc()} ... ") try: sys.stdout.write(response_type(route_table.delete()) + '\n') except Exception as e: sys.stdout.write('failed\n') util.log_error(str(e) + '\n') def desc(): return "%s (%s, %s)" % (security_group.id, u.get_name(security_group.tags), security_group.group_name) for security_group in vpc.security_groups.all(): # default group is undeletable, skip if security_group.group_name == 'default': continue sys.stdout.write('Deleting security group %s ... ' % (desc())) try: sys.stdout.write(response_type(security_group.delete()) + '\n') except Exception as e: sys.stdout.write('failed\n') util.log_error(str(e) + '\n') sys.stdout.write("Deleting VPC %s ... " % vpc.id) try: sys.stdout.write(response_type(vpc.delete()) + '\n') except Exception as e: sys.stdout.write('failed\n') util.log_error(str(e) + '\n')
def create_resources(): print(f"Creating {u.get_prefix()} resources in region {u.get_region()}") vpc, security_group = network_setup() keypair_setup() # saves private key locally to keypair_fn # create EFS efss = u.get_efs_dict() efs_name = u.get_efs_name() efs_id = efss.get(efs_name, '') if not efs_id: print("Creating EFS " + efs_name) efs_id = u.create_efs(efs_name) else: print("Reusing EFS " + efs_name) efs_client = u.get_efs_client() # create mount target for each subnet in the VPC # added retries because efs is not immediately available max_failures = 10 retry_interval_sec = 1 for subnet in vpc.subnets.all(): for retry_attempt in range(max_failures): try: sys.stdout.write("Creating efs mount target for %s ... " % (subnet.availability_zone, )) sys.stdout.flush() response = efs_client.create_mount_target( FileSystemId=efs_id, SubnetId=subnet.id, SecurityGroups=[security_group.id]) if u.is_good_response(response): print("success") break except Exception as e: if 'already exists' in str( e): # ignore "already exists" errors print('already exists') break # Takes couple of seconds for EFS to come online, with # errors like this: # Creating efs mount target for us-east-1f ... Failed with An error occurred (IncorrectFileSystemLifeCycleState) when calling the CreateMountTarget operation: None, retrying in 1 sec print("Got %s, retrying in %s sec" % (str(e), retry_interval_sec)) time.sleep(retry_interval_sec) else: print("Giving up.")
def delete_efs(): efss = u.get_efs_dict() efs_id = efss.get(EFS_NAME, '') efs_client = u.get_efs_client() if efs_id: try: # delete mount targets first print("About to delete %s (%s)" % (efs_id, EFS_NAME)) response = efs_client.describe_mount_targets(FileSystemId=efs_id) assert u.is_good_response(response) for mount_response in response['MountTargets']: id_ = mount_response['MountTargetId'] sys.stdout.write('Deleting mount target %s ... ' % (id_, )) sys.stdout.flush() response = efs_client.delete_mount_target(MountTargetId=id_) print(response_type(response)) sys.stdout.write('Deleting EFS %s (%s)... ' % (efs_id, EFS_NAME)) sys.stdout.flush() u.delete_efs_by_id(efs_id) except Exception as e: sys.stdout.write(f'failed with {e}\n') util.log_error(str(e) + '\n')
def network_setup() -> Tuple[Any, Any]: """Creates VPC if it doesn't already exists, configures it for public internet access, returns vpc, subnet, security_group""" # from https://gist.github.com/nguyendv/8cfd92fc8ed32ebb78e366f44c2daea6 ec2 = u.get_ec2_resource() client = u.get_ec2_client() existing_vpcs = u.get_vpc_dict() zones = u.get_zones() # create VPC from scratch. Remove this if default VPC works well enough. create_non_default_vpc = False if create_non_default_vpc: vpc_name = u.get_vpc_name() if u.get_vpc_name() in existing_vpcs: print("Reusing VPC " + vpc_name) vpc = existing_vpcs[vpc_name] subnets = list(vpc.subnets.all()) assert len(subnets) == len( zones ), "Has %s subnets, but %s zones, something went wrong during resource creation, try delete_resources.py/create_resources.py" % ( len(subnets), len(zones)) else: print("Creating VPC " + vpc_name) vpc = ec2.create_vpc(CidrBlock='192.168.0.0/16') # enable DNS on the VPC response = vpc.modify_attribute(EnableDnsHostnames={"Value": True}) assert u.is_good_response(response) response = vpc.modify_attribute(EnableDnsSupport={"Value": True}) assert u.is_good_response(response) vpc.create_tags(Tags=u.create_name_tags(vpc_name)) vpc.wait_until_available() gateways = u.get_gateway_dict(vpc) gateway_name = u.get_gateway_name() if gateway_name in gateways: print("Reusing gateways " + gateway_name) else: print("Creating internet gateway " + gateway_name) ig = ec2.create_internet_gateway() ig.attach_to_vpc(VpcId=vpc.id) ig.create_tags(Tags=u.create_name_tags(gateway_name)) # check that attachment succeeded attach_state = u.extract_attr_for_match(ig.attachments, State=-1, VpcId=vpc.id) assert attach_state == 'available', "vpc %s is in state %s" % ( vpc.id, attach_state) route_table = vpc.create_route_table() route_table_name = u.get_route_table_name() route_table.create_tags(Tags=u.create_name_tags(route_table_name)) dest_cidr = '0.0.0.0/0' route_table.create_route(DestinationCidrBlock=dest_cidr, GatewayId=ig.id) # check success for route in route_table.routes: # result looks like this # ec2.Route(route_table_id='rtb-a8b438cf', # destination_cidr_block='0.0.0.0/0') if route.destination_cidr_block == dest_cidr: break else: # sometimes get # AssertionError: Route for 0.0.0.0/0 not found in [ec2.Route(route_table_id='rtb-cd9153b0', destination_cidr_block='192.168.0.0/16')] # TODO: add a wait/retry? assert False, "Route for %s not found in %s" % ( dest_cidr, route_table.routes) assert len(zones) <= 16 # for cidr/20 to fit into cidr/16 ip = 0 for zone in zones: cidr_block = '192.168.%d.0/20' % (ip, ) ip += 16 print("Creating subnet %s in zone %s" % (cidr_block, zone)) subnet = vpc.create_subnet(CidrBlock=cidr_block, AvailabilityZone=zone) subnet.create_tags(Tags=[{ 'Key': 'Name', 'Value': f'{vpc_name}-subnet' }, { 'Key': 'Region', 'Value': zone }]) response = client.modify_subnet_attribute( MapPublicIpOnLaunch={'Value': True}, SubnetId=subnet.id) assert u.is_good_response(response) u.wait_until_available(subnet) assert subnet.map_public_ip_on_launch, "Subnet doesn't enable public IP by default, why?" route_table.associate_with_subnet(SubnetId=subnet.id) # Setup security group for non-default VPC # existing_security_groups = u.get_security_group_dict() # security_group_nd_name = u.get_security_group_nd_name() # if security_group_nd_name in existing_security_groups: # print("Reusing non-default security group " + security_group_nd_name) # security_group_nd = existing_security_groups[security_group_nd_name] # assert security_group_nd.vpc_id == vpc.id, f"Found non-default security group {security_group_nd} " \ # f"attached to {security_group_nd.vpc_id} but expected {vpc.id}" # else: # security_group_nd = create_security_group(security_group_nd_name, vpc.id) # Setup things on default VPC for zone-agnostic launching vpc = u.get_default_vpc() if not vpc: util.log(f"Creating default VPC for region {u.get_region()}") client.create_default_vpc() vpc = u.get_default_vpc() assert vpc, "Could not create default VPC?" existing_security_groups = u.get_security_group_dict() security_group_name = u.get_security_group_name() if security_group_name in existing_security_groups: print("Reusing security group " + security_group_name) security_group = existing_security_groups[security_group_name] assert security_group.vpc_id == vpc.id, f"Found security group {security_group} " \ f"attached to {security_group.vpc_id} but expected {vpc.id}" else: security_group = create_security_group(security_group_name, vpc.id) # Uncomment the following when setting up two VPC's # security_group = create_security_group(security_group_name, vpc.id, security_group_nd) return vpc, security_group
def authorize_from_group(this_security_group: SecurityGroup, other_security_group: SecurityGroup): """Helper function to authorize all traffic from other_group. Can be used to authorized within-group traffic as authorize_from_group(group, group)""" # Authorizing ingress doesn't work with security group names in a non-default VPC, # so must use more complicated syntax: https://github.com/boto/boto3/issues/158 response_ = {} for protocol in ['icmp']: try: rule = { 'FromPort': -1, 'IpProtocol': protocol, 'IpRanges': [], 'PrefixListIds': [], 'ToPort': -1, 'UserIdGroupPairs': [{ 'GroupId': other_security_group.id }] } response_ = this_security_group.authorize_ingress( IpPermissions=[rule]) except Exception as e: if response_['Error']['Code'] == 'InvalidPermission.Duplicate': print("Warning, got " + str(e)) else: assert False, "Failed while authorizing icml ingress with " + str( e) for protocol in ['tcp', 'udp']: try: rule = { 'FromPort': 0, 'IpProtocol': protocol, 'IpRanges': [], 'PrefixListIds': [], 'ToPort': 65535, 'UserIdGroupPairs': [{ 'GroupId': other_security_group.id }] } response_ = this_security_group.authorize_ingress( IpPermissions=[rule]) except Exception as e: if response_['Error']['Code'] == 'InvalidPermission.Duplicate': print("Warning, got " + str(e)) else: assert False, "Failed while authorizing tcp/udp ingress with " + str( e) # authorize EFA traffic user_id = u.get_account_number() response = None try: rule = { "IpProtocol": "-1", "Ipv6Ranges": [], "PrefixListIds": [], 'UserIdGroupPairs': [{ 'Description': 'efa', 'GroupId': other_security_group.id, 'UserId': user_id }] } response_ = this_security_group.authorize_ingress( IpPermissions=[rule]) assert u.is_good_response(response_), str(response) rule = { "IpProtocol": "-1", "PrefixListIds": [], 'UserIdGroupPairs': [{ 'Description': 'efa', 'GroupId': other_security_group.id, 'UserId': user_id }] } response_ = this_security_group.authorize_egress( IpPermissions=[rule]) assert u.is_good_response(response_), str(response) except Exception as e: if 'Error' in response_ and 'Code' in response[ 'Error'] and response_['Error'][ 'Code'] == 'InvalidPermission.Duplicate': print( f"Warning while authorizing ingress from {this_security_group.description} ({this_security_group.id}) to " f"{other_security_group.description} ({other_security_group.id}) with message '{e}'" ) else: assert False, ( f"Failed while authorizing ingress from {this_security_group.description} ({this_security_group.id}) to " f"{other_security_group.description} ({other_security_group.id}) with message '{e}' and response '{response}'" )
def create_security_group(security_group_name: str, vpc_id: str, other_group: Optional[SecurityGroup] = None): """Creates security group with proper ports open. Optionally allows all traffic from other_group""" print("Creating security group " + security_group_name) ec2 = u.get_ec2_resource() security_group: SecurityGroup = ec2.create_security_group( GroupName=security_group_name, Description=security_group_name, VpcId=vpc_id) security_group.create_tags(Tags=u.create_name_tags(security_group_name)) # allow ICMP access for public ping security_group.authorize_ingress(CidrIp='0.0.0.0/0', IpProtocol='icmp', FromPort=-1, ToPort=-1) # open public ports # always include SSH port which is required for basic functionality assert 22 in PUBLIC_TCP_RANGES, "Must enable SSH access" for port in PUBLIC_TCP_RANGES: if util.is_iterable(port): assert len(port) == 2 from_port, to_port = port else: from_port, to_port = port, port response = security_group.authorize_ingress(IpProtocol="tcp", CidrIp="0.0.0.0/0", FromPort=from_port, ToPort=to_port) assert u.is_good_response(response) for port in PUBLIC_UDP_RANGES: if util.is_iterable(port): assert len(port) == 2 from_port, to_port = port else: from_port, to_port = port, port response = security_group.authorize_ingress(IpProtocol="udp", CidrIp="0.0.0.0/0", FromPort=from_port, ToPort=to_port) assert u.is_good_response(response) def authorize_from_group(this_security_group: SecurityGroup, other_security_group: SecurityGroup): """Helper function to authorize all traffic from other_group. Can be used to authorized within-group traffic as authorize_from_group(group, group)""" # Authorizing ingress doesn't work with security group names in a non-default VPC, # so must use more complicated syntax: https://github.com/boto/boto3/issues/158 response_ = {} for protocol in ['icmp']: try: rule = { 'FromPort': -1, 'IpProtocol': protocol, 'IpRanges': [], 'PrefixListIds': [], 'ToPort': -1, 'UserIdGroupPairs': [{ 'GroupId': other_security_group.id }] } response_ = this_security_group.authorize_ingress( IpPermissions=[rule]) except Exception as e: if response_['Error']['Code'] == 'InvalidPermission.Duplicate': print("Warning, got " + str(e)) else: assert False, "Failed while authorizing icml ingress with " + str( e) for protocol in ['tcp', 'udp']: try: rule = { 'FromPort': 0, 'IpProtocol': protocol, 'IpRanges': [], 'PrefixListIds': [], 'ToPort': 65535, 'UserIdGroupPairs': [{ 'GroupId': other_security_group.id }] } response_ = this_security_group.authorize_ingress( IpPermissions=[rule]) except Exception as e: if response_['Error']['Code'] == 'InvalidPermission.Duplicate': print("Warning, got " + str(e)) else: assert False, "Failed while authorizing tcp/udp ingress with " + str( e) # authorize EFA traffic user_id = u.get_account_number() response = None try: rule = { "IpProtocol": "-1", "Ipv6Ranges": [], "PrefixListIds": [], 'UserIdGroupPairs': [{ 'Description': 'efa', 'GroupId': other_security_group.id, 'UserId': user_id }] } response_ = this_security_group.authorize_ingress( IpPermissions=[rule]) assert u.is_good_response(response_), str(response) rule = { "IpProtocol": "-1", "PrefixListIds": [], 'UserIdGroupPairs': [{ 'Description': 'efa', 'GroupId': other_security_group.id, 'UserId': user_id }] } response_ = this_security_group.authorize_egress( IpPermissions=[rule]) assert u.is_good_response(response_), str(response) except Exception as e: if 'Error' in response_ and 'Code' in response[ 'Error'] and response_['Error'][ 'Code'] == 'InvalidPermission.Duplicate': print( f"Warning while authorizing ingress from {this_security_group.description} ({this_security_group.id}) to " f"{other_security_group.description} ({other_security_group.id}) with message '{e}'" ) else: assert False, ( f"Failed while authorizing ingress from {this_security_group.description} ({this_security_group.id}) to " f"{other_security_group.description} ({other_security_group.id}) with message '{e}' and response '{response}'" ) authorize_from_group(security_group, security_group) # if using multiple security groups, which is required for the case of default + non-default VPC # also authorize all traffic between them if other_group: authorize_from_group(security_group, other_group) authorize_from_group(other_group, security_group) return security_group
def delete_vpc(vpc, partial=True): """Deletes VPC + all resources, if "partial" set to True, only deletes associated security groups """ print("Deleting VPC %s (%s) subresources:" % (VPC_NAME, vpc.id)) # don't modify default VPC if not partial: for subnet in vpc.subnets.all(): try: sys.stdout.write("Deleting subnet %s ... " % subnet.id) sys.stdout.write(response_type(subnet.delete()) + '\n') except Exception as e: sys.stdout.write('failed\n') util.log_error(str(e) + '\n') for gateway in vpc.internet_gateways.all(): sys.stdout.write("Deleting gateway %s ... " % gateway.id) # note: if instances are using VPC, this fails with # botocore.exceptions.ClientError: An error occurred (DependencyViolation) when calling the DetachInternetGateway operation: Network vpc-ca4abab3 has some mapped public address(es). Please unmap those public address(es) before detaching the gateway. sys.stdout.write('detached ... ' if u.is_good_response( gateway.detach_from_vpc( VpcId=vpc.id)) else ' detach_failed ') sys.stdout.write('deleted ' if u.is_good_response( gateway.delete()) else ' delete_failed ') sys.stdout.write('\n') def desc(): return "%s (%s)" % (route_table.id, u.get_name( route_table.tags)) for route_table in vpc.route_tables.all(): sys.stdout.write(f"Deleting route table {desc()} ... ") try: sys.stdout.write( response_type(route_table.delete()) + '\n') except Exception as e: sys.stdout.write('failed\n') util.log_error(str(e) + '\n') else: util.log( f"vpc {vpc.id} is a default VPC, only doing partial deletion") def desc(): return "%s (%s, %s)" % (security_group.id, u.get_name(security_group.tags), security_group.group_name) ncluster_security_groups = u.get_security_group_names() for security_group in vpc.security_groups.all(): # default group is undeletable, skip if security_group.group_name == 'default': continue # don't delete groups created outside of ncluster framework if security_group.group_name not in ncluster_security_groups: continue sys.stdout.write('Deleting security group %s ... ' % (desc())) try: sys.stdout.write(response_type(security_group.delete()) + '\n') except Exception as e: sys.stdout.write('failed\n') util.log_error(str(e) + '\n') if not partial: sys.stdout.write("Deleting VPC %s ... " % vpc.id) try: sys.stdout.write(response_type(vpc.delete()) + '\n') except Exception as e: sys.stdout.write('failed\n') util.log_error(str(e) + '\n')
def response_type(response): return 'ok' if u.is_good_response(response) else 'failed'
def network_setup(): """Creates VPC if it doesn't already exists, configures it for public internet access, returns vpc, subnet, security_group""" # from https://gist.github.com/nguyendv/8cfd92fc8ed32ebb78e366f44c2daea6 ec2 = u.get_ec2_resource() client = u.get_ec2_client() existing_vpcs = u.get_vpc_dict() zones = u.get_zones() # create VPC from scratch. Remove this if default VPC works well enough. vpc_name = u.get_vpc_name() if u.get_vpc_name() in existing_vpcs: print("Reusing VPC " + vpc_name) vpc = existing_vpcs[vpc_name] subnets = list(vpc.subnets.all()) assert len(subnets) == len( zones ), "Has %s subnets, but %s zones, something went wrong during resource creation, try delete_resources.py/create_resources.py" % ( len(subnets), len(zones)) else: print("Creating VPC " + vpc_name) vpc = ec2.create_vpc(CidrBlock='192.168.0.0/16') # enable DNS on the VPC response = vpc.modify_attribute(EnableDnsHostnames={"Value": True}) assert u.is_good_response(response) response = vpc.modify_attribute(EnableDnsSupport={"Value": True}) assert u.is_good_response(response) vpc.create_tags(Tags=u.create_name_tags(vpc_name)) vpc.wait_until_available() gateways = u.get_gateway_dict(vpc) gateway_name = u.get_gateway_name() if gateway_name in gateways: print("Reusing gateways " + gateway_name) else: print("Creating internet gateway " + gateway_name) ig = ec2.create_internet_gateway() ig.attach_to_vpc(VpcId=vpc.id) ig.create_tags(Tags=u.create_name_tags(gateway_name)) # check that attachment succeeded attach_state = u.extract_attr_for_match(ig.attachments, State=-1, VpcId=vpc.id) assert attach_state == 'available', "vpc %s is in state %s" % ( vpc.id, attach_state) route_table = vpc.create_route_table() route_table_name = u.get_route_table_name() route_table.create_tags(Tags=u.create_name_tags(route_table_name)) dest_cidr = '0.0.0.0/0' route_table.create_route(DestinationCidrBlock=dest_cidr, GatewayId=ig.id) # check success for route in route_table.routes: # result looks like this # ec2.Route(route_table_id='rtb-a8b438cf', # destination_cidr_block='0.0.0.0/0') if route.destination_cidr_block == dest_cidr: break else: # sometimes get # AssertionError: Route for 0.0.0.0/0 not found in [ec2.Route(route_table_id='rtb-cd9153b0', destination_cidr_block='192.168.0.0/16')] # TODO: add a wait/retry? assert False, "Route for %s not found in %s" % (dest_cidr, route_table.routes) assert len(zones) <= 16 # for cidr/20 to fit into cidr/16 ip = 0 for zone in zones: cidr_block = '192.168.%d.0/20' % (ip, ) ip += 16 print("Creating subnet %s in zone %s" % (cidr_block, zone)) subnet = vpc.create_subnet(CidrBlock=cidr_block, AvailabilityZone=zone) subnet.create_tags(Tags=[{ 'Key': 'Name', 'Value': f'{vpc_name}-subnet' }, { 'Key': 'Region', 'Value': zone }]) response = client.modify_subnet_attribute( MapPublicIpOnLaunch={'Value': True}, SubnetId=subnet.id) assert u.is_good_response(response) u.wait_until_available(subnet) assert subnet.map_public_ip_on_launch, "Subnet doesn't enable public IP by default, why?" route_table.associate_with_subnet(SubnetId=subnet.id) # Use default VPC from now on vpc = u.get_default_vpc() if not vpc: util.log(f"Creating default VPC for region {u.get_region()}") client.create_default_vpc() vpc = u.get_default_vpc() assert vpc, "Could not create default VPC?" existing_security_groups = u.get_security_group_dict() security_group_name = u.get_security_group_name() if security_group_name in existing_security_groups: print("Reusing security group " + security_group_name) security_group = existing_security_groups[security_group_name] assert security_group.vpc_id == vpc.id, f"Found security group {security_group} " \ f"attached to {security_group.vpc_id} but expected {vpc.id}" else: print("Creating security group " + security_group_name) security_group = ec2.create_security_group( GroupName=security_group_name, Description=security_group_name, VpcId=vpc.id) security_group.create_tags( Tags=u.create_name_tags(security_group_name)) # allow ICMP access for public ping security_group.authorize_ingress(CidrIp='0.0.0.0/0', IpProtocol='icmp', FromPort=-1, ToPort=-1) # open public ports # always include SSH port which is required for basic functionality assert 22 in PUBLIC_TCP_RANGES, "Must enable SSH access" for port in PUBLIC_TCP_RANGES: if util.is_iterable(port): assert len(port) == 2 from_port, to_port = port else: from_port, to_port = port, port response = security_group.authorize_ingress(IpProtocol="tcp", CidrIp="0.0.0.0/0", FromPort=from_port, ToPort=to_port) assert u.is_good_response(response) for port in PUBLIC_UDP_RANGES: if util.is_iterable(port): assert len(port) == 2 from_port, to_port = port else: from_port, to_port = port, port response = security_group.authorize_ingress(IpProtocol="udp", CidrIp="0.0.0.0/0", FromPort=from_port, ToPort=to_port) assert u.is_good_response(response) # allow ingress within security group # Authorizing ingress doesn't work with names in a non-default VPC, # so must use more complicated syntax # https://github.com/boto/boto3/issues/158 response = {} for protocol in ['icmp']: try: rule = { 'FromPort': -1, 'IpProtocol': protocol, 'IpRanges': [], 'PrefixListIds': [], 'ToPort': -1, 'UserIdGroupPairs': [{ 'GroupId': security_group.id }] } response = security_group.authorize_ingress( IpPermissions=[rule]) except Exception as e: if response['Error']['Code'] == 'InvalidPermission.Duplicate': print("Warning, got " + str(e)) else: assert False, "Failed while authorizing ingress with " + str( e) for protocol in ['tcp', 'udp']: try: rule = { 'FromPort': 0, 'IpProtocol': protocol, 'IpRanges': [], 'PrefixListIds': [], 'ToPort': 65535, 'UserIdGroupPairs': [{ 'GroupId': security_group.id }] } response = security_group.authorize_ingress( IpPermissions=[rule]) except Exception as e: if response['Error']['Code'] == 'InvalidPermission.Duplicate': print("Warning, got " + str(e)) else: assert False, "Failed while authorizing ingress with " + str( e) return vpc, security_group