예제 #1
0
    def prepare_s3bucket_artifact(self, is_zip=False):
        """
        Add a Hook which will prepare the Lambda Code artifact and upload to an S3 Bucket if it
        doesn't already exist.

        Add Parameters for CodeS3Bucket and CodeS3Key. The Parameter values are placeholder values that
        will be updated by the hook.
        """
        ignore_changes = True
        # always create an artifact on CREATE
        stack_hooks = StackHooks()
        stack_hooks.add(
            name='PrepLambdaCodeArtifactToS3Bucket',
            stack_action=['create',],
            stack_timing='pre',
            hook_method=self.prepare_s3bucket_artifact_hook,
            cache_method=self.prepare_s3bucket_artifact_cache,
            hook_arg=is_zip,
        )
        self.stack.add_hooks(stack_hooks)

        # only UPDATE an artifact if --auto-publish-code is true
        if self.paco_ctx.auto_publish_code == True:
            ignore_changes = False
            stack_hooks = StackHooks()
            stack_hooks.add(
                name='PrepLambdaCodeArtifactToS3Bucket',
                stack_action=['update'],
                stack_timing='pre',
                hook_method=self.prepare_s3bucket_artifact_hook,
                cache_method=self.prepare_s3bucket_artifact_cache,
                hook_arg=is_zip,
            )
            self.stack.add_hooks(stack_hooks)

        self.s3bucket_param = self.create_cfn_parameter(
            name='CodeS3Bucket',
            description="S3 Bucket for the Lambda Code artifact",
            param_type='String',
            value='',
            ignore_changes=ignore_changes,
        )
        self.s3key_param = self.create_cfn_parameter(
            name='CodeS3Key',
            description="S3 Key for the Lambda Code artifact",
            param_type='String',
            value='',
            ignore_changes=ignore_changes,
        )
        cfn_s3_code = {
            'S3Bucket': troposphere.Ref(self.s3bucket_param),
            'S3Key': troposphere.Ref(self.s3key_param),
        }
        return cfn_s3_code
예제 #2
0
    def init_resource(self):

        stack_hooks = StackHooks()
        # Stack hooks for saving state to the Paco bucket
        stack_hooks.add(
            name='EventsRule.State',
            stack_action=['create', 'update'],
            stack_timing='post',
            hook_method=self.stack_hook_eventsrule_state,
            cache_method=self.stack_hook_eventsrule_state_cache_id,
        )

        # CloudWatch Events Rule
        self.stack_group.add_new_stack(self.aws_region,
                                       self.resource,
                                       paco.cftemplates.eventsrule.EventsRule,
                                       account_ctx=self.account_ctx,
                                       stack_tags=self.stack_tags,
                                       stack_hooks=stack_hooks)
예제 #3
0
 def init(self):
     stack_hooks = StackHooks()
     for hook_action in ['create', 'update']:
         stack_hooks.add(
             name='CodeCommitSSHPublicKey',
             stack_action=hook_action,
             stack_timing='post',
             hook_method=self.codecommit_post_stack_hook,
             cache_method=self.codecommit_post_stack_hook_cache_id,
             hook_arg=self.config)
     # CodeCommit Repository
     codecommit_stack = self.add_new_stack(
         self.aws_region,
         self.config,
         paco.cftemplates.CodeCommit,
         extra_context={'repo_list': self.repo_list},
         stack_hooks=stack_hooks)
     codecommit_stack.set_termination_protection(True)
     self.stack_list.append(codecommit_stack)
