コード例 #1
0
 def test_with_no_stack_type(self):
     mock_response = make_describe_stacks_response(MOCK_STACK_DESCRIPTION)
     with mock_aws.patch_client(
             'cloudformation', 'describe_stacks',
             return_value=mock_response) as mock_describe_stacks:
         with self.assertRaisesRegexp(RuntimeError, MOCK_STACK_ARN):
             stack_info.get_stack_info(MOCK_STACK_ARN)
         mock_describe_stacks.client_factory.assert_called_once_with(
             'cloudformation', region_name=MOCK_REGION)
         mock_describe_stacks.assert_called_once_with(
             StackName=MOCK_STACK_ARN)
コード例 #2
0
def _add_built_in_settings(settings, stack_arn):
    stack = stack_info.get_stack_info(stack_arn)
    if stack.stack_type != stack.STACK_TYPE_RESOURCE_GROUP:
        return

    access_stack = stack.deployment.deployment_access
    if access_stack:
        pool = access_stack.resources.get_by_logical_id(
            "PlayerAccessIdentityPool", "Custom::CognitoIdentityPool", True)
        if pool:
            settings["CloudCanvas::IdentityPool"] = pool.physical_id
            print 'Adding setting CloudCanvas::IdentityPool = {}'.format(
                settings["CloudCanvas::IdentityPool"])
        else:
            print 'Skipping setting CloudCanvas::IdentityPool: PlayerAccessIdentityPool not found.'
    else:
        print 'Skipping setting CloudCanvas::IdentityPool: access stack not found.'

    project_service_lambda = stack.deployment.project.resources.get_by_logical_id(
        "ServiceLambda", "AWS::Lambda::Function", True)
    if project_service_lambda:
        settings[
            "CloudCanvas::ServiceLambda"] = project_service_lambda.physical_id
        print 'Adding setting CloudCanvas::ServiceLambda = {}'.format(
            settings["CloudCanvas::ServiceLambda"])
    else:
        print 'Skipping setting CloudCanvas::ServiceLambda: resource not found.'

    settings["CloudCanvas::DeploymentName"] = stack.deployment.deployment_name
コード例 #3
0
def handler(event, context):
    '''Entry point for the Custom::AccessControl resource handler.'''

    props = properties.load(
        event,
        {
            'ConfigurationBucket': properties.String(),  # Currently not used
            'ConfigurationKey': properties.String()
        }
    )  # Depend on unique upload id in key to force Cloud Formation to call handler

    # Validate RequestType
    request_type = event['RequestType']
    if request_type not in ['Create', 'Update', 'Delete']:
        raise RuntimeError('Unexpected request type: {}'.format(request_type))

    # Get stack_info for the AccessControl resource's stack.
    stack_arn = event['StackId']
    stack = stack_info.get_stack_info(stack_arn)

    # Physical ID is always the same.
    physical_resource_id = aws_utils.get_stack_name_from_stack_arn(
        stack_arn) + '-' + event['LogicalResourceId']

    # The AccessControl resource has no output values.
    data = {}

    # Accumlate problems encountered so we can give a full report.
    problems = ProblemList()

    # Apply access control as determined by the Cloud Canvas stack type.
    if stack.stack_type == stack.STACK_TYPE_RESOURCE_GROUP:
        were_changes = _apply_resource_group_access_control(
            request_type, stack, problems)
    elif stack.stack_type == stack.STACK_TYPE_DEPLOYMENT_ACCESS:
        were_changes = _apply_deployment_access_control(
            request_type, stack, problems)
    elif stack.stack_type == stack.STACK_TYPE_PROJECT:
        were_changes = _apply_project_access_control(request_type, stack,
                                                     problems)
    else:
        raise RuntimeError(
            'The Custom::AccessControl resource can only be used in resource group, deployment access, or project stack templates.'
        )

    # If there were any problems, provide an error message with all the details.
    if problems:
        raise RuntimeError(
            'Found invalid AccessControl metadata:\n    {}'.format(problems))

    # If there were changes, wait a few seconds for them to propagate
    if were_changes:
        print 'Delaying {} seconds for change propagation'.format(
            PROPAGATION_DELAY_SECONDS)
        time.sleep(PROPAGATION_DELAY_SECONDS)

    # Successful execution.
    custom_resource_response.succeed(event, context, data,
                                     physical_resource_id)
