From 3dc50ddcf3a689948e169c05073490b079ea8e16 Mon Sep 17 00:00:00 2001 From: EC2 Default User Date: Mon, 21 Aug 2023 17:38:23 +0000 Subject: [PATCH 01/13] updating to include a container scan before deployment (failed if high severity finds > 0) --- typescript/codepipeline-build-deploy/0 | 0 typescript/codepipeline-build-deploy/0] | 0 .../lib/codepipeline-build-deploy-stack.ts | 29 +++++++++++++++++-- .../test-buildspec.yaml | 10 +++++++ 4 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 typescript/codepipeline-build-deploy/0 create mode 100644 typescript/codepipeline-build-deploy/0] create mode 100644 typescript/codepipeline-build-deploy/test-buildspec.yaml diff --git a/typescript/codepipeline-build-deploy/0 b/typescript/codepipeline-build-deploy/0 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/typescript/codepipeline-build-deploy/0] b/typescript/codepipeline-build-deploy/0] new file mode 100644 index 0000000000..e69de29bb2 diff --git a/typescript/codepipeline-build-deploy/lib/codepipeline-build-deploy-stack.ts b/typescript/codepipeline-build-deploy/lib/codepipeline-build-deploy-stack.ts index 1e062b3d1a..49d95196ad 100644 --- a/typescript/codepipeline-build-deploy/lib/codepipeline-build-deploy-stack.ts +++ b/typescript/codepipeline-build-deploy/lib/codepipeline-build-deploy-stack.ts @@ -22,7 +22,7 @@ export class CodepipelineBuildDeployStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); - // modify gitignore file to remove unneeded files from the codecommit copy + // Modify gitignore file to remove unneeded files from the codecommit copy let gitignore = fs.readFileSync('.gitignore').toString().split(/\r?\n/); gitignore.push('.git/'); gitignore = gitignore.filter(g => g != 'node_modules/'); @@ -73,6 +73,18 @@ export class CodepipelineBuildDeployStack extends cdk.Stack { }, }); + const scanImage = new codebuild.Project(this, "scanImage", { + buildSpec: codebuild.BuildSpec.fromSourceFilename("test-buildspec.yaml"), + source: codebuild.Source.codeCommit({ repository: codeRepo }), + environment: { + buildImage: codebuild.LinuxBuildImage.AMAZON_LINUX_2_4, + environmentVariables: { + IMAGE_REPO_NAME: { value: imageRepo.repositoryName }, + REPOSITORY_DIGEST: { value: imageRepo.repositoryUriForDigest } + }, + }, + }); + // CodeBuild project that builds the Docker image const buildTest = new codebuild.Project(this, "BuildTest", { buildSpec: codebuild.BuildSpec.fromSourceFilename("buildspec.yaml"), @@ -219,6 +231,7 @@ export class CodepipelineBuildDeployStack extends cdk.Stack { // Creates new pipeline artifacts const sourceArtifact = new pipeline.Artifact("SourceArtifact"); const buildArtifact = new pipeline.Artifact("BuildArtifact"); + const scanArtifact = new pipeline.Artifact("ScanArtifact"); // Creates the source stage for CodePipeline const sourceStage = { @@ -257,6 +270,18 @@ export class CodepipelineBuildDeployStack extends cdk.Stack { }), ], }; + + const securityTestStage = { + stageName: "SecurityScan", + actions: [ + new pipelineactions.CodeBuildAction({ + actionName: "ScanImage", + input: buildArtifact, + project: scanImage, + outputs: [scanArtifact], + }), + ], + }; // Creates a new CodeDeploy Deployment Group const deploymentGroup = new codedeploy.EcsDeploymentGroup( @@ -289,7 +314,7 @@ export class CodepipelineBuildDeployStack extends cdk.Stack { // Creates an AWS CodePipeline with source, build, and deploy stages new pipeline.Pipeline(this, "BuildDeployPipeline", { pipelineName: "ImageBuildDeployPipeline", - stages: [sourceStage, testStage, buildStage, deployStage], + stages: [sourceStage, testStage, buildStage, securityTestStage, deployStage], }); // Outputs the ALB public endpoint diff --git a/typescript/codepipeline-build-deploy/test-buildspec.yaml b/typescript/codepipeline-build-deploy/test-buildspec.yaml new file mode 100644 index 0000000000..7f6d62b3d5 --- /dev/null +++ b/typescript/codepipeline-build-deploy/test-buildspec.yaml @@ -0,0 +1,10 @@ +version: 0.2 + +phases: + build: + commands: + - aws ecr start-image-scan --repository-name $IMAGE_REPO_NAME --image-id $REPOSITORY_DIGEST + - aws ecr describe-image-scan-findings $IMAGE_REPO_NAME --image-id $REPOSITORY_DIGEST > scan.json + - export highSeverity=$(cat scan.json | jq -r .imageScanFindings.findingSeverityCounts.HIGH) + post-build: + - if [ $highSeverity > 0 ]; then exit 1; fi \ No newline at end of file From b72a1558236858273a354a7f8e3a5dcef40dbf4d Mon Sep 17 00:00:00 2001 From: EC2 Default User Date: Mon, 21 Aug 2023 17:38:57 +0000 Subject: [PATCH 02/13] removing randomly created files --- typescript/codepipeline-build-deploy/0 | 0 typescript/codepipeline-build-deploy/0] | 0 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 typescript/codepipeline-build-deploy/0 delete mode 100644 typescript/codepipeline-build-deploy/0] diff --git a/typescript/codepipeline-build-deploy/0 b/typescript/codepipeline-build-deploy/0 deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/typescript/codepipeline-build-deploy/0] b/typescript/codepipeline-build-deploy/0] deleted file mode 100644 index e69de29bb2..0000000000 From fc1ba3e2170ffec928052e3f3744ce89c3375ba3 Mon Sep 17 00:00:00 2001 From: EC2 Default User Date: Mon, 21 Aug 2023 19:15:15 +0000 Subject: [PATCH 03/13] fixing typos in the test-buildspec and a fix to call get disgest as function --- .../lib/codepipeline-build-deploy-stack.ts | 30 ++++++++++--------- .../test-buildspec.yaml | 10 ++++--- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/typescript/codepipeline-build-deploy/lib/codepipeline-build-deploy-stack.ts b/typescript/codepipeline-build-deploy/lib/codepipeline-build-deploy-stack.ts index 49d95196ad..b1714b0192 100644 --- a/typescript/codepipeline-build-deploy/lib/codepipeline-build-deploy-stack.ts +++ b/typescript/codepipeline-build-deploy/lib/codepipeline-build-deploy-stack.ts @@ -73,26 +73,27 @@ export class CodepipelineBuildDeployStack extends cdk.Stack { }, }); - const scanImage = new codebuild.Project(this, "scanImage", { + // CodeBuild project that builds the Docker image + const buildTest = new codebuild.Project(this, "BuildTest", { + buildSpec: codebuild.BuildSpec.fromSourceFilename("buildspec.yaml"), + source: codebuild.Source.codeCommit({ repository: codeRepo }), + environment: { + buildImage: codebuild.LinuxBuildImage.AMAZON_LINUX_2_4, + } + }); + + // CodeBuild project that runs the ecr image scan + const scanImage = new codebuild.Project(this, "ScanImage", { buildSpec: codebuild.BuildSpec.fromSourceFilename("test-buildspec.yaml"), source: codebuild.Source.codeCommit({ repository: codeRepo }), environment: { buildImage: codebuild.LinuxBuildImage.AMAZON_LINUX_2_4, environmentVariables: { IMAGE_REPO_NAME: { value: imageRepo.repositoryName }, - REPOSITORY_DIGEST: { value: imageRepo.repositoryUriForDigest } + REPOSITORY_DIGEST: { value: imageRepo.repositoryUriForDigest() } }, }, }); - - // CodeBuild project that builds the Docker image - const buildTest = new codebuild.Project(this, "BuildTest", { - buildSpec: codebuild.BuildSpec.fromSourceFilename("buildspec.yaml"), - source: codebuild.Source.codeCommit({ repository: codeRepo }), - environment: { - buildImage: codebuild.LinuxBuildImage.AMAZON_LINUX_2_4, - } - }); // Grants CodeBuild project access to pull/push images from/to ECR repo imageRepo.grantPullPush(buildImage); @@ -252,7 +253,7 @@ export class CodepipelineBuildDeployStack extends cdk.Stack { actions: [ new pipelineactions.CodeBuildAction({ actionName: "JestCDK", - input: new pipeline.Artifact("SourceArtifact"), + input: sourceArtifact, project: buildTest, }), ], @@ -264,19 +265,20 @@ export class CodepipelineBuildDeployStack extends cdk.Stack { actions: [ new pipelineactions.CodeBuildAction({ actionName: "DockerBuildPush", - input: new pipeline.Artifact("SourceArtifact"), + input: sourceArtifact, project: buildImage, outputs: [buildArtifact], }), ], }; + // Creates the build stage that runs the ecr container image scan const securityTestStage = { stageName: "SecurityScan", actions: [ new pipelineactions.CodeBuildAction({ actionName: "ScanImage", - input: buildArtifact, + input: sourceArtifact, project: scanImage, outputs: [scanArtifact], }), diff --git a/typescript/codepipeline-build-deploy/test-buildspec.yaml b/typescript/codepipeline-build-deploy/test-buildspec.yaml index 7f6d62b3d5..3c8d0bc08b 100644 --- a/typescript/codepipeline-build-deploy/test-buildspec.yaml +++ b/typescript/codepipeline-build-deploy/test-buildspec.yaml @@ -3,8 +3,10 @@ version: 0.2 phases: build: commands: - - aws ecr start-image-scan --repository-name $IMAGE_REPO_NAME --image-id $REPOSITORY_DIGEST - - aws ecr describe-image-scan-findings $IMAGE_REPO_NAME --image-id $REPOSITORY_DIGEST > scan.json + - aws ecr start-image-scan --repository-name $IMAGE_REPO_NAME --image-id imageDigest=$REPOSITORY_DIGEST + - aws ecr describe-image-scan-findings $IMAGE_REPO_NAME --image-id imageDigest=$REPOSITORY_DIGEST > scan.json - export highSeverity=$(cat scan.json | jq -r .imageScanFindings.findingSeverityCounts.HIGH) - post-build: - - if [ $highSeverity > 0 ]; then exit 1; fi \ No newline at end of file + post_build: + commands: + - echo "Failing build if high severity vulnerabilities found by ECR Image Scan" + ## - if [ $highSeverity > 0 ]; then exit 1; fi ## Uncomment this to break build given high severity vulnerabilities \ No newline at end of file From f480eecce365b10d8abff922d6718ad3516e90b1 Mon Sep 17 00:00:00 2001 From: EC2 Default User Date: Mon, 21 Aug 2023 20:15:53 +0000 Subject: [PATCH 04/13] updating buildspec to wait for scan to be complete before continuing --- .../lib/codepipeline-build-deploy-stack.ts | 2 +- .../codepipeline-build-deploy/scan.json | 345 ++++++++++++++++++ .../test-buildspec.yaml | 7 +- 3 files changed, 350 insertions(+), 4 deletions(-) create mode 100644 typescript/codepipeline-build-deploy/scan.json diff --git a/typescript/codepipeline-build-deploy/lib/codepipeline-build-deploy-stack.ts b/typescript/codepipeline-build-deploy/lib/codepipeline-build-deploy-stack.ts index b1714b0192..55de56ec22 100644 --- a/typescript/codepipeline-build-deploy/lib/codepipeline-build-deploy-stack.ts +++ b/typescript/codepipeline-build-deploy/lib/codepipeline-build-deploy-stack.ts @@ -90,7 +90,7 @@ export class CodepipelineBuildDeployStack extends cdk.Stack { buildImage: codebuild.LinuxBuildImage.AMAZON_LINUX_2_4, environmentVariables: { IMAGE_REPO_NAME: { value: imageRepo.repositoryName }, - REPOSITORY_DIGEST: { value: imageRepo.repositoryUriForDigest() } + IMAGE_TAG: { value: "latest" } }, }, }); diff --git a/typescript/codepipeline-build-deploy/scan.json b/typescript/codepipeline-build-deploy/scan.json new file mode 100644 index 0000000000..0dd5e9cebb --- /dev/null +++ b/typescript/codepipeline-build-deploy/scan.json @@ -0,0 +1,345 @@ +{ + "imageScanStatus": { + "status": "COMPLETE", + "description": "The scan was completed successfully." + }, + "repositoryName": "codepipelinebuilddeploystack-imagerepo1d8a68af-1d46t02aedsq", + "registryId": "026356163217", + "imageId": { + "imageTag": "latest", + "imageDigest": "sha256:44bed34e64b0676934b5a9cf2915738c49c654609aa61872c355e879a920334e" + }, + "imageScanFindings": { + "imageScanCompletedAt": 1692647551.0, + "vulnerabilitySourceUpdatedAt": 1692612680.0, + "findings": [ + { + "severity": "HIGH", + "attributes": [ + { + "value": "7.5", + "key": "CVSS3_SCORE" + }, + { + "value": "7.88.1-r1", + "key": "package_version" + }, + { + "value": "curl", + "key": "package_name" + }, + { + "value": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", + "key": "CVSS3_VECTOR" + } + ], + "name": "CVE-2023-28319", + "uri": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-28319" + }, + { + "severity": "HIGH", + "attributes": [ + { + "value": "7.5", + "key": "CVSS3_SCORE" + }, + { + "value": "1.2.4-r1", + "key": "package_version" + }, + { + "value": "libwebp", + "key": "package_name" + }, + { + "value": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "key": "CVSS3_VECTOR" + } + ], + "name": "CVE-2023-1999", + "uri": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-1999" + }, + { + "severity": "HIGH", + "attributes": [ + { + "value": "7.5", + "key": "CVSS3_SCORE" + }, + { + "value": "1.8.4-r0", + "key": "package_version" + }, + { + "value": "libx11", + "key": "package_name" + }, + { + "value": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "key": "CVSS3_VECTOR" + } + ], + "name": "CVE-2023-3138", + "uri": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-3138" + }, + { + "severity": "HIGH", + "attributes": [ + { + "value": "7.8", + "key": "CVSS3_SCORE" + }, + { + "value": "6.3_p20221119-r0", + "key": "package_version" + }, + { + "value": "ncurses", + "key": "package_name" + }, + { + "value": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", + "key": "CVSS3_VECTOR" + } + ], + "name": "CVE-2023-29491", + "uri": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-29491" + }, + { + "severity": "HIGH", + "attributes": [ + { + "value": "7.5", + "key": "CVSS3_SCORE" + }, + { + "value": "1.51.0-r0", + "key": "package_version" + }, + { + "value": "nghttp2", + "key": "package_name" + }, + { + "value": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "key": "CVSS3_VECTOR" + } + ], + "name": "CVE-2023-35945", + "uri": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-35945" + }, + { + "severity": "HIGH", + "attributes": [ + { + "value": "7.5", + "key": "CVSS3_SCORE" + }, + { + "value": "3.0.8-r3", + "key": "package_version" + }, + { + "value": "openssl", + "key": "package_name" + }, + { + "value": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", + "key": "CVSS3_VECTOR" + } + ], + "name": "CVE-2023-2650", + "uri": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-2650" + }, + { + "severity": "MEDIUM", + "attributes": [ + { + "value": "5.9", + "key": "CVSS3_SCORE" + }, + { + "value": "7.88.1-r1", + "key": "package_version" + }, + { + "value": "curl", + "key": "package_name" + }, + { + "value": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H", + "key": "CVSS3_VECTOR" + } + ], + "name": "CVE-2023-28320", + "uri": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-28320" + }, + { + "severity": "MEDIUM", + "attributes": [ + { + "value": "5.9", + "key": "CVSS3_SCORE" + }, + { + "value": "7.88.1-r1", + "key": "package_version" + }, + { + "value": "curl", + "key": "package_name" + }, + { + "value": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:N", + "key": "CVSS3_VECTOR" + } + ], + "name": "CVE-2023-28321", + "uri": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-28321" + }, + { + "severity": "MEDIUM", + "attributes": [ + { + "value": "5.3", + "key": "CVSS3_SCORE" + }, + { + "value": "3.0.8-r3", + "key": "package_version" + }, + { + "value": "openssl", + "key": "package_name" + }, + { + "value": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L", + "key": "CVSS3_VECTOR" + } + ], + "name": "CVE-2023-3446", + "uri": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-3446" + }, + { + "severity": "MEDIUM", + "attributes": [ + { + "value": "5.3", + "key": "CVSS3_SCORE" + }, + { + "value": "3.0.8-r3", + "key": "package_version" + }, + { + "value": "openssl", + "key": "package_name" + }, + { + "value": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L", + "key": "CVSS3_VECTOR" + } + ], + "name": "CVE-2023-3817", + "uri": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-3817" + }, + { + "severity": "MEDIUM", + "attributes": [ + { + "value": "5.3", + "key": "CVSS3_SCORE" + }, + { + "value": "3.0.8-r3", + "key": "package_version" + }, + { + "value": "openssl", + "key": "package_name" + }, + { + "value": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N", + "key": "CVSS3_VECTOR" + } + ], + "name": "CVE-2023-2975", + "uri": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-2975" + }, + { + "severity": "MEDIUM", + "attributes": [ + { + "value": "5.9", + "key": "CVSS3_SCORE" + }, + { + "value": "3.0.8-r3", + "key": "package_version" + }, + { + "value": "openssl", + "key": "package_name" + }, + { + "value": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H", + "key": "CVSS3_VECTOR" + } + ], + "name": "CVE-2023-1255", + "uri": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-1255" + }, + { + "severity": "MEDIUM", + "attributes": [ + { + "value": "6.5", + "key": "CVSS3_SCORE" + }, + { + "value": "4.4.0-r3", + "key": "package_version" + }, + { + "value": "tiff", + "key": "package_name" + }, + { + "value": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H", + "key": "CVSS3_VECTOR" + } + ], + "name": "CVE-2023-3316", + "uri": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-3316" + }, + { + "severity": "LOW", + "attributes": [ + { + "value": "3.7", + "key": "CVSS3_SCORE" + }, + { + "value": "7.88.1-r1", + "key": "package_version" + }, + { + "value": "curl", + "key": "package_name" + }, + { + "value": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N", + "key": "CVSS3_VECTOR" + } + ], + "name": "CVE-2023-28322", + "uri": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-28322" + } + ], + "findingSeverityCounts": { + "HIGH": 6, + "MEDIUM": 7, + "LOW": 1 + } + } +} diff --git a/typescript/codepipeline-build-deploy/test-buildspec.yaml b/typescript/codepipeline-build-deploy/test-buildspec.yaml index 3c8d0bc08b..b2637e8fe6 100644 --- a/typescript/codepipeline-build-deploy/test-buildspec.yaml +++ b/typescript/codepipeline-build-deploy/test-buildspec.yaml @@ -3,10 +3,11 @@ version: 0.2 phases: build: commands: - - aws ecr start-image-scan --repository-name $IMAGE_REPO_NAME --image-id imageDigest=$REPOSITORY_DIGEST - - aws ecr describe-image-scan-findings $IMAGE_REPO_NAME --image-id imageDigest=$REPOSITORY_DIGEST > scan.json + - aws ecr start-image-scan --repository-name $IMAGE_REPO_NAME --image-id imageTag=$IMAGE_TAG + - aws ecr describe-image-scan-findings --repository-name $IMAGE_REPO_NAME --image-id imageTag=$IMAGE_TAG > scan.json + - while [ $(cat scan.json | jq -r .imageScanStatus.status) != 'COMPLETE' ]; do sleep 5 ; aws ecr describe-image-scan-findings --repository-name $IMAGE_REPO_NAME --image-id imageTag=$IMAGE_TAG > scan.json; done - export highSeverity=$(cat scan.json | jq -r .imageScanFindings.findingSeverityCounts.HIGH) post_build: commands: - echo "Failing build if high severity vulnerabilities found by ECR Image Scan" - ## - if [ $highSeverity > 0 ]; then exit 1; fi ## Uncomment this to break build given high severity vulnerabilities \ No newline at end of file + - if [ $highSeverity > 0 ]; then exit 1; fi ## Uncomment this to break build given high severity vulnerabilities \ No newline at end of file From 8b8eb126d99d71d819ec5a6e8fca9badfab9756b Mon Sep 17 00:00:00 2001 From: EC2 Default User Date: Mon, 21 Aug 2023 20:21:08 +0000 Subject: [PATCH 05/13] updating gitignire --- .../codepipeline-build-deploy/.gitignore | 10 - .../codepipeline-build-deploy/.npmignore | 6 - .../codepipeline-build-deploy/README.md | 82 ----- .../codepipeline-build-deploy/app/Dockerfile | 7 - .../codepipeline-build-deploy/app/README.md | 5 - .../app/appspec.yaml | 9 - .../app/buildspec.yaml | 31 -- .../app/site-assets/about.html | 20 - .../app/site-assets/contact.html | 20 - .../app/site-assets/error.html | 13 - .../app/site-assets/index.html | 20 - .../app/site-assets/styles.css | 38 -- .../app/taskdef.json | 22 -- .../bin/codepipeline-build-deploy.ts | 21 -- .../codepipeline-build-deploy/buildspec.yaml | 16 - typescript/codepipeline-build-deploy/cdk.json | 51 --- .../codepipeline-build-deploy/jest.config.js | 14 - .../lambda/trigger-build.js | 21 -- .../lib/codepipeline-build-deploy-stack.ts | 327 ----------------- .../codepipeline-build-deploy/package.json | 28 -- .../codepipeline-build-deploy/scan.json | 345 ------------------ .../test-buildspec.yaml | 13 - .../test/codepipeline-build-deploy.test.ts | 50 --- .../codepipeline-build-deploy/tsconfig.json | 30 -- 24 files changed, 1199 deletions(-) delete mode 100644 typescript/codepipeline-build-deploy/.gitignore delete mode 100644 typescript/codepipeline-build-deploy/.npmignore delete mode 100644 typescript/codepipeline-build-deploy/README.md delete mode 100644 typescript/codepipeline-build-deploy/app/Dockerfile delete mode 100644 typescript/codepipeline-build-deploy/app/README.md delete mode 100644 typescript/codepipeline-build-deploy/app/appspec.yaml delete mode 100644 typescript/codepipeline-build-deploy/app/buildspec.yaml delete mode 100644 typescript/codepipeline-build-deploy/app/site-assets/about.html delete mode 100644 typescript/codepipeline-build-deploy/app/site-assets/contact.html delete mode 100644 typescript/codepipeline-build-deploy/app/site-assets/error.html delete mode 100644 typescript/codepipeline-build-deploy/app/site-assets/index.html delete mode 100644 typescript/codepipeline-build-deploy/app/site-assets/styles.css delete mode 100644 typescript/codepipeline-build-deploy/app/taskdef.json delete mode 100644 typescript/codepipeline-build-deploy/bin/codepipeline-build-deploy.ts delete mode 100644 typescript/codepipeline-build-deploy/buildspec.yaml delete mode 100644 typescript/codepipeline-build-deploy/cdk.json delete mode 100644 typescript/codepipeline-build-deploy/jest.config.js delete mode 100644 typescript/codepipeline-build-deploy/lambda/trigger-build.js delete mode 100644 typescript/codepipeline-build-deploy/lib/codepipeline-build-deploy-stack.ts delete mode 100644 typescript/codepipeline-build-deploy/package.json delete mode 100644 typescript/codepipeline-build-deploy/scan.json delete mode 100644 typescript/codepipeline-build-deploy/test-buildspec.yaml delete mode 100644 typescript/codepipeline-build-deploy/test/codepipeline-build-deploy.test.ts delete mode 100644 typescript/codepipeline-build-deploy/tsconfig.json diff --git a/typescript/codepipeline-build-deploy/.gitignore b/typescript/codepipeline-build-deploy/.gitignore deleted file mode 100644 index cdc469d801..0000000000 --- a/typescript/codepipeline-build-deploy/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -*.js -!jest.config.js -!*/trigger-build.js -*.d.ts -node_modules - -# CDK asset staging directory -.cdk.staging -cdk.out -report.xml \ No newline at end of file diff --git a/typescript/codepipeline-build-deploy/.npmignore b/typescript/codepipeline-build-deploy/.npmignore deleted file mode 100644 index c1d6d45dcf..0000000000 --- a/typescript/codepipeline-build-deploy/.npmignore +++ /dev/null @@ -1,6 +0,0 @@ -*.ts -!*.d.ts - -# CDK asset staging directory -.cdk.staging -cdk.out diff --git a/typescript/codepipeline-build-deploy/README.md b/typescript/codepipeline-build-deploy/README.md deleted file mode 100644 index 38ed1bfe82..0000000000 --- a/typescript/codepipeline-build-deploy/README.md +++ /dev/null @@ -1,82 +0,0 @@ -# Building and Deploying a Docker Image With AWS CodePipeline - -## - -![Stability: Stable](https://img.shields.io/badge/stability-Stable-success.svg?style=for-the-badge) - -> **This is a stable example. It should successfully build out of the box** -> -> This example is built on Construct Libraries marked "Stable" and does not have any infrastructure prerequisites to build. - ---- - - - -## Overview - -This AWS Cloud Development Kit (CDK) TypeScript example demonstrates how to configure AWS CodePipeline with CodeCommit, CodeBuild, and CodeDeploy to build and deploy a Docker image to an Elastic Container Service (ECS) cluster running [AWS Fargate](https://aws.amazon.com/fargate/) (serverless compute for containers). - -## Real-world Example - -When working in fast-paced development environments, CI/CD (Continuous Integration and Continuous Delivery) pipelines are used to automatically build, test, and deploy application changes across multiple accounts and environments. This allows new features and bug fixes to be tested and deployed quickly to continuously improve the application. - -## Requirements - -- [TypeScript v3.8+](https://www.typescriptlang.org/) - - [AWS CDK in TypeScript](https://docs.aws.amazon.com/cdk/v2/guide/work-with-cdk-typescript.html) -- [AWS CDK v2.x](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html) - -## AWS Services Utilized - -- CodePipeline -- CodeCommit -- CodeBuild -- CodeDeploy -- Elastic Container Service (ECS) -- Fargate -- Elastic Container Registry (ECR) -- Lambda - -## Deploying - -- Authenticate to an AWS account via a Command Line Interface (CLI). -- Navigate to this `codepipeline-build-deploy` directory. -- `npm install` to install required dependencies -- `cdk synth` to generate and review the CloudFormation template. -- `cdk diff` to compare local changes with what is currently deployed. -- `npm run test` to run the tests we specify in `codepipeline-build-deploy.test.ts`. -- `cdk deploy` to deploy the stack to the AWS account you're authenticated to. - -## Output - -After a successful deployment, CDK will output a public endpoint for: - -- Application Load Balancer (ALB) - -## Testing - -- To test that the Docker image was built and deployed successfully to ECS, we can use the Application Load Balancer (ALB) public endpoint, e.g. `http://xyz123.us-east-1.elb.amazonaws.com`. -- The simple containerized application contains multiple pages for testing: - - `/index.html` - - `/about.html` - - `/contact.html` - - `/error.html` -- Navigate to the AWS CodePipeline console and select `ImageBuildDeployPipeline`. Then click on `Release change` to trigger the pipeline and observe the workflow in action end-to-end. -- Navigate to the AWS Console to view the services that were deployed: - - CodePipeline pipeline - - CodeCommit repository - - CodeBuild project - - CodeDeploy application - - ECS cluster - - ECS service on Fargate - - ECR image repository - - Lambda functions - -## Further Improvements - -- Add [manual approval actions](https://docs.aws.amazon.com/codepipeline/latest/userguide/approvals-action-add.html) to the CodePipeline workflow. -- [Deploy to multiple accounts](https://docs.aws.amazon.com/codedeploy/latest/userguide/deployments-cross-account.html) with CodeDeploy. -- Set up Elastic Container Registry (ECR) repository [cross-account or cross-region replication](https://docs.aws.amazon.com/AmazonECR/latest/userguide/replication.html). -- Protect the public Application Load Balancer (ALB) from attacks and malicious traffic using [Web Application Firewall (WAF)](https://docs.aws.amazon.com/waf/latest/developerguide/waf-chapter.html). -- [Enable SSL/TLS](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/create-https-listener.html) at the Application Load Balancer (ALB) using AWS Certificate Manager. -- Set up a [Route 53 domain](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/routing-to-elb-load-balancer.html) to route traffic to an Application Load Balancer (ALB). \ No newline at end of file diff --git a/typescript/codepipeline-build-deploy/app/Dockerfile b/typescript/codepipeline-build-deploy/app/Dockerfile deleted file mode 100644 index 1fad0fe591..0000000000 --- a/typescript/codepipeline-build-deploy/app/Dockerfile +++ /dev/null @@ -1,7 +0,0 @@ -FROM public.ecr.aws/nginx/nginx:stable-alpine - -COPY ./site-assets/ /usr/share/nginx/html - -EXPOSE 80 - -CMD ["nginx", "-g", "daemon off;"] diff --git a/typescript/codepipeline-build-deploy/app/README.md b/typescript/codepipeline-build-deploy/app/README.md deleted file mode 100644 index a72f090112..0000000000 --- a/typescript/codepipeline-build-deploy/app/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Simple Containerized Application - -## Overview - -This repository contains a simple application that will run in an Nginx container. The image will be built with Docker and stored in Amazon Elastic Container Registry (ECR). diff --git a/typescript/codepipeline-build-deploy/app/appspec.yaml b/typescript/codepipeline-build-deploy/app/appspec.yaml deleted file mode 100644 index 600e18e24d..0000000000 --- a/typescript/codepipeline-build-deploy/app/appspec.yaml +++ /dev/null @@ -1,9 +0,0 @@ -version: 0.0 -Resources: - - TargetService: - Type: AWS::ECS::Service - Properties: - TaskDefinition: "TASK_DEFINITION_ARN" - LoadBalancerInfo: - ContainerName: "web" - ContainerPort: "80" diff --git a/typescript/codepipeline-build-deploy/app/buildspec.yaml b/typescript/codepipeline-build-deploy/app/buildspec.yaml deleted file mode 100644 index 60a2cd617f..0000000000 --- a/typescript/codepipeline-build-deploy/app/buildspec.yaml +++ /dev/null @@ -1,31 +0,0 @@ -version: 0.2 - -phases: - pre_build: - commands: - - cd app - - echo Logging in to Amazon ECR... - - aws --version - - aws ecr get-login-password --region $REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com - build: - commands: - - echo Building the Docker image... - - docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG . - - docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG - post_build: - commands: - - echo Pushing the Docker image... - - docker push $AWS_ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG - - echo Container image to be used $REPOSITORY_URI:$IMAGE_TAG - - sed -i "s|REPOSITORY_URI|${REPOSITORY_URI}|g" taskdef.json - - sed -i "s|IMAGE_TAG|${IMAGE_TAG}|g" taskdef.json - - sed -i "s|TASK_ROLE_ARN|${TASK_ROLE_ARN}|g" taskdef.json - - sed -i "s|EXECUTION_ROLE_ARN|${EXECUTION_ROLE_ARN}|g" taskdef.json - - sed -i "s|TASK_DEFINITION_ARN|${TASK_DEFINITION_ARN}|g" appspec.yaml - - cat appspec.yaml && cat taskdef.json - - cp appspec.yaml ../ - - cp taskdef.json ../ -artifacts: - files: - - "appspec.yaml" - - "taskdef.json" diff --git a/typescript/codepipeline-build-deploy/app/site-assets/about.html b/typescript/codepipeline-build-deploy/app/site-assets/about.html deleted file mode 100644 index c38ebca5ce..0000000000 --- a/typescript/codepipeline-build-deploy/app/site-assets/about.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - About - - -

