// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: MIT-0 package com.myorg; import com.myorg.buildAndPublishPackage.buildAndPublishPackage; import software.amazon.awscdk.RemovalPolicy; import software.amazon.awscdk.services.codebuild.*; import software.amazon.awscdk.services.codepipeline.StageOptions; import software.amazon.awscdk.services.codepipeline.actions.CodeBuildAction; import software.amazon.awscdk.services.codepipeline.actions.CodeBuildActionProps; import software.amazon.awscdk.services.codepipeline.actions.CodeCommitSourceAction; import software.amazon.awscdk.services.iam.Effect; import software.amazon.awscdk.services.iam.Policy; import software.amazon.awscdk.services.iam.PolicyStatement; import software.amazon.awscdk.services.s3.BlockPublicAccess; import software.amazon.awscdk.services.s3.BucketEncryption; import software.constructs.Construct; import software.amazon.awscdk.Stack; import software.amazon.awscdk.StackProps; import software.amazon.awscdk.services.codecommit.Repository; import software.amazon.awscdk.services.codeartifact.CfnDomain; import software.amazon.awscdk.services.codeartifact.CfnRepository; import software.amazon.awscdk.services.s3.Bucket; import software.amazon.awscdk.services.kms.Key; import software.amazon.awscdk.services.codepipeline.Pipeline; import software.amazon.awscdk.services.codepipeline.Artifact; import io.github.cdklabs.cdknag.NagSuppressions; import io.github.cdklabs.cdknag.NagPackSuppression; import java.util.Arrays; import java.util.Map; public class JavaCdkCicdCodeartifactStack extends Stack { public JavaCdkCicdCodeartifactStack(final Construct scope, final String id) { this(scope, id, null); } public JavaCdkCicdCodeartifactStack(final Construct scope, final String id, final StackProps props) { super(scope, id, props); final Repository repo = Repository.Builder.create(this, "CodeCommitRepository") .repositoryName("JavaSampleRepository") .build(); final CfnDomain codeartifactDomain = CfnDomain.Builder.create(this, "CodeArtifactDomain") .domainName("aws-java-sample-domain") .build(); final CfnRepository mvnPrivateCodeartifactRepository = CfnRepository.Builder.create(this, "MvnPrivateCodeArtifactRepository") .domainName(codeartifactDomain.getDomainName()) .repositoryName("mvn") .externalConnections(Arrays.asList("public:maven-central")) .build(); mvnPrivateCodeartifactRepository.addDependsOn(codeartifactDomain); final Key codebuildEncryptionKey = Key.Builder.create(this, "codebuildEncryptionKey") .enableKeyRotation(true) .build(); final Bucket accessLogsBucket = Bucket.Builder.create(this, "AccessLogsBucket") .bucketName("sample-java-cdk-access-logs-" + this.getAccount()) .blockPublicAccess(BlockPublicAccess.BLOCK_ALL) .encryption(BucketEncryption.KMS) .encryptionKey(codebuildEncryptionKey) .removalPolicy(RemovalPolicy.DESTROY) .enforceSsl(true) .autoDeleteObjects(true) .build(); final Bucket pipelineArtifactBucket = Bucket.Builder.create(this, "PipelineArtifactBucket") .bucketName("sample-java-cdk-artifact-" + this.getAccount()) .serverAccessLogsBucket(accessLogsBucket) .blockPublicAccess(BlockPublicAccess.BLOCK_ALL) .encryption(BucketEncryption.KMS) .encryptionKey(codebuildEncryptionKey) .removalPolicy(RemovalPolicy.DESTROY) .enforceSsl(true) .autoDeleteObjects(true) .build(); NagSuppressions.addResourceSuppressions(accessLogsBucket, Arrays.asList( new NagPackSuppression.Builder() .id("AwsSolutions-S1") .reason("Cannot log to itself") .build() ), true); final Pipeline pipeline = Pipeline.Builder.create(this, "PackagePipeline") .pipelineName("java-sample-pipeline") .restartExecutionOnUpdate(true) .artifactBucket(pipelineArtifactBucket) .build(); final Artifact sourceOutput = new Artifact("SourceArtifact"); final CodeCommitSourceAction sourceAction = CodeCommitSourceAction.Builder.create() .actionName("CodeCommit") .repository(repo) .output(sourceOutput) .branch("main") .build(); pipeline.addStage(StageOptions.builder() .stageName("Source") .actions(Arrays.asList(sourceAction)) .build() ); final PipelineProject runUnitTestsProject = PipelineProject.Builder.create(this, "RunUnitTests") .environment(BuildEnvironment.builder() .privileged(false) .computeType(ComputeType.MEDIUM) .buildImage(LinuxBuildImage.STANDARD_5_0) .build() ) .encryptionKey(pipeline.getArtifactBucket().getEncryptionKey()) .buildSpec(BuildSpec.fromObject(Map.of( "version", "0.2", "phases", Map.of( "pre_build", Map.of( "commands", Arrays.asList( "export CODEARTIFACT_AUTH_TOKEN=`aws codeartifact get-authorization-token --domain aws-java-sample-domain --query authorizationToken --output text`", "export CODEARTIFACT_REPOSITORY_URL=`aws codeartifact get-repository-endpoint --domain aws-java-sample-domain --repository mvn --format maven --query repositoryEndpoint --output text`" ) ), "build", Map.of( "commands", Arrays.asList( "mvn package --settings settings.xml" ) ) ) ))) .build(); runUnitTestsProject.getRole().attachInlinePolicy( Policy.Builder.create(this, "RunUnitTestsPolicy") .statements(Arrays.asList( PolicyStatement.Builder.create() .effect(Effect.ALLOW) .resources(Arrays.asList("*")) .actions(Arrays.asList("sts:GetServiceBearerToken")) .conditions(Map.of( "StringEquals", Map.of( "sts:AWSServiceName", "codeartifact.amazonaws.com" ) )) .build(), PolicyStatement.Builder.create() .effect(Effect.ALLOW) .resources(Arrays.asList(codeartifactDomain.getAttrArn())) .actions(Arrays.asList("codeartifact:GetAuthorizationToken")) .build(), PolicyStatement.Builder.create() .effect(Effect.ALLOW) .resources(Arrays.asList(mvnPrivateCodeartifactRepository.getAttrArn())) .actions(Arrays.asList( "codeartifact:ReadFromRepository", "codeartifact:GetRepositoryEndpoint", "codeartifact:List*" )) .build() )) .build() ); pipeline.addStage(StageOptions.builder() .stageName("Test") .actions(Arrays.asList( new CodeBuildAction(CodeBuildActionProps.builder() .actionName("run-unit-tests") .project(runUnitTestsProject) .input(sourceOutput) .build()) )) .build() ); final PipelineProject selfMutateProject = PipelineProject.Builder.create(this, "SelfMutate") .environment(BuildEnvironment.builder() .privileged(false) .computeType(ComputeType.MEDIUM) .buildImage(LinuxBuildImage.STANDARD_5_0) .build() ) .encryptionKey(pipeline.getArtifactBucket().getEncryptionKey()) .buildSpec(BuildSpec.fromObject(Map.of( "version", "0.2", "phases", Map.of( "pre_build", Map.of( "commands", Arrays.asList( "npm install -g aws-cdk", "export CODEARTIFACT_AUTH_TOKEN=`aws codeartifact get-authorization-token --domain aws-java-sample-domain --query authorizationToken --output text`", "export CODEARTIFACT_REPOSITORY_URL=`aws codeartifact get-repository-endpoint --domain aws-java-sample-domain --repository mvn --format maven --query repositoryEndpoint --output text`", "export CODEARTIFACT_ACCOUNT_ID=`aws sts get-caller-identity --query \"Account\" --output text`" ) ), "build", Map.of( "commands", Arrays.asList( "cdk deploy --require-approval=never" ) ) ) ))) .build(); selfMutateProject.getRole().attachInlinePolicy( Policy.Builder.create(this, "SelfMutatePolicy") .statements(Arrays.asList( PolicyStatement.Builder.create() .effect(Effect.ALLOW) .resources(Arrays.asList("*")) .actions(Arrays.asList("sts:GetServiceBearerToken")) .conditions(Map.of( "StringEquals", Map.of( "sts:AWSServiceName", "codeartifact.amazonaws.com" ) )) .build(), PolicyStatement.Builder.create() .effect(Effect.ALLOW) .resources(Arrays.asList(codeartifactDomain.getAttrArn())) .actions(Arrays.asList("codeartifact:GetAuthorizationToken")) .build(), PolicyStatement.Builder.create() .effect(Effect.ALLOW) .resources(Arrays.asList(mvnPrivateCodeartifactRepository.getAttrArn())) .actions(Arrays.asList( "codeartifact:ReadFromRepository", "codeartifact:GetRepositoryEndpoint", "codeartifact:List*" )) .build(), PolicyStatement.Builder.create() .effect(Effect.ALLOW) .resources(Arrays.asList("*")) .actions(Arrays.asList("cloudformation:DescribeStacks")) .build(), PolicyStatement.Builder.create() .effect(Effect.ALLOW) .resources(Arrays.asList("*")) .actions(Arrays.asList("iam:PassRole")) .build(), PolicyStatement.Builder.create() .effect(Effect.ALLOW) .resources(Arrays.asList("arn:aws:iam::*:role/cdk-*")) .actions(Arrays.asList("sts:AssumeRole")) .build() )) .build() ); pipeline.addStage(StageOptions.builder() .stageName("UpdatePipeline") .actions(Arrays.asList( new CodeBuildAction(CodeBuildActionProps.builder() .actionName("self-mutate") .project(selfMutateProject) .input(sourceOutput) .build()) )) .build() ); final buildAndPublishPackage samplePackageProject = new buildAndPublishPackage(this, "BuildSamplePackage", "sample-package", codeartifactDomain.getAttrArn(), mvnPrivateCodeartifactRepository.getAttrArn(), pipelineArtifactBucket.getEncryptionKey()); pipeline.addStage(StageOptions.builder() .stageName("BuildAndPublishPackages") .actions(Arrays.asList( new CodeBuildAction(CodeBuildActionProps.builder() .actionName("sample-package") .project(samplePackageProject.project) .input(sourceOutput) .build()) )) .build() ); NagSuppressions.addResourceSuppressionsByPath(this, "/JavaCdkCicdCodeartifactStack/PackagePipeline/Role/DefaultPolicy/Resource", Arrays.asList( new NagPackSuppression.Builder() .id("AwsSolutions-IAM5") .reason("Defined by a default policy") .appliesTo(Arrays.asList( "Action::s3:Abort*", "Action::s3:DeleteObject*", "Action::s3:GetBucket*", "Action::s3:GetObject*", "Action::s3:List*", "Action::kms:GenerateDataKey*", "Action::kms:ReEncrypt*", "Resource::<PipelineArtifactBucketD127CCF6.Arn>/*" )) .build() )); NagSuppressions.addResourceSuppressionsByPath(this, "/JavaCdkCicdCodeartifactStack/PackagePipeline/Source/CodeCommit/CodePipelineActionRole/DefaultPolicy/Resource", Arrays.asList( new NagPackSuppression.Builder() .id("AwsSolutions-IAM5") .reason("Defined by a default policy") .appliesTo(Arrays.asList( "Action::s3:Abort*", "Action::s3:DeleteObject*", "Action::s3:GetBucket*", "Action::s3:GetObject*", "Action::s3:List*", "Action::kms:GenerateDataKey*", "Action::kms:ReEncrypt*", "Resource::<PipelineArtifactBucketD127CCF6.Arn>/*" )) .build() )); NagSuppressions.addResourceSuppressionsByPath(this, "/JavaCdkCicdCodeartifactStack/RunUnitTests/Role/DefaultPolicy/Resource", Arrays.asList( new NagPackSuppression.Builder() .id("AwsSolutions-IAM5") .reason("Defined by a default policy") .appliesTo(Arrays.asList( "Resource::arn:<AWS::Partition>:logs:<AWS::Region>:<AWS::AccountId>:log-group:/aws/codebuild/<RunUnitTests2AD5FFEA>:*", "Resource::arn:<AWS::Partition>:codebuild:<AWS::Region>:<AWS::AccountId>:report-group/<RunUnitTests2AD5FFEA>-*", "Action::s3:GetBucket*", "Action::s3:GetObject*", "Action::s3:List*", "Action::kms:GenerateDataKey*", "Action::kms:ReEncrypt*", "Resource::<PipelineArtifactBucketD127CCF6.Arn>/*" )) .build() )); NagSuppressions.addResourceSuppressionsByPath(this, "/JavaCdkCicdCodeartifactStack/RunUnitTestsPolicy/Resource", Arrays.asList( new NagPackSuppression.Builder() .id("AwsSolutions-IAM5") .reason("Defined by a default policy") .appliesTo(Arrays.asList( "Resource::*", "Action::codeartifact:List*" )) .build() )); NagSuppressions.addResourceSuppressionsByPath(this, "/JavaCdkCicdCodeartifactStack/SelfMutate/Role/DefaultPolicy/Resource", Arrays.asList( new NagPackSuppression.Builder() .id("AwsSolutions-IAM5") .reason("Defined by a default policy") .appliesTo(Arrays.asList( "Resource::arn:<AWS::Partition>:logs:<AWS::Region>:<AWS::AccountId>:log-group:/aws/codebuild/<SelfMutate95ADA46F>:*", "Resource::arn:<AWS::Partition>:codebuild:<AWS::Region>:<AWS::AccountId>:report-group/<SelfMutate95ADA46F>-*", "Action::s3:GetBucket*", "Action::s3:GetObject*", "Action::s3:List*", "Action::kms:GenerateDataKey*", "Action::kms:ReEncrypt*", "Resource::<PipelineArtifactBucketD127CCF6.Arn>/*" )) .build() )); NagSuppressions.addResourceSuppressionsByPath(this, "/JavaCdkCicdCodeartifactStack/SelfMutatePolicy/Resource", Arrays.asList( new NagPackSuppression.Builder() .id("AwsSolutions-IAM5") .reason("Defined by a default policy") .appliesTo(Arrays.asList( "Resource::*", "Action::codeartifact:List*", "Resource::arn:aws:iam::*:role/cdk-*" )) .build() )); NagSuppressions.addResourceSuppressionsByPath(this, "/JavaCdkCicdCodeartifactStack/BuildSamplePackage/sample-package/Role/DefaultPolicy/Resource", Arrays.asList( new NagPackSuppression.Builder() .id("AwsSolutions-IAM5") .reason("Defined by a default policy") .appliesTo(Arrays.asList( "Resource::arn:<AWS::Partition>:logs:<AWS::Region>:<AWS::AccountId>:log-group:/aws/codebuild/<BuildSamplePackagesamplepackageB2962058>:*", "Resource::arn:<AWS::Partition>:codebuild:<AWS::Region>:<AWS::AccountId>:report-group/<BuildSamplePackagesamplepackageB2962058>-*", "Action::s3:GetBucket*", "Action::s3:GetObject*", "Action::s3:List*", "Action::kms:GenerateDataKey*", "Action::kms:ReEncrypt*", "Resource::<PipelineArtifactBucketD127CCF6.Arn>/*" )) .build() )); NagSuppressions.addResourceSuppressionsByPath(this, "/JavaCdkCicdCodeartifactStack/BuildSamplePackage/PublishPolicy/Resource", Arrays.asList( new NagPackSuppression.Builder() .id("AwsSolutions-IAM5") .reason("Defined by a default policy") .appliesTo(Arrays.asList( "Resource::*", "Action::codeartifact:List*" )) .build() )); } }