コード例 #4
0
def _get_project_service_lambda_arn(stack_arn):
    stack = stack_info.get_stack_info(stack_arn)

    if stack.stack_type == stack.STACK_TYPE_RESOURCE_GROUP:
        project_service_lambda = stack.deployment.project.resources.get_by_logical_id(
            "ServiceLambda", "AWS::Lambda::Function", True)
        if project_service_lambda:
            return project_service_lambda.resource_arn
    return None
コード例 #5
0
 def test_with_deployment_stack(self):
     mock_response = make_describe_stacks_response(
         make_stack_description(stack_info.StackInfo.STACK_TYPE_DEPLOYMENT))
     with mock_aws.patch_client(
             'cloudformation', 'describe_stacks',
             return_value=mock_response) as mock_describe_stacks:
         result = stack_info.get_stack_info(MOCK_STACK_ARN)
         self.assertIsInstance(result, stack_info.DeploymentInfo)
         self.assertEqual(result.stack_type,
                          stack_info.StackInfo.STACK_TYPE_DEPLOYMENT)
         mock_describe_stacks.client_factory.assert_called_once_with(
             'cloudformation', region_name=MOCK_REGION)
         mock_describe_stacks.assert_called_once_with(
             StackName=MOCK_STACK_ARN)
コード例 #6
0
def validate_identity_metadata(stack_arn, user_pool_logical_id, client_names):
    client_names_set = set(client_names)
    stack = stack_info.get_stack_info(stack_arn)
    user_pool = stack.resources.get_by_logical_id(user_pool_logical_id)
    for identity in user_pool.metadata.get('CloudCanvas',
                                           {}).get('Identities', []):
        if 'IdentityPoolLogicalName' not in identity:
            raise RuntimeError(
                'Missing IdentityPoolLogicalName in Identities metadata')
        client_app = identity.get('ClientApp')
        if not client_app:
            raise RuntimeError('Missing ClientApp in Identities metadata')
        if client_app not in client_names_set:
            raise RuntimeError(
                'ClientApp {} is not in the list of ClientApps'.format(
                    client_app))