예제 #4
0
    def add_bucket(self,
                   bucket,
                   bucket_name_prefix=None,
                   bucket_name_suffix=None,
                   stack_hooks=None,
                   change_protected=False):
        "Add a bucket: will create a stack and stack hooks as needed"
        if self.bucket_context['config'] != None:
            raise PacoBucketExists("Bucket already exists: %s" %
                                   (self.resource_ref))

        bucket.bucket_name_prefix = bucket_name_prefix
        bucket.bucket_name_suffix = bucket_name_suffix
        res_group = get_parent_by_interface(bucket, schemas.IResourceGroup)
        if res_group != None:
            self.bucket_context['group_id'] = res_group.name
        self.bucket_context['config'] = bucket
        self.bucket_context['ref'] = self.resource_ref
        bucket.resolve_ref_obj = self

        if bucket.external_resource == True:
            # if the bucket already exists, do not create a stack for it
            pass
        else:
            if change_protected == False:
                if stack_hooks == None:
                    stack_hooks = StackHooks()
                # S3 Delete on Stack Delete hook
                stack_hooks.add('S3StackGroup', 'delete', 'pre',
                                self.stack_hook_pre_delete, None,
                                self.bucket_context)
                self.add_stack(
                    bucket_policy_only=False,
                    stack_hooks=stack_hooks,
                    stack_tags=self.stack_tags,
                )
