Example #1
0
    def create_cfn_ref_list_param(
        self,
        param_type,
        name,
        description,
        value,
        ref_attribute=None,
        default=None,
        noecho=False,
    ):
        "Create a CloudFormation Parameter from a list of refs"
        stack_output_param = StackOutputParam(name, param_template=self)
        for list_item in value:
            if is_ref(list_item):
                if ref_attribute != None:
                    list_item += '.' + ref_attribute
                stack = self.paco_ctx.get_ref(list_item)
                if isinstance(stack, Stack) == False:
                    raise PacoException(
                        PacoErrorCode.Unknown,
                        message="Reference must resolve to a stack")
                stack_output_key = self.stack.get_stack_outputs_key_from_ref(
                    Reference(list_item))
                stack_output_param.add_stack_output(stack, stack_output_key)
            else:
                stack_output_param.add_value(list_item)

        return self.create_cfn_parameter(
            param_type,
            name,
            description,
            stack_output_param,
            default,
            noecho,
        )
Example #2
0
 def register_stack_output_config(self, config_ref, stack_output_key):
     "Register stack output config"
     if config_ref.startswith('paco.ref'):
         raise PacoException(
             PacoErrorCode.Unknown,
             message=
             'Registered stack output config reference must not start with paco.ref: '
             + config_ref)
     stack_output_config = StackOutputConfig(config_ref, stack_output_key)
     self.stack.stack_output_config_list.append(stack_output_config)
Example #3
0
 def add_record_set(self,
                    account_ctx,
                    region,
                    resource,
                    dns,
                    record_set_type,
                    enabled=True,
                    resource_records=None,
                    alias_dns_name=None,
                    alias_hosted_zone_id=None,
                    stack_group=None,
                    async_stack_provision=False,
                    config_ref=None):
     record_set_config = {
         'enabled': enabled,
         'dns': dns,
         'alias_dns_name': alias_dns_name,
         'alias_hosted_zone_id': alias_hosted_zone_id,
         'record_set_type': record_set_type,
         'resource_records': resource_records
     }
     if stack_group == None:
         # I don't believe this case happens anymore, and it doesn't
         # look like it does anything.
         raise PacoException(PacoErrorCode.Unknown)
         #record_set_stack_group = Route53RecordSetStackGroup(
         #    self.paco_ctx, account_ctx, self
         #)
         #record_set_stack_group.add_new_stack(
         #    region,
         #    resource,
         #    Route53RecordSet,
         #    extra_context={'record_set_config': record_set_config, 'record_set_name': dns.domain_name}
         #)
     else:
         stack_account_ctx = account_ctx
         if is_ref(dns.hosted_zone):
             hosted_zone_obj = get_model_obj_from_ref(
                 dns.hosted_zone, self.paco_ctx.project)
             stack_account_ctx = self.paco_ctx.get_account_context(
                 account_ref=hosted_zone_obj.account)
         stack_orders = None
         if async_stack_provision == True:
             stack_orders = [StackOrder.PROVISION, StackOrder.WAITLAST]
         stack_group.add_new_stack(region,
                                   resource,
                                   Route53RecordSet,
                                   account_ctx=stack_account_ctx,
                                   stack_orders=stack_orders,
                                   extra_context={
                                       'record_set_config':
                                       record_set_config,
                                       'record_set_name': dns.domain_name
                                   })
Example #4
0
    def lb_hosted_zone_id(self, lb_type, lb_region):
        nlb_zone_id = {
            'us-east-2': 'ZLMOA37VPKANP',
            'us-east-1': 'Z26RNL4JYFTOTI',
            'us-west-1': 'Z24FKFUX50B4VW',
            'us-west-2': 'Z18D5FSROUN65G',
            'ap-south-1': 'ZVDDRBQ08TROA',
            'ap-northeast-3': 'Z1GWIQ4HH19I5X',
            'ap-northeast-2': 'ZIBE1TIR4HY56',
            'ap-southeast-1': 'ZKVM4W9LS7TM',
            'ap-southeast-2': 'ZCT6FZBF4DROD',
            'ap-northeast-1': 'Z31USIVHYNEOWT',
            'ca-central-1': 'Z2EPGBW3API2WT',
            'cn-north-1': 'Z3QFB96KMJ7ED6',
            'cn-northwest-1': 'ZQEIKTCZ8352D',
            'eu-central-1': 'Z3F0SRJ5LGBH90',
            'eu-west-1': 'Z2IFOLAFXWLO4F',
            'eu-west-2': 'ZD4D7Y8KGAS4G',
            'eu-west-3': 'Z1CMS0P5QUZ6D5',
            'eu-north-1': 'Z1UDT6IFJ4EJM',
            'sa-east-1': 'ZTK26PT1VY4CU'
        }
        lb_zone_id = {
            'us-east-2': 'Z3AADJGX6KTTL2',
            'us-east-1': 'Z35SXDOTRQ7X7K',
            'us-west-1': 'Z368ELLRRE2KJ0',
            'us-west-2': 'Z1H1FL5HABSF5',
            'ap-south-1': 'ZP97RAFLXTNZK',
            'ap-northeast-3': 'Z5LXEXXYW11ES',
            'ap-northeast-2': 'ZWKZPGTI48KDX',
            'ap-southeast-1': 'Z1LMS91P8CMLE5',
            'ap-southeast-2': 'Z1GM3OXH4ZPM65',
            'ap-northeast-1': 'Z14GRHDCWA56QT',
            'ca-central-1': 'ZQSVJUPU6J1EY',
            'cn-north-1': 'Z3BX2TMKNYI13Y',
            'cn-northwest-1': 'Z3BX2TMKNYI13Y',
            'eu-central-1': 'Z215JYRZR1TBD5',
            'eu-west-1': 'Z32O12XQLNTSW2',
            'eu-west-2': 'ZHURV8PSTC4K8',
            'eu-west-3': 'Z3Q77PNBQS71R4',
            'eu-north-1': 'Z23TAZ6LKFMNIO',
            'sa-east-1': 'Z2P70J7HTTTPLU'
        }

        if lb_type == 'elb' or lb_type == 'alb':
            return lb_zone_id[lb_region]
        elif lb_type == 'nlb':
            return nlb_zone_id[lb_region]
        else:
            raise PacoException(PacoErrorCode.Unknown)