コード例 #7
0
def get_identity_mappings(stack_arn, updated_resources={}):
    # Collect a list of stacks to search for resource metadata.
    stacks_to_search = []
    stack = stack_info.get_stack_info(stack_arn)
    if stack.stack_type == stack_info.StackInfo.STACK_TYPE_DEPLOYMENT_ACCESS or stack.stack_type == stack_info.StackInfo.STACK_TYPE_RESOURCE_GROUP:
        # Pools can be linked between resource groups and the deployment access stack.
        stacks_to_search.extend(stack.deployment.resource_groups)
        if stack.deployment.deployment_access:
            stacks_to_search.append(stack.deployment.deployment_access)
    elif stack.stack_type == stack.STACK_TYPE_PROJECT:
        # The project stack can have pools that aren't linked to a deployment.
        stacks_to_search.append(stack)

    # Fetch the stack descriptions and collect information from Custom::CognitoIdentityPool and Custom::CognitoUserPool resources.
    identity_pool_mappings = []
    idp_by_pool_name = {}
    for stack in stacks_to_search:
        for resource in stack.resources:
            if resource.type == 'Custom::CognitoIdentityPool':
                identity_pool_mappings.append({
                    'identity_pool_resource':
                    resource,
                })
                print 'Found CognitoIdentityPool {}.{}'.format(
                    stack.stack_name, resource.logical_id)
            elif resource.type == 'Custom::CognitoUserPool':
                identities = resource.metadata.get('CloudCanvas',
                                                   {}).get('Identities', [])
                updated_resource = updated_resources.get(
                    stack.stack_arn, {}).get(resource.logical_id, {})
                physical_id = updated_resource.get('physical_id',
                                                   resource.physical_id)
                # Skip user pools that haven't been created yet, they will be linked later on creation.
                if physical_id:
                    for identity in identities:
                        pool_name = identity.get('IdentityPoolLogicalName')
                        client_app = identity.get('ClientApp')
                        if not client_app:
                            raise RuntimeError(
                                'Missing ClientApp in Identities metadata for stack {} resource {}'
                                .format(stack.stack_name, resource.logical_id))

                        # Get the client id from the updated_resources parameter if possible, otherwise get it from the pool.
                        client_id = updated_resource.get(
                            'client_apps', {}).get('client_app',
                                                   {}).get('client_id')
                        if not client_id:
                            client_id = user_pool.get_client_id(
                                physical_id, client_app)

                        if not client_id:
                            # A client may be missing if there's more than one pool updating at the same time and the list of clients is changing.
                            print 'Unable to find client named {} in user pool with physical id {} defined in stack {} resource {}'.format(
                                client_app, physical_id, stack.stack_name,
                                resource.logical_id)
                        else:
                            pools = idp_by_pool_name.get(pool_name, [])
                            pools.append({
                                'ClientId':
                                client_id,
                                'ProviderName':
                                user_pool.get_provider_name(physical_id),
                                'ServerSideTokenCheck':
                                True
                            })
                            idp_by_pool_name[pool_name] = pools
                            print 'Found CognitoUserPool {}.{} mapped to {} with client id {}'.format(
                                stack.stack_name, resource.logical_id,
                                pool_name, client_id)

    # Combine the user pool mappings with the identity pool mappings.
    for mapping in identity_pool_mappings:
        mapping['providers'] = idp_by_pool_name.get(
            mapping['identity_pool_resource'].logical_id, [])

    return identity_pool_mappings
コード例 #8
0
def create_access_control_role(id_data,
                               stack_arn,
                               logical_role_name,
                               assume_role_service,
                               delay_for_propagation=True,
                               default_policy=None):
    '''Create an IAM Role for a resource group stack. 
    
    Developers typlically do not have the IAM permissions necessary to create
    roles, either directly or indirectly via Cloud Formation. However, many
    resource require that a role be specified (e.g. the role assumed when a
    Lambda function is invoked). 
    
    To overcome this delema, custom resource handlers can use this method and
    act as a proxies for directly defining roles in resource-group-template.json 
    files.
        
    The Custom::AcessControl resource handler will manage the policies attached
    to this role.
    
    Args:

        resource_group_stack_arn: Identifies the stack that "owns" the role.

        logical_role_name: Appended to the stack name to construct the actual role name.

        assume_role_service: Identifies the AWS service that is allowed to assume the role.

        default_policy (named): An IAM policy document that will be attached to the role.

    Returns:

        The ARN of the created role.

    '''

    role_name = get_access_control_role_name(stack_arn, logical_role_name)
    owning_stack_info = stack_info.get_stack_info(stack_arn)

    deployment_name = "NONE"
    resource_group_name = "NONE"
    project_name = "NONE"

    if owning_stack_info.stack_type == stack_info.StackInfo.STACK_TYPE_RESOURCE_GROUP:
        deployment_name = owning_stack_info.deployment.deployment_name
        resource_group_name = owning_stack_info.resource_group_name
        project_name = owning_stack_info.deployment.project.project_name
    elif owning_stack_info.stack_type == stack_info.StackInfo.STACK_TYPE_DEPLOYMENT:
        deployment_name = owning_stack_info.deployment.deployment_name
        project_name = owning_stack_info.project.project_name
    elif owning_stack_info.stack_type == stack_info.StackInfo.STACK_TYPE_PROJECT:
        project_name = owning_stack_info.project_name

    path = '/{project_name}/{deployment_name}/{resource_group_name}/{logical_role_name}/AccessControl/'.format(
        project_name=project_name,
        deployment_name=deployment_name,
        resource_group_name=resource_group_name,
        logical_role_name=logical_role_name)

    assume_role_policy_document = ASSUME_ROLE_POLICY_DOCUMENT.replace(
        '{assume_role_service}', assume_role_service)

    try:
        res = iam.create_role(
            RoleName=role_name,
            AssumeRolePolicyDocument=assume_role_policy_document,
            Path=path)
        role_arn = res['Role']['Arn']
    except ClientError as e:
        if e.response["Error"]["Code"] != 'EntityAlreadyExists':
            raise e
        res = iam.get_role(RoleName=role_name)
        role_arn = res['Role']['Arn']

    if default_policy:
        iam.put_role_policy(RoleName=role_name,
                            PolicyName='Default',
                            PolicyDocument=default_policy)

    set_id_data_abstract_role_mapping(id_data, logical_role_name, role_name)

    # Allow time for the role to propagate before lambda tries to assume
    # it, which lambda tries to do when the function is created.
    if delay_for_propagation:
        time.sleep(PROPAGATION_DELAY_SECONDS)

    return role_arn
