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, )
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)
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 })
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)
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 })
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)
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
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