Example #5
0
 def add_record_set(self,
                    account_ctx,
                    region,
                    resource,
                    dns,
                    record_set_type,
                    enabled=True,
                    resource_records=None,
                    alias_dns_name=None,
                    alias_hosted_zone_id=None,
                    stack_group=None,
                    config_ref=None):
     record_set_config = {
         'enabled': enabled,
         'dns': dns,
         'alias_dns_name': alias_dns_name,
         'alias_hosted_zone_id': alias_hosted_zone_id,
         'record_set_type': record_set_type,
         'resource_records': resource_records
     }
     if stack_group == None:
         # I don't believe this case happens anymore, and it doesn't
         # look like it does anything.
         raise PacoException(PacoErrorCode.Unknown)
         #record_set_stack_group = Route53RecordSetStackGroup(
         #    self.paco_ctx, account_ctx, self
         #)
         #record_set_stack_group.add_new_stack(
         #    region,
         #    resource,
         #    Route53RecordSet,
         #    extra_context={'record_set_config': record_set_config, 'record_set_name': dns.domain_name}
         #)
     else:
         stack_group.add_new_stack(region,
                                   resource,
                                   Route53RecordSet,
                                   extra_context={
                                       'record_set_config':
                                       record_set_config,
                                       'record_set_name': dns.domain_name
                                   })
Example #6
0
    def __init__(
        self,
        stack,
        paco_ctx,
    ):
        peering_config = stack.resource
        network_config = get_parent_by_interface(peering_config, schemas.INetwork)
        env_name = get_parent_by_interface(peering_config, schemas.IEnvironment).name
        netenv_name = get_parent_by_interface(peering_config, schemas.INetworkEnvironment).name
        super().__init__(
            stack,
            paco_ctx,
        )
        self.set_aws_name('VPCPeering')
        self.init_template('VPC Peering')

        # VPC Peering
        vpc_id_param = self.create_cfn_parameter(
            name='VpcId',
            param_type='AWS::EC2::VPC::Id',
            description='The VPC Id',
            value='paco.ref netenv.{}.<environment>.<region>.network.vpc.id'.format(netenv_name),
        )

        # Peer
        any_peering_enabled = False
        for peer in peering_config.keys():
            peer_config = peering_config[peer]
            if peer_config.is_enabled() and peer_config.peer_type == 'requester':
                any_peering_enabled = True
            else:
                continue

            if peer_config.network_environment != None:
                peer_config = self.get_peer_config(peer_config)

            vpc_peering_connection_res = troposphere.ec2.VPCPeeringConnection(
                'VPCPeeringConnection' + peer.title(),
                PeerOwnerId = peer_config.peer_account_id,
                PeerRegion = peer_config.peer_region,
                PeerVpcId = peer_config.peer_vpcid,
                PeerRoleArn = f'arn:aws:iam::{peer_config.peer_account_id}:role/{peer_config.peer_role_name}',
                VpcId = troposphere.Ref(vpc_id_param)
            )

            self.template.add_resource(vpc_peering_connection_res)
            # Routes
            for route in peer_config.routing:
                for peer_az in range(0, network_config.availability_zones):
                    peer_az_str = str(peer_az+1)
                    resource_name_suffix = peer.title() + 'AZ' + peer_az_str
                    route_table_param = self.create_cfn_parameter(
                        name='PeerRouteTableId' + resource_name_suffix,
                        param_type='String',
                        description='The route table ID for AZ {}.'.format(peer_az_str),
                        value='{}.az{}.route_table.id'.format(route.local_segment, peer_az_str),
                    )

                    remote_availability_zones = self.paco_ctx.get_ref(peer_config.network_environment+'.network.availability_zones')
                    for route_az in range(0, remote_availability_zones):
                        route_az_str = str(route_az+1)
                        if route.remote_segment != None:
                            route_cidr = self.paco_ctx.get_ref(route.remote_segment+f'.az{route_az_str}_cidr')
                        elif route.cidr != None:
                            raise PacoException("cidr is not supported yet, please use remote_segment.")
                        else:
                            raise PacoException("remote_segment must be specified in VPC Peer.")

                        peer_route_name = self.create_cfn_logical_id_join(['PeeringRoute', resource_name_suffix, f'RemoteAZ{route_az_str}'], camel_case=True)
                        peer_route_res = troposphere.ec2.Route(
                            peer_route_name,
                            DestinationCidrBlock = route_cidr,
                            VpcPeeringConnectionId = troposphere.Ref(vpc_peering_connection_res),
                            RouteTableId = troposphere.Ref(route_table_param)
                        )
                        self.template.add_resource(peer_route_res)

        self.set_enabled(any_peering_enabled)
