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')
Пример #2
0
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.")
Пример #3
0
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')
Пример #4
0
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
Пример #5
0
    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}'"
                )
Пример #6
0
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
Пример #7
0
    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')
Пример #8
0
def response_type(response):
    return 'ok' if u.is_good_response(response) else 'failed'
Пример #9
0
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