def test_condition_equality(self): self.assertEqualWithHash( Condition(StringLike("s3:prefix", ["home/${aws:username}/*"])), Condition(StringLike("s3:prefix", ["home/${aws:username}/*"]))) self.assertNotEqualWithHash( Condition(StringLike("s3:prefix", ["home/${aws:username}/*"])), Condition(StringLike("s3:prefix", ["other/${aws:username}/*"]))) self.assertNotEqualWithHash( Condition(StringLike("s3:prefix", ["home/${aws:username}/*"])), Condition(StringEquals("s3:prefix", ["home/${aws:username}/*"])))
def init_systemsmanagersession_permission(self, permission_config, assume_role_res): if 'ManagedPolicyArns' not in assume_role_res.properties.keys(): assume_role_res.properties['ManagedPolicyArns'] = [] resource_group_condition_list = [] for resource in permission_config.resources: resource_ref = Reference(resource) # Initialize The network environments that we need access into resource_obj = resource_ref.get_model_obj(self.paco_ctx.project) if schemas.IResourceGroup.providedBy(resource_obj): resource_group_condition_list.append( StringLike({ 'ssm:resourceTag/Paco-Application-Group-Name': resource_obj.name })) if len(resource_group_condition_list) == 0: return statement_list = [] statement_list.append( Statement( Sid='SessionManagerStartSession', Effect=Allow, Action=[ Action('ssm', 'StartSession'), ], Resource=[ 'arn:aws:ec2:*:*:instance/*', 'arn:aws:ssm:*::document/AWS-StartPortForwardingSession' ], Condition=Condition(resource_group_condition_list))) statement_list.append( Statement( Sid='SessionManagerPortForward', Effect=Allow, Action=[ Action('ssm', 'StartSession'), ], Resource=[ 'arn:aws:ssm:*::document/AWS-StartPortForwardingSession' ])) statement_list.append( Statement(Sid='SessionManagerTerminateSession', Effect=Allow, Action=[ Action('ssm', 'TerminateSession'), Action('ssm', 'ResumeSession'), ], Resource=['arn:aws:ssm:*:*:session/${aws:username}-*'])) managed_policy_res = troposphere.iam.ManagedPolicy( title=self.create_cfn_logical_id_join(["SystemsManagerSession"]), PolicyDocument=PolicyDocument(Version="2012-10-17", Statement=statement_list), Roles=[troposphere.Ref(assume_role_res)]) self.template.add_resource(managed_policy_res)
def kms_key_statements(key_arn, bucket_arn, bucket_prefix): s3_endpoint = Join('', ["s3.", REGION, "amazonaws.com"]) return [ Statement(Effect=Allow, Action=[ awacs.kms.Decrypt, awacs.kms.GenerateDataKey, ], Resource=[key_arn], Condition=Condition([ StringEquals("kms:ViaService", s3_endpoint), StringLike("kms:EncryptionContext:aws:s3:arn", Join('', [bucket_arn, bucket_prefix, "*"])) ])) ]
s3.S3_ARN("*"), ], ), Statement( Action=[s3.ListBucket], Effect=Allow, Resource=[s3.S3_ARN("myBucket")], Condition=Condition( StringEquals({ 's3:prefix': ['', 'home/'], 's3:delimiter': ['/'], }), ), ), Statement( Action=[s3.ListBucket], Effect=Allow, Resource=[s3.S3_ARN("myBucket")], Condition=Condition(StringLike("s3:prefix", ["home/${aws:username}/*"])), ), Statement( Action=[Action("s3", "*")], Effect=Allow, Resource=[ s3.S3_ARN("myBucket/home/${aws:username}"), s3.S3_ARN("myBucket/home/${aws:username}/*"), ], ), ], ) print(pd.to_json())
def create_template(self): """Create template (main function called by Stacker).""" template = self.template variables = self.get_variables() template.add_version("2010-09-09") template.add_description("Kubernetes Master via EKS - V1.0.0") # Resources ccpsecuritygroup = template.add_resource( ec2.SecurityGroup( "ClusterControlPlaneSecurityGroup", GroupDescription="Cluster communication with worker nodes", Tags=[ { "Key": Sub("kubernetes.io/cluster/${EksClusterName}"), "Value": "owned", }, { "Key": "Product", "Value": "Kubernetes" }, { "Key": "Project", "Value": "eks" }, { "Key": "Name", "Value": Sub("${EksClusterName}-sg-worker-nodes") }, ], VpcId=variables["VPC"].ref, )) template.add_output( Output( ccpsecuritygroup.title, Description="Cluster communication with worker nodes", Export=Export( Sub("${AWS::StackName}-ControlPlaneSecurityGroup")), Value=ccpsecuritygroup.ref(), )) eksservicerole = template.add_resource( iam.Role( "EksServiceRole", AssumeRolePolicyDocument=make_simple_assume_policy( "eks.amazonaws.com"), ManagedPolicyArns=[ IAM_POLICY_ARN_PREFIX + "AmazonEKSClusterPolicy" ], Policies=[ iam.Policy( PolicyName="EksServiceRolePolicy", PolicyDocument=PolicyDocument(Statement=[ Statement( Action=[ awacs.iam.CreateServiceLinkedRole, awacs.iam.PutRolePolicy, ], Condition=Condition( StringLike( "iam:AWSServiceName", "elasticloadbalancing.amazonaws.com", )), Effect=Allow, Resource=[ Sub("arn:aws:iam::${AWS::AccountId}:role/" "aws-service-role/" "elasticloadbalancing.amazonaws.com/" "AWSServiceRoleForElasticLoadBalancing*" ) ], ) ]), ) ], )) ekscluster = template.add_resource( eks.Cluster( "EksCluster", Name=variables["EksClusterName"].ref, Version=variables["EksVersion"].ref, RoleArn=eksservicerole.get_att("Arn"), ResourcesVpcConfig=eks.ResourcesVpcConfig( SecurityGroupIds=[ccpsecuritygroup.ref()], SubnetIds=variables["EksSubnets"].ref, ), )) template.add_output( Output( "%sName" % ekscluster.title, Description="EKS Cluster Name", Export=Export( Sub("${AWS::StackName}-%sName" % ekscluster.title)), Value=ekscluster.ref(), )) template.add_output( Output( "%sEndpoint" % ekscluster.title, Description="EKS Cluster Endpoint", Export=Export( Sub("${AWS::StackName}-%sEndpoint" % ekscluster.title)), Value=ekscluster.get_att("Endpoint"), )) # Additional Outputs template.add_output( Output( "VpcId", Description="EKS Cluster VPC Id", Export=Export(Sub("${AWS::StackName}-VpcId")), Value=variables["VPC"].ref, )) template.add_output( Output( "Subnets", Description="EKS Cluster Subnets", Export=Export(Sub("${AWS::StackName}-Subnets")), Value=Join(",", variables["EksSubnets"].ref), ))
def script_manager_ecr_deploy(self, ecr_deploy_group, asg_dict, asg_config, template): policy_statements = [] for ecr_deploy_name in ecr_deploy_group.keys(): ecr_deploy = ecr_deploy_group[ecr_deploy_name] if ecr_deploy == None: continue if ecr_deploy and len(ecr_deploy.release_phase.ecs) > 0: pull_repos = [] push_repos = [] for repository in ecr_deploy.repositories: source_ecr_obj = get_model_obj_from_ref(repository.source_repo, self.paco_ctx.project) source_env = get_parent_by_interface(source_ecr_obj, schemas.IEnvironmentRegion) source_account_id = self.paco_ctx.get_ref(source_env.network.aws_account+".id") dest_ecr_obj = get_model_obj_from_ref(repository.dest_repo, self.paco_ctx.project) dest_env = get_parent_by_interface(dest_ecr_obj, schemas.IEnvironmentRegion) dest_account_id = self.paco_ctx.get_ref(dest_env.network.aws_account+".id") pull_repo_arn = f'arn:aws:ecr:{source_env.region}:{source_account_id}:repository/{source_ecr_obj.repository_name}' push_repo_arn = f'arn:aws:ecr:{dest_env.region}:{dest_account_id}:repository/{dest_ecr_obj.repository_name}' pull_repos.append(pull_repo_arn) push_repos.append(push_repo_arn) policy_statements.append( Statement( Sid=f'ScriptManagerECRDeployPull', Effect=Allow, Action=[ Action('ecr', 'GetDownloadUrlForLayer'), Action('ecr', 'BatchGetImage'), ], Resource=pull_repos ) ) policy_statements.append( Statement( Sid=f'ScriptManagerECRDeployPush', Effect=Allow, Action=[ Action('ecr', 'GetDownloadUrlForLayer'), Action('ecr', 'BatchCheckLayerAvailability'), Action('ecr', 'PutImage'), Action('ecr', 'InitiateLayerUpload'), Action('ecr', 'UploadLayerPart'), Action('ecr', 'CompleteLayerUpload'), ], Resource=push_repos ) ) iam_cluster_cache = [] idx = 0 for command in ecr_deploy.release_phase.ecs: service_obj = get_model_obj_from_ref(command.service, self.paco_ctx.project) ecs_services_obj = get_parent_by_interface(service_obj, schemas.IECSServices) ecs_release_phase_cluster_arn_param = self.create_cfn_parameter( param_type='String', name=f'ECSReleasePhaseClusterArn{idx}', description=f'ECS Release Phase Cluster Arn {idx}', value=ecs_services_obj.cluster + '.arn' ) ecs_release_phase_cluster_name_param = self.create_cfn_parameter( param_type='String', name=f'ECSReleasePhaseClusterName{idx}', description=f'ECS Release Phase Cluster Name {idx}', value=ecs_services_obj.cluster + '.name' ) ecs_release_phase_service_name_param = self.create_cfn_parameter( param_type='String', name=f'ECSReleasePhaseServiceName{idx}', description=f'ECS Release Phase Cluster Name {idx}', value=command.service + '.name' ) ecs_cluster_asg_tag = troposphere.autoscaling.Tag( f'PACO_CB_RP_ECS_CLUSTER_ID_{idx}', troposphere.Ref(ecs_release_phase_cluster_name_param), True ) ecs_service_asg_tag = troposphere.autoscaling.Tag( f'PACO_CB_RP_ECS_SERVICE_ID_{idx}', troposphere.Ref(ecs_release_phase_service_name_param), True ) asg_dict['Tags'].append(ecs_cluster_asg_tag) asg_dict['Tags'].append(ecs_service_asg_tag) if ecs_services_obj.cluster not in iam_cluster_cache: policy_statements.append( Statement( Sid=f'ECSReleasePhaseSSMSendCommand{idx}', Effect=Allow, Action=[ Action('ssm', 'SendCommand'), ], Resource=[ 'arn:aws:ec2:*:*:instance/*' ], Condition=Condition( StringLike({ 'ssm:resourceTag/Paco-ECSCluster-Name': troposphere.Ref(ecs_release_phase_cluster_name_param) }) ) ) ) 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) }) ) ) ) iam_cluster_cache.append(ecs_services_obj.cluster) 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=[ '*' ] ) ) 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' ] ) ) 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'), Action('ecr', 'GetAuthorizationToken') ], Resource=[ '*' ] ) ) policy_statements.append( Statement( Sid='IAMPassRole', Effect=Allow, Action=[ Action('iam', 'passrole') ], Resource=[ '*' ] ) ) ecs_release_phase_project_policy_res = troposphere.iam.ManagedPolicy( title='ECSReleasePhase', PolicyDocument=PolicyDocument( Version="2012-10-17", Statement=policy_statements ), Roles=[self.instance_iam_role_name] ) template.add_resource(ecs_release_phase_project_policy_res)
def create_template(self): """Create template (main function called by Stacker).""" template = self.template variables = self.get_variables() template.add_version('2010-09-09') template.add_description('Kubernetes Master via EKS - V1.0.0') # Resources ccpsecuritygroup = template.add_resource( ec2.SecurityGroup( 'ClusterControlPlaneSecurityGroup', GroupDescription='Cluster communication with worker nodes', Tags=[ {'Key': Sub('kubernetes.io/cluster/${EksClusterName}'), 'Value': 'owned'}, {'Key': 'Product', 'Value': 'Kubernetes'}, {'Key': 'Project', 'Value': 'eks'}, {'Key': 'Name', 'Value': Sub('${EksClusterName}-sg-worker-nodes')} ], VpcId=variables['VPC'].ref ) ) template.add_output( Output( ccpsecuritygroup.title, Description='Cluster communication with worker nodes', Export=Export( Sub('${AWS::StackName}-ControlPlaneSecurityGroup') ), Value=ccpsecuritygroup.ref() ) ) eksservicerole = template.add_resource( iam.Role( 'EksServiceRole', AssumeRolePolicyDocument=make_simple_assume_policy( 'eks.amazonaws.com' ), ManagedPolicyArns=[ IAM_POLICY_ARN_PREFIX + i for i in [ 'AmazonEKSClusterPolicy', 'AmazonEKSServicePolicy' ] ], Policies=[ iam.Policy( PolicyName='EksServiceRolePolicy', PolicyDocument=PolicyDocument( Statement=[ Statement( Action=[awacs.iam.CreateServiceLinkedRole, awacs.iam.PutRolePolicy], Condition=Condition( StringLike( 'iam:AWSServiceName', 'elasticloadbalancing.amazonaws.com' # noqa ) ), Effect=Allow, Resource=[ Sub('arn:aws:iam::${AWS::AccountId}:role/' # noqa 'aws-service-role/' 'elasticloadbalancing.amazonaws.com/' # noqa 'AWSServiceRoleForElasticLoadBalancing*') # noqa ] ) ] ) ) ] ) ) ekscluster = template.add_resource( eks.Cluster( 'EksCluster', Name=variables['EksClusterName'].ref, Version=variables['EksVersion'].ref, RoleArn=eksservicerole.get_att('Arn'), ResourcesVpcConfig=eks.ResourcesVpcConfig( SecurityGroupIds=[ccpsecuritygroup.ref()], SubnetIds=variables['EksSubnets'].ref ) ) ) template.add_output( Output( "%sName" % ekscluster.title, Description='EKS Cluster Name', Export=Export( Sub("${AWS::StackName}-%sName" % ekscluster.title) ), Value=ekscluster.ref() ) ) template.add_output( Output( "%sEndpoint" % ekscluster.title, Description='EKS Cluster Endpoint', Export=Export( Sub("${AWS::StackName}-%sEndpoint" % ekscluster.title) ), Value=ekscluster.get_att('Endpoint') ) ) # Additional Outputs template.add_output( Output( 'VpcId', Description='EKS Cluster VPC Id', Export=Export( Sub('${AWS::StackName}-VpcId') ), Value=variables['VPC'].ref ) ) template.add_output( Output( 'Subnets', Description='EKS Cluster Subnets', Export=Export( Sub('${AWS::StackName}-Subnets') ), Value=Join(',', variables['EksSubnets'].ref) ) )
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 cloudtrail_template(self, cloudtrail): "Template for CloudTrail" # Troposphere Template Initialization self.init_template(f'KMS Customer Managed Key (CMK) for CloudTrail') users = [] for user in cloudtrail.kms_users: if is_ref(user): user_obj = get_model_obj_from_ref(user, self.paco_ctx.project) user = user_obj.username users.append( f"arn:aws:iam::{self.paco_ctx.project['accounts']['master'].account_id}:user/{user}" ) accounts = [ f"arn:aws:cloudtrail:*:{account.account_id}:trail/*" for account in cloudtrail.get_accounts() ] cloudtrail_policy = PolicyDocument( Version='2012-10-17', Statement=[ Statement( Sid="Allows admin of the key", Effect=Allow, Principal=Principal( "AWS", [f'arn:aws:iam::{self.stack.account_ctx.id}:root']), Action=[ awacs.kms.CreateAlias, awacs.kms.CreateCustomKeyStore, awacs.kms.CreateGrant, awacs.kms.CreateKey, awacs.kms.DescribeCustomKeyStores, awacs.kms.DescribeKey, awacs.kms.EnableKey, awacs.kms.EnableKeyRotation, awacs.kms.ListAliases, awacs.kms.ListGrants, awacs.kms.ListKeyPolicies, awacs.kms.ListKeys, awacs.kms.ListResourceTags, awacs.kms.ListRetirableGrants, awacs.kms.PutKeyPolicy, awacs.kms.UpdateAlias, awacs.kms.UpdateCustomKeyStore, awacs.kms.UpdateKeyDescription, awacs.kms.RevokeGrant, awacs.kms.DisableKey, awacs.kms.DisableKeyRotation, awacs.kms.GetKeyPolicy, awacs.kms.GetKeyRotationStatus, awacs.kms.GetParametersForImport, awacs.kms.DeleteAlias, awacs.kms.DeleteCustomKeyStore, awacs.kms.DeleteImportedKeyMaterial, awacs.kms.ScheduleKeyDeletion, awacs.kms.CancelKeyDeletion, awacs.kms.TagResource ], Resource=['*'], ), Statement( Sid="Allow CloudTrail access", Effect=Allow, Principal=Principal("Service", ['cloudtrail.amazonaws.com']), Action=[awacs.kms.DescribeKey], Resource=['*'], ), Statement(Sid="Allow CloudTrail log decrypt permissions", Effect=Allow, Action=[awacs.kms.Decrypt], Principal=Principal("AWS", users), Resource=['*'], Condition=Condition([ Null({ "kms:EncryptionContext:aws:cloudtrail:arn": False }) ])), Statement(Sid="Allow CloudTrail to encrypt logs", Effect=Allow, Principal=Principal("Service", ["cloudtrail.amazonaws.com"]), Action=[awacs.kms.GenerateDataKey], Resource=['*'], Condition=Condition([ StringLike({ "kms:EncryptionContext:aws:cloudtrail:arn": accounts }) ])), ]) kms_dict = { 'Description': 'CMK for CloudTrail', 'EnableKeyRotation': True, 'KeyPolicy': cloudtrail_policy, } kms_res = troposphere.kms.Key.from_dict('KMS', kms_dict) self.template.add_resource(kms_res) # Outputs self.create_output( title='CMKArn', description="The CMK Arn", value=troposphere.GetAtt(kms_res, 'Arn'), ref=self.resource.paco_ref_parts + ".kms.arn", ) self.create_output( title='CMKId', description="The CMK Id", value=troposphere.Ref(kms_res), ref=self.resource.paco_ref_parts + ".kms.id", )
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
def add_resources(self): """Add resources to template.""" template = self.template variables = self.get_variables() vpnrole = template.add_resource( iam.Role( 'VPNRole', AssumeRolePolicyDocument=iam_policies.assumerolepolicy('ec2'), ManagedPolicyArns=variables['VPNManagedPolicies'].ref, Path='/', Policies=[ iam.Policy( PolicyName=Join('-', [ 'customer-vpn-server-role', variables['EnvironmentName'].ref, variables['CustomerName'].ref ]), PolicyDocument=Policy( Version='2012-10-17', Statement=[ # ModifyInstanceAttribute is for src/dst check Statement(Action=[ awacs.ec2.DescribeRouteTables, awacs.ec2.DescribeAddresses, awacs.ec2.AssociateAddress, awacs.ec2.CreateRoute, awacs.ec2.ReplaceRoute, awacs.ec2.ModifyInstanceAttribute ], Effect=Allow, Resource=['*']), Statement( Action=[ awacs.aws.Action('s3', 'Get*'), awacs.aws.Action('s3', 'List*'), awacs.aws.Action('s3', 'Put*') ], Effect=Allow, Resource=[ Join( '', [ 'arn:aws:s3:::', variables['ChefDataBucketName'] .ref, # noqa pylint: disable=line-too-long '/', variables['EnvironmentName']. ref, '/', variables['BucketKey'].ref, '/*' ]) ]), Statement( Action=[awacs.s3.ListBucket], Effect=Allow, Resource=[ Join('', [ 'arn:aws:s3:::', variables['ChefDataBucketName'].ref ]) # noqa pylint: disable=line-too-long ], Condition=Condition( StringLike( 's3:prefix', [ Join( '', [ variables[ 'EnvironmentName']. ref, # noqa pylint: disable=line-too-long '/', variables['BucketKey']. ref, # noqa pylint: disable=line-too-long '/*' ]) ]))) ])) ])) vpninstanceprofile = template.add_resource( iam.InstanceProfile('VPNInstanceProfile', Path='/', Roles=[Ref(vpnrole)])) amiid = template.add_resource( cfn_custom_classes.AMIId( 'AMIId', Condition='MissingVPNAMI', Platform=variables['VPNOS'].ref, Region=Ref('AWS::Region'), ServiceToken=variables['AMILookupArn'].ref)) # Lookup subnets from core VPC stack subnetlookuplambdarole = template.add_resource( iam.Role( 'SubnetLookupLambdaRole', Condition='PrivateSubnetCountOmitted', AssumeRolePolicyDocument=iam_policies.assumerolepolicy( 'lambda'), ManagedPolicyArns=[ IAM_ARN_PREFIX + 'AWSLambdaBasicExecutionRole' ], Policies=[ iam.Policy( PolicyName=Join('-', [ 'subnetlookup-lambda-role', variables['EnvironmentName'].ref, variables['CustomerName'].ref ]), PolicyDocument=Policy( Version='2012-10-17', Statement=[ Statement( Action=[ awacs.aws.Action( 'cloudformation', 'DescribeStack*'), awacs.aws.Action( 'cloudformation', 'Get*') ], Effect=Allow, Resource=[ Join('', [ 'arn:aws:cloudformation:', Ref('AWS::Region'), ':', Ref('AWS::AccountId'), ':stack/', variables['CoreVPCStack'].ref, '/*' ]) ]) ])) ])) cfncustomresourcesubnetlookup = template.add_resource( awslambda.Function( 'CFNCustomResourceSubnetLookup', Condition='PrivateSubnetCountOmitted', Description='Find subnets created by core stack', Code=awslambda.Code( ZipFile=variables['SubnetLookupLambdaFunction']), Handler='index.handler', Role=GetAtt(subnetlookuplambdarole, 'Arn'), Runtime='python2.7', Timeout=10)) subnetlookup = template.add_resource( cfn_custom_classes.SubnetLookup( 'SubnetLookup', Condition='PrivateSubnetCountOmitted', CoreVPCStack=variables['CoreVPCStack'].ref, Region=Ref('AWS::Region'), ServiceToken=GetAtt(cfncustomresourcesubnetlookup, 'Arn'))) common_userdata_prefix = [ "#cloud-config\n", "package_update: true\n", "package_upgrade: false\n", "write_files:\n", " - path: /usr/local/bin/update_vpn_routes.sh\n", " permissions: '0755'\n", " content: |\n", " #!/bin/bash\n", " \n", " export AWS_DEFAULT_REGION=\"", Ref('AWS::Region'), "\"\n", " my_instance_id=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)\n", # noqa pylint: disable=line-too-long " \n", " publicroutetableid=", If( 'PrivateSubnetCountOmitted', GetAtt(subnetlookup.title, 'PublicRouteTableId'), If( 'PublicRouteTableSpecified', variables['PublicRouteTable'].ref, ImportValue( Sub("${%s}-PublicRouteTable" % variables['CoreVPCStack'].name)))), # noqa pylint: disable=line-too-long "\n", " private_route_tables=(", If( 'PrivateSubnetCountOmitted', GetAtt(subnetlookup.title, 'PrivateRouteTables'), If( '3PrivateSubnetsCreated', If( 'PublicRouteTableSpecified', Join(' ', [ variables['PrivateRouteTable1'].ref, variables['PrivateRouteTable2'].ref, variables['PrivateRouteTable3'].ref ]), Join( ' ', [ ImportValue( Sub("${%s}-PrivateRouteTable1" % variables['CoreVPCStack'].name)), # noqa pylint: disable=line-too-long ImportValue( Sub("${%s}-PrivateRouteTable2" % variables['CoreVPCStack'].name)), # noqa pylint: disable=line-too-long ImportValue( Sub("${%s}-PrivateRouteTable3" % variables['CoreVPCStack'].name)) ])), # noqa pylint: disable=line-too-long If( '2PrivateSubnetsCreated', If( 'PublicRouteTableSpecified', Join(' ', [ variables['PrivateRouteTable1'].ref, variables['PrivateRouteTable2'].ref ]), Join( ' ', [ ImportValue( Sub("${%s}-PrivateRouteTable1" % variables['CoreVPCStack'].name)), # noqa pylint: disable=line-too-long ImportValue( Sub("${%s}-PrivateRouteTable2" % variables['CoreVPCStack'].name)) ])), # noqa pylint: disable=line-too-long, If( 'PublicRouteTableSpecified', variables['PrivateRouteTable1'].ref, ImportValue( Sub("${%s}-PrivateRouteTable1" % variables['CoreVPCStack'].name)))))), # noqa pylint: disable=line-too-long ")\n", "\n", " openvpnroutepubdest=", variables['VPNSubnet'].ref, "\n", " \n", " # Disabling sourceDestCheck\n", " aws ec2 modify-instance-attribute --instance-id ${my_instance_id} --source-dest-check \"{\\\"Value\\\": false}\"\n", # noqa pylint: disable=line-too-long " \n", " if aws ec2 describe-route-tables | grep ${openvpnroutepubdest}; then\n", # noqa pylint: disable=line-too-long " # Update 'OpenVPNRoutePub' to point to this instance\n", # noqa pylint: disable=line-too-long " aws ec2 replace-route --route-table-id ${publicroutetableid} --destination-cidr-block ${openvpnroutepubdest} --instance-id ${my_instance_id}\n", # noqa pylint: disable=line-too-long " # Update private routes\n", " for i in \"${private_route_tables[@]}\"\n", " do\n", " aws ec2 replace-route --route-table-id $i --destination-cidr-block ${openvpnroutepubdest} --instance-id ${my_instance_id}\n", # noqa pylint: disable=line-too-long " done\n", " else\n", " # Create 'OpenVPNRoutePub'\n", " aws ec2 create-route --route-table-id ${publicroutetableid} --destination-cidr-block ${openvpnroutepubdest} --instance-id ${my_instance_id}\n", # noqa pylint: disable=line-too-long " # Create private routes\n", " for i in \"${private_route_tables[@]}\"\n", " do\n", " aws ec2 create-route --route-table-id $i --destination-cidr-block ${openvpnroutepubdest} --instance-id ${my_instance_id}\n", # noqa pylint: disable=line-too-long " done\n", " fi\n", " \n", "\n", " - path: /etc/chef/sync_cookbooks.sh\n", " permissions: '0755'\n", " owner: 'root'\n", " group: 'root'\n", " content: |\n", " #!/bin/bash\n", " set -e -o pipefail\n", " \n", " aws --region ", Ref('AWS::Region'), " s3 sync s3://", variables['ChefBucketName'].ref, "/", variables['EnvironmentName'].ref, "/", variables['BucketKey'].ref, "/ /etc/chef/\n", " if compgen -G \"/etc/chef/cookbooks-*.tar.gz\" > /dev/null; then\n", # noqa pylint: disable=line-too-long " echo \"Cookbook archive found.\"\n", " if [ -d \"/etc/chef/cookbooks\" ]; then\n", " echo \"Removing previously extracted cookbooks.\"\n", # noqa pylint: disable=line-too-long " rm -r /etc/chef/cookbooks\n", " fi\n", " echo \"Extracting highest numbered cookbook archive.\"\n", # noqa pylint: disable=line-too-long " cbarchives=(/etc/chef/cookbooks-*.tar.gz)\n", " tar -zxf \"${cbarchives[@]: -1}\" -C /etc/chef\n", " chown -R root:root /etc/chef\n", " fi\n", " \n", "\n", " - path: /etc/chef/perform_chef_run.sh\n", " permissions: '0755'\n", " owner: 'root'\n", " group: 'root'\n", " content: |\n", " #!/bin/bash\n", " set -e -o pipefail\n", " \n", " chef-client -z -r '", If('ChefRunListSpecified', variables['ChefRunList'].ref, Join('', ['recipe[', variables['CustomerName'].ref, '_vpn]'])), "' -c /etc/chef/client.rb -E ", variables['EnvironmentName'].ref, " --force-formatter --no-color -F min\n", "\n", " - path: /etc/chef/client.rb\n", " permissions: '0644'\n", " owner: 'root'\n", " group: 'root'\n", " content: |\n", " log_level :info\n", " log_location '/var/log/chef/client.log'\n", " ssl_verify_mode :verify_none\n", " cookbook_path '/etc/chef/cookbooks'\n", " node_path '/etc/chef/nodes'\n", " role_path '/etc/chef/roles'\n", " data_bag_path '/etc/chef/data_bags'\n", " environment_path '/etc/chef/environments'\n", " local_mode 'true'\n", "\n", " - path: /etc/chef/environments/", variables['EnvironmentName'].ref, ".json\n", " permissions: '0644'\n", " owner: 'root'\n", " group: 'root'\n", " content: |\n", " {\n", " \"name\": \"", variables['EnvironmentName'].ref, "\",\n", " \"default_attributes\": {\n", " \"sturdy\": {\n", " \"openvpn\": {\n", " \"core_vpc_cidr\": \"", variables['VpcCidr'].ref, "\",\n", " \"vpn_elastic_ip\": \"", variables['VpnEipPublicIp'].ref, "\",\n", " \"vpn_subnet_cidr\": \"", variables['VPNSubnet'].ref, "\",\n", " \"chef_data_bucket_name\": \"", variables['ChefDataBucketName'].ref, "\",\n", " \"chef_data_bucket_folder\": \"", variables['EnvironmentName'].ref, "/", variables['BucketKey'].ref, "\",\n", " \"chef_data_bucket_region\": \"", Ref('AWS::Region'), "\"\n", " }\n", " }\n", " },\n", " \"json_class\": \"Chef::Environment\",\n", " \"description\": \"", variables['EnvironmentName'].ref, " environment\",\n", " \"chef_type\": \"environment\"\n", " }\n", "\n", "runcmd:\n", " - set -euf\n", " - echo 'Attaching EIP'\n", " - pip install aws-ec2-assign-elastic-ip\n", # Allowing this command to fail (with ||true) as sturdy_openvpn # 2.3.0+ can handle this association instead. This will be removed # entirely in the next major release of this module (at which time # use of the updated sturdy_openvpn cookbook will be required) " - aws-ec2-assign-elastic-ip --region ", Ref('AWS::Region'), " --valid-ips ", variables['VpnEipPublicIp'].ref, " || true\n", " - echo 'Updating Routes'\n", " - /usr/local/bin/update_vpn_routes.sh\n", " - echo 'Installing Chef'\n", " - curl --max-time 10 --retry-delay 5 --retry 5 -L https://www.chef.io/chef/install.sh | bash -s -- -v ", # noqa pylint: disable=line-too-long variables['ChefClientVersion'].ref, "\n", " - echo 'Configuring Chef'\n", " - mkdir -p /var/log/chef /etc/chef/data_bags /etc/chef/nodes /etc/chef/roles\n", # noqa pylint: disable=line-too-long " - chmod 0755 /etc/chef\n", " - /etc/chef/sync_cookbooks.sh\n", " - /etc/chef/perform_chef_run.sh\n" ] vpnserverlaunchconfig = template.add_resource( autoscaling.LaunchConfiguration( 'VpnServerLaunchConfig', AssociatePublicIpAddress=True, BlockDeviceMappings=[ # CentOS AMIs don't include this by default ec2.BlockDeviceMapping( DeviceName='/dev/sda1', Ebs=ec2.EBSBlockDevice(DeleteOnTermination=True)) ], IamInstanceProfile=Ref(vpninstanceprofile), ImageId=If('MissingVPNAMI', GetAtt(amiid, 'ImageId'), variables['VPNAMI'].ref), InstanceType=variables['ManagementInstanceType'].ref, InstanceMonitoring=False, # extra granularity not worth cost KeyName=If('SSHKeySpecified', variables['KeyName'].ref, Ref('AWS::NoValue')), PlacementTenancy=variables['VpcInstanceTenancy'].ref, SecurityGroups=variables['VPNSecurityGroups'].ref, UserData=If( 'RHELUserData', Base64( Join( '', common_userdata_prefix + [ "yum_repos:\n", " epel:\n", " name: Extra Packages for $releasever - $basearch\n", # noqa pylint: disable=line-too-long " baseurl: http://download.fedoraproject.org/pub/epel/7/$basearch\n", # noqa pylint: disable=line-too-long " enabled: true\n", " failovermethod: priority\n", " gpgcheck: true\n", " gpgkey: https://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-7\n", # noqa pylint: disable=line-too-long "packages:\n", " - awscli\n", " - python-pip\n", " - python2-boto\n", " - python2-boto3\n" ])), Base64( Join( '', common_userdata_prefix + [ "packages:\n", " - awscli\n", " - python-pip\n", " - python-boto\n", " - python-boto3\n" ]))))) template.add_resource( autoscaling.AutoScalingGroup( 'VPNServerASG', MinSize=1, MaxSize=1, LaunchConfigurationName=Ref(vpnserverlaunchconfig), Tags=[ autoscaling.Tag( 'Name', Join('-', [ variables['CustomerName'].ref, 'vpn', variables['EnvironmentName'].ref ]), True), autoscaling.Tag('environment', variables['EnvironmentName'].ref, True), autoscaling.Tag('customer', variables['CustomerName'].ref, True) ], VPCZoneIdentifier=If( 'PublicSubnetsOmitted', GetAtt(subnetlookup.title, 'PublicSubnetList'), variables['PublicSubnets'].ref)))
def add_resources(self): """Add resources to template.""" template = self.template variables = self.get_variables() # IAM Instance Roles and Profiles commonpolicy = template.add_resource( iam.ManagedPolicy( 'CommonPolicy', Description='Common instance policy; allows SSM management ' 'and CloudWatch publishing.', Path='/', PolicyDocument=Policy( Version='2012-10-17', Statement=[ Statement( Action=[awacs.ec2.DescribeInstances, awacs.ec2.DescribeTags, awacs.ec2.CreateTags], Effect=Allow, Resource=['*'] ), Statement( Action=[ awacs.logs.CreateLogGroup, awacs.logs.CreateLogStream, awacs.logs.PutLogEvents, awacs.logs.DescribeLogStreams, awacs.logs.DescribeLogGroups ], Effect=Allow, Resource=[ 'arn:aws:logs:*:*:*' ] ), Statement( Action=[awacs.cloudwatch.PutMetricData], Effect=Allow, Resource=['*'] ), Statement( Action=[awacs.aws.Action('s3', 'List*'), awacs.s3.GetBucketVersioning], Effect=Allow, Resource=[ variables['ChefBucketArn'].ref ] ), Statement( Action=[awacs.aws.Action('s3', 'Get*'), awacs.aws.Action('s3', 'List*')], Effect=Allow, Resource=[ Join('', [variables['ChefBucketArn'].ref, '/*']) ] ), Statement( Action=[awacs.aws.Action('s3', 'Get*'), awacs.aws.Action('s3', 'List*')], Effect=Allow, Resource=[ Join('', [variables['ChefDataBucketArn'].ref, '/all/*']) ] ), Statement( # Required for Jenkins invoked CodeBuild Action=[awacs.s3.GetBucketVersioning], Effect=Allow, Resource=[variables['ChefDataBucketArn'].ref] ), Statement( Action=[awacs.s3.ListBucket], Effect=Allow, Resource=[ Join('', [variables['ChefDataBucketArn'].ref]) ], Condition=Condition( StringLike('s3:prefix', ['', 'all/*']) ) ), Statement( # For wildcard matching of parameters Action=[awacs.ssm.DescribeParameters], Effect=Allow, Resource=['*'] ), Statement( Action=[awacs.ssm.GetParameters], Effect=Allow, Resource=[ Join(':', ['arn:aws:ssm', Ref('AWS::Region'), Ref('AWS::AccountId'), 'parameter/all.*']), Join(':', ['arn:aws:ssm', Ref('AWS::Region'), Ref('AWS::AccountId'), 'parameter/all/*']) ] ), # Platform downloading Statement( Action=[awacs.s3.ListBucket, awacs.s3.GetObject], Effect=Allow, Resource=[ 'arn:aws:s3:::sturdy-platform*', 'arn:aws:s3:::onica-platform*', ] ), # SSM # Adapted from EC2RoleforSSM with the following changes # * Dropping the Put/Get S3 * access # * Dropping the GetParameters * access Statement( Action=[awacs.ssm.DescribeAssociation, awacs.aws.Action( 'ssm', 'GetDeployablePatchSnapshotForInstance' ), awacs.ssm.GetDocument, awacs.ssm.ListAssociations, awacs.ssm.ListInstanceAssociations, awacs.ssm.PutInventory, awacs.ssm.UpdateAssociationStatus, awacs.ssm.UpdateInstanceAssociationStatus, awacs.aws.Action( 'ssm', 'UpdateInstanceInformation')], Effect=Allow, Resource=['*'] ), Statement( Action=[awacs.aws.Action('ec2messages', 'AcknowledgeMessage'), awacs.aws.Action('ec2messages', 'DeleteMessage'), awacs.aws.Action('ec2messages', 'FailMessage'), awacs.aws.Action('ec2messages', 'GetEndpoint'), awacs.aws.Action('ec2messages', 'GetMessages'), awacs.aws.Action('ec2messages', 'SendReply')], Effect=Allow, Resource=['*'] ), Statement( Action=[awacs.ec2.DescribeInstanceStatus], Effect=Allow, Resource=['*'] ), Statement( Action=[awacs.ds.CreateComputer, awacs.ds.DescribeDirectories], Effect=Allow, Resource=['*'] ), Statement( Action=[awacs.s3.ListBucket], Effect=Allow, Resource=[ 'arn:aws:s3:::amazon-ssm-packages-*' ] ) ] ) ) ) commonrole = template.add_resource( iam.Role( 'CommonRole', AssumeRolePolicyDocument=iam_policies.assumerolepolicy('ec2'), ManagedPolicyArns=[Ref(commonpolicy)], Path='/' ) ) commoninstanceprofile = template.add_resource( iam.InstanceProfile( 'CommonInstanceProfile', Path='/', Roles=[Ref(commonrole)] ) ) template.add_output( Output( commonpolicy.title, Description='Common instance policy', Export=Export( Sub('${AWS::StackName}-%s' % commonpolicy.title) ), Value=Ref(commonpolicy) ) ) template.add_output( Output( commonrole.title, Description='Common instance role', Export=Export(Sub('${AWS::StackName}-%s' % commonrole.title)), Value=Ref(commonrole) ) ) template.add_output( Output( commoninstanceprofile.title, Description='Common instance profile', Export=Export( Sub('${AWS::StackName}-%s' % commoninstanceprofile.title) ), Value=Ref(commoninstanceprofile) ) )