Example #7
0
    def create_codebuild_cfn(self, template, pipeline_config, action_config,
                             config_ref):
        # CodeBuild
        compute_type_param = self.create_cfn_parameter(
            param_type='String',
            name='CodeBuildComputeType',
            description=
            'The type of compute environment. This determines the number of CPU cores and memory the build environment uses.',
            value=action_config.codebuild_compute_type,
        )
        image_param = self.create_cfn_parameter(
            param_type='String',
            name='CodeBuildImage',
            description=
            'The image tag or image digest that identifies the Docker image to use for this build project.',
            value=action_config.codebuild_image,
        )
        deploy_env_name_param = self.create_cfn_parameter(
            param_type='String',
            name='DeploymentEnvironmentName',
            description=
            'The name of the environment codebuild will be deploying into.',
            value=action_config.deployment_environment,
        )
        # If ECS Release Phase, then create the needed parameters
        release_phase = action_config.release_phase
        ecs_release_phase_cluster_arn_param = []
        ecs_release_phase_cluster_name_param = []
        ecs_release_phase_service_arn_param = []
        if release_phase != None and release_phase.ecs != None:
            idx = 0
            for command in release_phase.ecs:
                service_obj = get_model_obj_from_ref(command.service,
                                                     self.paco_ctx.project)
                service_obj = get_parent_by_interface(service_obj,
                                                      schemas.IECSServices)
                cluster_arn_param = self.create_cfn_parameter(
                    param_type='String',
                    name=f'ReleasePhaseECSClusterArn{idx}',
                    description='ECS Cluster Arn',
                    value=service_obj.cluster + '.arn',
                )
                ecs_release_phase_cluster_arn_param.append(cluster_arn_param)
                cluster_arn_param = self.create_cfn_parameter(
                    param_type='String',
                    name=f'ReleasePhaseECSClusterName{idx}',
                    description='ECS Cluster Name',
                    value=service_obj.cluster + '.name',
                )
                ecs_release_phase_cluster_name_param.append(cluster_arn_param)
                service_arn_param = self.create_cfn_parameter(
                    param_type='String',
                    name=f'ReleasePhaseECSServiceArn{idx}',
                    description='ECS Service Arn',
                    value=command.service + '.arn',
                )
                ecs_release_phase_service_arn_param.append(service_arn_param)
                idx += 1
        self.project_role_name = self.create_iam_resource_name(
            name_list=[self.res_name_prefix, 'CodeBuild-Project'],
            filter_id='IAM.Role.RoleName')

        # codecommit_repo_users ManagedPolicies
        managed_policy_arns = []
        for user_ref in action_config.codecommit_repo_users:
            user = get_model_obj_from_ref(user_ref, self.paco_ctx.project)
            # codecommit_stack = user.__parent__.__parent__.__parent__.stack
            user_logical_id = self.gen_cf_logical_name(user.username)
            codecommit_user_policy_param = self.create_cfn_parameter(
                param_type='String',
                name='CodeCommitUserPolicy' + user_logical_id,
                description='The CodeCommit User Policy for ' + user.username,
                value=user_ref + '.policy.arn',
            )
            managed_policy_arns.append(
                troposphere.Ref(codecommit_user_policy_param))

        project_role_res = troposphere.iam.Role(
            title='CodeBuildProjectRole',
            template=template,
            RoleName=self.project_role_name,
            ManagedPolicyArns=managed_policy_arns,
            AssumeRolePolicyDocument=PolicyDocument(
                Version="2012-10-17",
                Statement=[
                    Statement(
                        Effect=Allow,
                        Action=[AssumeRole],
                        Principal=Principal("Service",
                                            ['codebuild.amazonaws.com']),
                    )
                ]))

        project_policy_name = self.create_iam_resource_name(
            name_list=[self.res_name_prefix, 'CodeBuild-Project'],
            filter_id='IAM.Policy.PolicyName')

        # Project Policy
        policy_statements = []
        if self.enable_artifacts_bucket:
            policy_statements.append(
                Statement(
                    Sid='S3Access',
                    Effect=Allow,
                    Action=[
                        Action('s3', 'PutObject'),
                        Action('s3', 'PutObjectAcl'),
                        Action('s3', 'GetObject'),
                        Action('s3', 'GetObjectAcl'),
                        Action('s3', 'ListBucket'),
                        Action('s3', 'DeleteObject'),
                        Action('s3', 'GetBucketPolicy'),
                        Action('s3', 'HeadObject'),
                    ],
                    Resource=[
                        troposphere.Sub('arn:aws:s3:::${ArtifactsBucketName}'),
                        troposphere.Sub(
                            'arn:aws:s3:::${ArtifactsBucketName}/*'),
                    ]))
        if pipeline_config.configuration.disable_codepipeline == False:
            policy_statements.append(
                Statement(Sid='KMSCMK',
                          Effect=Allow,
                          Action=[Action('kms', '*')],
                          Resource=[troposphere.Ref(self.cmk_arn_param)]))
        policy_statements.append(
            Statement(Sid='CloudWatchLogsAccess',
                      Effect=Allow,
                      Action=[
                          Action('logs', 'CreateLogGroup'),
                          Action('logs', 'CreateLogStream'),
                          Action('logs', 'PutLogEvents'),
                      ],
                      Resource=['arn:aws:logs:*:*:*']))

        release_phase = action_config.release_phase
        if release_phase != None and release_phase.ecs != None:
            ssm_doc = self.paco_ctx.project['resource']['ssm'].ssm_documents[
                'paco_ecs_docker_exec']
            # SSM Exec Document
            policy_statements.append(
                Statement(Sid='ECSReleasePhaseSSMCore',
                          Effect=Allow,
                          Action=[
                              Action('ssm', 'ListDocuments'),
                              Action('ssm', 'ListDocumentVersions'),
                              Action('ssm', 'DescribeDocument'),
                              Action('ssm', 'GetDocument'),
                              Action('ssm', 'DescribeInstanceInformation'),
                              Action('ssm', 'DescribeDocumentParameters'),
                              Action('ssm', 'CancelCommand'),
                              Action('ssm', 'ListCommands'),
                              Action('ssm', 'ListCommandInvocations'),
                              Action('ssm', 'DescribeAutomationExecutions'),
                              Action('ssm', 'DescribeInstanceProperties'),
                              Action('ssm', 'GetCommandInvocation'),
                              Action('ec2', 'DescribeInstanceStatus'),
                          ],
                          Resource=['*']))
            policy_statements.append(
                Statement(
                    Sid=f'ECSReleasePhaseSSMSendCommandDocument',
                    Effect=Allow,
                    Action=[
                        Action('ssm', 'SendCommand'),
                    ],
                    Resource=[
                        f'arn:aws:ssm:{self.aws_region}:{self.account_ctx.get_id()}:document/paco_ecs_docker_exec'
                    ]))
            idx = 0
            for command in release_phase.ecs:
                policy_statements.append(
                    Statement(
                        Sid=f'ECSReleasePhaseSSMSendCommand{idx}',
                        Effect=Allow,
                        Action=[
                            Action('ssm', 'SendCommand'),
                        ],
                        Resource=[f'arn:aws:ec2:*:*:instance/*'],
                        Condition=Condition(
                            StringLike({
                                'ssm:resourceTag/Paco-ECSCluster-Name':
                                troposphere.Ref(
                                    ecs_release_phase_cluster_name_param[idx])
                            }))))

                policy_statements.append(
                    Statement(
                        Sid=f'ECSRelasePhaseClusterAccess{idx}',
                        Effect=Allow,
                        Action=[
                            Action('ecs', 'DescribeServices'),
                            Action('ecs', 'RunTask'),
                            Action('ecs', 'StopTask'),
                            Action('ecs', 'DescribeContainerInstances'),
                            Action('ecs', 'ListTasks'),
                            Action('ecs', 'DescribeTasks'),
                        ],
                        Resource=['*'],
                        Condition=Condition(
                            StringEquals({
                                'ecs:cluster':
                                troposphere.Ref(
                                    ecs_release_phase_cluster_arn_param[idx])
                            }))))
                idx += 1

            policy_statements.append(
                Statement(Sid='ECSReleasePhaseSSMAutomationExecution',
                          Effect=Allow,
                          Action=[
                              Action('ssm', 'StartAutomationExecution'),
                              Action('ssm', 'StopAutomationExecution'),
                              Action('ssm', 'GetAutomationExecution'),
                          ],
                          Resource=['arn:aws:ssm:::automation-definition/']))
            # ECS Policies
            policy_statements.append(
                Statement(Sid='ECSRelasePhaseECS',
                          Effect=Allow,
                          Action=[
                              Action('ecs', 'DescribeTaskDefinition'),
                              Action('ecs', 'DeregisterTaskDefinition'),
                              Action('ecs', 'RegisterTaskDefinition'),
                              Action('ecs', 'ListTagsForResource'),
                              Action('ecr', 'DescribeImages')
                          ],
                          Resource=['*']))

            # IAM Pass Role
            policy_statements.append(
                Statement(Sid='IAMPassRole',
                          Effect=Allow,
                          Action=[Action('iam', 'passrole')],
                          Resource=['*']))

        if len(action_config.secrets) > 0:
            secrets_arn_list = []
            for secret_ref in action_config.secrets:
                name_hash = md5sum(str_data=secret_ref)
                secret_arn_param = self.create_cfn_parameter(
                    param_type='String',
                    name='SecretsArn' + name_hash,
                    description=
                    'Secrets Manager Secret Arn to expose access to',
                    value=secret_ref + '.arn')
                secrets_arn_list.append(troposphere.Ref(secret_arn_param))
            policy_statements.append(
                Statement(Sid='SecretsManager',
                          Effect=Allow,
                          Action=[
                              Action('secretsmanager', 'GetSecretValue'),
                          ],
                          Resource=secrets_arn_list))

        project_policy_res = troposphere.iam.PolicyType(
            title='CodeBuildProjectPolicy',
            PolicyName=project_policy_name,
            PolicyDocument=PolicyDocument(Statement=policy_statements),
            Roles=[troposphere.Ref(project_role_res)])
        project_policy_res.DependsOn = project_role_res
        template.add_resource(project_policy_res)

        # User defined policies
        for policy in action_config.role_policies:
            policy_name = self.create_resource_name_join(
                name_list=[
                    self.res_name_prefix, 'CodeBuild-Project', policy.name
                ],
                separator='-',
                filter_id='IAM.Policy.PolicyName',
                hash_long_names=True,
                camel_case=True)
            statement_list = []

            for statement in policy.statement:
                action_list = []
                for action in statement.action:
                    action_parts = action.split(':')
                    action_list.append(Action(action_parts[0],
                                              action_parts[1]))
                statement_list.append(
                    Statement(Effect=statement.effect,
                              Action=action_list,
                              Resource=statement.resource))
            troposphere.iam.PolicyType(
                title=self.create_cfn_logical_id('CodeBuildProjectPolicy' +
                                                 policy.name,
                                                 camel_case=True),
                template=template,
                PolicyName=policy_name,
                PolicyDocument=PolicyDocument(Statement=statement_list, ),
                Roles=[troposphere.Ref(project_role_res)])

        # ECR Permission Policies
        self.set_ecr_repositories_statements(
            action_config.ecr_repositories, template,
            f'{self.res_name_prefix}-CodeBuild-Project',
            [troposphere.Ref(project_role_res)])

        # CodeBuild Project Resource
        timeout_mins_param = self.create_cfn_parameter(
            param_type='String',
            name='TimeoutInMinutes',
            description=
            'How long, in minutes, from 5 to 480 (8 hours), for AWS CodeBuild to wait before timing out any related build that did not get marked as completed.',
            value=action_config.timeout_mins,
        )

        # Environment Variables
        codebuild_env_vars = [{
            'Name': 'DeploymentEnvironmentName',
            'Value': troposphere.Ref(deploy_env_name_param)
        }]
        if pipeline_config.configuration.disable_codepipeline == False:
            codebuild_env_vars.append({
                'Name':
                'KMSKey',
                'Value':
                troposphere.Ref(self.cmk_arn_param)
            })
        if self.enable_artifacts_bucket:
            codebuild_env_vars.append({
                'Name':
                'ArtifactsBucket',
                'Value':
                troposphere.Ref(self.artifacts_bucket_name_param),
            })
        # If ECS Release Phase, then add the config to the environment
        release_phase = action_config.release_phase
        if release_phase != None and release_phase.ecs != None:
            idx = 0
            for command in release_phase.ecs:
                codebuild_env_vars.append({
                    'Name':
                    f'PACO_CB_RP_ECS_CLUSTER_ID_{idx}',
                    'Value':
                    troposphere.Ref(ecs_release_phase_cluster_arn_param[idx])
                })
                codebuild_env_vars.append({
                    'Name':
                    f'PACO_CB_RP_ECS_SERVICE_ID_{idx}',
                    'Value':
                    troposphere.Ref(ecs_release_phase_service_arn_param[idx])
                })
                idx += 1

        # CodeBuild: Environment
        project_dict = {
            'Name':
            troposphere.Ref(self.resource_name_prefix_param),
            'Artifacts': {
                'Type': 'NO_ARTIFACTS'
            },
            'Description':
            troposphere.Ref('AWS::StackName'),
            'ServiceRole':
            troposphere.GetAtt('CodeBuildProjectRole', 'Arn'),
            'Environment': {
                'Type': 'LINUX_CONTAINER',
                'ComputeType': troposphere.Ref(compute_type_param),
                'Image': troposphere.Ref(image_param),
                'EnvironmentVariables': codebuild_env_vars,
                'PrivilegedMode': action_config.privileged_mode
            },
            'Source': {
                'Type': 'NO_SOURCE'
            },
            'TimeoutInMinutes':
            troposphere.Ref(timeout_mins_param),
            'Tags':
            troposphere.codebuild.Tags(
                Name=troposphere.Ref(self.resource_name_prefix_param))
        }

        if action_config.buildspec:
            project_dict['Source']['BuildSpec'] = action_config.buildspec

        if pipeline_config.configuration.disable_codepipeline == False:
            project_dict['EncryptionKey'] = troposphere.Ref(self.cmk_arn_param)
            project_dict['Artifacts'] = {'Type': 'CODEPIPELINE'}
            project_dict['Source']['Type'] = 'CODEPIPELINE'
        else:
            if action_config.artifacts == None or action_config.artifacts.type == 'NO_ARTIFACTS':
                project_dict['Artifacts'] = {
                    'Type': 'NO_ARTIFACTS',
                }
            else:
                project_dict['Artifacts'] = {
                    'Type': action_config.artifacts.type,
                    'Location':
                    troposphere.Ref(self.artifacts_bucket_name_param),
                    'NamespaceType': action_config.artifacts.namespace_type,
                    'Packaging': action_config.artifacts.packaging,
                    'Name': action_config.artifacts.name
                }
                if action_config.artifacts.path != None:
                    project_dict['Artifacts'][
                        'Path'] = action_config.artifacts.path
            if action_config.source.github != None:
                github_config = action_config.source.github
                project_dict['Source']['Type'] = 'GITHUB'
                location = f'https://github.com/{github_config.github_owner}/{github_config.github_repository}.git'
                project_dict['Source']['Location'] = location
                project_dict['Source'][
                    'ReportBuildStatus'] = github_config.report_build_status
                if github_config.deployment_branch_name != None:
                    project_dict[
                        'SourceVersion'] = github_config.deployment_branch_name
            else:
                raise PacoException(
                    "CodeBuild source must be configured when Codepipeline is disabled."
                )

        if action_config.concurrent_build_limit > 0:
            project_dict[
                'ConcurrentBuildLimit'] = action_config.concurrent_build_limit

        if action_config.vpc_config != None:
            vpc_config = action_config.vpc_config
            vpc_id_param = self.create_cfn_parameter(
                name='VPC',
                param_type='AWS::EC2::VPC::Id',
                description='The VPC Id',
                value='paco.ref netenv.{}.<environment>.<region>.network.vpc.id'
                .format(self.env_ctx.netenv.name),
            )

            security_group_list = []
            for sg_ref in vpc_config.security_groups:
                ref = Reference(sg_ref)
                sg_param_name = self.gen_cf_logical_name('SecurityGroupId' +
                                                         ref.parts[-2] +
                                                         ref.parts[-1])
                sg_param = self.create_cfn_parameter(
                    name=sg_param_name,
                    param_type='String',
                    description='Security Group Id',
                    value=sg_ref + '.id',
                )
                security_group_list.append(troposphere.Ref(sg_param))

            # security_group_list_param = self.create_cfn_ref_list_param(
            #     param_type='List<AWS::EC2::SecurityGroup::Id>',
            #     name='SecurityGroupList',
            #     description='List of security group ids to attach to CodeBuild.',
            #     value=vpc_config.security_groups,
            #     ref_attribute='id',
            # )
            subnet_id_list = []
            subnet_arn_list = []
            az_size = self.env_ctx.netenv[self.account_ctx.name][
                self.aws_region].network.availability_zones
            for segment_ref in vpc_config.segments:
                for az_idx in range(1, az_size + 1):
                    # Subnet Ids
                    segment_name = self.create_cfn_logical_id(
                        f"Segment{segment_ref.split('.')[-1]}AZ{az_idx}")
                    subnet_id_param = self.create_cfn_parameter(
                        name=segment_name,
                        param_type='AWS::EC2::Subnet::Id',
                        description=
                        f'VPC Subnet Id in AZ{az_idx} for CodeBuild VPC Config',
                        value=segment_ref + f'.az{az_idx}.subnet_id')
                    subnet_id_list.append(troposphere.Ref(subnet_id_param))
                    # Subnet Arns
                    subnet_arn_param = self.create_cfn_parameter(
                        name=segment_name + 'Arn',
                        param_type='String',
                        description=
                        f'VPC Subnet Id ARN in AZ{az_idx} for CodeBuild VPC Config',
                        value=segment_ref + f'.az{az_idx}.subnet_id.arn')
                    subnet_arn_list.append(troposphere.Ref(subnet_arn_param))

            if len(subnet_id_list) == 0:
                raise PacoException(
                    "CodeBuild VPC Config must have at least one segment defined."
                )

            # VPC Config Permissions
            policy_statements.append(
                Statement(Sid='VpcConfigPermissions',
                          Effect=Allow,
                          Action=[
                              Action('ec2', 'CreateNetworkInterface'),
                              Action('ec2', 'DescribeDhcpOptions'),
                              Action('ec2', 'DescribeNetworkInterfaces'),
                              Action('ec2', 'DeleteNetworkInterface'),
                              Action('ec2', 'DescribeSubnets'),
                              Action('ec2', 'DescribeSecurityGroups'),
                              Action('ec2', 'DescribeVpcs'),
                          ],
                          Resource=['*']))
            policy_statements.append(
                Statement(
                    Sid='VpcConfigNetworkInterface',
                    Effect=Allow,
                    Action=[
                        Action('ec2', 'CreateNetworkInterfacePermission'),
                    ],
                    Resource=[
                        f'arn:aws:ec2:{self.aws_region}:{self.account_ctx.id}:network-interface/*'
                    ],
                    Condition=Condition([
                        StringEquals({
                            "ec2:AuthorizedService":
                            "codebuild.amazonaws.com"
                        }),
                        ArnEquals({"ec2:Subnet": subnet_arn_list})
                    ])))

            project_dict['VpcConfig'] = {
                'VpcId': troposphere.Ref(vpc_id_param),
                'SecurityGroupIds': security_group_list,
                'Subnets': subnet_id_list
            }

        # Batch Build Config
        batch_service_role_res = None
        if action_config.build_batch_config != None and action_config.build_batch_config.is_enabled(
        ):
            batch_config = action_config.build_batch_config

            batch_service_role_name = self.create_iam_resource_name(
                name_list=[
                    self.res_name_prefix, 'CodeBuild-BuildBatch-ServiceRole'
                ],
                filter_id='IAM.Role.RoleName')
            batch_service_role_res = troposphere.iam.Role(
                title='CodeBuildBuildBatchConfigServiceRole',
                template=template,
                RoleName=batch_service_role_name,
                AssumeRolePolicyDocument=PolicyDocument(
                    Version="2012-10-17",
                    Statement=[
                        Statement(
                            Effect=Allow,
                            Action=[AssumeRole],
                            Principal=Principal("Service",
                                                ['codebuild.amazonaws.com']),
                        )
                    ]))

            project_dict['BuildBatchConfig'] = {
                'BatchReportMode': batch_config.batch_report_mode,
                'CombineArtifacts': batch_config.combine_artifacts,
                'TimeoutInMins': batch_config.timeout_in_mins,
                'ServiceRole': troposphere.GetAtt(batch_service_role_res,
                                                  'Arn'),
                'Restrictions': {
                    'ComputeTypesAllowed':
                    batch_config.restrictions.compute_types_allowed,
                    'MaximumBuildsAllowed':
                    batch_config.restrictions.maximum_builds_allowed
                }
            }

        project_res = troposphere.codebuild.Project.from_dict(
            'CodeBuildProject', project_dict)
        project_res.DependsOn = project_policy_res
        if action_config.build_batch_config != None and action_config.build_batch_config.is_enabled(
        ):
            project_res.DependsOn = batch_service_role_res

        self.template.add_resource(project_res)

        if batch_service_role_res != None:
            build_batch_policy_statements = []
            build_batch_policy_statements.append(
                Statement(Sid='BatchServiceRole',
                          Effect=Allow,
                          Action=[
                              Action('codebuild', 'StartBuild'),
                              Action('codebuild', 'StopBuild'),
                              Action('codebuild', 'RetryBuild')
                          ],
                          Resource=[troposphere.GetAtt(project_res, 'Arn')]))

            batch_policy_name = self.create_iam_resource_name(
                name_list=[self.res_name_prefix, 'CodeBuild-BatchPolicy'],
                filter_id='IAM.Policy.PolicyName')
            batch_policy_res = troposphere.iam.PolicyType(
                title='CodeBuildBuildBatchPolicy',
                template=template,
                PolicyName=batch_policy_name,
                PolicyDocument=PolicyDocument(
                    Statement=build_batch_policy_statements),
                Roles=[troposphere.Ref(batch_service_role_res)])

            batch_policy_res.DependsOn = project_res

        self.create_output(title='ProjectArn',
                           value=troposphere.GetAtt(project_res, 'Arn'),
                           description='CodeBuild Project Arn',
                           ref=config_ref + '.project.arn')

        return project_res
