def handler(event, context):
    
    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
        'RoleLogicalId': properties.String(),
        'MetadataKey': properties.String(),
        'PhysicalResourceId': properties.String(),
        'UsePropagationDelay': properties.String(),
        'RequireRoleExists': properties.String(default='true'),
        'ResourceGroupStack': properties.String(default=''),
        'DeploymentStack': properties.String(default='')})

    if props.ResourceGroupStack is '' and props.DeploymentStack is '':
        raise ValidationError('A value for the ResourceGroupStack property or the DeploymentStack property must be provided.')

    if props.ResourceGroupStack is not '' and props.DeploymentStack is not '':
        raise ValidationError('A value for only the ResourceGroupStack property or the DeploymentStack property can be provided.')

    use_propagation_delay = props.UsePropagationDelay.lower() == 'true'
    
    data = {}
    stack_infos = []
    
    if props.ResourceGroupStack is not '':
        resource_group_info = stack_info.ResourceGroupInfo(props.ResourceGroupStack)
        
        # create a list of stack-infos, starting at the resource group level and working our way upward
        stack_infos = _build_stack_infos_list(resource_group_info) 
        
    else: # DeploymentStack
        deployment_info = stack_info.DeploymentInfo(props.DeploymentStack)
        
        # create a list of stack-infos, starting at the deployment level and working our way upward
        stack_infos = _build_stack_infos_list(deployment_info)
    
    # go through each of the stack infos, trying to find the specified role
    for stack in stack_infos:
        role = stack.resources.get_by_logical_id(props.RoleLogicalId, expected_type='AWS::IAM::Role', optional=True)
        
        if role is not None:
            break

    role_physical_id = None
    if role is not None:
        role_physical_id = role.physical_id

    if role_physical_id is None:
        if props.RequireRoleExists.lower() == 'true':
            raise ValidationError('Could not find role \'{}\'.'.format(props.RoleLogicalId))
    else:
        if type(stack_infos[0]) is stack_info.ResourceGroupInfo:
            _process_resource_group_stack(event['RequestType'], stack_infos[0], role_physical_id, props.MetadataKey, use_propagation_delay)
        else:
            for resource_group_info in stack_infos[0].resource_groups:
                _process_resource_group_stack(event['RequestType'], resource_group_info, role_physical_id, props.MetadataKey, use_propagation_delay)

    custom_resource_response.succeed(event, context, data, props.PhysicalResourceId)
Exemplo n.º 2
0
 def test_resource_group_name_discovered(self):
     mock_resource_group_name = 'test-name'
     mock_parameters = {'ResourceGroupName': mock_resource_group_name}
     with mock.patch('stack_info.ResourceGroupInfo.parameters',
                     new=mock.PropertyMock(return_value=mock_parameters)):
         target = stack_info.ResourceGroupInfo(MOCK_STACK_ARN)
         actual_resource_group_name = target.resource_group_name
         self.assertEquals(actual_resource_group_name,
                           mock_resource_group_name)
Exemplo n.º 3
0
 def test_constructor(self):
     target = stack_info.ResourceGroupInfo(
         MOCK_STACK_ARN,
         client=MOCK_CLIENT,
         stack_description=MOCK_STACK_DESCRIPTION)
     self.assertEquals(target.stack_type,
                       stack_info.StackInfo.STACK_TYPE_RESOURCE_GROUP)
     self.assertIs(target.client, MOCK_CLIENT)
     self.assertIs(target.stack_description, MOCK_STACK_DESCRIPTION)
Exemplo n.º 4
0
 def test_deployment_discovered(self):
     mock_deployment_stack_id = 'test-deployment-stack-id'
     mock_parameters = {'DeploymentStackArn': mock_deployment_stack_id}
     mock_deployment = 'test-deployment'
     with mock.patch('stack_info.DeploymentInfo',
                     return_value=mock_deployment) as mock_DeploymentInfo:
         with mock.patch(
                 'stack_info.ResourceGroupInfo.parameters',
                 new=mock.PropertyMock(return_value=mock_parameters)):
             target = stack_info.ResourceGroupInfo(MOCK_STACK_ARN)
             actual_deployment = target.deployment
             actual_deployment_2 = target.deployment
             self.assertEquals(actual_deployment, mock_deployment)
             self.assertEquals(actual_deployment_2, mock_deployment)
             mock_DeploymentInfo.assert_called_once_with(
                 mock_deployment_stack_id, client=target.client)
Exemplo n.º 5
0
 def test_deployment_provided(self):
     mock_deployment = 'test_deployment'
     target = stack_info.ResourceGroupInfo(MOCK_STACK_ARN,
                                           deployment_info=mock_deployment)
     self.assertEquals(target.deployment, mock_deployment)
Exemplo n.º 6
0
 def test_resource_group_name_provided(self):
     mock_resource_group_name = 'test_resource_group_name'
     target = stack_info.ResourceGroupInfo(
         MOCK_STACK_ARN, resource_group_name=mock_resource_group_name)
     self.assertEquals(target.resource_group_name, mock_resource_group_name)
Exemplo n.º 7
0
def create_role(stack_arn,
                logical_role_name,
                policy_name,
                assume_role_service,
                default_statements=[],
                policy_metadata_filter=_default_policy_metadata_filter):
    '''Create a role with a policy constructed using metadata found on a stack's resources.
    
    Args:

        stack_arn: Identifies the stack.

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

        policy_name: Identifies the metadata used to construct the policy and the name 
        of the policy attached to the created role.

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

        default_statements (named): A list containing additional statements included in 
        the policy. Default is [].  

        policy_metadata_filter (name): A function that takes a single parameter and returns 
        True if the provided value should be used. The default function always returns True.

    Returns:

        The ARN of the created role.

    Note:

        The policy attached to the role is constructed by looking in the CloudCanvas metadata
        object for a property identified by the policy_name parameter on each of the resources 
        defined by the specified stack. The value of this property can be an object with an
        "Action" property, or it can be a list of such objects. The value, or each value in 
        the list of values, is passed to the policy_metadata_filter function to determine if
        it should be used in the construction of the policy. An error occurs if the filter 
        accepts more than one value.
    '''

    role_name = _get_role_name(stack_arn, logical_role_name)

    resource_group = stack_info.ResourceGroupInfo(stack_arn)
    path = '/{project_name}/{deployment_name}/{resource_group_name}/{logical_role_name}/'.format(
        project_name=resource_group.deployment.project.project_name,
        deployment_name=resource_group.deployment.deployment_name,
        resource_group_name=resource_group.resource_group_name,
        logical_role_name=logical_role_name)

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

    print 'create_role {} with path {}'.format(role_name, path)
    res = iam.create_role(RoleName=role_name,
                          AssumeRolePolicyDocument=assume_role_policy_document,
                          Path=path)
    print 'create_role {} result: {}'.format(role_name, res)

    role_arn = res['Role']['Arn']

    _set_role_policy(role_name, stack_arn, policy_name, default_statements,
                     policy_metadata_filter)

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

    return role_arn