About Us

- - - diff --git a/typescript/codepipeline-build-deploy/app/site-assets/contact.html b/typescript/codepipeline-build-deploy/app/site-assets/contact.html deleted file mode 100644 index 8988f66079..0000000000 --- a/typescript/codepipeline-build-deploy/app/site-assets/contact.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - Contact - - -

Contact Us

- - - diff --git a/typescript/codepipeline-build-deploy/app/site-assets/error.html b/typescript/codepipeline-build-deploy/app/site-assets/error.html deleted file mode 100644 index 25e0112f1c..0000000000 --- a/typescript/codepipeline-build-deploy/app/site-assets/error.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - Error - - -

Sorry, that page doesn't exist...

- - diff --git a/typescript/codepipeline-build-deploy/app/site-assets/index.html b/typescript/codepipeline-build-deploy/app/site-assets/index.html deleted file mode 100644 index 45e8b20090..0000000000 --- a/typescript/codepipeline-build-deploy/app/site-assets/index.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - Homepage! - - -

Welcome to the Homepage!

- - - diff --git a/typescript/codepipeline-build-deploy/app/site-assets/styles.css b/typescript/codepipeline-build-deploy/app/site-assets/styles.css deleted file mode 100644 index 79aad61dad..0000000000 --- a/typescript/codepipeline-build-deploy/app/site-assets/styles.css +++ /dev/null @@ -1,38 +0,0 @@ -body { - text-align: center; - color: whitesmoke; - background-color: rgb(0, 128, 0, 0.5); - font-family: Verdana, Geneva, Tahoma, sans-serif; -} - -#current { - font-weight: bold; -} - -#error { - background-color: rgb(255, 0, 0, 0.5); -} - -ul { - background-color: rgb(127, 255, 212, 0.2); - list-style-type: none; - padding: 10px; -} - -li { - display: inline; - margin: 20px; - border-bottom: 3px solid transparent; -} - -li a { - text-decoration: none; - color: white; -} - -li a:hover { - color: rgb(127, 255, 0, 70); - padding: 5px 10px; - border-radius: 10px; - background-color: darkcyan; -} diff --git a/typescript/codepipeline-build-deploy/app/taskdef.json b/typescript/codepipeline-build-deploy/app/taskdef.json deleted file mode 100644 index e0d5f0e989..0000000000 --- a/typescript/codepipeline-build-deploy/app/taskdef.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "containerDefinitions": [ - { - "name": "web", - "image": "REPOSITORY_URI:IMAGE_TAG", - "portMappings": [ - { - "containerPort": 80, - "protocol": "tcp" - } - ], - "essential": true - } - ], - "taskRoleArn": "TASK_ROLE_ARN", - "executionRoleArn": "EXECUTION_ROLE_ARN", - "family": "codedeploy-sample", - "networkMode": "awsvpc", - "requiresCompatibilities": ["FARGATE"], - "cpu": "256", - "memory": "512" -} diff --git a/typescript/codepipeline-build-deploy/bin/codepipeline-build-deploy.ts b/typescript/codepipeline-build-deploy/bin/codepipeline-build-deploy.ts deleted file mode 100644 index d8dcc20fed..0000000000 --- a/typescript/codepipeline-build-deploy/bin/codepipeline-build-deploy.ts +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env node -import 'source-map-support/register'; -import * as cdk from 'aws-cdk-lib'; -import { CodepipelineBuildDeployStack } from '../lib/codepipeline-build-deploy-stack'; - -const app = new cdk.App(); -new CodepipelineBuildDeployStack(app, 'CodepipelineBuildDeployStack', { - /* If you don't specify 'env', this stack will be environment-agnostic. - * Account/Region-dependent features and context lookups will not work, - * but a single synthesized template can be deployed anywhere. */ - - /* Uncomment the next line to specialize this stack for the AWS Account - * and Region that are implied by the current CLI configuration. */ - // env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION }, - - /* Uncomment the next line if you know exactly what Account and Region you - * want to deploy the stack to. */ - // env: { account: '123456789012', region: 'us-east-1' }, - - /* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */ -}); \ No newline at end of file diff --git a/typescript/codepipeline-build-deploy/buildspec.yaml b/typescript/codepipeline-build-deploy/buildspec.yaml deleted file mode 100644 index 6ff7dc6758..0000000000 --- a/typescript/codepipeline-build-deploy/buildspec.yaml +++ /dev/null @@ -1,16 +0,0 @@ -version: 0.2 - -phases: - pre_build: - commands: - - npm install - build: - commands: - - npm run test - - npm run build -reports: - jest_reports: - files: - - report.xml - file-format: JUNITXML - base-directory: "./" \ No newline at end of file diff --git a/typescript/codepipeline-build-deploy/cdk.json b/typescript/codepipeline-build-deploy/cdk.json deleted file mode 100644 index 581a051d47..0000000000 --- a/typescript/codepipeline-build-deploy/cdk.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "app": "npx ts-node --prefer-ts-exts bin/codepipeline-build-deploy.ts", - "watch": { - "include": [ - "**" - ], - "exclude": [ - "README.md", - "cdk*.json", - "**/*.d.ts", - "**/*.js", - "tsconfig.json", - "package*.json", - "yarn.lock", - "node_modules", - "test" - ] - }, - "context": { - "@aws-cdk/aws-lambda:recognizeLayerVersion": true, - "@aws-cdk/core:checkSecretUsage": true, - "@aws-cdk/core:target-partitions": [ - "aws", - "aws-cn" - ], - "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, - "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, - "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, - "@aws-cdk/aws-iam:minimizePolicies": true, - "@aws-cdk/core:validateSnapshotRemovalPolicy": true, - "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, - "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, - "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, - "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, - "@aws-cdk/core:enablePartitionLiterals": true, - "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, - "@aws-cdk/aws-iam:standardizedServicePrincipals": true, - "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, - "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, - "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, - "@aws-cdk/aws-route53-patters:useCertificate": true, - "@aws-cdk/customresources:installLatestAwsSdkDefault": false, - "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, - "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, - "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, - "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, - "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, - "@aws-cdk/aws-redshift:columnId": true, - "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true - } -} diff --git a/typescript/codepipeline-build-deploy/jest.config.js b/typescript/codepipeline-build-deploy/jest.config.js deleted file mode 100644 index 79399a2182..0000000000 --- a/typescript/codepipeline-build-deploy/jest.config.js +++ /dev/null @@ -1,14 +0,0 @@ -module.exports = { - testEnvironment: 'node', - roots: ['/test'], - testMatch: ['**/*.test.ts'], - transform: { - '^.+\\.tsx?$': 'ts-jest' - }, - reporters: [ - 'default', - [ 'jest-junit', { - outputName: 'report.xml', - } ] - ] -}; diff --git a/typescript/codepipeline-build-deploy/lambda/trigger-build.js b/typescript/codepipeline-build-deploy/lambda/trigger-build.js deleted file mode 100644 index c8e2c37869..0000000000 --- a/typescript/codepipeline-build-deploy/lambda/trigger-build.js +++ /dev/null @@ -1,21 +0,0 @@ -const { - CodeBuildClient, - StartBuildCommand, -} = require("@aws-sdk/client-codebuild"); - -exports.handler = async (event) => { - const region = process.env.REGION; - const buildProjectName = process.env.CODEBUILD_PROJECT_NAME; - - const codebuild = new CodeBuildClient({ region: region }); - const buildCommand = new StartBuildCommand({ projectName: buildProjectName }); - - console.log("Triggering CodeBuild Project..."); - const buildResponse = await codebuild.send(buildCommand); - console.log(buildResponse); - - return { - statusCode: 200, - body: "CodeBuild Project building...", - }; -}; diff --git a/typescript/codepipeline-build-deploy/lib/codepipeline-build-deploy-stack.ts b/typescript/codepipeline-build-deploy/lib/codepipeline-build-deploy-stack.ts deleted file mode 100644 index 55de56ec22..0000000000 --- a/typescript/codepipeline-build-deploy/lib/codepipeline-build-deploy-stack.ts +++ /dev/null @@ -1,327 +0,0 @@ -import * as cdk from "aws-cdk-lib"; -import * as codebuild from "aws-cdk-lib/aws-codebuild"; -import * as codecommit from "aws-cdk-lib/aws-codecommit"; -import * as codedeploy from "aws-cdk-lib/aws-codedeploy"; -import * as pipeline from "aws-cdk-lib/aws-codepipeline"; -import * as pipelineactions from "aws-cdk-lib/aws-codepipeline-actions"; -import * as ec2 from "aws-cdk-lib/aws-ec2"; -import * as ecr from "aws-cdk-lib/aws-ecr"; -import * as ecs from "aws-cdk-lib/aws-ecs"; -import * as elb from "aws-cdk-lib/aws-elasticloadbalancingv2"; -import * as iam from "aws-cdk-lib/aws-iam"; -import * as lambda from "aws-cdk-lib/aws-lambda"; -import * as custom from "aws-cdk-lib/custom-resources"; -import { Construct } from "constructs"; -import * as path from "path"; -import * as fs from 'fs'; -import { Asset } from 'aws-cdk-lib/aws-s3-assets'; -import { IgnoreMode } from 'aws-cdk-lib'; -import { Code } from 'aws-cdk-lib/aws-codecommit'; - -export class CodepipelineBuildDeployStack extends cdk.Stack { - constructor(scope: Construct, id: string, props?: cdk.StackProps) { - super(scope, id, props); - - // Modify gitignore file to remove unneeded files from the codecommit copy - let gitignore = fs.readFileSync('.gitignore').toString().split(/\r?\n/); - gitignore.push('.git/'); - gitignore = gitignore.filter(g => g != 'node_modules/'); - gitignore.push('/node_modules/'); - - const codeAsset = new Asset(this, 'SourceAsset', { - path: path.join(__dirname, "../"), - ignoreMode: IgnoreMode.GIT, - exclude: gitignore, - }); - - const codeRepo = new codecommit.Repository(this, "repo", { - repositoryName: "simple-code-repo", - // Copies files from codepipeline-build-deploy directory to the repo as the initial commit - code: Code.fromAsset(codeAsset, 'main'), - }); - - // Creates an Elastic Container Registry (ECR) image repository - const imageRepo = new ecr.Repository(this, "imageRepo"); - - // Creates a Task Definition for the ECS Fargate service - const fargateTaskDef = new ecs.FargateTaskDefinition( - this, - "FargateTaskDef" - ); - fargateTaskDef.addContainer("container", { - containerName: "web", - image: ecs.ContainerImage.fromEcrRepository(imageRepo), - portMappings: [{ containerPort: 80 }], - }); - - // CodeBuild project that builds the Docker image - const buildImage = new codebuild.Project(this, "BuildImage", { - buildSpec: codebuild.BuildSpec.fromSourceFilename("app/buildspec.yaml"), - source: codebuild.Source.codeCommit({ repository: codeRepo }), - environment: { - privileged: true, - environmentVariables: { - AWS_ACCOUNT_ID: { value: process.env?.CDK_DEFAULT_ACCOUNT || "" }, - REGION: { value: process.env?.CDK_DEFAULT_REGION || "" }, - IMAGE_TAG: { value: "latest" }, - IMAGE_REPO_NAME: { value: imageRepo.repositoryName }, - REPOSITORY_URI: { value: imageRepo.repositoryUri }, - TASK_DEFINITION_ARN: { value: fargateTaskDef.taskDefinitionArn }, - TASK_ROLE_ARN: { value: fargateTaskDef.taskRole.roleArn }, - EXECUTION_ROLE_ARN: { value: fargateTaskDef.executionRole?.roleArn }, - }, - }, - }); - - // CodeBuild project that builds the Docker image - const buildTest = new codebuild.Project(this, "BuildTest", { - buildSpec: codebuild.BuildSpec.fromSourceFilename("buildspec.yaml"), - source: codebuild.Source.codeCommit({ repository: codeRepo }), - environment: { - buildImage: codebuild.LinuxBuildImage.AMAZON_LINUX_2_4, - } - }); - - // CodeBuild project that runs the ecr image scan - const scanImage = new codebuild.Project(this, "ScanImage", { - buildSpec: codebuild.BuildSpec.fromSourceFilename("test-buildspec.yaml"), - source: codebuild.Source.codeCommit({ repository: codeRepo }), - environment: { - buildImage: codebuild.LinuxBuildImage.AMAZON_LINUX_2_4, - environmentVariables: { - IMAGE_REPO_NAME: { value: imageRepo.repositoryName }, - IMAGE_TAG: { value: "latest" } - }, - }, - }); - - // Grants CodeBuild project access to pull/push images from/to ECR repo - imageRepo.grantPullPush(buildImage); - - // Lambda function that triggers CodeBuild image build project - const triggerCodeBuild = new lambda.Function(this, "BuildLambda", { - architecture: lambda.Architecture.ARM_64, - code: lambda.Code.fromAsset("./lambda"), - handler: "trigger-build.handler", - runtime: lambda.Runtime.NODEJS_18_X, - environment: { - REGION: process.env.CDK_DEFAULT_REGION!, - CODEBUILD_PROJECT_NAME: buildImage.projectName, - }, - // Allows this Lambda function to trigger the buildImage CodeBuild project - initialPolicy: [ - new iam.PolicyStatement({ - effect: iam.Effect.ALLOW, - actions: ["codebuild:StartBuild"], - resources: [buildImage.projectArn], - }), - ], - }); - - // Triggers a Lambda function using AWS SDK - const triggerLambda = new custom.AwsCustomResource( - this, - "BuildLambdaTrigger", - { - installLatestAwsSdk: true, - policy: custom.AwsCustomResourcePolicy.fromStatements([ - new iam.PolicyStatement({ - effect: iam.Effect.ALLOW, - actions: ["lambda:InvokeFunction"], - resources: [triggerCodeBuild.functionArn], - }), - ]), - onCreate: { - service: "Lambda", - action: "invoke", - physicalResourceId: custom.PhysicalResourceId.of("id"), - parameters: { - FunctionName: triggerCodeBuild.functionName, - InvocationType: "Event", - }, - }, - onUpdate: { - service: "Lambda", - action: "invoke", - parameters: { - FunctionName: triggerCodeBuild.functionName, - InvocationType: "Event", - }, - }, - } - ); - - // Creates VPC for the ECS Cluster - const clusterVpc = new ec2.Vpc(this, "ClusterVpc", { - ipAddresses: ec2.IpAddresses.cidr("10.50.0.0/16"), - }); - - // Deploys the cluster VPC after the initial image build triggers - clusterVpc.node.addDependency(triggerLambda); - - // Creates a new blue Target Group that routes traffic from the public Application Load Balancer (ALB) to the - // registered targets within the Target Group e.g. (EC2 instances, IP addresses, Lambda functions) - // https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-target-groups.html - const targetGroupBlue = new elb.ApplicationTargetGroup( - this, - "BlueTargetGroup", - { - targetGroupName: "alb-blue-tg", - targetType: elb.TargetType.IP, - port: 80, - vpc: clusterVpc, - } - ); - - // Creates a new green Target Group - const targetGroupGreen = new elb.ApplicationTargetGroup( - this, - "GreenTargetGroup", - { - targetGroupName: "alb-green-tg", - targetType: elb.TargetType.IP, - port: 80, - vpc: clusterVpc, - } - ); - - // Creates a Security Group for the Application Load Balancer (ALB) - const albSg = new ec2.SecurityGroup(this, "SecurityGroup", { - vpc: clusterVpc, - allowAllOutbound: true, - }); - albSg.addIngressRule( - ec2.Peer.anyIpv4(), - ec2.Port.tcp(80), - "Allows access on port 80/http", - false - ); - - // Creates a public ALB - const publicAlb = new elb.ApplicationLoadBalancer(this, "PublicAlb", { - vpc: clusterVpc, - internetFacing: true, - securityGroup: albSg, - }); - - // Adds a listener on port 80 to the ALB - const albListener = publicAlb.addListener("AlbListener80", { - open: false, - port: 80, - defaultTargetGroups: [targetGroupBlue], - }); - - // Creates an ECS Fargate service - const fargateService = new ecs.FargateService(this, "FargateService", { - desiredCount: 1, - serviceName: "fargate-frontend-service", - taskDefinition: fargateTaskDef, - cluster: new ecs.Cluster(this, "EcsCluster", { - enableFargateCapacityProviders: true, - vpc: clusterVpc, - }), - // Sets CodeDeploy as the deployment controller - deploymentController: { - type: ecs.DeploymentControllerType.CODE_DEPLOY, - }, - }); - - // Adds the ECS Fargate service to the ALB target group - fargateService.attachToApplicationTargetGroup(targetGroupBlue); - - // Creates new pipeline artifacts - const sourceArtifact = new pipeline.Artifact("SourceArtifact"); - const buildArtifact = new pipeline.Artifact("BuildArtifact"); - const scanArtifact = new pipeline.Artifact("ScanArtifact"); - - // Creates the source stage for CodePipeline - const sourceStage = { - stageName: "Source", - actions: [ - new pipelineactions.CodeCommitSourceAction({ - actionName: "AppCodeCommit", - branch: "main", - output: sourceArtifact, - repository: codeRepo, - }), - ], - }; - - // Run jest test and send result to CodeBuild - const testStage = { - stageName: "Test", - actions: [ - new pipelineactions.CodeBuildAction({ - actionName: "JestCDK", - input: sourceArtifact, - project: buildTest, - }), - ], - }; - - // Creates the build stage for CodePipeline - const buildStage = { - stageName: "Build", - actions: [ - new pipelineactions.CodeBuildAction({ - actionName: "DockerBuildPush", - input: sourceArtifact, - project: buildImage, - outputs: [buildArtifact], - }), - ], - }; - - // Creates the build stage that runs the ecr container image scan - const securityTestStage = { - stageName: "SecurityScan", - actions: [ - new pipelineactions.CodeBuildAction({ - actionName: "ScanImage", - input: sourceArtifact, - project: scanImage, - outputs: [scanArtifact], - }), - ], - }; - - // Creates a new CodeDeploy Deployment Group - const deploymentGroup = new codedeploy.EcsDeploymentGroup( - this, - "CodeDeployGroup", - { - service: fargateService, - // Configurations for CodeDeploy Blue/Green deployments - blueGreenDeploymentConfig: { - listener: albListener, - blueTargetGroup: targetGroupBlue, - greenTargetGroup: targetGroupGreen, - }, - } - ); - - // Creates the deploy stage for CodePipeline - const deployStage = { - stageName: "Deploy", - actions: [ - new pipelineactions.CodeDeployEcsDeployAction({ - actionName: "EcsFargateDeploy", - appSpecTemplateInput: buildArtifact, - taskDefinitionTemplateInput: buildArtifact, - deploymentGroup: deploymentGroup, - }), - ], - }; - - // Creates an AWS CodePipeline with source, build, and deploy stages - new pipeline.Pipeline(this, "BuildDeployPipeline", { - pipelineName: "ImageBuildDeployPipeline", - stages: [sourceStage, testStage, buildStage, securityTestStage, deployStage], - }); - - // Outputs the ALB public endpoint - new cdk.CfnOutput(this, "PublicAlbEndpoint", { - value: "http://" + publicAlb.loadBalancerDnsName, - }); - } -} diff --git a/typescript/codepipeline-build-deploy/package.json b/typescript/codepipeline-build-deploy/package.json deleted file mode 100644 index eb4f2b4c31..0000000000 --- a/typescript/codepipeline-build-deploy/package.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "codepipeline-build-deploy", - "version": "0.1.0", - "bin": { - "codepipeline-build-deploy": "bin/codepipeline-build-deploy.js" - }, - "scripts": { - "build": "tsc", - "watch": "tsc -w", - "test": "jest", - "cdk": "cdk" - }, - "devDependencies": { - "@types/jest": "^29.4.0", - "@types/node": "18.14.6", - "aws-cdk": "2.72.1", - "jest": "^29.5.0", - "ts-jest": "^29.0.5", - "ts-node": "^10.9.1", - "typescript": "~5.1.6" - }, - "dependencies": { - "aws-cdk-lib": "2.72.1", - "constructs": "^10.0.0", - "jest-junit": "^16.0.0", - "source-map-support": "^0.5.21" - } -} diff --git a/typescript/codepipeline-build-deploy/scan.json b/typescript/codepipeline-build-deploy/scan.json deleted file mode 100644 index 0dd5e9cebb..0000000000 --- a/typescript/codepipeline-build-deploy/scan.json +++ /dev/null @@ -1,345 +0,0 @@ -{ - "imageScanStatus": { - "status": "COMPLETE", - "description": "The scan was completed successfully." - }, - "repositoryName": "codepipelinebuilddeploystack-imagerepo1d8a68af-1d46t02aedsq", - "registryId": "026356163217", - "imageId": { - "imageTag": "latest", - "imageDigest": "sha256:44bed34e64b0676934b5a9cf2915738c49c654609aa61872c355e879a920334e" - }, - "imageScanFindings": { - "imageScanCompletedAt": 1692647551.0, - "vulnerabilitySourceUpdatedAt": 1692612680.0, - "findings": [ - { - "severity": "HIGH", - "attributes": [ - { - "value": "7.5", - "key": "CVSS3_SCORE" - }, - { - "value": "7.88.1-r1", - "key": "package_version" - }, - { - "value": "curl", - "key": "package_name" - }, - { - "value": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", - "key": "CVSS3_VECTOR" - } - ], - "name": "CVE-2023-28319", - "uri": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-28319" - }, - { - "severity": "HIGH", - "attributes": [ - { - "value": "7.5", - "key": "CVSS3_SCORE" - }, - { - "value": "1.2.4-r1", - "key": "package_version" - }, - { - "value": "libwebp", - "key": "package_name" - }, - { - "value": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", - "key": "CVSS3_VECTOR" - } - ], - "name": "CVE-2023-1999", - "uri": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-1999" - }, - { - "severity": "HIGH", - "attributes": [ - { - "value": "7.5", - "key": "CVSS3_SCORE" - }, - { - "value": "1.8.4-r0", - "key": "package_version" - }, - { - "value": "libx11", - "key": "package_name" - }, - { - "value": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", - "key": "CVSS3_VECTOR" - } - ], - "name": "CVE-2023-3138", - "uri": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-3138" - }, - { - "severity": "HIGH", - "attributes": [ - { - "value": "7.8", - "key": "CVSS3_SCORE" - }, - { - "value": "6.3_p20221119-r0", - "key": "package_version" - }, - { - "value": "ncurses", - "key": "package_name" - }, - { - "value": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", - "key": "CVSS3_VECTOR" - } - ], - "name": "CVE-2023-29491", - "uri": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-29491" - }, - { - "severity": "HIGH", - "attributes": [ - { - "value": "7.5", - "key": "CVSS3_SCORE" - }, - { - "value": "1.51.0-r0", - "key": "package_version" - }, - { - "value": "nghttp2", - "key": "package_name" - }, - { - "value": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", - "key": "CVSS3_VECTOR" - } - ], - "name": "CVE-2023-35945", - "uri": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-35945" - }, - { - "severity": "HIGH", - "attributes": [ - { - "value": "7.5", - "key": "CVSS3_SCORE" - }, - { - "value": "3.0.8-r3", - "key": "package_version" - }, - { - "value": "openssl", - "key": "package_name" - }, - { - "value": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", - "key": "CVSS3_VECTOR" - } - ], - "name": "CVE-2023-2650", - "uri": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-2650" - }, - { - "severity": "MEDIUM", - "attributes": [ - { - "value": "5.9", - "key": "CVSS3_SCORE" - }, - { - "value": "7.88.1-r1", - "key": "package_version" - }, - { - "value": "curl", - "key": "package_name" - }, - { - "value": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H", - "key": "CVSS3_VECTOR" - } - ], - "name": "CVE-2023-28320", - "uri": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-28320" - }, - { - "severity": "MEDIUM", - "attributes": [ - { - "value": "5.9", - "key": "CVSS3_SCORE" - }, - { - "value": "7.88.1-r1", - "key": "package_version" - }, - { - "value": "curl", - "key": "package_name" - }, - { - "value": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:N", - "key": "CVSS3_VECTOR" - } - ], - "name": "CVE-2023-28321", - "uri": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-28321" - }, - { - "severity": "MEDIUM", - "attributes": [ - { - "value": "5.3", - "key": "CVSS3_SCORE" - }, - { - "value": "3.0.8-r3", - "key": "package_version" - }, - { - "value": "openssl", - "key": "package_name" - }, - { - "value": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L", - "key": "CVSS3_VECTOR" - } - ], - "name": "CVE-2023-3446", - "uri": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-3446" - }, - { - "severity": "MEDIUM", - "attributes": [ - { - "value": "5.3", - "key": "CVSS3_SCORE" - }, - { - "value": "3.0.8-r3", - "key": "package_version" - }, - { - "value": "openssl", - "key": "package_name" - }, - { - "value": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L", - "key": "CVSS3_VECTOR" - } - ], - "name": "CVE-2023-3817", - "uri": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-3817" - }, - { - "severity": "MEDIUM", - "attributes": [ - { - "value": "5.3", - "key": "CVSS3_SCORE" - }, - { - "value": "3.0.8-r3", - "key": "package_version" - }, - { - "value": "openssl", - "key": "package_name" - }, - { - "value": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N", - "key": "CVSS3_VECTOR" - } - ], - "name": "CVE-2023-2975", - "uri": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-2975" - }, - { - "severity": "MEDIUM", - "attributes": [ - { - "value": "5.9", - "key": "CVSS3_SCORE" - }, - { - "value": "3.0.8-r3", - "key": "package_version" - }, - { - "value": "openssl", - "key": "package_name" - }, - { - "value": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H", - "key": "CVSS3_VECTOR" - } - ], - "name": "CVE-2023-1255", - "uri": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-1255" - }, - { - "severity": "MEDIUM", - "attributes": [ - { - "value": "6.5", - "key": "CVSS3_SCORE" - }, - { - "value": "4.4.0-r3", - "key": "package_version" - }, - { - "value": "tiff", - "key": "package_name" - }, - { - "value": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H", - "key": "CVSS3_VECTOR" - } - ], - "name": "CVE-2023-3316", - "uri": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-3316" - }, - { - "severity": "LOW", - "attributes": [ - { - "value": "3.7", - "key": "CVSS3_SCORE" - }, - { - "value": "7.88.1-r1", - "key": "package_version" - }, - { - "value": "curl", - "key": "package_name" - }, - { - "value": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N", - "key": "CVSS3_VECTOR" - } - ], - "name": "CVE-2023-28322", - "uri": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-28322" - } - ], - "findingSeverityCounts": { - "HIGH": 6, - "MEDIUM": 7, - "LOW": 1 - } - } -} diff --git a/typescript/codepipeline-build-deploy/test-buildspec.yaml b/typescript/codepipeline-build-deploy/test-buildspec.yaml deleted file mode 100644 index b2637e8fe6..0000000000 --- a/typescript/codepipeline-build-deploy/test-buildspec.yaml +++ /dev/null @@ -1,13 +0,0 @@ -version: 0.2 - -phases: - build: - commands: - - aws ecr start-image-scan --repository-name $IMAGE_REPO_NAME --image-id imageTag=$IMAGE_TAG - - aws ecr describe-image-scan-findings --repository-name $IMAGE_REPO_NAME --image-id imageTag=$IMAGE_TAG > scan.json - - while [ $(cat scan.json | jq -r .imageScanStatus.status) != 'COMPLETE' ]; do sleep 5 ; aws ecr describe-image-scan-findings --repository-name $IMAGE_REPO_NAME --image-id imageTag=$IMAGE_TAG > scan.json; done - - export highSeverity=$(cat scan.json | jq -r .imageScanFindings.findingSeverityCounts.HIGH) - post_build: - commands: - - echo "Failing build if high severity vulnerabilities found by ECR Image Scan" - - if [ $highSeverity > 0 ]; then exit 1; fi ## Uncomment this to break build given high severity vulnerabilities \ No newline at end of file diff --git a/typescript/codepipeline-build-deploy/test/codepipeline-build-deploy.test.ts b/typescript/codepipeline-build-deploy/test/codepipeline-build-deploy.test.ts deleted file mode 100644 index d17aa13c49..0000000000 --- a/typescript/codepipeline-build-deploy/test/codepipeline-build-deploy.test.ts +++ /dev/null @@ -1,50 +0,0 @@ -import * as cdk from "aws-cdk-lib"; -import { Template } from "aws-cdk-lib/assertions"; -import { CodepipelineBuildDeployStack } from "../lib/codepipeline-build-deploy-stack"; - -// Checks if the ECS Deployment Controller is set to AWS CodeDeploy -test("Deployment Controller Set", () => { - const app = new cdk.App(); - const stack = new CodepipelineBuildDeployStack(app, "MyTestStack"); - const template = Template.fromStack(stack); - - template.hasResourceProperties("AWS::ECS::Service", { - DeploymentController: { - Type: "CODE_DEPLOY", - }, - }); -}); - -// Checks if the ALB Security Group allows all traffic on port 80 -test("Security Group Port 80 Open", () => { - const app = new cdk.App(); - const stack = new CodepipelineBuildDeployStack(app, "MyTestStack"); - const template = Template.fromStack(stack); - - template.hasResourceProperties("AWS::EC2::SecurityGroup", { - SecurityGroupIngress: [ - { - CidrIp: "0.0.0.0/0", - FromPort: 80, - IpProtocol: "tcp", - ToPort: 80, - }, - ], - }); -}); - -// Checks if public access to the S3 Bucket is disabled -test("S3 Bucket Restricted", () => { - const app = new cdk.App(); - const stack = new CodepipelineBuildDeployStack(app, "MyTestStack"); - const template = Template.fromStack(stack); - - template.hasResourceProperties("AWS::S3::Bucket", { - PublicAccessBlockConfiguration: { - BlockPublicAcls: true, - BlockPublicPolicy: true, - IgnorePublicAcls: true, - RestrictPublicBuckets: true, - }, - }); -}); diff --git a/typescript/codepipeline-build-deploy/tsconfig.json b/typescript/codepipeline-build-deploy/tsconfig.json deleted file mode 100644 index fc44377a1e..0000000000 --- a/typescript/codepipeline-build-deploy/tsconfig.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "module": "commonjs", - "lib": [ - "es2020" - ], - "declaration": true, - "strict": true, - "noImplicitAny": true, - "strictNullChecks": true, - "noImplicitThis": true, - "alwaysStrict": true, - "noUnusedLocals": false, - "noUnusedParameters": false, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": false, - "inlineSourceMap": true, - "inlineSources": true, - "experimentalDecorators": true, - "strictPropertyInitialization": false, - "typeRoots": [ - "./node_modules/@types" - ] - }, - "exclude": [ - "node_modules", - "cdk.out" - ] -} From 0d52b9590bb82811479d3642c12debdf920bb640 Mon Sep 17 00:00:00 2001 From: EC2 Default User Date: Mon, 21 Aug 2023 20:21:16 +0000 Subject: [PATCH 06/13] removing scan.json --- .../codepipeline-build-deploy/.gitignore | 12 + .../codepipeline-build-deploy/.npmignore | 6 + .../codepipeline-build-deploy/README.md | 82 +++++ .../codepipeline-build-deploy/app/Dockerfile | 7 + .../codepipeline-build-deploy/app/README.md | 5 + .../app/appspec.yaml | 9 + .../app/buildspec.yaml | 31 ++ .../app/site-assets/about.html | 20 ++ .../app/site-assets/contact.html | 20 ++ .../app/site-assets/error.html | 13 + .../app/site-assets/index.html | 20 ++ .../app/site-assets/styles.css | 38 ++ .../app/taskdef.json | 22 ++ .../bin/codepipeline-build-deploy.ts | 21 ++ .../codepipeline-build-deploy/buildspec.yaml | 16 + typescript/codepipeline-build-deploy/cdk.json | 51 +++ .../codepipeline-build-deploy/jest.config.js | 14 + .../lambda/trigger-build.js | 21 ++ .../lib/codepipeline-build-deploy-stack.ts | 327 ++++++++++++++++++ .../codepipeline-build-deploy/package.json | 28 ++ .../test-buildspec.yaml | 13 + .../test/codepipeline-build-deploy.test.ts | 50 +++ .../codepipeline-build-deploy/tsconfig.json | 30 ++ 23 files changed, 856 insertions(+) create mode 100644 typescript/codepipeline-build-deploy/.gitignore create mode 100644 typescript/codepipeline-build-deploy/.npmignore create mode 100644 typescript/codepipeline-build-deploy/README.md create mode 100644 typescript/codepipeline-build-deploy/app/Dockerfile create mode 100644 typescript/codepipeline-build-deploy/app/README.md create mode 100644 typescript/codepipeline-build-deploy/app/appspec.yaml create mode 100644 typescript/codepipeline-build-deploy/app/buildspec.yaml create mode 100644 typescript/codepipeline-build-deploy/app/site-assets/about.html create mode 100644 typescript/codepipeline-build-deploy/app/site-assets/contact.html create mode 100644 typescript/codepipeline-build-deploy/app/site-assets/error.html create mode 100644 typescript/codepipeline-build-deploy/app/site-assets/index.html create mode 100644 typescript/codepipeline-build-deploy/app/site-assets/styles.css create mode 100644 typescript/codepipeline-build-deploy/app/taskdef.json create mode 100644 typescript/codepipeline-build-deploy/bin/codepipeline-build-deploy.ts create mode 100644 typescript/codepipeline-build-deploy/buildspec.yaml create mode 100644 typescript/codepipeline-build-deploy/cdk.json create mode 100644 typescript/codepipeline-build-deploy/jest.config.js create mode 100644 typescript/codepipeline-build-deploy/lambda/trigger-build.js create mode 100644 typescript/codepipeline-build-deploy/lib/codepipeline-build-deploy-stack.ts create mode 100644 typescript/codepipeline-build-deploy/package.json create mode 100644 typescript/codepipeline-build-deploy/test-buildspec.yaml create mode 100644 typescript/codepipeline-build-deploy/test/codepipeline-build-deploy.test.ts create mode 100644 typescript/codepipeline-build-deploy/tsconfig.json diff --git a/typescript/codepipeline-build-deploy/.gitignore b/typescript/codepipeline-build-deploy/.gitignore new file mode 100644 index 0000000000..ecf9c13c3e --- /dev/null +++ b/typescript/codepipeline-build-deploy/.gitignore @@ -0,0 +1,12 @@ +*.js +!jest.config.js +!*/trigger-build.js +*.d.ts +node_modules + +# CDK asset staging directory +.cdk.staging +cdk.out +report.xml + +scan.json \ No newline at end of file diff --git a/typescript/codepipeline-build-deploy/.npmignore b/typescript/codepipeline-build-deploy/.npmignore new file mode 100644 index 0000000000..c1d6d45dcf --- /dev/null +++ b/typescript/codepipeline-build-deploy/.npmignore @@ -0,0 +1,6 @@ +*.ts +!*.d.ts + +# CDK asset staging directory +.cdk.staging +cdk.out diff --git a/typescript/codepipeline-build-deploy/README.md b/typescript/codepipeline-build-deploy/README.md new file mode 100644 index 0000000000..38ed1bfe82 --- /dev/null +++ b/typescript/codepipeline-build-deploy/README.md @@ -0,0 +1,82 @@ +# Building and Deploying a Docker Image With AWS CodePipeline + +## + +![Stability: Stable](https://img.shields.io/badge/stability-Stable-success.svg?style=for-the-badge) + +> **This is a stable example. It should successfully build out of the box** +> +> This example is built on Construct Libraries marked "Stable" and does not have any infrastructure prerequisites to build. + +--- + + + +## Overview + +This AWS Cloud Development Kit (CDK) TypeScript example demonstrates how to configure AWS CodePipeline with CodeCommit, CodeBuild, and CodeDeploy to build and deploy a Docker image to an Elastic Container Service (ECS) cluster running [AWS Fargate](https://aws.amazon.com/fargate/) (serverless compute for containers). + +## Real-world Example + +When working in fast-paced development environments, CI/CD (Continuous Integration and Continuous Delivery) pipelines are used to automatically build, test, and deploy application changes across multiple accounts and environments. This allows new features and bug fixes to be tested and deployed quickly to continuously improve the application. + +## Requirements + +- [TypeScript v3.8+](https://www.typescriptlang.org/) + - [AWS CDK in TypeScript](https://docs.aws.amazon.com/cdk/v2/guide/work-with-cdk-typescript.html) +- [AWS CDK v2.x](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html) + +## AWS Services Utilized + +- CodePipeline +- CodeCommit +- CodeBuild +- CodeDeploy +- Elastic Container Service (ECS) +- Fargate +- Elastic Container Registry (ECR) +- Lambda + +## Deploying + +- Authenticate to an AWS account via a Command Line Interface (CLI). +- Navigate to this `codepipeline-build-deploy` directory. +- `npm install` to install required dependencies +- `cdk synth` to generate and review the CloudFormation template. +- `cdk diff` to compare local changes with what is currently deployed. +- `npm run test` to run the tests we specify in `codepipeline-build-deploy.test.ts`. +- `cdk deploy` to deploy the stack to the AWS account you're authenticated to. + +## Output + +After a successful deployment, CDK will output a public endpoint for: + +- Application Load Balancer (ALB) + +## Testing + +- To test that the Docker image was built and deployed successfully to ECS, we can use the Application Load Balancer (ALB) public endpoint, e.g. `http://xyz123.us-east-1.elb.amazonaws.com`. +- The simple containerized application contains multiple pages for testing: + - `/index.html` + - `/about.html` + - `/contact.html` + - `/error.html` +- Navigate to the AWS CodePipeline console and select `ImageBuildDeployPipeline`. Then click on `Release change` to trigger the pipeline and observe the workflow in action end-to-end. +- Navigate to the AWS Console to view the services that were deployed: + - CodePipeline pipeline + - CodeCommit repository + - CodeBuild project + - CodeDeploy application + - ECS cluster + - ECS service on Fargate + - ECR image repository + - Lambda functions + +## Further Improvements + +- Add [manual approval actions](https://docs.aws.amazon.com/codepipeline/latest/userguide/approvals-action-add.html) to the CodePipeline workflow. +- [Deploy to multiple accounts](https://docs.aws.amazon.com/codedeploy/latest/userguide/deployments-cross-account.html) with CodeDeploy. +- Set up Elastic Container Registry (ECR) repository [cross-account or cross-region replication](https://docs.aws.amazon.com/AmazonECR/latest/userguide/replication.html). +- Protect the public Application Load Balancer (ALB) from attacks and malicious traffic using [Web Application Firewall (WAF)](https://docs.aws.amazon.com/waf/latest/developerguide/waf-chapter.html). +- [Enable SSL/TLS](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/create-https-listener.html) at the Application Load Balancer (ALB) using AWS Certificate Manager. +- Set up a [Route 53 domain](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/routing-to-elb-load-balancer.html) to route traffic to an Application Load Balancer (ALB). \ No newline at end of file diff --git a/typescript/codepipeline-build-deploy/app/Dockerfile b/typescript/codepipeline-build-deploy/app/Dockerfile new file mode 100644 index 0000000000..1fad0fe591 --- /dev/null +++ b/typescript/codepipeline-build-deploy/app/Dockerfile @@ -0,0 +1,7 @@ +FROM public.ecr.aws/nginx/nginx:stable-alpine + +COPY ./site-assets/ /usr/share/nginx/html + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/typescript/codepipeline-build-deploy/app/README.md b/typescript/codepipeline-build-deploy/app/README.md new file mode 100644 index 0000000000..a72f090112 --- /dev/null +++ b/typescript/codepipeline-build-deploy/app/README.md @@ -0,0 +1,5 @@ +# Simple Containerized Application + +## Overview + +This repository contains a simple application that will run in an Nginx container. The image will be built with Docker and stored in Amazon Elastic Container Registry (ECR). diff --git a/typescript/codepipeline-build-deploy/app/appspec.yaml b/typescript/codepipeline-build-deploy/app/appspec.yaml new file mode 100644 index 0000000000..600e18e24d --- /dev/null +++ b/typescript/codepipeline-build-deploy/app/appspec.yaml @@ -0,0 +1,9 @@ +version: 0.0 +Resources: + - TargetService: + Type: AWS::ECS::Service + Properties: + TaskDefinition: "TASK_DEFINITION_ARN" + LoadBalancerInfo: + ContainerName: "web" + ContainerPort: "80" diff --git a/typescript/codepipeline-build-deploy/app/buildspec.yaml b/typescript/codepipeline-build-deploy/app/buildspec.yaml new file mode 100644 index 0000000000..60a2cd617f --- /dev/null +++ b/typescript/codepipeline-build-deploy/app/buildspec.yaml @@ -0,0 +1,31 @@ +version: 0.2 + +phases: + pre_build: + commands: + - cd app + - echo Logging in to Amazon ECR... + - aws --version + - aws ecr get-login-password --region $REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com + build: + commands: + - echo Building the Docker image... + - docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG . + - docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG + post_build: + commands: + - echo Pushing the Docker image... + - docker push $AWS_ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG + - echo Container image to be used $REPOSITORY_URI:$IMAGE_TAG + - sed -i "s|REPOSITORY_URI|${REPOSITORY_URI}|g" taskdef.json + - sed -i "s|IMAGE_TAG|${IMAGE_TAG}|g" taskdef.json + - sed -i "s|TASK_ROLE_ARN|${TASK_ROLE_ARN}|g" taskdef.json + - sed -i "s|EXECUTION_ROLE_ARN|${EXECUTION_ROLE_ARN}|g" taskdef.json + - sed -i "s|TASK_DEFINITION_ARN|${TASK_DEFINITION_ARN}|g" appspec.yaml + - cat appspec.yaml && cat taskdef.json + - cp appspec.yaml ../ + - cp taskdef.json ../ +artifacts: + files: + - "appspec.yaml" + - "taskdef.json" diff --git a/typescript/codepipeline-build-deploy/app/site-assets/about.html b/typescript/codepipeline-build-deploy/app/site-assets/about.html new file mode 100644 index 0000000000..c38ebca5ce --- /dev/null +++ b/typescript/codepipeline-build-deploy/app/site-assets/about.html @@ -0,0 +1,20 @@ + + + + + + + + About + + +