Example #8
0
    def create_codebuild_cfn(self, template, pipeline_config, action_config,
                             config_ref):
        # CodeBuild
        compute_type_param = self.create_cfn_parameter(
            param_type='String',
            name='CodeBuildComputeType',
            description=
            'The type of compute environment. This determines the number of CPU cores and memory the build environment uses.',
            value=action_config.codebuild_compute_type,
        )
        image_param = self.create_cfn_parameter(
            param_type='String',
            name='CodeBuildImage',
            description=
            'The image tag or image digest that identifies the Docker image to use for this build project.',
            value=action_config.codebuild_image,
        )
        deploy_env_name_param = self.create_cfn_parameter(
            param_type='String',
            name='DeploymentEnvironmentName',
            description=
            'The name of the environment codebuild will be deploying into.',
            value=action_config.deployment_environment,
        )
        # If ECS Release Phase, then create the needed parameters
        release_phase = action_config.release_phase
        ecs_release_phase_cluster_arn_param = []
        ecs_release_phase_cluster_name_param = []
        ecs_release_phase_service_arn_param = []
        if release_phase != None and release_phase.ecs != None:
            idx = 0
            for command in release_phase.ecs:
                service_obj = get_model_obj_from_ref(command.service,
                                                     self.paco_ctx.project)
                service_obj = get_parent_by_interface(service_obj,
                                                      schemas.IECSServices)
                cluster_arn_param = self.create_cfn_parameter(
                    param_type='String',
                    name=f'ReleasePhaseECSClusterArn{idx}',
                    description='ECS Cluster Arn',
                    value=service_obj.cluster + '.arn',
                )
                ecs_release_phase_cluster_arn_param.append(cluster_arn_param)
                cluster_arn_param = self.create_cfn_parameter(
                    param_type='String',
                    name=f'ReleasePhaseECSClusterName{idx}',
                    description='ECS Cluster Name',
                    value=service_obj.cluster + '.name',
                )
                ecs_release_phase_cluster_name_param.append(cluster_arn_param)
                service_arn_param = self.create_cfn_parameter(
                    param_type='String',
                    name=f'ReleasePhaseECSServiceArn{idx}',
                    description='ECS Service Arn',
                    value=command.service + '.arn',
                )
                ecs_release_phase_service_arn_param.append(service_arn_param)
                idx += 1
        self.project_role_name = self.create_iam_resource_name(
            name_list=[self.res_name_prefix, 'CodeBuild-Project'],
            filter_id='IAM.Role.RoleName')

        # codecommit_repo_users ManagedPolicies
        managed_policy_arns = []
        for user_ref in action_config.codecommit_repo_users:
            user = get_model_obj_from_ref(user_ref, self.paco_ctx.project)
            # codecommit_stack = user.__parent__.__parent__.__parent__.stack
            user_logical_id = self.gen_cf_logical_name(user.username)
            codecommit_user_policy_param = self.create_cfn_parameter(
                param_type='String',
                name='CodeCommitUserPolicy' + user_logical_id,
                description='The CodeCommit User Policy for ' + user.username,
                value=user_ref + '.policy.arn',
            )
            managed_policy_arns.append(
                troposphere.Ref(codecommit_user_policy_param))

        project_role_res = troposphere.iam.Role(
            title='CodeBuildProjectRole',
            template=template,
            RoleName=self.project_role_name,
            ManagedPolicyArns=managed_policy_arns,
            AssumeRolePolicyDocument=PolicyDocument(
                Version="2012-10-17",
                Statement=[
                    Statement(
                        Effect=Allow,
                        Action=[AssumeRole],
                        Principal=Principal("Service",
                                            ['codebuild.amazonaws.com']),
                    )
                ]))

        project_policy_name = self.create_iam_resource_name(
            name_list=[self.res_name_prefix, 'CodeBuild-Project'],
            filter_id='IAM.Policy.PolicyName')

        # Project Policy
        policy_statements = []
        if pipeline_config.configuration.disable_codepipeline == False:
            policy_statements.extend([
                Statement(
                    Sid='S3Access',
                    Effect=Allow,
                    Action=[
                        Action('s3', 'PutObject'),
                        Action('s3', 'PutObjectAcl'),
                        Action('s3', 'GetObject'),
                        Action('s3', 'GetObjectAcl'),
                        Action('s3', 'ListBucket'),
                        Action('s3', 'DeleteObject'),
                        Action('s3', 'GetBucketPolicy'),
                        Action('s3', 'HeadObject'),
                    ],
                    Resource=[
                        troposphere.Sub('arn:aws:s3:::${ArtifactsBucketName}'),
                        troposphere.Sub(
                            'arn:aws:s3:::${ArtifactsBucketName}/*'),
                    ]),
                Statement(Sid='KMSCMK',
                          Effect=Allow,
                          Action=[Action('kms', '*')],
                          Resource=[troposphere.Ref(self.cmk_arn_param)])
            ])
        policy_statements.append(
            Statement(Sid='CloudWatchLogsAccess',
                      Effect=Allow,
                      Action=[
                          Action('logs', 'CreateLogGroup'),
                          Action('logs', 'CreateLogStream'),
                          Action('logs', 'PutLogEvents'),
                      ],
                      Resource=['arn:aws:logs:*:*:*']))

        release_phase = action_config.release_phase
        if release_phase != None and release_phase.ecs != None:
            ssm_doc = self.paco_ctx.project['resource']['ssm'].ssm_documents[
                'paco_ecs_docker_exec']
            # SSM Exec Document
            policy_statements.append(
                Statement(Sid='ECSReleasePhaseSSMCore',
                          Effect=Allow,
                          Action=[
                              Action('ssm', 'ListDocuments'),
                              Action('ssm', 'ListDocumentVersions'),
                              Action('ssm', 'DescribeDocument'),
                              Action('ssm', 'GetDocument'),
                              Action('ssm', 'DescribeInstanceInformation'),
                              Action('ssm', 'DescribeDocumentParameters'),
                              Action('ssm', 'CancelCommand'),
                              Action('ssm', 'ListCommands'),
                              Action('ssm', 'ListCommandInvocations'),
                              Action('ssm', 'DescribeAutomationExecutions'),
                              Action('ssm', 'DescribeInstanceProperties'),
                              Action('ssm', 'GetCommandInvocation'),
                              Action('ec2', 'DescribeInstanceStatus'),
                          ],
                          Resource=['*']))
            policy_statements.append(
                Statement(
                    Sid=f'ECSReleasePhaseSSMSendCommandDocument',
                    Effect=Allow,
                    Action=[
                        Action('ssm', 'SendCommand'),
                    ],
                    Resource=[
                        f'arn:aws:ssm:{self.aws_region}:{self.account_ctx.get_id()}:document/paco_ecs_docker_exec'
                    ]))
            idx = 0
            for command in release_phase.ecs:
                policy_statements.append(
                    Statement(
                        Sid=f'ECSReleasePhaseSSMSendCommand{idx}',
                        Effect=Allow,
                        Action=[
                            Action('ssm', 'SendCommand'),
                        ],
                        Resource=[f'arn:aws:ec2:*:*:instance/*'],
                        Condition=Condition(
                            StringLike({
                                'ssm:resourceTag/Paco-ECSCluster-Name':
                                troposphere.Ref(
                                    ecs_release_phase_cluster_name_param[idx])
                            }))))

                policy_statements.append(
                    Statement(
                        Sid=f'ECSRelasePhaseClusterAccess{idx}',
                        Effect=Allow,
                        Action=[
                            Action('ecs', 'DescribeServices'),
                            Action('ecs', 'RunTask'),
                            Action('ecs', 'StopTask'),
                            Action('ecs', 'DescribeContainerInstances'),
                            Action('ecs', 'ListTasks'),
                            Action('ecs', 'DescribeTasks'),
                        ],
                        Resource=['*'],
                        Condition=Condition(
                            StringEquals({
                                'ecs:cluster':
                                troposphere.Ref(
                                    ecs_release_phase_cluster_arn_param[idx])
                            }))))
                idx += 1

            policy_statements.append(
                Statement(Sid='ECSReleasePhaseSSMAutomationExecution',
                          Effect=Allow,
                          Action=[
                              Action('ssm', 'StartAutomationExecution'),
                              Action('ssm', 'StopAutomationExecution'),
                              Action('ssm', 'GetAutomationExecution'),
                          ],
                          Resource=['arn:aws:ssm:::automation-definition/']))
            # ECS Policies
            policy_statements.append(
                Statement(Sid='ECSRelasePhaseECS',
                          Effect=Allow,
                          Action=[
                              Action('ecs', 'DescribeTaskDefinition'),
                              Action('ecs', 'DeregisterTaskDefinition'),
                              Action('ecs', 'RegisterTaskDefinition'),
                              Action('ecs', 'ListTagsForResource'),
                              Action('ecr', 'DescribeImages')
                          ],
                          Resource=['*']))

            # IAM Pass Role
            policy_statements.append(
                Statement(Sid='IAMPassRole',
                          Effect=Allow,
                          Action=[Action('iam', 'passrole')],
                          Resource=['*']))

        if len(action_config.secrets) > 0:
            secrets_arn_list = []
            for secret_ref in action_config.secrets:
                name_hash = md5sum(str_data=secret_ref)
                secret_arn_param = self.create_cfn_parameter(
                    param_type='String',
                    name='SecretsArn' + name_hash,
                    description=
                    'Secrets Manager Secret Arn to expose access to',
                    value=secret_ref + '.arn')
                secrets_arn_list.append(troposphere.Ref(secret_arn_param))
            policy_statements.append(
                Statement(Sid='SecretsManager',
                          Effect=Allow,
                          Action=[
                              Action('secretsmanager', 'GetSecretValue'),
                          ],
                          Resource=secrets_arn_list))

        project_policy_res = troposphere.iam.PolicyType(
            title='CodeBuildProjectPolicy',
            PolicyName=project_policy_name,
            PolicyDocument=PolicyDocument(Statement=policy_statements),
            Roles=[troposphere.Ref(project_role_res)])
        template.add_resource(project_policy_res)

        # User defined policies
        for policy in action_config.role_policies:
            policy_name = self.create_resource_name_join(
                name_list=[
                    self.res_name_prefix, 'CodeBuild-Project', policy.name
                ],
                separator='-',
                filter_id='IAM.Policy.PolicyName',
                hash_long_names=True,
                camel_case=True)
            statement_list = []

            for statement in policy.statement:
                action_list = []
                for action in statement.action:
                    action_parts = action.split(':')
                    action_list.append(Action(action_parts[0],
                                              action_parts[1]))
                statement_list.append(
                    Statement(Effect=statement.effect,
                              Action=action_list,
                              Resource=statement.resource))
            troposphere.iam.PolicyType(
                title=self.create_cfn_logical_id('CodeBuildProjectPolicy' +
                                                 policy.name,
                                                 camel_case=True),
                template=template,
                PolicyName=policy_name,
                PolicyDocument=PolicyDocument(Statement=statement_list, ),
                Roles=[troposphere.Ref(project_role_res)])

        # ECR Permission Policies
        self.set_ecr_repositories_statements(
            action_config.ecr_repositories, template,
            f'{self.res_name_prefix}-CodeBuild-Project',
            [troposphere.Ref(project_role_res)])

        # CodeBuild Project Resource
        timeout_mins_param = self.create_cfn_parameter(
            param_type='String',
            name='TimeoutInMinutes',
            description=
            'How long, in minutes, from 5 to 480 (8 hours), for AWS CodeBuild to wait before timing out any related build that did not get marked as completed.',
            value=action_config.timeout_mins,
        )

        # Environment Variables
        codebuild_env_vars = [{
            'Name': 'DeploymentEnvironmentName',
            'Value': troposphere.Ref(deploy_env_name_param)
        }]
        if pipeline_config.configuration.disable_codepipeline == False:
            codebuild_env_vars.extend([{
                'Name':
                'ArtifactsBucket',
                'Value':
                troposphere.Ref(self.artifacts_bucket_name_param),
            }, {
                'Name':
                'KMSKey',
                'Value':
                troposphere.Ref(self.cmk_arn_param)
            }])
        # If ECS Release Phase, then add the config to the environment
        release_phase = action_config.release_phase
        if release_phase != None and release_phase.ecs != None:
            idx = 0
            for command in release_phase.ecs:
                codebuild_env_vars.append({
                    'Name':
                    f'PACO_CB_RP_ECS_CLUSTER_ID_{idx}',
                    'Value':
                    troposphere.Ref(ecs_release_phase_cluster_arn_param[idx])
                })
                codebuild_env_vars.append({
                    'Name':
                    f'PACO_CB_RP_ECS_SERVICE_ID_{idx}',
                    'Value':
                    troposphere.Ref(ecs_release_phase_service_arn_param[idx])
                })
                idx += 1

        # CodeBuild: Environment
        source = troposphere.codebuild.Source(Type='CODEPIPELINE', )
        if action_config.buildspec != None and action_config.buildspec != '':
            source = troposphere.codebuild.Source(
                Type='CODEPIPELINE',
                BuildSpec=action_config.buildspec,
            )

        project_dict = {
            'Name':
            troposphere.Ref(self.resource_name_prefix_param),
            'Artifacts': {
                'Type': 'NO_ARTIFACTS'
            },
            'Description':
            troposphere.Ref('AWS::StackName'),
            'ServiceRole':
            troposphere.GetAtt('CodeBuildProjectRole', 'Arn'),
            'Environment': {
                'Type': 'LINUX_CONTAINER',
                'ComputeType': troposphere.Ref(compute_type_param),
                'Image': troposphere.Ref(image_param),
                'EnvironmentVariables': codebuild_env_vars,
                'PrivilegedMode': action_config.privileged_mode
            },
            'Source': {
                'Type': 'NO_SOURCE'
            },
            'TimeoutInMinutes':
            troposphere.Ref(timeout_mins_param),
            'Tags':
            troposphere.codebuild.Tags(
                Name=troposphere.Ref(self.resource_name_prefix_param))
        }

        if action_config.buildspec:
            project_dict['Source']['BuildSpec'] = action_config.buildspec

        if pipeline_config.configuration.disable_codepipeline == False:
            project_dict['EncryptionKey'] = troposphere.Ref(self.cmk_arn_param)
            project_dict['Artifacts'] = {'Type': 'CODEPIPELINE'}
            project_dict['Source']['Type'] = 'CODEPIPELINE'
        elif action_config.source.github != None:
            project_dict['Source']['Type'] = 'GITHUB'
            project_dict['Source'][
                'Location'] = action_config.source.github.location
            project_dict['Source'][
                'ReportBuildStatus'] = action_config.source.github.report_build_status
        else:
            raise PacoException(
                "CodeBuild source must be configured when Codepipeline is disabled."
            )

        if action_config.concurrent_build_limit > 0:
            project_dict[
                'ConcurrentBuildLimit'] = action_config.concurrent_build_limit

        project_res = troposphere.codebuild.Project.from_dict(
            'CodeBuildProject', project_dict)
        self.template.add_resource(project_res)

        self.create_output(title='ProjectArn',
                           value=troposphere.GetAtt(project_res, 'Arn'),
                           description='CodeBuild Project Arn',
                           ref=config_ref + '.project.arn')

        return project_res