예제 #5
0
    def __init__(self, stack, paco_ctx):
        cup = stack.resource
        super().__init__(stack, paco_ctx, iam_capabilities=["CAPABILITY_IAM"])
        self.set_aws_name('CUP', self.resource_group_name, self.resource.name)

        self.init_template('Cognito User Pool')
        if not cup.is_enabled():
            return

        cfn_export_dict = cup.cfn_export_dict

        # SNS Role for SMS
        if cup.mfa != 'off':
            # CloudFormation requires an SMS Role even if only software tokens are used
            sms_role_resource = troposphere.iam.Role(
                'CognitoSMSRole',
                AssumeRolePolicyDocument=PolicyDocument(
                    Statement=[
                        Statement(
                            Effect=Allow,
                            Principal=Principal('Service',"cognito-idp.amazonaws.com"),
                            Action=[Action('sts', 'AssumeRole')],
                            Condition=Condition([
                                StringEquals({"sts:ExternalId": cup.paco_ref_parts}),
                            ]),
                        ),
                    ],
                ),
                Policies=[
                    troposphere.iam.Policy(
                        PolicyName="AllowSMS",
                        PolicyDocument=Policy(
                            Version='2012-10-17',
                            Statement=[
                                Statement(
                                    Effect=Allow,
                                    Action=[awacs.sns.Publish],
                                    Resource=['*'],
                                )
                            ]
                        )
                    )
                ],
            )
            self.template.add_resource(sms_role_resource)
            cfn_export_dict['SmsConfiguration'] = {
                'ExternalId': cup.paco_ref_parts,
                'SnsCallerArn': troposphere.GetAtt(sms_role_resource, "Arn")
            }

        # Lambda Triggers
        lambda_trigger_mapping = [
            ('create_auth_challenge', 'CreateAuthChallenge'),
            ('custom_message', 'CustomMessage'),
            ('define_auth_challenge', 'DefineAuthChallenge'),
            ('post_authentication', 'PostAuthentication'),
            ('post_confirmation', 'PostConfirmation'),
            ('pre_authentication', 'PreAuthentication'),
            ('pre_sign_up', 'PreSignUp'),
            ('pre_token_generation', 'PreTokenGeneration'),
            ('user_migration', 'UserMigration'),
            ('verify_auth_challenge_response', 'VerifyAuthChallengeResponse'),
        ]
        self.lambda_trigger_params = {}
        if cup.lambda_triggers != None:
            triggers = {}
            for name, cfn_key in lambda_trigger_mapping:
                lambda_ref = getattr(cup.lambda_triggers, name, None)
                if lambda_ref != None:
                    if lambda_ref not in self.lambda_trigger_params:
                        self.lambda_trigger_params[lambda_ref] = self.create_cfn_parameter(
                            param_type='String',
                            name='LambdaTrigger' + md5sum(str_data=lambda_ref),
                            description=f'LambdaTrigger for Lambda {lambda_ref}',
                            value=lambda_ref + '.arn',
                        )
                    triggers[cfn_key] = troposphere.Ref(self.lambda_trigger_params[lambda_ref])
            cfn_export_dict['LambdaConfig'] = triggers

        # Cognito User Pool
        cup_resource = troposphere.cognito.UserPool.from_dict(
            'CognitoUserPool',
            cfn_export_dict
        )
        self.template.add_resource(cup_resource)

        # Add Lambda Permissions for Lambda Triggers
        # Need to do this after the cup_resource is created
        lambda_permissions = {}
        if cup.lambda_triggers != None:
            for name, cfn_key in lambda_trigger_mapping:
                lambda_ref = getattr(cup.lambda_triggers, name, None)
                if lambda_ref != None:
                    # Lambda Permission
                    if lambda_ref not in lambda_permissions:
                        lambda_permissions[lambda_ref] = True
                        troposphere.awslambda.Permission(
                            title='LambdaPermission' + md5sum(str_data=cup.paco_ref_parts),
                            template=self.template,
                            Action="lambda:InvokeFunction",
                            FunctionName=troposphere.Ref(self.lambda_trigger_params[lambda_ref]),
                            Principal='cognito-idp.amazonaws.com',
                            SourceArn=troposphere.GetAtt(cup_resource, "Arn"),
                        )

        # Outputs
        self.create_output(
            title=cup_resource.title + 'Id',
            description="Cognito UserPool Id",
            value=troposphere.Ref(cup_resource),
            ref=[cup.paco_ref_parts, cup.paco_ref_parts + ".id"],
        )
        self.create_output(
            title=cup_resource.title + 'Arn',
            description="Cognito UserPool Arn",
            value=troposphere.GetAtt(cup_resource, "Arn"),
            ref=cup.paco_ref_parts + ".arn"
        )
        self.create_output(
            title=cup_resource.title + 'ProviderName',
            description="Cognito UserPool ProviderName",
            value=troposphere.GetAtt(cup_resource, "ProviderName"),
            ref=[cup.paco_ref_parts + ".name", cup.paco_ref_parts + ".providername"],
        )
        self.create_output(
            title=cup_resource.title + 'Url',
            description="Cognito UserPool ProviderURL",
            value=troposphere.GetAtt(cup_resource, "ProviderURL"),
            ref=[cup.paco_ref_parts + ".url", cup.paco_ref_parts + ".providerurl"],
        )

        # Cognito User Pool Clients
        for client in cup.app_clients.values():
            cfn_export_dict = client.cfn_export_dict
            cfn_export_dict['UserPoolId'] = troposphere.Ref(cup_resource)
            client_logical_id = self.create_cfn_logical_id(f"{client.name}CognitoUserPoolClient")
            cupclient_resource = troposphere.cognito.UserPoolClient.from_dict(
                client_logical_id,
                cfn_export_dict
            )
            self.template.add_resource(cupclient_resource)
            self.create_output(
                title=cupclient_resource.title + 'Id',
                description="Cognito UserPoolClient Id",
                value=troposphere.Ref(cupclient_resource),
                ref=client.paco_ref_parts + ".id",
            )
            if client.domain_name:
                # ToDo: add support for custom domains
                up_domain_name = self.create_cfn_logical_id(f"{client.name}UserPoolDomain")
                domain_resource = troposphere.cognito.UserPoolDomain(
                    up_domain_name,
                    Domain=client.domain_name,
                    UserPoolId=troposphere.Ref(cup_resource)
                )
                self.template.add_resource(domain_resource)

        # UI Customizations
        if cup.ui_customizations != None:
            if cup.ui_customizations.logo_file != None or cup.ui_customizations.css_file != None:
                # Add a Hook to set UI Customizations
                # CloudFormation doesn't support the Logo customization
                # Paco also uses the hook for CSS (this could be migration to the CloudFormation ~shrug~)
                stack_hooks = StackHooks()
                stack_hooks.add(
                    name='SetCognitoUICustomizations',
                    stack_action=['create','update'],
                    stack_timing='post',
                    hook_method=self.add_ui_customizations_hook,
                    cache_method=self.add_ui_customizations_cache,
                    hook_arg=cup,
                )
                stack.add_hooks(stack_hooks)