コード例 #9
0
def handler(event, context):

    request_type = event['RequestType']
    logical_resource_id = event['LogicalResourceId']
    logical_role_name = logical_resource_id
    owning_stack_info = stack_info.get_stack_info(event['StackId'])
    rest_api_resource_name = owning_stack_info.stack_name + '-' + logical_resource_id
    id_data = aws_utils.get_data_from_custom_physical_resource_id(
        event.get('PhysicalResourceId', None))

    response_data = {}

    if request_type == 'Create':

        props = properties.load(event, PROPERTY_SCHEMA)
        role_arn = role_utils.create_access_control_role(
            id_data, owning_stack_info.stack_arn, logical_role_name,
            API_GATEWAY_SERVICE_NAME)
        swagger_content = get_configured_swagger_content(
            owning_stack_info, props, role_arn, rest_api_resource_name)
        rest_api_id = create_api_gateway(props, swagger_content)
        response_data['Url'] = get_api_url(rest_api_id,
                                           owning_stack_info.region)
        id_data['RestApiId'] = rest_api_id

    elif request_type == 'Update':

        rest_api_id = id_data.get('RestApiId', None)
        if not rest_api_id:
            raise RuntimeError(
                'No RestApiId found in id_data: {}'.format(id_data))

        props = properties.load(event, PROPERTY_SCHEMA)
        role_arn = role_utils.get_access_control_role_arn(
            id_data, logical_role_name)
        swagger_content = get_configured_swagger_content(
            owning_stack_info, props, role_arn, rest_api_resource_name)
        update_api_gateway(rest_api_id, props, swagger_content)
        response_data['Url'] = get_api_url(rest_api_id,
                                           owning_stack_info.region)

    elif request_type == 'Delete':

        if not id_data:

            # The will be no data in the id if Cloud Formation cancels a resource creation
            # (due to a failure in another resource) before it processes the resource create
            # response. Appearently Cloud Formation has an internal temporary id for the
            # resource and uses it for the delete request.
            #
            # Unfortunalty there isn't a good way to deal with this case. We don't have the
            # id data, so we can't clean up the things it identifies. At best we can allow the
            # stack cleanup to continue, leaving the rest API behind and role behind.

            print 'WARNING: No id_data provided on delete.'.format(id_data)

        else:

            rest_api_id = id_data.get('RestApiId', None)
            if not rest_api_id:
                raise RuntimeError(
                    'No RestApiId found in id_data: {}'.format(id_data))

            delete_api_gateway(rest_api_id)
            del id_data['RestApiId']

            role_utils.delete_access_control_role(id_data, logical_role_name)

    else:

        raise RuntimeError('Invalid RequestType: {}'.format(request_type))

    physical_resource_id = aws_utils.construct_custom_physical_resource_id_with_data(
        event['StackId'], logical_resource_id, id_data)

    custom_resource_response.succeed(event, context, response_data,
                                     physical_resource_id)