Пример #1
0
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')
Пример #2
0
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')
Пример #3
0
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']))
Пример #4
0
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')
Пример #5
0
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
Пример #6
0
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']
    }