From 18ffbaa787dea7ab1ca04133ae81d3bdff36b4b8 Mon Sep 17 00:00:00 2001 From: Brian Herrera Date: Wed, 1 Nov 2023 22:36:52 -0700 Subject: [PATCH 01/10] Add cdk-nag and update existing modules in requirements.txt Signed-off-by: Brian Herrera --- cdk/requirements.txt | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/cdk/requirements.txt b/cdk/requirements.txt index 7c3d4e2..0b0fb55 100644 --- a/cdk/requirements.txt +++ b/cdk/requirements.txt @@ -1,18 +1,21 @@ -attrs==22.1.0 -aws-cdk-lib==2.95.1 -cattrs==22.1.0 -constructs==10.1.84 -exceptiongroup==1.0.0rc8 -iniconfig==1.1.1 -jsii==1.88.0 -packaging==21.3 -pluggy==1.0.0 +attrs==23.1.0 +aws-cdk-lib==2.103.1 +aws-cdk.asset-awscli-v1==2.2.201 +aws-cdk.asset-kubectl-v20==2.1.2 +aws-cdk.asset-node-proxy-agent-v6==2.0.1 +cattrs==23.1.2 +cdk-nag==2.27.179 +constructs==10.3.0 +exceptiongroup==1.1.3 +importlib-resources==6.1.0 +iniconfig==2.0.0 +jsii==1.91.0 +packaging==23.2 +pluggy==1.3.0 publication==0.0.3 -py==1.11.0 -pyparsing==3.0.9 -pytest==7.1.2 +pytest==7.4.3 python-dateutil==2.8.2 six==1.16.0 tomli==2.0.1 typeguard==2.13.3 -typing_extensions==4.3.0 +typing_extensions==4.8.0 From c5a80959a6eca21699ab47d899d70730fca3c20f Mon Sep 17 00:00:00 2001 From: Brian Herrera Date: Thu, 2 Nov 2023 12:43:20 -0700 Subject: [PATCH 02/10] Add cdk-nag rule set to pipeline and server stacks. Signed-off-by: Brian Herrera --- cdk/app.py | 2 ++ cdk/jenkins_server/app.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/cdk/app.py b/cdk/app.py index c272378..0db28ef 100644 --- a/cdk/app.py +++ b/cdk/app.py @@ -10,6 +10,7 @@ import sys import aws_cdk as cdk +from cdk_nag import AwsSolutionsChecks from pipeline import JenkinsPipeline, MissingContextError # Account and region set by the default AWS profile or one specified with --profile @@ -17,6 +18,7 @@ REGION = os.environ.get('CDK_DEFAULT_REGION') app = cdk.App() +cdk.Aspects.of(app).add(AwsSolutionsChecks(verbose=True)) try: JenkinsPipeline(app, 'JenkinsPipelineStack', env=cdk.Environment(account=ACCOUNT, region=REGION)) diff --git a/cdk/jenkins_server/app.py b/cdk/jenkins_server/app.py index 470db6a..c9b1e2e 100644 --- a/cdk/jenkins_server/app.py +++ b/cdk/jenkins_server/app.py @@ -10,6 +10,7 @@ import sys import aws_cdk as cdk +from cdk_nag import AwsSolutionsChecks from jenkins_server import JenkinsServerStack # Account and region set by the default AWS profile or one specified with --profile @@ -17,6 +18,7 @@ REGION = os.environ.get('CDK_DEFAULT_REGION') app = cdk.App() +cdk.Aspects.of(app).add(AwsSolutionsChecks(verbose=True)) try: JenkinsServerStack(app, 'JenkinsServerStack', From 926a6b09a83f83a2ebda44f188730d46e28a4003 Mon Sep 17 00:00:00 2001 From: Brian Herrera Date: Thu, 2 Nov 2023 12:44:06 -0700 Subject: [PATCH 03/10] Add nag suppressions for cdk pipeline stack Signed-off-by: Brian Herrera --- cdk/pipeline.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/cdk/pipeline.py b/cdk/pipeline.py index 0fcab0d..6281b60 100644 --- a/cdk/pipeline.py +++ b/cdk/pipeline.py @@ -11,6 +11,7 @@ import aws_cdk.pipelines as pipelines from aws_cdk import Environment, Stack, Stage +from cdk_nag import NagSuppressions from constructs import Construct from jenkins_server.jenkins_server import JenkinsServerStack @@ -50,6 +51,7 @@ def __init__(self, scope: Construct, id: str, **kwargs) -> None: self.source = pipelines.CodePipelineSource.connection(self.repo, self.branch, connection_arn=self.codestar_connection) self._create_pipeline() + self._add_nag_suppressions() def _get_required_context(self, context_name): """Get context value and raise an exception if it does not exist.""" @@ -119,3 +121,30 @@ def _create_pipeline(self): pipelines.ManualApprovalStep('ReleaseToProd') ] ) + + def _add_nag_suppressions(self): + '''Add cdk-nag suppressions for the pipeline stack. + + The CDK Pipeline library generates internal constructs that are not defined here but may be generate rule violations + for cdk-nag. Adding suppressions at stack level to avoid errors. + + ''' + suppression_list = [ + { + 'id': 'AwsSolutions-S1', + 'reason': 'CDK Pipeline generates its own S3 buckets for pipeline assets.' + }, + { + 'id': 'AwsSolutions-IAM5', + 'reason': 'CDK Pipeline generates its own IAM permissions.' + }, + { + 'id': 'AwsSolutions-CB3', + 'reason': 'CDK Pipeline requires privileged mode for its CodeBuild project to build docker images.' + }, + { + 'id': 'AwsSolutions-CB4', + 'reason': 'CDK Pipeline generates its own CodeBuild project for pipeline assets.' + } + ] + NagSuppressions.add_stack_suppressions(self, suppression_list) From db9624eea22642232157c46c029c2be280a768aa Mon Sep 17 00:00:00 2001 From: Brian Herrera Date: Thu, 2 Nov 2023 12:45:07 -0700 Subject: [PATCH 04/10] Add fixes to cdk-nag errors Signed-off-by: Brian Herrera --- cdk/jenkins_server/jenkins_server.py | 66 ++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 8 deletions(-) diff --git a/cdk/jenkins_server/jenkins_server.py b/cdk/jenkins_server/jenkins_server.py index 5a17138..fbf61c7 100644 --- a/cdk/jenkins_server/jenkins_server.py +++ b/cdk/jenkins_server/jenkins_server.py @@ -13,12 +13,14 @@ import aws_cdk.aws_efs as efs import aws_cdk.aws_elasticloadbalancingv2 as elb import aws_cdk.aws_iam as iam +import aws_cdk.aws_kms as kms import aws_cdk.aws_logs as logs import aws_cdk.aws_sns as sns import aws_cdk.aws_s3 as s3 from os import path from aws_cdk import Stack +from cdk_nag import NagSuppressions from constructs import Construct @@ -43,11 +45,13 @@ def __init__(self, scope: Construct, id: str, **kwargs) -> None: self.cert_arn = self._load_cert_arn() self.vpc = self._create_vpc() - self.build_topic = sns.Topic(self, 'BuildTopic') + self.build_topic = sns.Topic(self, 'BuildTopic', master_key=kms.Key(self, "SNSKey", enable_key_rotation=True)) self.log_group = logs.LogGroup(self, 'LogGroup') self.file_system, self.access_point = self._create_efs() self.fargate_service = self._create_ecs() self._create_alb() + self._add_nag_suppressions() + def _load_stack_config(self, config_file): """Load stack config. The config file is expected to be in the same directory.""" @@ -69,12 +73,18 @@ def _load_cert_arn(self): def _create_vpc(self): """Create a new VPC or use an existing one if a VPC ID is provided.""" - if self.stack_tags['vpc-id'] == 'None': # Context values will be converted to string and cannot be empty during synth - return ec2.Vpc(self, 'VPC', - cidr=self.stack_config['vpc']['cidr'], - nat_gateways=self.stack_config['vpc']['nat_gateways'] - ) - return ec2.Vpc.from_lookup(self, 'VPC', vpc_id=self.stack_tags['vpc-id']) + if self.node.try_get_context('vpc-id'): + return ec2.Vpc.from_lookup(self, 'VPC', vpc_id=self.node.try_get_context('vpc-id')) + + if self.stack_tags.get('vpc-id', 'None') != 'None': # Tag values from pipeline will be converted to string and cannot be empty during synth + return ec2.Vpc.from_lookup(self, 'VPC', vpc_id=self.stack_tags.get('vpc-id')) + + vpc = ec2.Vpc(self, 'VPC', + cidr=self.stack_config['vpc']['cidr'], + nat_gateways=self.stack_config['vpc']['nat_gateways'] + ) + vpc.add_flow_log("JenkinsVPCFlowLog") + return vpc def _create_efs(self): """Create a file system with an access point for the jenkins home directory.""" @@ -216,7 +226,8 @@ def _create_alb(self): alb.log_access_logs( s3.Bucket(self, 'AccessLogsBucket', block_public_access=s3.BlockPublicAccess.BLOCK_ALL, - encryption=s3.BucketEncryption.S3_MANAGED + encryption=s3.BucketEncryption.S3_MANAGED, + enforce_ssl=True ) ) @@ -225,3 +236,42 @@ def _create_alb(self): if alb_config['public'] is True: alb.connections.allow_from_any_ipv4(ec2.Port.tcp(443)) + + def _add_nag_suppressions(self): + '''Add cdk-nag suppressions for the Jenkins server stack.''' + NagSuppressions.add_resource_suppressions_by_path(self, + '/JenkinsServerStack/TaskDef/TaskRole/DefaultPolicy/Resource', + [ + { + 'id': 'AwsSolutions-IAM5', + 'reason': 'Wildcard permissions required to allow pulling user added Jenkins configs from parameter store.' + } + ] + ) + NagSuppressions.add_resource_suppressions_by_path(self, + '/JenkinsServerStack/TaskDef/ExecutionRole/DefaultPolicy/Resource', + [ + { + 'id': 'AwsSolutions-IAM5', + 'reason': 'Execution role automatically created by ECS to pull images from ECR.' + } + ] + ) + NagSuppressions.add_resource_suppressions_by_path(self, + '/JenkinsServerStack/ALB/SecurityGroup/Resource', + [ + { + 'id': 'AwsSolutions-EC23', + 'reason': 'Allows enabling public access to server. Additional IP based security will be setup through WAF.' + } + ] + ) + NagSuppressions.add_resource_suppressions_by_path(self, + '/JenkinsServerStack/AccessLogsBucket/Resource', + [ + { + 'id': 'AwsSolutions-S1', + 'reason': 'This is the access logs bucket resource.' + } + ] + ) From 3d61c3e5566cd0bbb47d9e6ce8999d2392066dd5 Mon Sep 17 00:00:00 2001 From: Brian Herrera Date: Thu, 2 Nov 2023 14:42:15 -0700 Subject: [PATCH 05/10] Move suppression to stack level to avoid errors Signed-off-by: Brian Herrera --- cdk/jenkins_server/jenkins_server.py | 51 ++++++++-------------------- 1 file changed, 15 insertions(+), 36 deletions(-) diff --git a/cdk/jenkins_server/jenkins_server.py b/cdk/jenkins_server/jenkins_server.py index fbf61c7..9acc186 100644 --- a/cdk/jenkins_server/jenkins_server.py +++ b/cdk/jenkins_server/jenkins_server.py @@ -239,39 +239,18 @@ def _create_alb(self): def _add_nag_suppressions(self): '''Add cdk-nag suppressions for the Jenkins server stack.''' - NagSuppressions.add_resource_suppressions_by_path(self, - '/JenkinsServerStack/TaskDef/TaskRole/DefaultPolicy/Resource', - [ - { - 'id': 'AwsSolutions-IAM5', - 'reason': 'Wildcard permissions required to allow pulling user added Jenkins configs from parameter store.' - } - ] - ) - NagSuppressions.add_resource_suppressions_by_path(self, - '/JenkinsServerStack/TaskDef/ExecutionRole/DefaultPolicy/Resource', - [ - { - 'id': 'AwsSolutions-IAM5', - 'reason': 'Execution role automatically created by ECS to pull images from ECR.' - } - ] - ) - NagSuppressions.add_resource_suppressions_by_path(self, - '/JenkinsServerStack/ALB/SecurityGroup/Resource', - [ - { - 'id': 'AwsSolutions-EC23', - 'reason': 'Allows enabling public access to server. Additional IP based security will be setup through WAF.' - } - ] - ) - NagSuppressions.add_resource_suppressions_by_path(self, - '/JenkinsServerStack/AccessLogsBucket/Resource', - [ - { - 'id': 'AwsSolutions-S1', - 'reason': 'This is the access logs bucket resource.' - } - ] - ) + suppression_list = [ + { + 'id': 'AwsSolutions-IAM5', + 'reason': 'Wildcard permissions required to allow pulling user added Jenkins configs from parameter store.' + }, + { + 'id': 'AwsSolutions-EC23', + 'reason': 'Allows enabling public access to server. Additional IP based security will be setup through WAF.' + }, + { + 'id': 'AwsSolutions-S1', + 'reason': 'Access logs bucket does not need access logging enabled.' + } + ] + NagSuppressions.add_stack_suppressions(self, suppression_list) From 5ba30e27b9f56bd94fd6f587ab3778ecc6528b03 Mon Sep 17 00:00:00 2001 From: Brian Herrera Date: Thu, 2 Nov 2023 14:42:33 -0700 Subject: [PATCH 06/10] Add cdk-nag to test workflow Signed-off-by: Brian Herrera --- .github/workflows/tests.yaml | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index dcc904c..2b4baa6 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -13,10 +13,37 @@ jobs: - name: Set up Python 3 uses: actions/setup-python@v4 with: - python-version: '3.10' + python-version: '3.11' - name: Install dependencies run: | pip install -r requirements.txt - name: Run Pytest run: | python -m pytest -v + cdk_nag: + runs-on: ubuntu-latest + defaults: + run: + working-directory: cdk + steps: + - uses: actions/checkout@v3 + - name: Set up Python 3 + uses: actions/setup-python@v4 + with: + python-version: '3.11' + - name: Install dependencies + run: | + pip install -r requirements.txt + - name: Setup test environment + run: | + export CDK_DEFAULT_ACCOUNT=012345678912 + export CDK_DEFAULT_REGION=us-west-2 + cp tests/cdk.context.json . + - name: Synth pipeline and jenkins server stacks + run: | + cdk synth \ + --context codestar-connection=arn:aws:codestar-connections/connection_id \ + --context repo=org/repo \ + --context branch=branch \ + --context cert-arn=arn:aws:acm:certificate/certificate_id + cdk synth --app "python jenkins_server/app.py" --context cert-arn=arn:aws:acm:certificate/certificate_id --no-lookups From cc971274889599949b64db806676a3950df32c23 Mon Sep 17 00:00:00 2001 From: Brian Herrera Date: Thu, 2 Nov 2023 14:46:47 -0700 Subject: [PATCH 07/10] Add test cdk context file Signed-off-by: Brian Herrera --- cdk/tests/cdk.context.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 cdk/tests/cdk.context.json diff --git a/cdk/tests/cdk.context.json b/cdk/tests/cdk.context.json new file mode 100644 index 0000000..8859618 --- /dev/null +++ b/cdk/tests/cdk.context.json @@ -0,0 +1,8 @@ +{ + "availability-zones:account=012345678912:region=us-west-2": [ + "us-west-2a", + "us-west-2b", + "us-west-2c", + "us-west-2d" + ] + } From bc78dcfe0a0fa7171b424be7cf2bb227b46b4ef7 Mon Sep 17 00:00:00 2001 From: Brian Herrera Date: Thu, 2 Nov 2023 15:17:30 -0700 Subject: [PATCH 08/10] Fix error with vpc tests Signed-off-by: Brian Herrera --- cdk/jenkins_server/jenkins_server.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/cdk/jenkins_server/jenkins_server.py b/cdk/jenkins_server/jenkins_server.py index 9acc186..48f3488 100644 --- a/cdk/jenkins_server/jenkins_server.py +++ b/cdk/jenkins_server/jenkins_server.py @@ -73,18 +73,14 @@ def _load_cert_arn(self): def _create_vpc(self): """Create a new VPC or use an existing one if a VPC ID is provided.""" - if self.node.try_get_context('vpc-id'): - return ec2.Vpc.from_lookup(self, 'VPC', vpc_id=self.node.try_get_context('vpc-id')) - - if self.stack_tags.get('vpc-id', 'None') != 'None': # Tag values from pipeline will be converted to string and cannot be empty during synth - return ec2.Vpc.from_lookup(self, 'VPC', vpc_id=self.stack_tags.get('vpc-id')) - - vpc = ec2.Vpc(self, 'VPC', - cidr=self.stack_config['vpc']['cidr'], - nat_gateways=self.stack_config['vpc']['nat_gateways'] - ) - vpc.add_flow_log("JenkinsVPCFlowLog") - return vpc + if self.stack_tags.get('vpc-id', 'None') == 'None': # Tag values from pipeline will be converted to string and cannot be empty during synth + vpc = ec2.Vpc(self, 'VPC', + cidr=self.stack_config['vpc']['cidr'], + nat_gateways=self.stack_config['vpc']['nat_gateways'] + ) + vpc.add_flow_log("JenkinsVPCFlowLog") + return vpc + return ec2.Vpc.from_lookup(self, 'VPC', vpc_id=self.stack_tags.get('vpc-id')) def _create_efs(self): """Create a file system with an access point for the jenkins home directory.""" From 0149107c3cfdf097287b2f46d489837dc68e992d Mon Sep 17 00:00:00 2001 From: Brian Herrera Date: Thu, 2 Nov 2023 15:18:47 -0700 Subject: [PATCH 09/10] Update log group count. Logs added for VPC. Signed-off-by: Brian Herrera --- cdk/tests/test_jenkins_server_stack.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cdk/tests/test_jenkins_server_stack.py b/cdk/tests/test_jenkins_server_stack.py index c32d983..9f248da 100644 --- a/cdk/tests/test_jenkins_server_stack.py +++ b/cdk/tests/test_jenkins_server_stack.py @@ -47,7 +47,7 @@ def test_stack_context_values(template): def test_required_resources(template): template.resource_count_is("AWS::EC2::VPC", 1) template.resource_count_is("AWS::SNS::Topic", 1) - template.resource_count_is("AWS::Logs::LogGroup", 1) + template.resource_count_is("AWS::Logs::LogGroup", 2) template.resource_count_is("AWS::EFS::FileSystem", 1) template.resource_count_is("AWS::EFS::AccessPoint", 1) template.resource_count_is("AWS::ECS::Cluster", 1) From e35e695f1c0cf2dc55663392b963039ee18940db Mon Sep 17 00:00:00 2001 From: Brian Herrera Date: Thu, 2 Nov 2023 15:21:28 -0700 Subject: [PATCH 10/10] Install cdk Signed-off-by: Brian Herrera --- .github/workflows/tests.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 2b4baa6..691be18 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -33,6 +33,7 @@ jobs: python-version: '3.11' - name: Install dependencies run: | + npm install -g aws-cdk pip install -r requirements.txt - name: Setup test environment run: |