About Us

+ + + diff --git a/typescript/codepipeline-build-deploy/app/site-assets/contact.html b/typescript/codepipeline-build-deploy/app/site-assets/contact.html new file mode 100644 index 0000000000..8988f66079 --- /dev/null +++ b/typescript/codepipeline-build-deploy/app/site-assets/contact.html @@ -0,0 +1,20 @@ + + + + + + + + Contact + + +

Contact Us

+ + + diff --git a/typescript/codepipeline-build-deploy/app/site-assets/error.html b/typescript/codepipeline-build-deploy/app/site-assets/error.html new file mode 100644 index 0000000000..25e0112f1c --- /dev/null +++ b/typescript/codepipeline-build-deploy/app/site-assets/error.html @@ -0,0 +1,13 @@ + + + + + + + + Error + + +

Sorry, that page doesn't exist...

+ + diff --git a/typescript/codepipeline-build-deploy/app/site-assets/index.html b/typescript/codepipeline-build-deploy/app/site-assets/index.html new file mode 100644 index 0000000000..45e8b20090 --- /dev/null +++ b/typescript/codepipeline-build-deploy/app/site-assets/index.html @@ -0,0 +1,20 @@ + + + + + + + + Homepage! + + +

Welcome to the Homepage!

+ + + diff --git a/typescript/codepipeline-build-deploy/app/site-assets/styles.css b/typescript/codepipeline-build-deploy/app/site-assets/styles.css new file mode 100644 index 0000000000..79aad61dad --- /dev/null +++ b/typescript/codepipeline-build-deploy/app/site-assets/styles.css @@ -0,0 +1,38 @@ +body { + text-align: center; + color: whitesmoke; + background-color: rgb(0, 128, 0, 0.5); + font-family: Verdana, Geneva, Tahoma, sans-serif; +} + +#current { + font-weight: bold; +} + +#error { + background-color: rgb(255, 0, 0, 0.5); +} + +ul { + background-color: rgb(127, 255, 212, 0.2); + list-style-type: none; + padding: 10px; +} + +li { + display: inline; + margin: 20px; + border-bottom: 3px solid transparent; +} + +li a { + text-decoration: none; + color: white; +} + +li a:hover { + color: rgb(127, 255, 0, 70); + padding: 5px 10px; + border-radius: 10px; + background-color: darkcyan; +} diff --git a/typescript/codepipeline-build-deploy/app/taskdef.json b/typescript/codepipeline-build-deploy/app/taskdef.json new file mode 100644 index 0000000000..e0d5f0e989 --- /dev/null +++ b/typescript/codepipeline-build-deploy/app/taskdef.json @@ -0,0 +1,22 @@ +{ + "containerDefinitions": [ + { + "name": "web", + "image": "REPOSITORY_URI:IMAGE_TAG", + "portMappings": [ + { + "containerPort": 80, + "protocol": "tcp" + } + ], + "essential": true + } + ], + "taskRoleArn": "TASK_ROLE_ARN", + "executionRoleArn": "EXECUTION_ROLE_ARN", + "family": "codedeploy-sample", + "networkMode": "awsvpc", + "requiresCompatibilities": ["FARGATE"], + "cpu": "256", + "memory": "512" +} diff --git a/typescript/codepipeline-build-deploy/bin/codepipeline-build-deploy.ts b/typescript/codepipeline-build-deploy/bin/codepipeline-build-deploy.ts new file mode 100644 index 0000000000..d8dcc20fed --- /dev/null +++ b/typescript/codepipeline-build-deploy/bin/codepipeline-build-deploy.ts @@ -0,0 +1,21 @@ +#!/usr/bin/env node +import 'source-map-support/register'; +import * as cdk from 'aws-cdk-lib'; +import { CodepipelineBuildDeployStack } from '../lib/codepipeline-build-deploy-stack'; + +const app = new cdk.App(); +new CodepipelineBuildDeployStack(app, 'CodepipelineBuildDeployStack', { + /* If you don't specify 'env', this stack will be environment-agnostic. + * Account/Region-dependent features and context lookups will not work, + * but a single synthesized template can be deployed anywhere. */ + + /* Uncomment the next line to specialize this stack for the AWS Account + * and Region that are implied by the current CLI configuration. */ + // env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION }, + + /* Uncomment the next line if you know exactly what Account and Region you + * want to deploy the stack to. */ + // env: { account: '123456789012', region: 'us-east-1' }, + + /* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */ +}); \ No newline at end of file diff --git a/typescript/codepipeline-build-deploy/buildspec.yaml b/typescript/codepipeline-build-deploy/buildspec.yaml new file mode 100644 index 0000000000..6ff7dc6758 --- /dev/null +++ b/typescript/codepipeline-build-deploy/buildspec.yaml @@ -0,0 +1,16 @@ +version: 0.2 + +phases: + pre_build: + commands: + - npm install + build: + commands: + - npm run test + - npm run build +reports: + jest_reports: + files: + - report.xml + file-format: JUNITXML + base-directory: "./" \ No newline at end of file diff --git a/typescript/codepipeline-build-deploy/cdk.json b/typescript/codepipeline-build-deploy/cdk.json new file mode 100644 index 0000000000..581a051d47 --- /dev/null +++ b/typescript/codepipeline-build-deploy/cdk.json @@ -0,0 +1,51 @@ +{ + "app": "npx ts-node --prefer-ts-exts bin/codepipeline-build-deploy.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/core:validateSnapshotRemovalPolicy": true, + "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, + "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, + "@aws-cdk/aws-route53-patters:useCertificate": true, + "@aws-cdk/customresources:installLatestAwsSdkDefault": false, + "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, + "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, + "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, + "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, + "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, + "@aws-cdk/aws-redshift:columnId": true, + "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true + } +} diff --git a/typescript/codepipeline-build-deploy/jest.config.js b/typescript/codepipeline-build-deploy/jest.config.js new file mode 100644 index 0000000000..79399a2182 --- /dev/null +++ b/typescript/codepipeline-build-deploy/jest.config.js @@ -0,0 +1,14 @@ +module.exports = { + testEnvironment: 'node', + roots: ['/test'], + testMatch: ['**/*.test.ts'], + transform: { + '^.+\\.tsx?$': 'ts-jest' + }, + reporters: [ + 'default', + [ 'jest-junit', { + outputName: 'report.xml', + } ] + ] +}; diff --git a/typescript/codepipeline-build-deploy/lambda/trigger-build.js b/typescript/codepipeline-build-deploy/lambda/trigger-build.js new file mode 100644 index 0000000000..c8e2c37869 --- /dev/null +++ b/typescript/codepipeline-build-deploy/lambda/trigger-build.js @@ -0,0 +1,21 @@ +const { + CodeBuildClient, + StartBuildCommand, +} = require("@aws-sdk/client-codebuild"); + +exports.handler = async (event) => { + const region = process.env.REGION; + const buildProjectName = process.env.CODEBUILD_PROJECT_NAME; + + const codebuild = new CodeBuildClient({ region: region }); + const buildCommand = new StartBuildCommand({ projectName: buildProjectName }); + + console.log("Triggering CodeBuild Project..."); + const buildResponse = await codebuild.send(buildCommand); + console.log(buildResponse); + + return { + statusCode: 200, + body: "CodeBuild Project building...", + }; +}; diff --git a/typescript/codepipeline-build-deploy/lib/codepipeline-build-deploy-stack.ts b/typescript/codepipeline-build-deploy/lib/codepipeline-build-deploy-stack.ts new file mode 100644 index 0000000000..55de56ec22 --- /dev/null +++ b/typescript/codepipeline-build-deploy/lib/codepipeline-build-deploy-stack.ts @@ -0,0 +1,327 @@ +import * as cdk from "aws-cdk-lib"; +import * as codebuild from "aws-cdk-lib/aws-codebuild"; +import * as codecommit from "aws-cdk-lib/aws-codecommit"; +import * as codedeploy from "aws-cdk-lib/aws-codedeploy"; +import * as pipeline from "aws-cdk-lib/aws-codepipeline"; +import * as pipelineactions from "aws-cdk-lib/aws-codepipeline-actions"; +import * as ec2 from "aws-cdk-lib/aws-ec2"; +import * as ecr from "aws-cdk-lib/aws-ecr"; +import * as ecs from "aws-cdk-lib/aws-ecs"; +import * as elb from "aws-cdk-lib/aws-elasticloadbalancingv2"; +import * as iam from "aws-cdk-lib/aws-iam"; +import * as lambda from "aws-cdk-lib/aws-lambda"; +import * as custom from "aws-cdk-lib/custom-resources"; +import { Construct } from "constructs"; +import * as path from "path"; +import * as fs from 'fs'; +import { Asset } from 'aws-cdk-lib/aws-s3-assets'; +import { IgnoreMode } from 'aws-cdk-lib'; +import { Code } from 'aws-cdk-lib/aws-codecommit'; + +export class CodepipelineBuildDeployStack extends cdk.Stack { + constructor(scope: Construct, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + // Modify gitignore file to remove unneeded files from the codecommit copy + let gitignore = fs.readFileSync('.gitignore').toString().split(/\r?\n/); + gitignore.push('.git/'); + gitignore = gitignore.filter(g => g != 'node_modules/'); + gitignore.push('/node_modules/'); + + const codeAsset = new Asset(this, 'SourceAsset', { + path: path.join(__dirname, "../"), + ignoreMode: IgnoreMode.GIT, + exclude: gitignore, + }); + + const codeRepo = new codecommit.Repository(this, "repo", { + repositoryName: "simple-code-repo", + // Copies files from codepipeline-build-deploy directory to the repo as the initial commit + code: Code.fromAsset(codeAsset, 'main'), + }); + + // Creates an Elastic Container Registry (ECR) image repository + const imageRepo = new ecr.Repository(this, "imageRepo"); + + // Creates a Task Definition for the ECS Fargate service + const fargateTaskDef = new ecs.FargateTaskDefinition( + this, + "FargateTaskDef" + ); + fargateTaskDef.addContainer("container", { + containerName: "web", + image: ecs.ContainerImage.fromEcrRepository(imageRepo), + portMappings: [{ containerPort: 80 }], + }); + + // CodeBuild project that builds the Docker image + const buildImage = new codebuild.Project(this, "BuildImage", { + buildSpec: codebuild.BuildSpec.fromSourceFilename("app/buildspec.yaml"), + source: codebuild.Source.codeCommit({ repository: codeRepo }), + environment: { + privileged: true, + environmentVariables: { + AWS_ACCOUNT_ID: { value: process.env?.CDK_DEFAULT_ACCOUNT || "" }, + REGION: { value: process.env?.CDK_DEFAULT_REGION || "" }, + IMAGE_TAG: { value: "latest" }, + IMAGE_REPO_NAME: { value: imageRepo.repositoryName }, + REPOSITORY_URI: { value: imageRepo.repositoryUri }, + TASK_DEFINITION_ARN: { value: fargateTaskDef.taskDefinitionArn }, + TASK_ROLE_ARN: { value: fargateTaskDef.taskRole.roleArn }, + EXECUTION_ROLE_ARN: { value: fargateTaskDef.executionRole?.roleArn }, + }, + }, + }); + + // CodeBuild project that builds the Docker image + const buildTest = new codebuild.Project(this, "BuildTest", { + buildSpec: codebuild.BuildSpec.fromSourceFilename("buildspec.yaml"), + source: codebuild.Source.codeCommit({ repository: codeRepo }), + environment: { + buildImage: codebuild.LinuxBuildImage.AMAZON_LINUX_2_4, + } + }); + + // CodeBuild project that runs the ecr image scan + const scanImage = new codebuild.Project(this, "ScanImage", { + buildSpec: codebuild.BuildSpec.fromSourceFilename("test-buildspec.yaml"), + source: codebuild.Source.codeCommit({ repository: codeRepo }), + environment: { + buildImage: codebuild.LinuxBuildImage.AMAZON_LINUX_2_4, + environmentVariables: { + IMAGE_REPO_NAME: { value: imageRepo.repositoryName }, + IMAGE_TAG: { value: "latest" } + }, + }, + }); + + // Grants CodeBuild project access to pull/push images from/to ECR repo + imageRepo.grantPullPush(buildImage); + + // Lambda function that triggers CodeBuild image build project + const triggerCodeBuild = new lambda.Function(this, "BuildLambda", { + architecture: lambda.Architecture.ARM_64, + code: lambda.Code.fromAsset("./lambda"), + handler: "trigger-build.handler", + runtime: lambda.Runtime.NODEJS_18_X, + environment: { + REGION: process.env.CDK_DEFAULT_REGION!, + CODEBUILD_PROJECT_NAME: buildImage.projectName, + }, + // Allows this Lambda function to trigger the buildImage CodeBuild project + initialPolicy: [ + new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ["codebuild:StartBuild"], + resources: [buildImage.projectArn], + }), + ], + }); + + // Triggers a Lambda function using AWS SDK + const triggerLambda = new custom.AwsCustomResource( + this, + "BuildLambdaTrigger", + { + installLatestAwsSdk: true, + policy: custom.AwsCustomResourcePolicy.fromStatements([ + new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ["lambda:InvokeFunction"], + resources: [triggerCodeBuild.functionArn], + }), + ]), + onCreate: { + service: "Lambda", + action: "invoke", + physicalResourceId: custom.PhysicalResourceId.of("id"), + parameters: { + FunctionName: triggerCodeBuild.functionName, + InvocationType: "Event", + }, + }, + onUpdate: { + service: "Lambda", + action: "invoke", + parameters: { + FunctionName: triggerCodeBuild.functionName, + InvocationType: "Event", + }, + }, + } + ); + + // Creates VPC for the ECS Cluster + const clusterVpc = new ec2.Vpc(this, "ClusterVpc", { + ipAddresses: ec2.IpAddresses.cidr("10.50.0.0/16"), + }); + + // Deploys the cluster VPC after the initial image build triggers + clusterVpc.node.addDependency(triggerLambda); + + // Creates a new blue Target Group that routes traffic from the public Application Load Balancer (ALB) to the + // registered targets within the Target Group e.g. (EC2 instances, IP addresses, Lambda functions) + // https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-target-groups.html + const targetGroupBlue = new elb.ApplicationTargetGroup( + this, + "BlueTargetGroup", + { + targetGroupName: "alb-blue-tg", + targetType: elb.TargetType.IP, + port: 80, + vpc: clusterVpc, + } + ); + + // Creates a new green Target Group + const targetGroupGreen = new elb.ApplicationTargetGroup( + this, + "GreenTargetGroup", + { + targetGroupName: "alb-green-tg", + targetType: elb.TargetType.IP, + port: 80, + vpc: clusterVpc, + } + ); + + // Creates a Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, "SecurityGroup", { + vpc: clusterVpc, + allowAllOutbound: true, + }); + albSg.addIngressRule( + ec2.Peer.anyIpv4(), + ec2.Port.tcp(80), + "Allows access on port 80/http", + false + ); + + // Creates a public ALB + const publicAlb = new elb.ApplicationLoadBalancer(this, "PublicAlb", { + vpc: clusterVpc, + internetFacing: true, + securityGroup: albSg, + }); + + // Adds a listener on port 80 to the ALB + const albListener = publicAlb.addListener("AlbListener80", { + open: false, + port: 80, + defaultTargetGroups: [targetGroupBlue], + }); + + // Creates an ECS Fargate service + const fargateService = new ecs.FargateService(this, "FargateService", { + desiredCount: 1, + serviceName: "fargate-frontend-service", + taskDefinition: fargateTaskDef, + cluster: new ecs.Cluster(this, "EcsCluster", { + enableFargateCapacityProviders: true, + vpc: clusterVpc, + }), + // Sets CodeDeploy as the deployment controller + deploymentController: { + type: ecs.DeploymentControllerType.CODE_DEPLOY, + }, + }); + + // Adds the ECS Fargate service to the ALB target group + fargateService.attachToApplicationTargetGroup(targetGroupBlue); + + // Creates new pipeline artifacts + const sourceArtifact = new pipeline.Artifact("SourceArtifact"); + const buildArtifact = new pipeline.Artifact("BuildArtifact"); + const scanArtifact = new pipeline.Artifact("ScanArtifact"); + + // Creates the source stage for CodePipeline + const sourceStage = { + stageName: "Source", + actions: [ + new pipelineactions.CodeCommitSourceAction({ + actionName: "AppCodeCommit", + branch: "main", + output: sourceArtifact, + repository: codeRepo, + }), + ], + }; + + // Run jest test and send result to CodeBuild + const testStage = { + stageName: "Test", + actions: [ + new pipelineactions.CodeBuildAction({ + actionName: "JestCDK", + input: sourceArtifact, + project: buildTest, + }), + ], + }; + + // Creates the build stage for CodePipeline + const buildStage = { + stageName: "Build", + actions: [ + new pipelineactions.CodeBuildAction({ + actionName: "DockerBuildPush", + input: sourceArtifact, + project: buildImage, + outputs: [buildArtifact], + }), + ], + }; + + // Creates the build stage that runs the ecr container image scan + const securityTestStage = { + stageName: "SecurityScan", + actions: [ + new pipelineactions.CodeBuildAction({ + actionName: "ScanImage", + input: sourceArtifact, + project: scanImage, + outputs: [scanArtifact], + }), + ], + }; + + // Creates a new CodeDeploy Deployment Group + const deploymentGroup = new codedeploy.EcsDeploymentGroup( + this, + "CodeDeployGroup", + { + service: fargateService, + // Configurations for CodeDeploy Blue/Green deployments + blueGreenDeploymentConfig: { + listener: albListener, + blueTargetGroup: targetGroupBlue, + greenTargetGroup: targetGroupGreen, + }, + } + ); + + // Creates the deploy stage for CodePipeline + const deployStage = { + stageName: "Deploy", + actions: [ + new pipelineactions.CodeDeployEcsDeployAction({ + actionName: "EcsFargateDeploy", + appSpecTemplateInput: buildArtifact, + taskDefinitionTemplateInput: buildArtifact, + deploymentGroup: deploymentGroup, + }), + ], + }; + + // Creates an AWS CodePipeline with source, build, and deploy stages + new pipeline.Pipeline(this, "BuildDeployPipeline", { + pipelineName: "ImageBuildDeployPipeline", + stages: [sourceStage, testStage, buildStage, securityTestStage, deployStage], + }); + + // Outputs the ALB public endpoint + new cdk.CfnOutput(this, "PublicAlbEndpoint", { + value: "http://" + publicAlb.loadBalancerDnsName, + }); + } +} diff --git a/typescript/codepipeline-build-deploy/package.json b/typescript/codepipeline-build-deploy/package.json new file mode 100644 index 0000000000..eb4f2b4c31 --- /dev/null +++ b/typescript/codepipeline-build-deploy/package.json @@ -0,0 +1,28 @@ +{ + "name": "codepipeline-build-deploy", + "version": "0.1.0", + "bin": { + "codepipeline-build-deploy": "bin/codepipeline-build-deploy.js" + }, + "scripts": { + "build": "tsc", + "watch": "tsc -w", + "test": "jest", + "cdk": "cdk" + }, + "devDependencies": { + "@types/jest": "^29.4.0", + "@types/node": "18.14.6", + "aws-cdk": "2.72.1", + "jest": "^29.5.0", + "ts-jest": "^29.0.5", + "ts-node": "^10.9.1", + "typescript": "~5.1.6" + }, + "dependencies": { + "aws-cdk-lib": "2.72.1", + "constructs": "^10.0.0", + "jest-junit": "^16.0.0", + "source-map-support": "^0.5.21" + } +} diff --git a/typescript/codepipeline-build-deploy/test-buildspec.yaml b/typescript/codepipeline-build-deploy/test-buildspec.yaml new file mode 100644 index 0000000000..b2637e8fe6 --- /dev/null +++ b/typescript/codepipeline-build-deploy/test-buildspec.yaml @@ -0,0 +1,13 @@ +version: 0.2 + +phases: + build: + commands: + - aws ecr start-image-scan --repository-name $IMAGE_REPO_NAME --image-id imageTag=$IMAGE_TAG + - aws ecr describe-image-scan-findings --repository-name $IMAGE_REPO_NAME --image-id imageTag=$IMAGE_TAG > scan.json + - while [ $(cat scan.json | jq -r .imageScanStatus.status) != 'COMPLETE' ]; do sleep 5 ; aws ecr describe-image-scan-findings --repository-name $IMAGE_REPO_NAME --image-id imageTag=$IMAGE_TAG > scan.json; done + - export highSeverity=$(cat scan.json | jq -r .imageScanFindings.findingSeverityCounts.HIGH) + post_build: + commands: + - echo "Failing build if high severity vulnerabilities found by ECR Image Scan" + - if [ $highSeverity > 0 ]; then exit 1; fi ## Uncomment this to break build given high severity vulnerabilities \ No newline at end of file diff --git a/typescript/codepipeline-build-deploy/test/codepipeline-build-deploy.test.ts b/typescript/codepipeline-build-deploy/test/codepipeline-build-deploy.test.ts new file mode 100644 index 0000000000..d17aa13c49 --- /dev/null +++ b/typescript/codepipeline-build-deploy/test/codepipeline-build-deploy.test.ts @@ -0,0 +1,50 @@ +import * as cdk from "aws-cdk-lib"; +import { Template } from "aws-cdk-lib/assertions"; +import { CodepipelineBuildDeployStack } from "../lib/codepipeline-build-deploy-stack"; + +// Checks if the ECS Deployment Controller is set to AWS CodeDeploy +test("Deployment Controller Set", () => { + const app = new cdk.App(); + const stack = new CodepipelineBuildDeployStack(app, "MyTestStack"); + const template = Template.fromStack(stack); + + template.hasResourceProperties("AWS::ECS::Service", { + DeploymentController: { + Type: "CODE_DEPLOY", + }, + }); +}); + +// Checks if the ALB Security Group allows all traffic on port 80 +test("Security Group Port 80 Open", () => { + const app = new cdk.App(); + const stack = new CodepipelineBuildDeployStack(app, "MyTestStack"); + const template = Template.fromStack(stack); + + template.hasResourceProperties("AWS::EC2::SecurityGroup", { + SecurityGroupIngress: [ + { + CidrIp: "0.0.0.0/0", + FromPort: 80, + IpProtocol: "tcp", + ToPort: 80, + }, + ], + }); +}); + +// Checks if public access to the S3 Bucket is disabled +test("S3 Bucket Restricted", () => { + const app = new cdk.App(); + const stack = new CodepipelineBuildDeployStack(app, "MyTestStack"); + const template = Template.fromStack(stack); + + template.hasResourceProperties("AWS::S3::Bucket", { + PublicAccessBlockConfiguration: { + BlockPublicAcls: true, + BlockPublicPolicy: true, + IgnorePublicAcls: true, + RestrictPublicBuckets: true, + }, + }); +}); diff --git a/typescript/codepipeline-build-deploy/tsconfig.json b/typescript/codepipeline-build-deploy/tsconfig.json new file mode 100644 index 0000000000..fc44377a1e --- /dev/null +++ b/typescript/codepipeline-build-deploy/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": [ + "es2020" + ], + "declaration": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": false, + "inlineSourceMap": true, + "inlineSources": true, + "experimentalDecorators": true, + "strictPropertyInitialization": false, + "typeRoots": [ + "./node_modules/@types" + ] + }, + "exclude": [ + "node_modules", + "cdk.out" + ] +} From 14ef5d873857f5e80556a2dce63f0cac90dcf47d Mon Sep 17 00:00:00 2001 From: EC2 Default User Date: Mon, 21 Aug 2023 21:03:11 +0000 Subject: [PATCH 07/13] adding in logic to check if the container was scanned today, if not (or if the status of the scan is incomplete), then scan again --- typescript/codepipeline-build-deploy/test-buildspec.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/typescript/codepipeline-build-deploy/test-buildspec.yaml b/typescript/codepipeline-build-deploy/test-buildspec.yaml index b2637e8fe6..7a166e6cdf 100644 --- a/typescript/codepipeline-build-deploy/test-buildspec.yaml +++ b/typescript/codepipeline-build-deploy/test-buildspec.yaml @@ -3,9 +3,12 @@ version: 0.2 phases: build: commands: + - export outOfDate=0 - aws ecr start-image-scan --repository-name $IMAGE_REPO_NAME --image-id imageTag=$IMAGE_TAG - aws ecr describe-image-scan-findings --repository-name $IMAGE_REPO_NAME --image-id imageTag=$IMAGE_TAG > scan.json - - while [ $(cat scan.json | jq -r .imageScanStatus.status) != 'COMPLETE' ]; do sleep 5 ; aws ecr describe-image-scan-findings --repository-name $IMAGE_REPO_NAME --image-id imageTag=$IMAGE_TAG > scan.json; done + - export scanDate=$(date --date @$(cat scan.json | jq -r .imageScanFindings.imageScanCompletedAt) +%d.%m.%y.) + - if [ $scanDate != $(date +%d.%m.%y.) ]; then export outOfDate=1 ; fi + - while [ $outOfDate -eq 1 ] || [ $(cat scan.json | jq -r .imageScanStatus.status) != 'COMPLETE' ]; do sleep 15 ; aws ecr describe-image-scan-findings --repository-name $IMAGE_REPO_NAME --image-id imageTag=$IMAGE_TAG > scan.json; done - export highSeverity=$(cat scan.json | jq -r .imageScanFindings.findingSeverityCounts.HIGH) post_build: commands: From 2eceea68971c7414b052150b68e7928e5f9e95f1 Mon Sep 17 00:00:00 2001 From: EC2 Default User Date: Tue, 22 Aug 2023 00:06:54 +0000 Subject: [PATCH 08/13] fixing logic so start scan only starts if out of date (based on the describe image scan findings call) --- typescript/codepipeline-build-deploy/test-buildspec.yaml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/typescript/codepipeline-build-deploy/test-buildspec.yaml b/typescript/codepipeline-build-deploy/test-buildspec.yaml index 7a166e6cdf..cfb0061864 100644 --- a/typescript/codepipeline-build-deploy/test-buildspec.yaml +++ b/typescript/codepipeline-build-deploy/test-buildspec.yaml @@ -4,13 +4,12 @@ phases: build: commands: - export outOfDate=0 - - aws ecr start-image-scan --repository-name $IMAGE_REPO_NAME --image-id imageTag=$IMAGE_TAG - aws ecr describe-image-scan-findings --repository-name $IMAGE_REPO_NAME --image-id imageTag=$IMAGE_TAG > scan.json - export scanDate=$(date --date @$(cat scan.json | jq -r .imageScanFindings.imageScanCompletedAt) +%d.%m.%y.) - - if [ $scanDate != $(date +%d.%m.%y.) ]; then export outOfDate=1 ; fi - - while [ $outOfDate -eq 1 ] || [ $(cat scan.json | jq -r .imageScanStatus.status) != 'COMPLETE' ]; do sleep 15 ; aws ecr describe-image-scan-findings --repository-name $IMAGE_REPO_NAME --image-id imageTag=$IMAGE_TAG > scan.json; done + - if [ $scanDate != $(date +%d.%m.%y.) ]; then export outOfDate=1; aws ecr start-image-scan --repository-name $IMAGE_REPO_NAME --image-id imageTag=$IMAGE_TAG; fi + - while [ $outOfDate -eq 1 ] || [ $(cat scan.json | jq -r .imageScanStatus.status) != 'COMPLETE' ]; do sleep 15; aws ecr describe-image-scan-findings --repository-name $IMAGE_REPO_NAME --image-id imageTag=$IMAGE_TAG > scan.json; done - export highSeverity=$(cat scan.json | jq -r .imageScanFindings.findingSeverityCounts.HIGH) post_build: commands: - echo "Failing build if high severity vulnerabilities found by ECR Image Scan" - - if [ $highSeverity > 0 ]; then exit 1; fi ## Uncomment this to break build given high severity vulnerabilities \ No newline at end of file + ## - if [ $highSeverity > 0 ]; then exit 1; fi ## Uncomment this to break build given high severity vulnerabilities \ No newline at end of file From 0224db9e46f4dbca106360b4d919a893d3856e81 Mon Sep 17 00:00:00 2001 From: EC2 Default User Date: Wed, 30 Aug 2023 15:41:35 +0000 Subject: [PATCH 09/13] removing scan (scan on push from ecr enhanced scanning) --- .../codepipeline-build-deploy/test-buildspec.yaml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/typescript/codepipeline-build-deploy/test-buildspec.yaml b/typescript/codepipeline-build-deploy/test-buildspec.yaml index cfb0061864..337a9c5da5 100644 --- a/typescript/codepipeline-build-deploy/test-buildspec.yaml +++ b/typescript/codepipeline-build-deploy/test-buildspec.yaml @@ -4,12 +4,15 @@ phases: build: commands: - export outOfDate=0 + #- aws ecr start-image-scan --repository-name $IMAGE_REPO_NAME --image-id imageTag=$IMAGE_TAG || echo "Image already scanned today" - aws ecr describe-image-scan-findings --repository-name $IMAGE_REPO_NAME --image-id imageTag=$IMAGE_TAG > scan.json - - export scanDate=$(date --date @$(cat scan.json | jq -r .imageScanFindings.imageScanCompletedAt) +%d.%m.%y.) - - if [ $scanDate != $(date +%d.%m.%y.) ]; then export outOfDate=1; aws ecr start-image-scan --repository-name $IMAGE_REPO_NAME --image-id imageTag=$IMAGE_TAG; fi - - while [ $outOfDate -eq 1 ] || [ $(cat scan.json | jq -r .imageScanStatus.status) != 'COMPLETE' ]; do sleep 15; aws ecr describe-image-scan-findings --repository-name $IMAGE_REPO_NAME --image-id imageTag=$IMAGE_TAG > scan.json; done + - while [ $(cat scan.json | jq -r .imageScanStatus.status) != 'COMPLETE' ]; do sleep 15; aws ecr describe-image-scan-findings --repository-name $IMAGE_REPO_NAME --image-id imageTag=$IMAGE_TAG > scan.json; done + - cat scan.json - export highSeverity=$(cat scan.json | jq -r .imageScanFindings.findingSeverityCounts.HIGH) + - export findings=$(cat scan.json | jq -r .imageScanFindings.findings) post_build: commands: + - echo "Passing if there were no findings" + - if [ $findings == "[]" ]; then exit 0; fi - echo "Failing build if high severity vulnerabilities found by ECR Image Scan" - ## - if [ $highSeverity > 0 ]; then exit 1; fi ## Uncomment this to break build given high severity vulnerabilities \ No newline at end of file + - if [ -z $highSeverity ] && [ $highSeverity > 0 ]; then exit 1; fi ## Uncomment this to break build given high severity vulnerabilities \ No newline at end of file From 825eb0106b79f60a85c7c68f5fd7d6a893e52bee Mon Sep 17 00:00:00 2001 From: EC2 Default User Date: Thu, 14 Sep 2023 19:22:22 +0000 Subject: [PATCH 10/13] uploading code to create stage to scan image, breaking build if fails --- typescript/codepipeline-build-deploy/test-buildspec.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/typescript/codepipeline-build-deploy/test-buildspec.yaml b/typescript/codepipeline-build-deploy/test-buildspec.yaml index 337a9c5da5..c4dbeaddde 100644 --- a/typescript/codepipeline-build-deploy/test-buildspec.yaml +++ b/typescript/codepipeline-build-deploy/test-buildspec.yaml @@ -6,7 +6,7 @@ phases: - export outOfDate=0 #- aws ecr start-image-scan --repository-name $IMAGE_REPO_NAME --image-id imageTag=$IMAGE_TAG || echo "Image already scanned today" - aws ecr describe-image-scan-findings --repository-name $IMAGE_REPO_NAME --image-id imageTag=$IMAGE_TAG > scan.json - - while [ $(cat scan.json | jq -r .imageScanStatus.status) != 'COMPLETE' ]; do sleep 15; aws ecr describe-image-scan-findings --repository-name $IMAGE_REPO_NAME --image-id imageTag=$IMAGE_TAG > scan.json; done + - while [ $(cat scan.json | jq -r .imageScanStatus.status) != 'ACTIVE' ]; do sleep 15; aws ecr describe-image-scan-findings --repository-name $IMAGE_REPO_NAME --image-id imageTag=$IMAGE_TAG > scan.json; done - cat scan.json - export highSeverity=$(cat scan.json | jq -r .imageScanFindings.findingSeverityCounts.HIGH) - export findings=$(cat scan.json | jq -r .imageScanFindings.findings) @@ -15,4 +15,4 @@ phases: - echo "Passing if there were no findings" - if [ $findings == "[]" ]; then exit 0; fi - echo "Failing build if high severity vulnerabilities found by ECR Image Scan" - - if [ -z $highSeverity ] && [ $highSeverity > 0 ]; then exit 1; fi ## Uncomment this to break build given high severity vulnerabilities \ No newline at end of file + - if [ $findings != "[]" ] && [ -n $highSeverity ] && [ $highSeverity > 0 ]; then exit 1; fi ## Uncomment this to break build given high severity vulnerabilities \ No newline at end of file From 6aab07ed0611cd09fe2535900dac7ffb152fcce7 Mon Sep 17 00:00:00 2001 From: Jayson Sizer McIntosh Date: Thu, 8 Aug 2024 16:55:26 -0400 Subject: [PATCH 11/13] scaffolding for migrating off of codecommit to github --- .../codepipeline-build-deploy/.gitignore | 4 +- .../codepipeline-build-deploy/README.md | 4 +- .../bin/codepipeline-build-deploy.ts | 4 ++ .../lib/codepipeline-build-deploy-stack.ts | 69 +++++++++---------- .../codepipeline-build-deploy/package.json | 2 +- .../test-buildspec.yaml | 18 ----- 6 files changed, 40 insertions(+), 61 deletions(-) delete mode 100644 typescript/codepipeline-build-deploy/test-buildspec.yaml diff --git a/typescript/codepipeline-build-deploy/.gitignore b/typescript/codepipeline-build-deploy/.gitignore index ecf9c13c3e..cdc469d801 100644 --- a/typescript/codepipeline-build-deploy/.gitignore +++ b/typescript/codepipeline-build-deploy/.gitignore @@ -7,6 +7,4 @@ node_modules # CDK asset staging directory .cdk.staging cdk.out -report.xml - -scan.json \ No newline at end of file +report.xml \ No newline at end of file diff --git a/typescript/codepipeline-build-deploy/README.md b/typescript/codepipeline-build-deploy/README.md index 38ed1bfe82..405fe9a2a3 100644 --- a/typescript/codepipeline-build-deploy/README.md +++ b/typescript/codepipeline-build-deploy/README.md @@ -45,7 +45,9 @@ When working in fast-paced development environments, CI/CD (Continuous Integrati - `cdk synth` to generate and review the CloudFormation template. - `cdk diff` to compare local changes with what is currently deployed. - `npm run test` to run the tests we specify in `codepipeline-build-deploy.test.ts`. -- `cdk deploy` to deploy the stack to the AWS account you're authenticated to. +- `cdk deploy --context-json '{"githubUsername":"", "githubPAT":""}'` to deploy the stack to the AWS account you're authenticated to. + + ## Output diff --git a/typescript/codepipeline-build-deploy/bin/codepipeline-build-deploy.ts b/typescript/codepipeline-build-deploy/bin/codepipeline-build-deploy.ts index d8dcc20fed..8d46d0edf6 100644 --- a/typescript/codepipeline-build-deploy/bin/codepipeline-build-deploy.ts +++ b/typescript/codepipeline-build-deploy/bin/codepipeline-build-deploy.ts @@ -18,4 +18,8 @@ new CodepipelineBuildDeployStack(app, 'CodepipelineBuildDeployStack', { // env: { account: '123456789012', region: 'us-east-1' }, /* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */ + + context: { + githubUsername: app.node.tryGetContext('githubUsername'), + }, }); \ No newline at end of file diff --git a/typescript/codepipeline-build-deploy/lib/codepipeline-build-deploy-stack.ts b/typescript/codepipeline-build-deploy/lib/codepipeline-build-deploy-stack.ts index 55de56ec22..025b3eab73 100644 --- a/typescript/codepipeline-build-deploy/lib/codepipeline-build-deploy-stack.ts +++ b/typescript/codepipeline-build-deploy/lib/codepipeline-build-deploy-stack.ts @@ -1,8 +1,8 @@ import * as cdk from "aws-cdk-lib"; import * as codebuild from "aws-cdk-lib/aws-codebuild"; -import * as codecommit from "aws-cdk-lib/aws-codecommit"; import * as codedeploy from "aws-cdk-lib/aws-codedeploy"; import * as pipeline from "aws-cdk-lib/aws-codepipeline"; +import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; import * as pipelineactions from "aws-cdk-lib/aws-codepipeline-actions"; import * as ec2 from "aws-cdk-lib/aws-ec2"; import * as ecr from "aws-cdk-lib/aws-ecr"; @@ -16,13 +16,18 @@ import * as path from "path"; import * as fs from 'fs'; import { Asset } from 'aws-cdk-lib/aws-s3-assets'; import { IgnoreMode } from 'aws-cdk-lib'; -import { Code } from 'aws-cdk-lib/aws-codecommit'; export class CodepipelineBuildDeployStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); - // Modify gitignore file to remove unneeded files from the codecommit copy + // Retrieve the GitHub username/organization from the context + const githubUsername = this.node.tryGetContext('githubUsername'); + + // Retrieve the GitHub PAT from the context + const githubPAT = this.node.tryGetContext('githubPAT') + + // modify gitignore file to remove unneeded files from the codecommit copy let gitignore = fs.readFileSync('.gitignore').toString().split(/\r?\n/); gitignore.push('.git/'); gitignore = gitignore.filter(g => g != 'node_modules/'); @@ -33,11 +38,26 @@ export class CodepipelineBuildDeployStack extends cdk.Stack { ignoreMode: IgnoreMode.GIT, exclude: gitignore, }); - - const codeRepo = new codecommit.Repository(this, "repo", { - repositoryName: "simple-code-repo", - // Copies files from codepipeline-build-deploy directory to the repo as the initial commit - code: Code.fromAsset(codeAsset, 'main'), + + // Create a new Secrets Manager secret to store the GitHub PAT + const githubPATSecret = new secretsmanager.Secret(this, 'GithubPATSecret', { + secretName: 'github-pat-secret', + generateSecretString: { + secretStringTemplate: JSON.stringify({ token: githubPAT }), + excludePunctuation: true, + includeSpace: false, + generateStringKey: 'token', + }, + }); + + const codeRepo = new pipeline.CfnRepository(this, "repo", { + repositoryName: "${githubUsername}/simple-code-repo", // Replace with your GitHub repository name + repositoryType: "GitHub", + branchName: "main", // Replace with your default branch name if different + tokenInfo: { + secretArn: githubPATSecret.secretArn, + secretManagerTokenKey: 'token', + }, }); // Creates an Elastic Container Registry (ECR) image repository @@ -81,19 +101,6 @@ export class CodepipelineBuildDeployStack extends cdk.Stack { buildImage: codebuild.LinuxBuildImage.AMAZON_LINUX_2_4, } }); - - // CodeBuild project that runs the ecr image scan - const scanImage = new codebuild.Project(this, "ScanImage", { - buildSpec: codebuild.BuildSpec.fromSourceFilename("test-buildspec.yaml"), - source: codebuild.Source.codeCommit({ repository: codeRepo }), - environment: { - buildImage: codebuild.LinuxBuildImage.AMAZON_LINUX_2_4, - environmentVariables: { - IMAGE_REPO_NAME: { value: imageRepo.repositoryName }, - IMAGE_TAG: { value: "latest" } - }, - }, - }); // Grants CodeBuild project access to pull/push images from/to ECR repo imageRepo.grantPullPush(buildImage); @@ -232,7 +239,6 @@ export class CodepipelineBuildDeployStack extends cdk.Stack { // Creates new pipeline artifacts const sourceArtifact = new pipeline.Artifact("SourceArtifact"); const buildArtifact = new pipeline.Artifact("BuildArtifact"); - const scanArtifact = new pipeline.Artifact("ScanArtifact"); // Creates the source stage for CodePipeline const sourceStage = { @@ -253,7 +259,7 @@ export class CodepipelineBuildDeployStack extends cdk.Stack { actions: [ new pipelineactions.CodeBuildAction({ actionName: "JestCDK", - input: sourceArtifact, + input: new pipeline.Artifact("SourceArtifact"), project: buildTest, }), ], @@ -265,25 +271,12 @@ export class CodepipelineBuildDeployStack extends cdk.Stack { actions: [ new pipelineactions.CodeBuildAction({ actionName: "DockerBuildPush", - input: sourceArtifact, + input: new pipeline.Artifact("SourceArtifact"), project: buildImage, outputs: [buildArtifact], }), ], }; - - // Creates the build stage that runs the ecr container image scan - const securityTestStage = { - stageName: "SecurityScan", - actions: [ - new pipelineactions.CodeBuildAction({ - actionName: "ScanImage", - input: sourceArtifact, - project: scanImage, - outputs: [scanArtifact], - }), - ], - }; // Creates a new CodeDeploy Deployment Group const deploymentGroup = new codedeploy.EcsDeploymentGroup( @@ -316,7 +309,7 @@ export class CodepipelineBuildDeployStack extends cdk.Stack { // Creates an AWS CodePipeline with source, build, and deploy stages new pipeline.Pipeline(this, "BuildDeployPipeline", { pipelineName: "ImageBuildDeployPipeline", - stages: [sourceStage, testStage, buildStage, securityTestStage, deployStage], + stages: [sourceStage, testStage, buildStage, deployStage], }); // Outputs the ALB public endpoint diff --git a/typescript/codepipeline-build-deploy/package.json b/typescript/codepipeline-build-deploy/package.json index eb4f2b4c31..740335ebe1 100644 --- a/typescript/codepipeline-build-deploy/package.json +++ b/typescript/codepipeline-build-deploy/package.json @@ -20,7 +20,7 @@ "typescript": "~5.1.6" }, "dependencies": { - "aws-cdk-lib": "2.72.1", + "aws-cdk-lib": "2.80.0", "constructs": "^10.0.0", "jest-junit": "^16.0.0", "source-map-support": "^0.5.21" diff --git a/typescript/codepipeline-build-deploy/test-buildspec.yaml b/typescript/codepipeline-build-deploy/test-buildspec.yaml deleted file mode 100644 index c4dbeaddde..0000000000 --- a/typescript/codepipeline-build-deploy/test-buildspec.yaml +++ /dev/null @@ -1,18 +0,0 @@ -version: 0.2 - -phases: - build: - commands: - - export outOfDate=0 - #- aws ecr start-image-scan --repository-name $IMAGE_REPO_NAME --image-id imageTag=$IMAGE_TAG || echo "Image already scanned today" - - aws ecr describe-image-scan-findings --repository-name $IMAGE_REPO_NAME --image-id imageTag=$IMAGE_TAG > scan.json - - while [ $(cat scan.json | jq -r .imageScanStatus.status) != 'ACTIVE' ]; do sleep 15; aws ecr describe-image-scan-findings --repository-name $IMAGE_REPO_NAME --image-id imageTag=$IMAGE_TAG > scan.json; done - - cat scan.json - - export highSeverity=$(cat scan.json | jq -r .imageScanFindings.findingSeverityCounts.HIGH) - - export findings=$(cat scan.json | jq -r .imageScanFindings.findings) - post_build: - commands: - - echo "Passing if there were no findings" - - if [ $findings == "[]" ]; then exit 0; fi - - echo "Failing build if high severity vulnerabilities found by ECR Image Scan" - - if [ $findings != "[]" ] && [ -n $highSeverity ] && [ $highSeverity > 0 ]; then exit 1; fi ## Uncomment this to break build given high severity vulnerabilities \ No newline at end of file From 86aae5f3b63f8f6a76688b5a48cc345355b75269 Mon Sep 17 00:00:00 2001 From: Jayson Sizer McIntosh Date: Mon, 12 Aug 2024 17:25:25 -0400 Subject: [PATCH 12/13] initial attempt at Lambda to create GitHub repo (need to remove references to codecommit source) --- .../lib/codepipeline-build-deploy-stack.ts | 36 +++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/typescript/codepipeline-build-deploy/lib/codepipeline-build-deploy-stack.ts b/typescript/codepipeline-build-deploy/lib/codepipeline-build-deploy-stack.ts index 025b3eab73..80f25b66be 100644 --- a/typescript/codepipeline-build-deploy/lib/codepipeline-build-deploy-stack.ts +++ b/typescript/codepipeline-build-deploy/lib/codepipeline-build-deploy-stack.ts @@ -2,6 +2,7 @@ import * as cdk from "aws-cdk-lib"; import * as codebuild from "aws-cdk-lib/aws-codebuild"; import * as codedeploy from "aws-cdk-lib/aws-codedeploy"; import * as pipeline from "aws-cdk-lib/aws-codepipeline"; +import * as codepipeline_actions from 'aws-cdk-lib/aws-codepipeline-actions'; import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; import * as pipelineactions from "aws-cdk-lib/aws-codepipeline-actions"; import * as ec2 from "aws-cdk-lib/aws-ec2"; @@ -16,9 +17,14 @@ import * as path from "path"; import * as fs from 'fs'; import { Asset } from 'aws-cdk-lib/aws-s3-assets'; import { IgnoreMode } from 'aws-cdk-lib'; +import * as cr from 'aws-cdk-lib/custom-resources'; + +interface CodepipelineBuildDeployStackProps extends cdk.StackProps { + context: any; +} export class CodepipelineBuildDeployStack extends cdk.Stack { - constructor(scope: Construct, id: string, props?: cdk.StackProps) { + constructor(scope: Construct, id: string, props?: CodepipelineBuildDeployStackProps) { super(scope, id, props); // Retrieve the GitHub username/organization from the context @@ -49,9 +55,23 @@ export class CodepipelineBuildDeployStack extends cdk.Stack { generateStringKey: 'token', }, }); + + const createGitHubRepoLambda = new lambda.Function(this, 'CreateGitHubRepoLambda', { + runtime: lambda.Runtime.NODEJS_16_X, + code: lambda.Code.fromAsset("./lambda"), + handler: "push-to-github.handler", + environment: { + GITHUB_PAT_SECRET_NAME: githubPATSecret.secretName, + GITHUB_USERNAME: githubUsername + }, + }); + githubPATSecret.grantRead(createGitHubRepoLambda); + codeAsset.bucket.grantRead(createGitHubRepoLambda); + + /* const codeRepo = new pipeline.CfnRepository(this, "repo", { - repositoryName: "${githubUsername}/simple-code-repo", // Replace with your GitHub repository name + repositoryName: `${githubUsername}/simple-code-repo`, // Replace with your GitHub repository name repositoryType: "GitHub", branchName: "main", // Replace with your default branch name if different tokenInfo: { @@ -59,6 +79,7 @@ export class CodepipelineBuildDeployStack extends cdk.Stack { secretManagerTokenKey: 'token', }, }); + */ // Creates an Elastic Container Registry (ECR) image repository const imageRepo = new ecr.Repository(this, "imageRepo"); @@ -244,12 +265,23 @@ export class CodepipelineBuildDeployStack extends cdk.Stack { const sourceStage = { stageName: "Source", actions: [ + new codepipeline_actions.GitHubSourceAction({ + actionName: 'GitHub_Source', + owner: githubUsername, + repo: 'simple-code-repo', // The name of the new repository to be created + oauthToken: githubPAT, + output: new pipeline.Artifact(), + branch: 'main', // The default branch name for the new repository + runOrder: 1, // The order in which this action should run + }) + /* new pipelineactions.CodeCommitSourceAction({ actionName: "AppCodeCommit", branch: "main", output: sourceArtifact, repository: codeRepo, }), + */ ], }; From 53d44d1f763835f01ff209d5713633555ce6c830 Mon Sep 17 00:00:00 2001 From: JSM Date: Thu, 1 May 2025 16:37:00 -0400 Subject: [PATCH 13/13] updating the codepipeline-build-deploy project. Now works with CodeCommit and synth doesnt fail. Have not yet tested with github --- .../lib/codepipeline-build-deploy-stack.ts | 152 +++++++++++------- 1 file changed, 90 insertions(+), 62 deletions(-) diff --git a/typescript/codepipeline-build-deploy/lib/codepipeline-build-deploy-stack.ts b/typescript/codepipeline-build-deploy/lib/codepipeline-build-deploy-stack.ts index 80f25b66be..0491f8156e 100644 --- a/typescript/codepipeline-build-deploy/lib/codepipeline-build-deploy-stack.ts +++ b/typescript/codepipeline-build-deploy/lib/codepipeline-build-deploy-stack.ts @@ -1,5 +1,6 @@ import * as cdk from "aws-cdk-lib"; import * as codebuild from "aws-cdk-lib/aws-codebuild"; +import * as codecommit from "aws-cdk-lib/aws-codecommit"; import * as codedeploy from "aws-cdk-lib/aws-codedeploy"; import * as pipeline from "aws-cdk-lib/aws-codepipeline"; import * as codepipeline_actions from 'aws-cdk-lib/aws-codepipeline-actions'; @@ -27,12 +28,12 @@ export class CodepipelineBuildDeployStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: CodepipelineBuildDeployStackProps) { super(scope, id, props); - // Retrieve the GitHub username/organization from the context - const githubUsername = this.node.tryGetContext('githubUsername'); - - // Retrieve the GitHub PAT from the context - const githubPAT = this.node.tryGetContext('githubPAT') - + // Determine which repository type to use (GitHub or CodeCommit) + const useGitHub = this.node.tryGetContext('useGitHub') === 'true'; + + // Repository name to use + const repositoryName = this.node.tryGetContext('repositoryName') || 'simple-code-repo'; + // modify gitignore file to remove unneeded files from the codecommit copy let gitignore = fs.readFileSync('.gitignore').toString().split(/\r?\n/); gitignore.push('.git/'); @@ -44,43 +45,62 @@ export class CodepipelineBuildDeployStack extends cdk.Stack { ignoreMode: IgnoreMode.GIT, exclude: gitignore, }); - - // Create a new Secrets Manager secret to store the GitHub PAT - const githubPATSecret = new secretsmanager.Secret(this, 'GithubPATSecret', { - secretName: 'github-pat-secret', - generateSecretString: { - secretStringTemplate: JSON.stringify({ token: githubPAT }), - excludePunctuation: true, - includeSpace: false, - generateStringKey: 'token', - }, - }); - - const createGitHubRepoLambda = new lambda.Function(this, 'CreateGitHubRepoLambda', { - runtime: lambda.Runtime.NODEJS_16_X, - code: lambda.Code.fromAsset("./lambda"), - handler: "push-to-github.handler", - environment: { - GITHUB_PAT_SECRET_NAME: githubPATSecret.secretName, - GITHUB_USERNAME: githubUsername - }, - }); - - githubPATSecret.grantRead(createGitHubRepoLambda); - codeAsset.bucket.grantRead(createGitHubRepoLambda); - /* - const codeRepo = new pipeline.CfnRepository(this, "repo", { - repositoryName: `${githubUsername}/simple-code-repo`, // Replace with your GitHub repository name - repositoryType: "GitHub", - branchName: "main", // Replace with your default branch name if different - tokenInfo: { - secretArn: githubPATSecret.secretArn, - secretManagerTokenKey: 'token', - }, - }); - */ + // Create repository based on selected type + let codeRepo: codecommit.IRepository; + let githubOwner = ''; + let githubToken = ''; + if (useGitHub) { + // Retrieve the GitHub username/organization from the context (only needed for GitHub) + githubOwner = this.node.tryGetContext('githubUsername'); + + // Retrieve the GitHub PAT from the context (only needed for GitHub) + githubToken = this.node.tryGetContext('githubPAT'); + + if (!githubOwner || !githubToken) { + throw new Error('When using GitHub, both githubUsername and githubPAT context variables must be provided'); + } + + // Create a new Secrets Manager secret to store the GitHub PAT + const githubPATSecret = new secretsmanager.Secret(this, 'GithubPATSecret', { + secretName: 'github-pat-secret', + generateSecretString: { + secretStringTemplate: JSON.stringify({ token: githubToken }), + excludePunctuation: true, + includeSpace: false, + generateStringKey: 'token', + }, + }); + + const createGitHubRepoLambda = new lambda.Function(this, 'CreateGitHubRepoLambda', { + runtime: lambda.Runtime.NODEJS_16_X, + code: lambda.Code.fromAsset("./lambda"), + handler: "push-to-github.handler", + environment: { + GITHUB_PAT_SECRET_NAME: githubPATSecret.secretName, + GITHUB_USERNAME: githubOwner + }, + }); + + githubPATSecret.grantRead(createGitHubRepoLambda); + codeAsset.bucket.grantRead(createGitHubRepoLambda); + + // For GitHub, we still need a CodeCommit repo for the build process + codeRepo = new codecommit.Repository(this, 'CodeCommitRepo', { + repositoryName: repositoryName + '-mirror', + description: 'Mirror repository for the CodePipeline Build Deploy example', + code: codecommit.Code.fromAsset(codeAsset), + }); + } else { + // For CodeCommit, create a new repository + codeRepo = new codecommit.Repository(this, 'CodeCommitRepo', { + repositoryName: repositoryName, + description: 'Repository for the CodePipeline Build Deploy example', + code: codecommit.Code.fromAsset(codeAsset), + }); + } + // Creates an Elastic Container Registry (ECR) image repository const imageRepo = new ecr.Repository(this, "imageRepo"); @@ -98,7 +118,12 @@ export class CodepipelineBuildDeployStack extends cdk.Stack { // CodeBuild project that builds the Docker image const buildImage = new codebuild.Project(this, "BuildImage", { buildSpec: codebuild.BuildSpec.fromSourceFilename("app/buildspec.yaml"), - source: codebuild.Source.codeCommit({ repository: codeRepo }), + source: useGitHub + ? codebuild.Source.gitHub({ + owner: githubOwner, + repo: repositoryName, + }) + : codebuild.Source.codeCommit({ repository: codeRepo }), environment: { privileged: true, environmentVariables: { @@ -117,7 +142,12 @@ export class CodepipelineBuildDeployStack extends cdk.Stack { // CodeBuild project that builds the Docker image const buildTest = new codebuild.Project(this, "BuildTest", { buildSpec: codebuild.BuildSpec.fromSourceFilename("buildspec.yaml"), - source: codebuild.Source.codeCommit({ repository: codeRepo }), + source: useGitHub + ? codebuild.Source.gitHub({ + owner: githubOwner, + repo: repositoryName, + }) + : codebuild.Source.codeCommit({ repository: codeRepo }), environment: { buildImage: codebuild.LinuxBuildImage.AMAZON_LINUX_2_4, } @@ -265,23 +295,21 @@ export class CodepipelineBuildDeployStack extends cdk.Stack { const sourceStage = { stageName: "Source", actions: [ - new codepipeline_actions.GitHubSourceAction({ - actionName: 'GitHub_Source', - owner: githubUsername, - repo: 'simple-code-repo', // The name of the new repository to be created - oauthToken: githubPAT, - output: new pipeline.Artifact(), - branch: 'main', // The default branch name for the new repository - runOrder: 1, // The order in which this action should run - }) - /* - new pipelineactions.CodeCommitSourceAction({ - actionName: "AppCodeCommit", - branch: "main", - output: sourceArtifact, - repository: codeRepo, - }), - */ + useGitHub + ? new pipelineactions.GitHubSourceAction({ + actionName: 'GitHub_Source', + owner: githubOwner, + repo: repositoryName, + oauthToken: cdk.SecretValue.unsafePlainText(githubToken), + output: sourceArtifact, + branch: 'main', + }) + : new pipelineactions.CodeCommitSourceAction({ + actionName: "AppCodeCommit", + branch: "main", + output: sourceArtifact, + repository: codeRepo, + }), ], }; @@ -291,7 +319,7 @@ export class CodepipelineBuildDeployStack extends cdk.Stack { actions: [ new pipelineactions.CodeBuildAction({ actionName: "JestCDK", - input: new pipeline.Artifact("SourceArtifact"), + input: sourceArtifact, project: buildTest, }), ], @@ -303,7 +331,7 @@ export class CodepipelineBuildDeployStack extends cdk.Stack { actions: [ new pipelineactions.CodeBuildAction({ actionName: "DockerBuildPush", - input: new pipeline.Artifact("SourceArtifact"), + input: sourceArtifact, project: buildImage, outputs: [buildArtifact], }),