Skip to content

Commit cc2192e

Browse files
committed
add eps nag pack
1 parent c6b5b7e commit cc2192e

11 files changed

Lines changed: 517 additions & 25 deletions

File tree

packages/cdkConstructs/src/apps/createApp.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
} from "aws-cdk-lib"
77
import {AwsSolutionsChecks} from "cdk-nag"
88
import {getConfigFromEnvVar, getBooleanConfigFromEnvVar} from "../config"
9+
import {EpsNagPack} from "../nag/pack/epsNagPack"
910

1011
export interface StandardStackProps extends StackProps {
1112
/** Semantic version of the deployment (from `versionNumber`). */
@@ -71,8 +72,10 @@ export function createApp({
7172
}
7273

7374
const app = new App()
75+
app.node.setContext("isPullRequest", isPullRequest)
7476

7577
Aspects.of(app).add(new AwsSolutionsChecks({verbose: true}))
78+
Aspects.of(app).add(new EpsNagPack({verbose: true}))
7679

7780
Tags.of(app).add("TagVersion", "1")
7881
Tags.of(app).add("Programme", "EPS")

packages/cdkConstructs/src/constructs/PythonLambdaFunction.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
} from "aws-cdk-lib/aws-lambda"
1818
import {join} from "node:path"
1919
import {createSharedLambdaResources} from "./lambdaSharedResources"
20+
import {addSuppressions} from "../utils/helpers"
2021

2122
export interface PythonLambdaFunctionProps {
2223
/**
@@ -207,15 +208,11 @@ export class PythonLambdaFunction extends Construct {
207208

208209
// Suppress CFN guard rules for Lambda function
209210
const cfnLambda = lambdaFunction.node.defaultChild as CfnFunction
210-
cfnLambda.cfnOptions.metadata = {
211-
guard: {
212-
SuppressedRules: [
213-
"LAMBDA_DLQ_CHECK",
214-
"LAMBDA_INSIDE_VPC",
215-
"LAMBDA_CONCURRENCY_CHECK"
216-
]
217-
}
218-
}
211+
addSuppressions([cfnLambda], [
212+
"LAMBDA_DLQ_CHECK",
213+
"LAMBDA_INSIDE_VPC",
214+
"LAMBDA_CONCURRENCY_CHECK"
215+
])
219216

220217
// Create policy for external services to invoke this Lambda
221218
const executionManagedPolicy = new ManagedPolicy(this, "ExecuteLambdaManagedPolicy", {

packages/cdkConstructs/src/constructs/TypescriptLambdaFunction.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {NodejsFunction, NodejsFunctionProps} from "aws-cdk-lib/aws-lambda-nodejs
1515
import {Construct} from "constructs"
1616
import {join} from "node:path"
1717
import {createSharedLambdaResources} from "./lambdaSharedResources"
18+
import {addSuppressions} from "../utils/helpers"
1819

1920
export interface TypescriptLambdaFunctionProps {
2021
/**
@@ -231,15 +232,11 @@ export class TypescriptLambdaFunction extends Construct {
231232
})
232233

233234
const cfnLambda = lambdaFunction.node.defaultChild as CfnFunction
234-
cfnLambda.cfnOptions.metadata = {
235-
guard: {
236-
SuppressedRules: [
237-
"LAMBDA_DLQ_CHECK",
238-
"LAMBDA_INSIDE_VPC",
239-
"LAMBDA_CONCURRENCY_CHECK"
240-
]
241-
}
242-
}
235+
addSuppressions([cfnLambda], [
236+
"LAMBDA_DLQ_CHECK",
237+
"LAMBDA_INSIDE_VPC",
238+
"LAMBDA_CONCURRENCY_CHECK"
239+
])
243240

244241
const executionManagedPolicy = new ManagedPolicy(this, "ExecuteLambdaManagedPolicy", {
245242
description: `execute lambda ${functionName}`,

packages/cdkConstructs/src/constructs/lambdaSharedResources.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
} from "aws-cdk-lib/aws-iam"
1414
import {NagSuppressions} from "cdk-nag"
1515
import {LAMBDA_INSIGHTS_LAYER_ARNS} from "../config"
16+
import {addSuppressions} from "../utils/helpers"
1617

1718
export interface SharedLambdaResourceProps {
1819
readonly functionName: string
@@ -65,13 +66,7 @@ export const createSharedLambdaResources = (
6566
})
6667

6768
const cfnlogGroup = logGroup.node.defaultChild as CfnLogGroup
68-
cfnlogGroup.cfnOptions.metadata = {
69-
guard: {
70-
SuppressedRules: [
71-
"CW_LOGGROUP_RETENTION_PERIOD_CHECK"
72-
]
73-
}
74-
}
69+
addSuppressions([cfnlogGroup], ["CW_LOGGROUP_RETENTION_PERIOD_CHECK"])
7570

7671
new CfnSubscriptionFilter(scope, "LambdaLogsSplunkSubscriptionFilter", {
7772
destinationArn: splunkDeliveryStream.streamArn,

packages/cdkConstructs/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export * from "./constructs/PythonLambdaFunction.js"
44
export * from "./apps/createApp.js"
55
export * from "./config/index.js"
66
export * from "./utils/helpers.js"
7+
export * from "./nag/pack/epsNagPack.js"
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
import {NagMessageLevel, NagPack, NagPackProps} from "cdk-nag"
2+
import {IConstruct} from "constructs"
3+
import {CfnResource} from "aws-cdk-lib"
4+
import {ApiGatewayMutualTls} from "../rules"
5+
import {LambdaFunctionPublicAccessProhibited} from "cdk-nag/lib/rules/lambda"
6+
import {CloudWatchLogGroupEncrypted} from "cdk-nag/lib/rules/cloudwatch"
7+
import {ALBHttpDropInvalidHeaderEnabled, ELBLoggingEnabled, ELBTlsHttpsListenersOnly} from "cdk-nag/lib/rules/elb"
8+
import {APIGWAccessLogging, APIGWStructuredLogging} from "cdk-nag/lib/rules/apigw"
9+
import {
10+
IAMNoInlinePolicy,
11+
IAMPolicyNoStatementsWithAdminAccess,
12+
IAMPolicyNoStatementsWithFullAccess
13+
} from "cdk-nag/lib/rules/iam"
14+
import {S3BucketPublicReadProhibited, S3BucketPublicWriteProhibited, S3DefaultEncryptionKMS} from "cdk-nag/lib/rules/s3"
15+
import {SecretsManagerUsingKMSKey} from "cdk-nag/lib/rules/secretsmanager"
16+
import {SNSEncryptedKMS} from "cdk-nag/lib/rules/sns"
17+
import {VPCDefaultSecurityGroupClosed, VPCFlowLogsEnabled} from "cdk-nag/lib/rules/vpc"
18+
import {WAFv2LoggingEnabled} from "cdk-nag/lib/rules/waf"
19+
20+
// Nag pack implementing EPS specific rules
21+
// It implements API gateway must have mutual TLS enabled
22+
// and rules from https://github.com/cdklabs/cdk-nag/blob/main/RULES.md that are not in aws-solutions pack
23+
24+
export class EpsNagPack extends NagPack {
25+
constructor(props?: NagPackProps) {
26+
super(props)
27+
this.packName = "EpsNagPack"
28+
}
29+
30+
public visit(node: IConstruct): void {
31+
if (node instanceof CfnResource) {
32+
this.applyRule({
33+
ruleSuffixOverride: "EPS1",
34+
info: "API Gateway must does not use mutual TLS.",
35+
explanation: "All non pull request deployments must enforce mutual TLS on api gateways.",
36+
level: NagMessageLevel.ERROR,
37+
rule: ApiGatewayMutualTls,
38+
node: node
39+
})
40+
this.applyRule({
41+
ruleSuffixOverride: "EPS2",
42+
info: "The Lambda function permission grants public access.",
43+
explanation:
44+
"Public access allows anyone on the internet to perform unauthenticated actions on the function.",
45+
level: NagMessageLevel.ERROR,
46+
rule: LambdaFunctionPublicAccessProhibited,
47+
node: node
48+
})
49+
this.applyRule({
50+
ruleSuffixOverride: "EPS3",
51+
info: "The CloudWatch Log Group is not encrypted with an AWS KMS key.",
52+
explanation:
53+
"To help protect sensitive data at rest, ensure encryption is enabled for your Amazon CloudWatch Log Groups.",
54+
level: NagMessageLevel.ERROR,
55+
rule: CloudWatchLogGroupEncrypted,
56+
node: node
57+
})
58+
this.applyRule({
59+
ruleSuffixOverride: "EPS4",
60+
info: "The ALB does not have invalid HTTP header dropping enabled.",
61+
explanation:
62+
"Ensure that your Application Load Balancers (ALB) are configured to drop http headers.",
63+
level: NagMessageLevel.ERROR,
64+
rule: ALBHttpDropInvalidHeaderEnabled,
65+
node: node
66+
})
67+
this.applyRule({
68+
ruleSuffixOverride: "EPS5",
69+
info: "The WAFv2 web ACL does not have logging enabled.",
70+
explanation:
71+
// eslint-disable-next-line max-len
72+
"AWS WAF logging provides detailed information about the traffic that is analyzed by your web ACL. The logs record the time that AWS WAF received the request from your AWS resource, information about the request, and an action for the rule that each request matched.",
73+
level: NagMessageLevel.ERROR,
74+
rule: WAFv2LoggingEnabled,
75+
node: node
76+
})
77+
78+
this.applyRule({
79+
ruleSuffixOverride: "EPS6",
80+
info: "The API does not have access logging enabled.",
81+
explanation:
82+
"Enabling access logs helps operators view who accessed an API and how the caller accessed the API.",
83+
level: NagMessageLevel.ERROR,
84+
rule: APIGWAccessLogging,
85+
node: node
86+
})
87+
this.applyRule({
88+
ruleSuffixOverride: "EPS7",
89+
info: "The API Gateway logs are not configured for the JSON format.",
90+
explanation:
91+
"JSON Structured logging makes it easier to derive queries to answer questions about your application.",
92+
level: NagMessageLevel.ERROR,
93+
rule: APIGWStructuredLogging,
94+
node: node
95+
})
96+
this.applyRule({
97+
ruleSuffixOverride: "EPS8",
98+
info: "The ELB does not have access logs enabled.",
99+
explanation:
100+
"Access logs allow operators to analyze traffic patterns and identify and troubleshoot security issues.",
101+
level: NagMessageLevel.ERROR,
102+
rule: ELBLoggingEnabled,
103+
node: node
104+
})
105+
this.applyRule({
106+
ruleSuffixOverride: "EPS9",
107+
info: "The ELB listener is not configured for secure (HTTPs or SSL) protocols for client communication.",
108+
explanation:
109+
// eslint-disable-next-line max-len
110+
"The SSL protocols enable secure communication by encrypting the communication between the client and the load balancer.",
111+
level: NagMessageLevel.ERROR,
112+
rule: ELBTlsHttpsListenersOnly,
113+
node: node
114+
})
115+
this.applyRule({
116+
ruleSuffixOverride: "EPS10",
117+
info: "The IAM Group, User, or Role contains an inline policy.",
118+
explanation:
119+
// eslint-disable-next-line max-len
120+
"AWS recommends to use managed policies instead of inline policies. The managed policies allow reusability, versioning and rolling back, and delegating permissions management.",
121+
level: NagMessageLevel.ERROR,
122+
rule: IAMNoInlinePolicy,
123+
node: node
124+
})
125+
this.applyRule({
126+
ruleSuffixOverride: "EPS11",
127+
// eslint-disable-next-line max-len
128+
info: "The IAM policy grants admin access, meaning the policy allows a principal to perform all actions on all resources.",
129+
explanation:
130+
// eslint-disable-next-line max-len
131+
'AWS Identity and Access Management (IAM) can help you incorporate the principles of least privilege and separation of duties with access permissions and authorizations, restricting policies from containing "Effect": "Allow" with "Action": "*" over "Resource": "*". Allowing users to have more privileges than needed to complete a task may violate the principle of least privilege and separation of duties.',
132+
level: NagMessageLevel.ERROR,
133+
rule: IAMPolicyNoStatementsWithAdminAccess,
134+
node: node
135+
})
136+
this.applyRule({
137+
ruleSuffixOverride: "EPS12",
138+
// eslint-disable-next-line max-len
139+
info: "The IAM policy grants full access, meaning the policy allows a principal to perform all actions on individual resources.",
140+
explanation:
141+
// eslint-disable-next-line max-len
142+
"Ensure IAM Actions are restricted to only those actions that are needed. Allowing users to have more privileges than needed to complete a task may violate the principle of least privilege and separation of duties.",
143+
level: NagMessageLevel.ERROR,
144+
rule: IAMPolicyNoStatementsWithFullAccess,
145+
node: node
146+
})
147+
this.applyRule({
148+
ruleSuffixOverride: "EPS13",
149+
// eslint-disable-next-line max-len
150+
info: "The S3 Bucket does not prohibit public read access through its Block Public Access configurations and bucket ACLs.",
151+
explanation:
152+
"The management of access should be consistent with the classification of the data.",
153+
level: NagMessageLevel.ERROR,
154+
rule: S3BucketPublicReadProhibited,
155+
node: node
156+
})
157+
this.applyRule({
158+
ruleSuffixOverride: "EPS14",
159+
// eslint-disable-next-line max-len
160+
info: "The S3 Bucket does not prohibit public write access through its Block Public Access configurations and bucket ACLs.",
161+
explanation:
162+
"The management of access should be consistent with the classification of the data.",
163+
level: NagMessageLevel.ERROR,
164+
rule: S3BucketPublicWriteProhibited,
165+
node: node
166+
})
167+
this.applyRule({
168+
ruleSuffixOverride: "EPS15",
169+
info: "The S3 Bucket is not encrypted with a KMS Key by default.",
170+
explanation:
171+
// eslint-disable-next-line max-len
172+
"Ensure that encryption is enabled for your Amazon Simple Storage Service (Amazon S3) buckets. Because sensitive data can exist at rest in an Amazon S3 bucket, enable encryption at rest to help protect that data.",
173+
level: NagMessageLevel.ERROR,
174+
rule: S3DefaultEncryptionKMS,
175+
node: node
176+
})
177+
this.applyRule({
178+
ruleSuffixOverride: "EPS16",
179+
info: "The secret is not encrypted with a KMS Customer managed key.",
180+
explanation:
181+
// eslint-disable-next-line max-len
182+
"To help protect data at rest, ensure encryption with AWS Key Management Service (AWS KMS) is enabled for AWS Secrets Manager secrets. Because sensitive data can exist at rest in Secrets Manager secrets, enable encryption at rest to help protect that data.",
183+
level: NagMessageLevel.ERROR,
184+
rule: SecretsManagerUsingKMSKey,
185+
node: node
186+
})
187+
this.applyRule({
188+
ruleSuffixOverride: "EPS17",
189+
info: "The SNS topic does not have KMS encryption enabled.",
190+
explanation:
191+
// eslint-disable-next-line max-len
192+
"Because sensitive data can exist at rest in published messages, enable encryption at rest to help protect that data.",
193+
level: NagMessageLevel.ERROR,
194+
rule: SNSEncryptedKMS,
195+
node: node
196+
})
197+
this.applyRule({
198+
ruleSuffixOverride: "EPS18",
199+
info: "The VPC's default security group allows inbound or outbound traffic.",
200+
explanation:
201+
// eslint-disable-next-line max-len
202+
"When creating a VPC through CloudFormation, the default security group will always be open. Therefore it is important to always close the default security group after stack creation whenever a VPC is created. Restricting all the traffic on the default security group helps in restricting remote access to your AWS resources.",
203+
level: NagMessageLevel.ERROR,
204+
rule: VPCDefaultSecurityGroupClosed,
205+
node: node
206+
})
207+
this.applyRule({
208+
ruleSuffixOverride: "EPS19",
209+
info: "The VPC does not have an associated Flow Log.",
210+
explanation:
211+
// eslint-disable-next-line max-len
212+
"The VPC flow logs provide detailed records for information about the IP traffic going to and from network interfaces in your Amazon Virtual Private Cloud (Amazon VPC). By default, the flow log record includes values for the different components of the IP flow, including the source, destination, and protocol.",
213+
level: NagMessageLevel.ERROR,
214+
rule: VPCFlowLogsEnabled,
215+
node: node
216+
})
217+
}
218+
}
219+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import {parse} from "path"
2+
import {CfnResource, Stack} from "aws-cdk-lib"
3+
import {CfnDomainName} from "aws-cdk-lib/aws-apigateway"
4+
import {NagRules, NagRuleCompliance} from "cdk-nag"
5+
/**
6+
* APIs gateway have mutual TLS enabled unless this is a pull request
7+
* @param node the CfnResource to check
8+
*/
9+
export default Object.defineProperty(
10+
(node: CfnResource): NagRuleCompliance => {
11+
if (node instanceof CfnDomainName) {
12+
const stack = Stack.of(node)
13+
// Pull from context or props
14+
const isPullRequest =
15+
stack.node.tryGetContext("isPullRequest") === true
16+
17+
if (isPullRequest) {
18+
return NagRuleCompliance.COMPLIANT
19+
}
20+
21+
const mutualTls = node.mutualTlsAuthentication
22+
if (mutualTls === undefined) {
23+
return NagRuleCompliance.NON_COMPLIANT
24+
}
25+
26+
const trustStoreUri = NagRules.resolveIfPrimitive(
27+
node,
28+
(mutualTls as CfnDomainName.MutualTlsAuthenticationProperty)
29+
.truststoreUri
30+
)
31+
32+
const trustStoreVersion = NagRules.resolveIfPrimitive(
33+
node,
34+
(mutualTls as CfnDomainName.MutualTlsAuthenticationProperty)
35+
.truststoreVersion
36+
)
37+
38+
if (trustStoreUri === undefined || trustStoreVersion === undefined) {
39+
return NagRuleCompliance.NON_COMPLIANT
40+
}
41+
42+
return NagRuleCompliance.COMPLIANT
43+
}
44+
45+
return NagRuleCompliance.NOT_APPLICABLE
46+
},
47+
"name",
48+
{value: parse(__filename).name}
49+
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export {default as ApiGatewayMutualTls} from "./ApiGatewayMutualTls"

0 commit comments

Comments
 (0)