def load_existing_data(parm): parm.ram_shares = \ utilities.boto3_call('get_resource_shares', request={ 'resourceOwner': 'SELF', } ) parm.ram_resources = \ utilities.boto3_call('list_resources', request={ 'resourceOwner': 'SELF', 'resourceType': 'route53resolver:ResolverRule', } ) parm.ram_principals = \ utilities.boto3_call('list_principals', request={ 'resourceOwner': 'SELF', 'resourceType': 'route53resolver:ResolverRule', } ) parm.r53resolver_rules = \ utilities.boto3_call('list_resolver_rules')
def retrieve_cfn_export(event, wex, export): # Generate template import statement, however use generated value to # resolve endpoint addresses via route53resolver export_name = utilities.import_value(event, wex, export) export_name = export_name['Fn::ImportValue'] for export in utilities.boto3_call('list_exports'): if export['Name'] == export_name: return export['Value'] raise RuntimeError(f'{export_name} not found in CFN exports')
def count_exported_subnets(event, context): m = event['templateParameterValues']['Lob'] + \ '-' + event['templateParameterValues']['Environment'] + \ '-' + re.sub("(.).*?-", "\\1", event['region']) + \ '-vpc-stk-PrivateSubnet(\\d)-Id' count = 0 exports = utilities.boto3_call('list_exports') for export in exports: if re.match(m, export['Name']): count += 1 return min(count, int(event['templateParameterValues']['MaxIpAddresses']))
def target_endpoint_ips(parm): if parm.kind == 'AwsZones': endpoint_id = retrieve_cfn_export(parm.event, parm.wex, 'endpoint_inbound') return [{ 'Ip': ip['Ip'], 'Port': '53', } for ip in utilities.boto3_call('list_resolver_endpoint_ip_addresses', request={ 'ResolverEndpointId': endpoint_id, })] elif parm.kind == 'OnPremZones': return [{ 'Ip': target_ip, 'Port': 53, } for target_ip in parm.region_data['OnPremResolverIps']] else: raise RuntimeError('Internal: kind should be AwsZones|OnPremZones')
def sync_remote_associations(event, context): access_token = generate_access_token(event, context) local_exported_rules = set([ re.sub('^.*\\/', '', resource['arn']) for resource in utilities.boto3_call('list_resources', request={ 'resourceOwner': 'SELF' }) if resource['resourceShareArn'] == event['ResourceProperties']['ShareArn'] and resource['type'] == 'route53resolver:ResolverRule' ]) remote_exported_vpcs = set([ export['Value'] for export in utilities.boto3_call('list_exports', access_token=access_token) if utilities.is_exported_vpc(export) ]) # `need`: associations we should have need = set() for vpc in remote_exported_vpcs - \ set(event['ResourceProperties']['VpcDni']): for rule_id in local_exported_rules: need.add((vpc, rule_id)) logger.debug(f'local needs: {need}') # `have`: associations we actually have have = set([ ( association['VPCId'], association['ResolverRuleId'] ) for association in utilities.boto3_call('list_resolver_rule_associations', access_token=access_token) if association['ResolverRuleId'] in local_exported_rules ]) logger.debug(f'remote has: {have}') remote_rules = [ remote_rule['Id'] for remote_rule in utilities.boto3_call('list_resolver_rules', access_token=access_token) ] logger.debug(f'=== {remote_rules} ===') # remove extra first for pair in have - need: logger.debug(f'Removing: {pair}') utilities.boto3_call('disassociate_resolver_rule', request={ 'VPCId': pair[0], 'ResolverRuleId': pair[1], }, access_token=access_token) # create missing; format error nicely as this happens often remote_rules = list() for pair in need - have: logger.debug(f'>>> Creating: {pair} <<<') try: for _ in range(3): if pair[1] in remote_rules: logger.debug('=== Found! ===') break logger.debug(f'=== Not found: {pair[1]} (retrying) ===') remote_rules = [ remote_rule['Id'] for remote_rule in utilities.boto3_call('list_resolver_rules', access_token=access_token) ] # ready or not - attempt to associate; raises on error utilities.boto3_call('associate_resolver_rule', request={ 'VPCId': pair[0], 'ResolverRuleId': pair[1], 'Name': 'Do not remove manually', }, access_token=access_token) except Exception as e: account = re.sub('^arn:aws:iam::(\\d+):.*', '\\1', event['ResourceProperties']['RoleARN']) domain = [ rr['DomainName'] for rr in utilities.boto3_call('list_resolver_rules') if rr['Id'] == pair[1] ] raise RuntimeError(f'{account}: failed to associate RR {pair[1]}' f' {domain}' f' to the VPC {pair[0]} : {e}') from e
def create_template(event, context): ''' Assuming the correct template; do not attempt to recover. ''' parm = Namespace( event=event, region_name=event['region'], region_data=dict(), wex=event['fragment']['Mappings'].pop('Wex'), ) # calculate region data by updating `default` with the specific region for k in ['default', parm.region_name]: if k in parm.wex['Infoblox']['Regions']: parm.region_data.update( deepcopy(parm.wex['Infoblox']['Regions'][k])) for k, v in [ ('Instantiate', 'kind'), ('Environment', 'env'), ('TargetEnvironment', 'target_env'), ('MaxRulesPerShare', 'max_rules'), ]: parm.__setattr__(v, event['templateParameterValues'][k]) # prefix for the resources exported by this stack (eg. RAM shares) parm.share_prefix = \ f'wex-{parm.kind}-zones-share-{parm.target_env}'.lower() # list of principals to share rules to parm.principals = sorted( set(parm.wex['Accounts']) - set([parm.event['accountId']])) # cache target endpoint ips, they are common for all the rules parm.target_endpoint_ips = target_endpoint_ips(parm) parm.resources = dict() # acts as a symlink to event[..] event['fragment']['Resources'] = parm.resources if parm.kind == 'OnPremZones': if 'VpcDni' not in parm.region_data: parm.region_data['VpcDni'] = set() parm.vpcs = [ export['Value'] for export in utilities.boto3_call('list_exports') if utilities.is_exported_vpc(export) and export['Value'] not in parm.region_data['VpcDni'] ] else: parm.vpcs = list() # do not ever associate hosted zones for zone in clean_zone_names(parm.wex[parm.kind]): rule_id, rule_data = resource_rule(parm, zone) parm.resources[rule_id] = rule_data # Associate to all locally exported VPCs # (except the ones listed in the DNI section) for vpc_id in parm.vpcs: rule_assoc_id, rule_assoc_data = \ resource_rule_association(parm, vpc_id, rule_id) parm.resources[rule_assoc_id] = rule_assoc_data load_existing_data(parm) # load existing infra; note some keys might be missing, like '1' here: # { # 0: [ # "giftvoucher.com.", # ... # ], # 2: [ # "giftvoucher.net.", # ... # ], # ... # } infra_pre = join_resources(parm) infra_post = pre_to_post( infra_pre, set([ resource['Properties']['DomainName'] for resource in parm.resources.values() if resource['Type'] == 'AWS::Route53Resolver::ResolverRule' ]), parm.max_rules) principal_pre = join_principals(parm) principal_post = pre_to_post(principal_pre, deepcopy(parm.principals), parm.max_rules) throttle = None # chain resources together to avoid API throttling for principal_slot, principal_list in principal_post.items(): for zone_slot, zone_list in infra_post.items(): share_id, share_data = \ resource_share(parm, zone_slot, zone_list, principal_slot, principal_list) # add a twist, make 'em depend one from the other if throttle is not None: share_data['DependsOn'] = [throttle] throttle = share_id parm.resources[share_id] = share_data for principal in principal_list: saa_id, saa_data = \ resource_auto_associate(parm, share_id, principal) saa_data['DependsOn'] = [throttle] throttle = saa_id parm.resources[saa_id] = saa_data return { 'requestId': event['requestId'], 'status': 'SUCCESS', 'fragment': event['fragment'] }