def describe_vpc_cached(vpc_id: str, account_id: str, region: str, **kwargs) -> dict: """ Describe a specific VPC from our cache of VPC descriptions for a given account and region Return a dict if things go as expected. We accept additional arguments here to handle the case where the arguments are given as a dictionary with additional keys. Such as when filtering the main config for only VPCs which actually exist. :param vpc_id: A vpc id e.g. vpc-abc123 :type vpc_id: str :param account_id: An AWS account id number :type account: str :param region: An AWS region where the service client should be created. e.g. us-east-1 :type region: str :param **kwargs: Handle trailing arguments in case where dictionary of arguments is provided. :type **kwargs: dict :returns: A list of dictionaries representing all the VPCs for a given account and region. :rtype: list """ LOGGER.debug( f'Fetching description of VPC {vpc_id} for account {account_id} in region {region}' ) vpcs = describe_account_vpcs_cached(account_id, region) if vpc_dict := next((x for x in vpcs if x['VpcId'] == vpc_id), None): return vpc_dict
def get_all_env_peerings(config: Sequence[dict], metadata: Mapping) -> List[dict]: """ Given the configuration, find all the peerings which exist across all accounts and regions. See https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2.html#EC2.Client.describe_vpc_peering_connections :param config: A list containing dictionaries representing the configuration for the environment :type config: list :param key: The key to deduplicate on. :returns: A deduplicated list of all peerings for this environment. :rtype: list """ # Get a dictionary of accounts, regions, vpcs and the vpcs they should be peered to config_peer_map = deepcopy(get_peering_map(config)) # Initialise a filter to get vpc peerings relevant to the current environment being worked on filters = [{'Name': 'tag:peerd_created', 'Values': ['true']}, {'Name': 'tag:peerd_environment', 'Values': [metadata['environment']]}, {'Name': 'status-code', 'Values': ['active']}] # Get all existing peerings from accounts listed in the config file peerings: List[dict] = [] for account_id in config_peer_map.keys(): for region in config_peer_map[account_id].keys(): peerings.extend(get_all_peerings(account_id, region, filters)) # Now get all existing peerings from all accounts that participate in peerings # (There might be accounts not listed in the config file) for peering in peerings: for side in ('RequesterVpcInfo', 'AccepterVpcInfo'): account_id = peering[side]['OwnerId'] region = peering[side]['Region'] if not config_peer_map[account_id][region]: LOGGER.debug(f'Found peering to unconfigured account {account_id} region {region}. Inspecting...') peerings.extend(get_all_peerings(account_id, region, filters)) config_peer_map[account_id][region] = True LOGGER.debug(f'Discovered additional peerings in {account_id} {region}') # Add the 2 lists, and ceduplicate peerings. These will be all existing peerings even between accounts deleted from the config file return deduplicate_list_dicts(peerings, 'VpcPeeringConnectionId')
def aws_client(account: str, service: str, region: str): """ Initialises a sts client, gets assume role credentials and initialises a AWS client connection to a requested resource. Returns the AWS product/resource client connection. Caches the client connection for re-use. For reference: http://boto3.readthedocs.io/en/latest/guide/configuration.html :param account: An AWS account id number :type account: str :param service: An AWS service e.g. ec2 :type service: str :param region: An AWS region where the service client should be created. e.g. us-east-1 :type region: str """ global CLIENT_CACHE # In this block we get credentials for the target account by assuming # into the given account using a sts client connection # If we can't we return none and cache none. if not (credentials := get_role_credentials(account, aws_sts_client())): LOGGER.warning( f'Unable to tokenise into {account} for service {service} in region {region}. Moving on' ) CLIENT_CACHE[account][region][service] = None return None
def common_vpc_cidrs(peer_desc: list) -> bool: """ Returns true if the VPCs share any CIDRs in common Returns false if the VPCs share no CIDRs in common Example Input: ``` [ { 'account_id': '1234567890', 'vpc_id': 'vpc-123', 'region': 'us-west-2' } { 'account_id': '3456789012', 'vpc_id': 'vpc-456', 'region': 'us-west-1' } ] ``` :param peer_desc: A list containing two dictionaries describing the account, vpc and region of two sides of a peering :type peer_desc: list :returns: True if there 1 or more common CIDRs, False otherwise. """ requester_vpc_cidrs = list_vpc_cidrs(peer_desc[0]['vpc_id'], peer_desc[0]['account_id'], peer_desc[0]['region']) accepter_vpc_cidrs = list_vpc_cidrs(peer_desc[1]['vpc_id'], peer_desc[1]['account_id'], peer_desc[1]['region']) if bool(set(requester_vpc_cidrs) & set(accepter_vpc_cidrs)): LOGGER.info(f'VPC {peer_desc[0]["vpc_id"]} cidrs {requester_vpc_cidrs} and {peer_desc[0]["vpc_id"]} cidrs {accepter_vpc_cidrs} share a common cidr.') return bool(set(requester_vpc_cidrs) & set(accepter_vpc_cidrs))
def aws_sts_client() -> Any: """ Uses default boto credentials locations, such as the instance metadata to return a sts client connection and caches it as a global variable for reuse. :returns: AWS STS client connection """ sts_client = boto3.client('sts', config=Config(retries=dict(max_attempts=10))) LOGGER.info(f'Found the following STS identity:\n{json.dumps(sts_client.get_caller_identity(), indent=2)}') return sts_client
def get_vpc_peering(vpc_id: str, remote_vpc_id: str, account_id: str, region: str, filters: list = []) -> dict: """ Returns a dictionary describing a specific VPC peering between two VPCs From the perspective of a given account. Note: Same peering can have slightly different content based on perspective account. Note: This function is greedy in the sense that the requester does not have to be the requester as AWS defines it in https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2.html This function will match peerings even when the remote_vpc_id is the requester. Example usage: ``` filters = [{'Name': 'tag:peerd_created', 'Values': ['true']}, {'Name': 'tag:peerd_environment', 'Values': [metadata['environment']]}, {'Name': 'status-code', 'Values': ['active']}] if not (peering := get_vpc_peering(vpc_id, remote_vpc_id, account_id, region, filters)): LOGGER.warning(f'No active peering between {vpc_id} and {remote_vpc_id} for this environment' f' {metadata["environment"]}. It may exist as part of another environment.') continue ``` :param vpc_id: A vpc id e.g. vpc-abc123 :type vpc_id: str :param remote_vpc_id: A vpc id e.g. vpc-abc123 :type remote_vpc_id: str :param account_id: An AWS account id number :type account: str :param region: An AWS region. e.g. us-east-1 :type region: str :param filters: A standard list of boto3 filters (list of dics). :type filters: list :returns: A list containing all peering dictionaries for an account-region. :rtype: list """ vpc_peerings = get_all_peerings(account_id, region, filters) for peering in vpc_peerings: if peering['AccepterVpcInfo']['VpcId'] in [vpc_id, remote_vpc_id]: if peering['RequesterVpcInfo']['VpcId'] in [vpc_id, remote_vpc_id]: LOGGER.debug( f"Found peering {peering['VpcPeeringConnectionId']} between {vpc_id} and {remote_vpc_id}" ) return peering LOGGER.debug( f"No active peering between {vpc_id} and {remote_vpc_id} for given filters {filters}" ) return None
def describe_account_vpcs_cached(account_id: str, region: str) -> List[dict]: """ Describe all the VPCs in a given account in a given region and return them as a list of descriptions See https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2.html#EC2.Client.describe_vpcs :param account_id: An AWS account id number :type account: str :param region: An AWS region where the service client should be created. e.g. us-east-1 :type region: str :returns: A list of dictionaries representing all the VPCs for a given account and region. :rtype: list """ LOGGER.info(f'Fetching description of all VPCs for account {account_id} in region {region}') ec2_client = aws_client(account_id, 'ec2', region) return ec2_client.describe_vpcs()['Vpcs']
def check_iam_role_capability(account_id: str) -> bool: """ Check that we can assume a role in each account and perform an ec2 action to validate it. Allows us to filter out accounts which do not work for the script and inform the user. :param account_id: An AWS Account Id :type account_id: str :returns: True if the role and EC2 client works. False otherwise. :rtype: bool """ try: if ec2_client := aws_client(account_id, 'ec2', 'us-east-1'): ec2_client.describe_vpcs() LOGGER.info(f'Able to assume role in {account_id} and access the EC2 API.') return True LOGGER.warning(f'Unable to assume role in {account_id} and access the EC2 API.')
def tag_resource(client, resource: str, tags: dict, dryrun: bool = False): """ Tags an AWS resource with the provided tags. Example Usage: ``` ec2_client = aws_client(account_id, 'ec2', region) tags = {'peerd_support': '*****@*****.**', 'peerd_datetime': str(datetime.now())} tag_resource(ec2_client, peerding_id, tags) ``` :param client: An AWS boto3 client connection to an AWS resource :type client: boto3 client :param resource: The name of the resource e.g. rt-abc123 :type resource: string :param tags: Dictionary of tags to apply to a resource :type tags: dict :returns: Nothing :raises BaseException: Raises an exception if there was some problem tagging the resource """ tags = [{'Key': key, 'Value': value} for (key, value) in tags.items()] for x in range(5): try: LOGGER.debug(f'Tagging {resource}') client.create_tags(Resources=[resource], Tags=tags, DryRun=dryrun) LOGGER.debug(f'Tagging {resource} successful') return except botocore.exceptions.ClientError as err: if err.response['Error']['Code'] == 'DryRunOperation': LOGGER.debug(f'Tagging {resource} successful') return LOGGER.info( f'Tagging {resource} encountered error: {err.response["Error"]["Message"]}. Will retry.' ) sleep(1) continue except BaseException: LOGGER.warning("Unexpected error: %s", sys.exc_info()[1], exc_info=True) raise Exception(f'Could not tag resource {resource}')
def get_deletable_peerings(peerings: list, vpc_list: list) -> list: """ Given a list of peerings that exist in the infrastructure, and a list of vpcs that are configured to be members of this environment, returns a list of peerings which should be deleted. :param config: A list containing dictionaries representing the configuration for the environment :type config: list :param key: The key to deduplicate on. :returns: A deduplicated list of all peerings for this environment. :rtype: list """ peerings_to_delete = [] for peering in peerings: accepter = peering['AccepterVpcInfo'] requester = peering['RequesterVpcInfo'] if (accepter['VpcId'] not in vpc_list) or (requester['VpcId'] not in vpc_list): LOGGER.debug(f"Peering between {requester['VpcId']} and {accepter['VpcId']} can be deleted.") peerings_to_delete.append(peering) return deduplicate_list_dicts(peerings_to_delete, 'VpcPeeringConnectionId')
def clean_route_tables(peering_id: str, vpc_id: str, account_id: str, region: str, dryrun: bool) -> None: """ Deletes any routes pointing at a given peering id for a given vpc. Only applies to route tables with tag peerd_eligible:true :param peering_id: A vpc peering id e.g. pcx-011a291e5affc8d95 :type peering_id: str :param vpc_id: A vpc id e.g. vpc-abc123 :type vpc_id: str :param account_id: An AWS account id number :type account: str :param region: An AWS region where the service client should be created. e.g. us-east-1 :type region: str """ # Initialise EC2 client ec2_client = aws_client(account_id, 'ec2', region) # Get the route tables for the VPC if not (route_tables := vpc_route_tables(vpc_id, account_id, region)): LOGGER.warning(f'No peerd_eligible route tables in VPC {vpc_id}') return
def get_role_credentials(account: str, sts_client: Any) -> dict: """ Assumes a role and returns credentials for said role. Requires the COMMON_PRINCIPAL_NAME to be set, usually from metadata. Example returned dictionary: ``` { "Expiration": "2020-01-27T11:55:44Z", "Token": "abc123", "SecretAccessKey": "def456", "AccessKeyId": "ABCDEF123", "Type": "AWS-SOMETHING", "LastUpdated": "2020-01-27T10:55:45Z", "Code": "Success" } :param account: An AWS account id number :type account: str :param sts_client: an aws client connection to the sts service :type resource: boto3 client :returns: Dictionary containing the credentials from the assume role action :rtype: dict """ try: arn = f'arn:aws:iam::{account}:role/{COMMON_PRINCIPAL_NAME}' LOGGER.info(f'Attempting to tokenise into: {arn}') credentials = sts_client.assume_role(RoleArn=arn, RoleSessionName=ROLE_SESSION_NAME)['Credentials'] LOGGER.info(f'Successfully tokenised into: {arn}') return credentials except BaseException: LOGGER.warning("Unexpected error: %s", sys.exc_info()[1], exc_info=True) return {}
def accept_vpc_peerings(target_peerings: list, metadata: dict, dryrun: bool): """ Loops through a list of peerings, with existing peering id, to accept them. Requests, accepts and tags them. Repairs any half open peerings. Example target_peerings: ``` [ [ { "account_id": "415432961280", "peering_id": "pcx-41u5h345h2", "vpc_id": "vpc-e08fb484", "region": "ap-southeast-2", "cidr_overrides": [ "10.53.101.0/27" ], "peering_tags": [ { "peerd_az_affinity": "0" } ] }, { "account_id": 415432961280, "peering_id": "pcx-41u5h345h2", "vpc_id": "vpc-7a83b81e", "region": "ap-southeast-2" } ] ] ``` :param target_peerings: A list of lists representing the requester and accepter for each peering. :type target_peerings: list :param metadata: A dictionary with the environment, owner, etc for tagging :type metadata: list """ for peering_descriptor in target_peerings: # Unpack some common variables account_id = peering_descriptor[0]['account_id'] vpc_id = peering_descriptor[0]['vpc_id'] region = peering_descriptor[0]['region'] local_tags = peering_descriptor[0].get('peering_tags', {}) remote_account_id = peering_descriptor[1]['account_id'] remote_vpc_id = peering_descriptor[1]['vpc_id'] remote_region = peering_descriptor[1]['region'] remote_tags = peering_descriptor[1].get('peering_tags', {}) peering_id = peering_descriptor[1]['peering_id'] tags = peering_descriptor[1]['tags'] try: # Accept the VPC Peering LOGGER.info( f"Accepting peering request {peering_id} between {account_id} {vpc_id} {region} and " f"{remote_account_id} {remote_vpc_id} {remote_region}") ec2_client = aws_client(remote_account_id, 'ec2', remote_region) # Wait until the peering exists # The AWS API is eventually consistent and we need to wait. LOGGER.info(f'Waiting for peering to exist...') ec2_client.get_waiter('vpc_peering_connection_exists').wait( VpcPeeringConnectionIds=[peering_id], WaiterConfig={'Delay': 5}) # Tag the VPC Peering tags['Name'] = f'peerd peering to {account_id} {vpc_id} {region}' tags['peerd_role'] = 'accepter' tag_resource(ec2_client, peering_id, tags, dryrun=dryrun) # Accept the peering try: ec2_client.accept_vpc_peering_connection( VpcPeeringConnectionId=peering_id, DryRun=dryrun) except ClientError as err: if err.response['Error']['Code'] == 'DryRunOperation': continue raise except BaseException: LOGGER.error("Unexpected error: %s", sys.exc_info()[1], exc_info=True) continue LOGGER.info( f"Successfully accepted peering {peering_id} between {account_id} {vpc_id} " f"{region} and {remote_account_id} {remote_vpc_id} {remote_region}" )
f" and {remote_account_id} {remote_vpc_id} {remote_region}" ) continue # If the peering is pending acceptance move to tagging and acceptance # Only the remote account can accept the VPC peering. elif peering['Status']['Code'] == 'pending-acceptance' and peering[ 'RequesterVpcInfo']['VpcId'] == vpc_id: peering_id = peering['VpcPeeringConnectionId'] LOGGER.warning( f"Pending-Acceptance peering {peering_id} between {account_id} {vpc_id} {region}" f" and {remote_account_id} {remote_vpc_id} {remote_region}. Will attempt recovery." ) # We're in some weird state and need to report to a human else: LOGGER.warning( f"Peering between {account_id} {vpc_id} {region}" f" and {remote_account_id} {remote_vpc_id} {remote_region}" f" is in state {peering['Status']['Code']}.") continue # Tag the VPC Peering tags = { 'Name': f'peerd peering to {remote_account_id} {remote_vpc_id} {remote_region}', 'resource_owner': metadata['resource_owner'], 'business_unit': metadata['business_unit'], 'service_name': metadata['service_name'], 'peerd_created': 'true', 'peerd_support': metadata['support'], 'peerd_datetime': str(datetime.now()), 'peerd_role': 'requester', 'peerd_environment': metadata['environment'] }
def create_vpc_peerings(target_peerings: Sequence, metadata: Mapping, dryrun: bool) -> None: """ Loops through a list of peerings to create them. Requests, accepts and tags them. Repairs any half open peerings. Example target_peerings: ``` [ [ { "account_id": 415432961280, "vpc_id": "vpc-e08fb484", "region": "ap-southeast-2", "cidr_overrides": [ "10.53.101.0/27" ], "peering_tags": [ { "peerd_az_affinity": "0" } ] }, { "account_id": 415432961280, "vpc_id": "vpc-7a83b81e", "region": "ap-southeast-2" } ] ] ``` :param target_peerings: A list of lists representing the requester and accepter for each peering. :type target_peerings: list :param metadata: A dictionary with the environment, owner, etc for tagging :type metadata: list """ pending_acceptance_peerings = [] for peering_descriptor in target_peerings: # Unpack some common variables account_id = peering_descriptor[0]['account_id'] vpc_id = peering_descriptor[0]['vpc_id'] region = peering_descriptor[0]['region'] local_tags = peering_descriptor[0].get('peering_tags', {}) remote_account_id = peering_descriptor[1]['account_id'] remote_vpc_id = peering_descriptor[1]['vpc_id'] remote_region = peering_descriptor[1]['region'] remote_tags = peering_descriptor[1].get('peering_tags', {}) # Create a VPC peering request try: ec2_client = aws_client(account_id, 'ec2', region) # If the peering doesn't exist, create it if not (peering := get_vpc_peering(vpc_id, remote_vpc_id, account_id, region)): LOGGER.info( f"Creating peering request between {account_id} {vpc_id} {region}" f" and {remote_account_id} {remote_vpc_id} {remote_region}" ) try: ec2_peering_response = ec2_client.create_vpc_peering_connection( VpcId=vpc_id, PeerVpcId=remote_vpc_id, PeerOwnerId=remote_account_id, PeerRegion=remote_region, DryRun=dryrun)['VpcPeeringConnection'] except ClientError as err: if err.response['Error']['Code'] == 'DryRunOperation': continue raise peering_id = ec2_peering_response['VpcPeeringConnectionId'] # Wait for the vpc peering to exist before moving on LOGGER.info(f'Waiting for peering {peering_id} to exist...') ec2_client.get_waiter('vpc_peering_connection_exists').wait( VpcPeeringConnectionIds=[peering_id], WaiterConfig={'Delay': 5}) # If the peering exists and is active, do nothing. elif peering['Status']['Code'] == 'active': LOGGER.info( f"Active peering {peering['VpcPeeringConnectionId']} between {account_id} {vpc_id} {region}" f" and {remote_account_id} {remote_vpc_id} {remote_region}" ) continue # If the peering is pending acceptance move to tagging and acceptance # Only the remote account can accept the VPC peering. elif peering['Status']['Code'] == 'pending-acceptance' and peering[ 'RequesterVpcInfo']['VpcId'] == vpc_id: peering_id = peering['VpcPeeringConnectionId'] LOGGER.warning( f"Pending-Acceptance peering {peering_id} between {account_id} {vpc_id} {region}" f" and {remote_account_id} {remote_vpc_id} {remote_region}. Will attempt recovery." )
# If we can't we return none and cache none. if not (credentials := get_role_credentials(account, aws_sts_client())): LOGGER.warning(f'Unable to tokenise into {account} for service {service} in region {region}. Moving on') return None # In this block we use the account credentials we got above, to create and cache a client # connection to an AWS service. try: client = boto3.client( service, region_name=region, aws_access_key_id=credentials['AccessKeyId'], aws_secret_access_key=credentials['SecretAccessKey'], aws_session_token=credentials['SessionToken'], config=Config(retries=dict(max_attempts=10))) LOGGER.debug('Obtained fresh client connection.') return client except BaseException: LOGGER.error(f'Unexpected error: {sys.exc_info()[1]}', exc_info=True) def tag_resource(client: Any, resource: str, tags: Mapping, dryrun: bool = False) -> None: """ Tags an AWS resource with the provided tags. Example Usage: ``` ec2_client = aws_client(account_id, 'ec2', region) tags = {'peerd_support': '*****@*****.**', 'peerd_datetime': str(datetime.now())} tag_resource(ec2_client, peerding_id, tags)
def update_route_tables(target_peerings: list, metadata: Mapping, dryrun: bool) -> None: """ Loops through a list of peerings and updates the route tables on each side. Example target_peerings: ``` [ [ { "account_id": 415432961280, "vpc_id": "vpc-e08fb484", "region": "ap-southeast-2", "cidr_overrides": [ "10.53.101.0/27" ], "peering_tags": [ { "peerd_az_affinity": "0" } ] }, { "account_id": 415432961280, "vpc_id": "vpc-7a83b81e", "region": "ap-southeast-2" } ] ] ``` :param target_peerings: A list of lists representing the requester and accepter for each peering. :type target_peerings: list :param metadata: A dictionary with the environment, owner, etc for tagging :type metadata: list """ # We need to handle both sides of the peerings so we append reverse of each peering. # This means every side of every peering will be seen by a single loop. # We do this to avoid extra code handling the AWS concept of "accepter" and "requester" # We also construct our route table cache by account and region, which means if we looped # by requester and accepter we could cache stale route table contents. target_peerings.extend([x[::-1] for x in target_peerings]) # Loop through the target peerings for peering_descriptor in target_peerings: # Unpack some common variables account_id = peering_descriptor[0]['account_id'] vpc_id = peering_descriptor[0]['vpc_id'] region = peering_descriptor[0]['region'] remote_account_id = peering_descriptor[1]['account_id'] remote_vpc_id = peering_descriptor[1]['vpc_id'] remote_region = peering_descriptor[1]['region'] # Get the remote CIDRs from the AWS VPC API, or use the overrides in the config if they exist. remote_cidrs = peering_descriptor[1].get( 'cidr_overrides', list_vpc_cidrs(remote_vpc_id, remote_account_id, remote_region)) LOGGER.info( f"Inspecting route tables in {account_id} {vpc_id} " f"{region}, peer: {remote_account_id} {remote_vpc_id} {remote_region}" ) # Initialise a ec2 client connection ec2_client = aws_client(account_id, 'ec2', region) # Get active VPC peering if one exists, else continue # We want to avoid adding routes for inactive peerings. filters = [{ 'Name': 'tag:peerd_created', 'Values': ['true'] }, { 'Name': 'tag:peerd_environment', 'Values': [metadata['environment']] }, { 'Name': 'status-code', 'Values': ['active', 'provisioning'] }] if not (peering := get_vpc_peering(vpc_id, remote_vpc_id, account_id, region, filters)): # Since we filter for the peerd environment, remind the user that there could be a peering # But that it might exist as part of another environment, and thus we won't be touching it. LOGGER.warning( f'No active peering between {vpc_id} and {remote_vpc_id} for this environment' f' {metadata["environment"]}. It may exist as part of another environment.' ) continue peering_id = peering['VpcPeeringConnectionId'] # Wait until the peering is active, not provisioning (boto waiter doesn't accept filters for vpc peering api) # We must wait for active state to install routes, otherwise the peering will be ignored. # Note, usually this step takes a few seconds, but can sometimes take up to a minute or two in rare cases. if peering['Status']['Code'] == 'provisioning': while not ec2_client.describe_vpc_peering_connections( Filters=[{ 'Name': 'status-code', 'Values': ['active'] }, { 'Name': 'vpc-peering-connection-id', 'Values': [peering_id] }])['VpcPeeringConnections']: LOGGER.info( f'Waiting for peering {peering_id} to become active...') sleep(5) # Get the route tables for the local vpc relevant to the peering. # The vpc_route_tables function will only return peerd_eligible:true tables # Since we will have cases where RTs should not be altered. if not (route_tables := vpc_route_tables(vpc_id, account_id, region)): LOGGER.warning(f'No peerd_eligible route tables in VPC {vpc_id}') continue
def delete_unneeded_peerings(config: Sequence[dict], metadata: Mapping, dryrun: bool) -> None: """ Compares the infrastructure with the configuration and applies route cleanup and peering deletion logic to remove VPC peerings. :param peering_id: A vpc peering id e.g. pcx-011a291e5affc8d95 :type peering_id: str :param vpc_id: A vpc id e.g. vpc-abc123 :type vpc_id: str :param account_id: An AWS account id number :type account: str :param region: An AWS region where the service client should be created. e.g. us-east-1 :type region: str """ LOGGER.info('Beginning deletion phase...') # Get a list of all VPCs configured for this environment config_vpc_list = list_dict_values(config, 'vpc_id') # Get all peerings that exist for this environment LOGGER.info( f'Getting all peerings active in AWS for environment {metadata["environment"]}' ) # Only filter working accounts now, as there could be times where accounts don't work # but we might want to keep the peerings until we remove them from the configuration. filtered_config = filter_working_accounts(config) peerings = get_all_env_peerings(filtered_config, metadata) # Determine which peerings no longer relate to any vpc in the environment LOGGER.info( f'Calculating which peerings may be deleted (do not appear in configuration)' ) deletable_peerings = get_deletable_peerings(peerings, config_vpc_list) # Iterate through the deletable peerings for peering in deletable_peerings: peering_id = peering['VpcPeeringConnectionId'] LOGGER.info( 'Working on peering {} between {} {} {} and {} {} {}'.format( peering_id, peering['RequesterVpcInfo']['OwnerId'], peering['RequesterVpcInfo']['VpcId'], peering['RequesterVpcInfo']['Region'], peering['AccepterVpcInfo']['OwnerId'], peering['AccepterVpcInfo']['VpcId'], peering['AccepterVpcInfo']['Region'], )) # Clean up the route tables on both sides for vpc_info in [ peering['RequesterVpcInfo'], peering['AccepterVpcInfo'] ]: clean_route_tables(peering_id, vpc_info['VpcId'], vpc_info['OwnerId'], vpc_info['Region'], dryrun) # Delete the peering LOGGER.info(f"Deleting peering {peering_id}...") ec2_client = aws_client(peering['RequesterVpcInfo']['OwnerId'], 'ec2', peering['RequesterVpcInfo']['Region']) try: ec2_client.delete_vpc_peering_connection( VpcPeeringConnectionId=peering_id, DryRun=dryrun) except ClientError as err: if err.response['Error']['Code'] != 'DryRunOperation': raise LOGGER.info(f"Deleted peering {peering_id} successfully.")
f" and {remote_account_id} {remote_vpc_id} {remote_region}" ) continue # If the peering is pending acceptance move to tagging and acceptance # Only the remote account can accept the VPC peering. elif peering['Status']['Code'] == 'pending-acceptance' and peering[ 'RequesterVpcInfo']['VpcId'] == vpc_id: peerding_id = peering['VpcPeeringConnectionId'] LOGGER.warning( f"Pending peering between {account_id} {vpc_id} {region}" f" and {remote_account_id} {remote_vpc_id} {remote_region}. Will attempt recovery." ) # We're in some weird state and need to report to a human else: LOGGER.warning( f"Peering between {account_id} {vpc_id} {region}" f" and {remote_account_id} {remote_vpc_id} {remote_region}" f" is in state {peering['Status']['Code']}.") continue # Tag the VPC Peering tags = { 'Name': f'peerd peering to {remote_account_id} {remote_vpc_id} {remote_region}', 'resource_owner': metadata['resource_owner'], 'business_unit': metadata['business_unit'], 'service_name': metadata['service_name'], 'peerd_created': 'true', 'peerd_support': metadata['support'], 'peerd_datetime': str(datetime.now()), 'peerd_role': 'requester', 'peerd_environment': metadata['environment'] }