예제 #6
0
    def init_users(self, model_obj):
        "Initialize IAM User StackGroups"
        self.stack_group_filter = model_obj.paco_ref_parts
        master_account_ctx = self.paco_ctx.get_account_context(account_ref='paco.ref accounts.master')
        # StackGroup
        for account_name in self.paco_ctx.project['accounts'].keys():
            account_ctx = self.paco_ctx.get_account_context('paco.ref accounts.'+account_name)
            self.iam_user_stack_groups[account_name] = IAMUserStackGroup(self.paco_ctx, account_ctx, account_name, self)

        stack_hooks = StackHooks()
        for user_name in self.iam.users.keys():
            user_config = self.iam.users[user_name]
            # Stack hooks for managing access keys
            for hook_action in ['create', 'update']:
                stack_hooks.add(
                    name='IAMUserAccessKeys',
                    stack_action=hook_action,
                    stack_timing='post',
                    hook_method=self.iam_user_access_keys_hook,
                    cache_method=self.iam_user_access_keys_hook_cache_id,
                    hook_arg=user_config
                )

        # If account_whitelist is not ['all'] then validate that there are no accounts specified
        # in permissions that are not part of the account_whitelist
        for iam_user in self.iam.users.values():
            allowed = {}
            for account in iam_user.account_whitelist:
                allowed[account] = None
            # If all accounts are allowed, no need to do this check.
            if 'all' in allowed:
                break
            for permission in iam_user.permissions.values():
                # Some permissions don't have accounts so we ignore them here
                if hasattr(permission, 'accounts') == False:
                    continue
                for account in permission.accounts:
                    if account != 'all' and account not in allowed:
                        raise InvalidAccountPermission(
    f"User {iam_user.name} has permission {permission.name} for account {account} - that account is not granted in that user's account_whitelist."
                        )

        # stack for the IAM User - this only exists in the Master account
        # and delegate roles are provisioned in accounts
        self.iam_user_stack_groups['master'].add_new_stack(
            master_account_ctx.config.region,
            self.iam.users,
            IAMUsers,
            account_ctx=master_account_ctx,
            stack_hooks=stack_hooks,
        )

        for user in self.iam.users.values():
            # Build a list of permissions for each account
            permissions_by_account = {}
            # Initialize permission_by_account for all accounts
            for account_name in self.paco_ctx.project['accounts'].keys():
                permissions_by_account[account_name] = []

            for permission_name in user.permissions.keys():
                permission_config = user.permissions[permission_name]
                init_method = getattr(self, "init_{}_permission".format(permission_config.type.lower()))
                init_method(permission_config, permissions_by_account)

            # Give access to accounts the user has explicit access to
            for account_name in self.paco_ctx.project['accounts'].keys():
                account_ctx = self.paco_ctx.get_account_context('paco.ref accounts.' + account_name)
                # IAM User delegate stack
                self.iam_user_stack_groups[account_name].add_new_stack(
                    master_account_ctx.config.region,
                    user,
                    IAMUserAccountDelegates,
                    stack_orders=[StackOrder.PROVISION ,StackOrder.WAITLAST],
                    account_ctx=account_ctx,
                    extra_context={
                        'permissions_list': permissions_by_account[account_name],
                        'account_id': account_ctx.id,
                        'master_account_id': master_account_ctx.id,
                    }
                )

        # Print out the SwitchRole URLs for each user
        for user in self.iam.users.values():
            print(f'{user.description} Switch Role URLs')
            for account_name in self.paco_ctx.project['accounts'].keys():
                if account_name not in user.account_whitelist and 'all' not in user.account_whitelist:
                    continue
                account_id = self.paco_ctx.get_account_context(account_name=account_name).id
                print(f'{account_name.capitalize()}:\nhttps://signin.aws.amazon.com/switchrole?account={account_id}&roleName=IAM-User-Account-Delegate-Role-{user.name}')
            print()