in security-and-compliance-account/stacks/pipeline_stack/cdk_stack.py [0:0]
def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
super().__init__(scope, id, **kwargs)
#####################################---START PREREQS---##########################################
# Create a new Code Commit Repo for holding compliance code
source_repo = codecommit.Repository(
self,
'CodeCommitRepo',
repository_name = params['CODE_COMMIT_SOURCE_REPO_NAME'],
description = 'Compliance code repository'
)
# Source Repo ARN is going to be passed into the subsequent stacks that are created after this one.
self.source_repo_arn = source_repo.repository_arn
# CodePipeline ecryption key
pipeline_encryption_key = kms.Key(
self,
'PipelineEncryptionKey',
alias = 'goldmine/codepipeline/security-compliance',
description = 'Encryprion for security and compliance codepipeline',
enabled = True,
enable_key_rotation = True
)
# CodePipeline Bucket
pipeline_bucket = s3.Bucket(
self,
'CodePipelineS3Bucket',
access_control = s3.BucketAccessControl.PRIVATE,
bucket_name = 'pipeline-bucket-sec-compliance-'+core.Aws.ACCOUNT_ID,
encryption = s3.BucketEncryption.KMS,
encryption_key = pipeline_encryption_key,
lifecycle_rules = [
s3.LifecycleRule(
enabled = True,
id = 'LccRule1-ExpireAllNoncurrentIn8Days',
noncurrent_version_expiration = core.Duration.days(8),
prefix = ''
)
],
public_read_access = False,
removal_policy = core.RemovalPolicy.DESTROY,
versioned = True
)
# Terraform Backend Bucket
# The bucket is created in the same region as the pipeline.
# If you want to use an existing bucket then avoid creating this bucket.
# Instead, directly supply it to the code build job in the pipeline below.
# Make sure that the code pipeline role has required access to your bucket.
tf_backend_bucket = s3.Bucket(
self,
'TfBackendS3Bucket',
access_control = s3.BucketAccessControl.PRIVATE,
bucket_name = 'tf-backend-bucket-'+core.Aws.ACCOUNT_ID,
encryption = s3.BucketEncryption.KMS,
encryption_key = pipeline_encryption_key,
public_read_access = False,
removal_policy = core.RemovalPolicy.DESTROY,
versioned = True
)
# Retrieve cross account role list from params
cross_account_role_list = []
for tf_workload in params['TERRAFORM_APPLICATION_WORKLOADS']:
cross_account_role_list.append(tf_workload['CROSS_ACCOUNT_ROLE_ARN'])
print(cross_account_role_list)
# IAM Role for CodePipeline
code_pipeline_role = iam.Role(
self,
'CodePipelineRole',
assumed_by = iam.ServicePrincipal('codepipeline.amazonaws.com')
)
# IAM Policy for CodePipeline
code_pipeline_policy = iam.Policy(
self,
'CodePipelinePolicy',
roles = [
code_pipeline_role
],
statements = [
iam.PolicyStatement(
sid = 'KmsAllowKeyUsage',
actions = [
'kms:DescribeKey',
'kms:GetKeyPolicy',
'kms:List*',
'kms:Encrypt',
'kms:Decrypt',
'kms:ReEncrypt*',
'kms:Generate*'
],
effect = iam.Effect.ALLOW,
resources = [
pipeline_encryption_key.key_arn
]
),
iam.PolicyStatement(
sid = 'CodeCommitRepoAccess',
actions = [
'codecommit:GetBranch',
'codecommit:GetCommit',
'codecommit:UploadArchive',
'codecommit:GetUploadArchiveStatus',
'codecommit:CancelUploadArchive'
],
effect = iam.Effect.ALLOW,
resources = [
source_repo.repository_arn
]
),
iam.PolicyStatement(
sid = 'PipelineBucketAccess',
actions = [
's3:GetBucket*',
's3:ListBucket*'
],
effect = iam.Effect.ALLOW,
resources = [
pipeline_bucket.bucket_arn
]
),
iam.PolicyStatement(
sid = 'PipelineBucketObjectAccess',
actions = [
's3:AbortMultipartUpload',
's3:GetObject*',
's3:PutObject*',
's3:DeleteObject*',
's3:RestoreObject',
's3:ListMultipartUploadParts'
],
effect = iam.Effect.ALLOW,
resources = [
pipeline_bucket.bucket_arn+'/*'
]
),
iam.PolicyStatement(
sid = 'PassRoleAccess',
actions = [
'iam:PassRole'
],
effect = iam.Effect.ALLOW,
resources = ['*']
),
iam.PolicyStatement(
sid = 'BuildStartStopAccess',
actions = [
'codebuild:StartBuild',
'codebuild:BatchGetBuilds'
],
effect = iam.Effect.ALLOW,
resources = ['*']
),
]
)
# IAM Role for CodeBuild projects
code_build_role = iam.Role(
self,
'CodeBuildRole',
assumed_by = iam.ServicePrincipal('codebuild.amazonaws.com')
)
# IAM Policy for CodeBuild Projects
code_build_policy = iam.Policy(
self,
'CodeBuildPolicy',
roles = [
code_build_role
],
statements = [
iam.PolicyStatement(
sid = 'CloudWatchLogsPermissionsForAllCodeBuildProjects',
actions = [
'logs:*'
],
effect = iam.Effect.ALLOW,
resources = ['*']
),
iam.PolicyStatement(
sid = 'TerraformBackendBucketAccess',
actions = [
's3:GetBucket*',
's3:ListBucket*'
],
effect = iam.Effect.ALLOW,
resources = [
tf_backend_bucket.bucket_arn
]
),
iam.PolicyStatement(
sid = 'TerraformBackendObjectAccess',
actions = [
's3:GetObject*',
's3:PutObject*'
],
effect = iam.Effect.ALLOW,
resources = [
tf_backend_bucket.bucket_arn+'/*'
]
),
iam.PolicyStatement(
sid = 'CodeCommitAccessPolicy',
actions = [
'codecommit:*'
],
effect = iam.Effect.ALLOW,
resources = [
source_repo.repository_arn
]
),
iam.PolicyStatement(
sid = 'CodeBuildPermissions',
actions = [
'codebuild:Get*',
'codebuild:List*',
'codebuild:Describe*',
'codebuild:*Report*',
'codebuild:BatchPutTestCases'
],
effect = iam.Effect.ALLOW,
resources = ['*']
),
iam.PolicyStatement(
sid = 'AssumeCrossAccountRoleForCodePull',
actions = [
'sts:AssumeRole'
],
effect = iam.Effect.ALLOW,
resources = cross_account_role_list
),
iam.PolicyStatement(
sid = 'FetchAZForTerraformPlan',
actions = [
'ec2:DescribeAvailabilityZones'
],
effect = iam.Effect.ALLOW,
resources = ['*']
),
iam.PolicyStatement(
sid = 'RegionBasedAMILookupForTF',
actions = [
'ec2:DescribeImages'
],
effect = iam.Effect.ALLOW,
resources = ['*']
)
]
)
# CodePipeline Encryption Key Policy
pipeline_encryption_key.add_to_resource_policy(
statement = iam.PolicyStatement(
sid = 'KmsAllowKeyAdministration',
actions = [
'kms:*'
],
effect = iam.Effect.ALLOW,
principals = [
iam.AccountRootPrincipal()
],
resources = ['*']
)
)
pipeline_encryption_key.add_to_resource_policy(
statement = iam.PolicyStatement(
sid = 'KmsAllowKeyUsage',
actions = [
'kms:Decrypt',
'kms:DescribeKey',
'kms:Encrypt',
'kms:GenerateDataKey',
'kms:GenerateDataKeyWithoutPlainText',
'kms:ReEncrypt',
'kms:ReEncryptTo',
'kms:ReEncryptFrom',
'kms:TagResource',
'kms:CreateKey'
],
effect = iam.Effect.ALLOW,
principals = [
iam.ArnPrincipal(
arn = code_pipeline_role.role_arn
)
],
resources = ['*']
)
)
# CodePipeline Bucket Policy
pipeline_bucket.add_to_resource_policy(
iam.PolicyStatement(
sid = 'CodePipelineUsage',
actions = [
's3:List*',
's3:Get*',
's3:Put*',
's3:Delete*',
's3:AbortMultipartUpload',
's3:RestoreObject',
's3:ListMultipartUploadParts'
],
effect = iam.Effect.ALLOW,
principals = [
iam.ArnPrincipal(
arn = code_pipeline_role.role_arn
)
],
resources = [
pipeline_bucket.bucket_arn,
pipeline_bucket.bucket_arn+'/*'
]
),
)
# Terraform Backend Bucket Policy
tf_backend_bucket.add_to_resource_policy(
iam.PolicyStatement(
sid = 'CodePipelineUsage',
actions = [
's3:List*',
's3:Get*',
's3:Put*',
's3:Delete*',
's3:AbortMultipartUpload',
's3:RestoreObject',
's3:ListMultipartUploadParts'
],
effect = iam.Effect.ALLOW,
principals = [
iam.ArnPrincipal(
arn = code_build_role.role_arn
)
],
resources = [
tf_backend_bucket.bucket_arn,
tf_backend_bucket.bucket_arn+'/*'
]
),
)
#####################################---END---##########################################
# Create code build project for pulling code from remote terraform workload repo
code_build_code_pull = codebuild.PipelineProject(
self,
'CodeBuildForCodePull',
build_spec = codebuild.BuildSpec.from_source_filename('buildspec-code-pull.yml'),
description = 'CodeBuild project for pulling code from remote terraform workload repos',
environment = codebuild.BuildEnvironment(
build_image = codebuild.LinuxBuildImage.from_code_build_image_id(
'aws/codebuild/amazonlinux2-x86_64-standard:3.0'
),
compute_type = codebuild.ComputeType.SMALL
),
project_name = 'cb-code-pull-'+params['CODE_COMMIT_SOURCE_REPO_NAME']+'-'+params['CODE_COMMIT_SOURCE_REPO_BRANCH'],
role = code_build_role
)
# Create code build project for carrying out compliance checks on terraform workload repo
code_build_compliance_check = codebuild.PipelineProject(
self,
'CodeBuildForComplianceCheck',
build_spec = codebuild.BuildSpec.from_source_filename('buildspec-compliance-check.yml'),
description = 'CodeBuild project for carrying out compliance checks on terraform workload repo',
environment = codebuild.BuildEnvironment(
build_image = codebuild.LinuxBuildImage.from_code_build_image_id(
'aws/codebuild/amazonlinux2-x86_64-standard:3.0'
),
compute_type = codebuild.ComputeType.SMALL
),
project_name = 'cb-compliance-check-'+params['CODE_COMMIT_SOURCE_REPO_NAME']+'-'+params['CODE_COMMIT_SOURCE_REPO_BRANCH'],
role = code_build_role
)
# Create code build project for merging compliance code into main branch
code_build_code_merge = codebuild.PipelineProject(
self,
'CodeBuildForCodeMerge',
build_spec = codebuild.BuildSpec.from_source_filename('buildspec-code-merge.yml'),
description = 'CodeBuild project for merging compliance code into main branch',
environment = codebuild.BuildEnvironment(
build_image = codebuild.LinuxBuildImage.from_code_build_image_id(
'aws/codebuild/amazonlinux2-x86_64-standard:3.0'
),
compute_type = codebuild.ComputeType.SMALL
),
project_name = 'cb-code-merge-'+params['CODE_COMMIT_SOURCE_REPO_NAME']+'-'+params['CODE_COMMIT_SOURCE_REPO_BRANCH'],
role = code_build_role
)
# Create CodePipeline for compliance check
pipeline = codepipeline.Pipeline(
self,
'SecurityAndCompliancePipeline',
artifact_bucket = pipeline_bucket,
pipeline_name = 'pipeline-'+params['CODE_COMMIT_SOURCE_REPO_NAME']+'-'+params['CODE_COMMIT_SOURCE_REPO_BRANCH'],
role = code_pipeline_role
)
# Add CodeCommit source repo as the action
pipeline.add_stage(
stage_name = 'Source',
actions = [
codepipeline_actions.CodeCommitSourceAction(
action_name = "Source",
output = codepipeline.Artifact(artifact_name = 'SourceArtifact'),
repository = source_repo,
branch = params['CODE_COMMIT_SOURCE_REPO_BRANCH'],
trigger = codepipeline_actions.CodeCommitTrigger.EVENTS
)
]
)
# Add stage to pull terraform source workload
tf_code_artifact_name_prefix = "tf_code_"
pull_tf_code_stage = pipeline.add_stage(stage_name = 'PullTerraformCode')
for tf_workload in params['TERRAFORM_APPLICATION_WORKLOADS']:
pull_tf_code_stage.add_action(
codepipeline_actions.CodeBuildAction(
input = codepipeline.Artifact(artifact_name = 'SourceArtifact'),
project = code_build_code_pull,
environment_variables = {
'CROSS_ACCOUNT_ROLE': codebuild.BuildEnvironmentVariable(
value = tf_workload['CROSS_ACCOUNT_ROLE_ARN'],
type = codebuild.BuildEnvironmentVariableType.PLAINTEXT
),
'TF_WOKLOAD_REPO_URL': codebuild.BuildEnvironmentVariable(
value = tf_workload['GIT_REPO_URL'],
type = codebuild.BuildEnvironmentVariableType.PLAINTEXT
)
},
outputs = [
codepipeline.Artifact(artifact_name = tf_code_artifact_name_prefix+tf_workload['APP_ID'])
],
type = codepipeline_actions.CodeBuildActionType.BUILD,
action_name = 'PullCode_'+tf_workload['APP_ID'],
run_order = 10
)
)
# Add stage to perform compliance check on terraform source workload
compliance_check_stage = pipeline.add_stage(stage_name = 'PerformComplianceCheck')
for tf_workload in params['TERRAFORM_APPLICATION_WORKLOADS']:
compliance_check_stage.add_action(
codepipeline_actions.CodeBuildAction(
input = codepipeline.Artifact(artifact_name = 'SourceArtifact'),
project = code_build_compliance_check,
environment_variables = {
'TF_SOURCE_CODE_FOLDER': codebuild.BuildEnvironmentVariable(
value = 'CODEBUILD_SRC_DIR_'+tf_code_artifact_name_prefix+tf_workload['APP_ID'],
type = codebuild.BuildEnvironmentVariableType.PLAINTEXT
),
'TF_BACKEND_S3_BUCKET': codebuild.BuildEnvironmentVariable(
value = tf_backend_bucket.bucket_name,
type = codebuild.BuildEnvironmentVariableType.PLAINTEXT
)
},
extra_inputs = [
codepipeline.Artifact(artifact_name = tf_code_artifact_name_prefix+tf_workload['APP_ID'])
],
outputs = [
codepipeline.Artifact(artifact_name = 'report_'+tf_workload['APP_ID'])
],
type = codepipeline_actions.CodeBuildActionType.BUILD,
action_name = 'ComplianceCheck_'+tf_workload['APP_ID'],
run_order = 10
)
)
# Add stage to perform compliance check on terraform source workload
code_merge_stage = pipeline.add_stage(stage_name = 'MergeCode')
code_merge_stage.add_action(
codepipeline_actions.CodeBuildAction(
input = codepipeline.Artifact(artifact_name = 'SourceArtifact'),
project = code_build_code_merge,
environment_variables = {
'CODE_COMMIT_SOURCE_REPO_NAME': codebuild.BuildEnvironmentVariable(
value = params['CODE_COMMIT_SOURCE_REPO_NAME'],
type = codebuild.BuildEnvironmentVariableType.PLAINTEXT
),
'CODE_COMMIT_SOURCE_BRANCH': codebuild.BuildEnvironmentVariable(
value = params['CODE_COMMIT_SOURCE_REPO_BRANCH'],
type = codebuild.BuildEnvironmentVariableType.PLAINTEXT
),
'CODE_COMMIT_TARGET_BRANCH': codebuild.BuildEnvironmentVariable(
value = 'main',
type = codebuild.BuildEnvironmentVariableType.PLAINTEXT
)
},
outputs = [
codepipeline.Artifact(artifact_name = 'merge_response')
],
type = codepipeline_actions.CodeBuildActionType.BUILD,
action_name = 'CodeMerge',
run_order = 10
)
)
########################### List of Outputs ##########################
core.CfnOutput(
self,
'OutSourceRepoArn',
value = source_repo.repository_arn,
description = 'Source Repository ARN',
export_name = 'GOLDMINE-SOURCE-REPO-ARN'
)
core.CfnOutput(
self,
'OutSourceRepoHttpUrl',
value = source_repo.repository_clone_url_http,
description = 'Source Repository Http URL',
export_name = 'GOLDMINE-SOURCE-REPO-HTTP-URL'
)
core.CfnOutput(
self,
'OutPipelineBucketName',
value = pipeline_bucket.bucket_name,
description = 'Pipeline Bucket Name',
export_name = 'PIPELINE-BUCKET-NAME'
)