From 6b112a8f1916c677bfbb4169c998a17812833af2 Mon Sep 17 00:00:00 2001 From: kevinwochan Date: Mon, 29 Apr 2024 12:20:45 +1000 Subject: [PATCH 01/42] feat: implemented rules and 1 test --- src/packs/serverless.ts | 217 ++++++++++++++++++++ src/rules/apigw/APIGWStructuredLogging.ts | 47 +++++ src/rules/apigw/index.ts | 1 + src/rules/appsync/AppSyncTracing.ts | 24 +++ src/rules/appsync/index.ts | 1 + src/rules/eventbridge/EventBusDLQ.ts | 24 +++ src/rules/eventbridge/index.ts | 1 + src/rules/lambda/LambdaDefaultMemorySize.ts | 24 +++ src/rules/lambda/LambdaDefaultTimeout.ts | 24 +++ src/rules/lambda/LambdaLogging.ts | 24 +++ src/rules/lambda/LambdaTracing.ts | 24 +++ src/rules/lambda/index.ts | 4 + src/rules/sns/SNSDeadLetterQueue.ts | 25 +++ src/rules/sns/index.ts | 2 + test/rules/Lambda.test.ts | 25 +++ 15 files changed, 467 insertions(+) create mode 100644 src/packs/serverless.ts create mode 100644 src/rules/apigw/APIGWStructuredLogging.ts create mode 100644 src/rules/appsync/AppSyncTracing.ts create mode 100644 src/rules/eventbridge/EventBusDLQ.ts create mode 100644 src/rules/lambda/LambdaDefaultMemorySize.ts create mode 100644 src/rules/lambda/LambdaDefaultTimeout.ts create mode 100644 src/rules/lambda/LambdaLogging.ts create mode 100644 src/rules/lambda/LambdaTracing.ts create mode 100644 src/rules/sns/SNSDeadLetterQueue.ts diff --git a/src/packs/serverless.ts b/src/packs/serverless.ts new file mode 100644 index 0000000000..15b98de1c4 --- /dev/null +++ b/src/packs/serverless.ts @@ -0,0 +1,217 @@ +import { CfnResource } from 'aws-cdk-lib'; +import { IConstruct } from 'constructs'; +import { NagPack, NagPackProps } from '../nag-pack'; +import { NagMessageLevel } from '../nag-rules'; +import { APIGWAccessLogging, APIGWXrayEnabled, APIGWStructuredLogging, } from '../rules/apigw'; +import { AppSyncTracing } from '../rules/appsync'; +import { EventBusDLQ } from '../rules/eventbridge'; +import { LambdaDLQ, LambdaDefaultMemorySize, LambdaLogging, LambdaDefaultTimeout, LambdaTracing } from '../rules/lambda'; +import { CloudWatchLogGroupRetentionPeriod } from '../rules/cloudwatch'; +import { SNSDeadLetterQueue } from '../rules/sns'; +import { SQSQueueDLQ, } from '../rules/sqs'; +import { StepFunctionStateMachineAllLogsToCloudWatch, StepFunctionStateMachineXray, } from '../rules/stepfunctions'; + + +/** + * Serverless Checks are a compilation of rules to validate infrastructure-as-code template against recommended practices. + * + */ +export class ServerlessChecks extends NagPack { + constructor(props?: NagPackProps) { + super(props); + this.packName = 'Serverless'; + } + public visit(node: IConstruct): void { + if (node instanceof CfnResource) { + this.checkLambda(node); + this.checkCloudwatch(node); + this.checkApiGw(node); + this.checkAppSync(node); + this.checkEventBridge(node); + this.checkSNS(node); + this.checkSQS(node); + this.checkStepFunctions(node); + } + } + + /** + * Check Lambda Resources + * @param node the CfnResource to check + * @param ignores list of ignores for the resource + */ + private checkLambda(node: CfnResource) { + this.applyRule({ + info: 'Ensure that Lambda functions have an explcity timeout value', + explanation: "Lambda functions have a default timeout of 3 seconds. If your timeout value is too short, Lambda might terminate invocations prematurely. On the other side, setting the timeout much higher than the average execution may cause functions to execute for longer upon code malfunction, resulting in higher costs and possibly reaching concurrency limits depending on how such functions are invoked. You can also use AWS Lambda Power Tuning to test your function at different timeout settings to find the one that matches your cost and performance requirements the best.", + level: NagMessageLevel.ERROR, + rule: LambdaDefaultTimeout, + node: node, + }); + this.applyRule({ + info: 'Ensure that Lambda functions have an explicit memory value', + explanation: "Lambda allocates CPU power in proportion to the amount of memory configured. By default, your functions have 128 MB of memory allocated. You can increase that value up to 10 GB. With more CPU resources, your Lambda function's duration might decrease. You can use tools such as AWS Lambda Power Tuning to test your function at different memory settings to find the one that matches your cost and performance requirements the best.", + level: NagMessageLevel.ERROR, + rule: LambdaDefaultMemorySize, + node: node, + }); + this.applyRule({ + info: 'Ensure Lambda functions have a onFaliure destination configured', + explanation: 'When a lambda function has a onFailure destination configured, failed messages can be temporarily stored to be later reviewed', + level: NagMessageLevel.ERROR, + rule: LambdaDLQ, + node: node, + }) + this.applyRule({ + info: 'The Lambda function should have tracing set to Tracing.ACTIVE', + explanation: + "When a Lambda function has ACTIVE tracing, Lambda automatically samples invocation requests, based on the sampling algorithm specified by X-Ray.", + level: NagMessageLevel.ERROR, + rule: LambdaTracing, + node: node, + }); + + this.applyRule({ + info: 'Ensure that Lambda functions have a corresponding Log Group', + explanation: "Lambda captures logs for all requests handled by your function and sends them to Amazon CloudWatch Logs. You can insert logging statements into your code to help you validate that your code is working as expected. Lambda sends all logs from your code to the CloudWatch logs group associated with a Lambda function.", + level: NagMessageLevel.ERROR, + rule: LambdaLogging, + node: node + }); + + this.applyRule({ + info: 'Ensure that Lambda functions have a defined DLQ', + explanation: "When a Dead Letter Queue (DLQ) is specified, messages that fail to deliver to targets are stored in the Dead Letter Queue", + level: NagMessageLevel.ERROR, + rule: LambdaDLQ, + node: node + }); + } + + /** + * Check Cloudwatch Resources + * @param node the CfnResource to check + * @param ignores list of ignores for the resource + */ + private checkCloudwatch(node: CfnResource) { + this.applyRule({ + info: 'Ensure that CloudWatch Log Groups have an explcity retention policy', + explanation: "By default, logs are kept indefinitely and never expire. You can adjust the retention policy for each log group, keeping the indefinite retention, or choosing a retention period between 10 years and one day.", + level: NagMessageLevel.ERROR, + rule: CloudWatchLogGroupRetentionPeriod, + node: node, + }); + } + + /** + * Check API Gateway Resources + * @param node the CfnResource to check + * @param ignores list of ignores for the resource + */ + private checkApiGw(node: CfnResource) { + this.applyRule({ + info: 'Ensure tracing is enabled', + explanation: "Amazon API Gateway provides active tracing support for AWS X-Ray. Enable active tracing on your API stages to sample incoming requests and send traces to X-Ray.", + level: NagMessageLevel.ERROR, + rule: APIGWXrayEnabled, + node: node, + }); + this.applyRule({ + info: 'Ensure API Gateway stages have access logging enabled', + explanation: "API Gateway provides access logging for API stages. Enable access logging on your API stages to monitor API requests and responses.", + level: NagMessageLevel.ERROR, + rule: APIGWAccessLogging, + node: node, + }); + this.applyRule({ + info: 'Ensure API Gateway stages have access logging enabled', + explanation: "API Gateway provides access logging for API stages. Enable access logging on your API stages to monitor API requests and responses.", + level: NagMessageLevel.ERROR, + rule: APIGWStructuredLogging, + node: node, + }); + } + + /** + * Check AppSync Resources + * @param node the CfnResource to check + * @param ignores list of ignores for the resource + */ + private checkAppSync(node: CfnResource) { + this.applyRule({ + info: 'Ensure tracing is enabled', + explanation: "AWS AppSync provides active tracing support for AWS X-Ray. Enable active tracing on your API stages to sample incoming requests and send traces to X-Ray.", + level: NagMessageLevel.ERROR, + rule: AppSyncTracing, + node: node, + }); + } + + + /** + * Check EventBridge Resources + * @param node the CfnResource to check + * @param ignores list of ignores for the resource + */ + private checkEventBridge(node: CfnResource) { + this.applyRule({ + info: 'Ensure eventbridge targets have a DLQ configured', + explanation: "When a Dead Letter Queue (DLQ) is specified, messages that fail to deliver to targets are stored in the Dead Letter Queue", + level: NagMessageLevel.ERROR, + rule: EventBusDLQ, + node: node, + }); + + } + + /** + * Check SNS Resources + * @param node the CfnResource to check + * @param ignores list of ignores for the resource + */ + private checkSNS(node: CfnResource) { + this.applyRule({ + info: 'Ensure SNS subscriptions have a DLQ configured', + explanation: "When a Dead Letter Queue (DLQ) is specified, messages that fail to deliver to targets are stored in the Dead Letter Queue", + level: NagMessageLevel.ERROR, + rule: SNSDeadLetterQueue, + node: node, + }); + } + + /** + * Check SQS Resources + * @param node the CfnResource to check + * @param ignores list of ignores for the resource + */ + private checkSQS(node: CfnResource) { + this.applyRule({ + info: 'Ensure SQS queues have a DLQ configured', + explanation: "When a Dead Letter Queue (DLQ) is specified, messages that fail to deliver to targets are stored in the Dead Letter Queue", + level: NagMessageLevel.ERROR, + rule: SQSQueueDLQ, + node: node, + }); + } + + /** + * Check StepFunctions Resources + * @param node the CfnResource to check + * @param ignores list of ignores for the resource + */ + private checkStepFunctions(node: CfnResource) { + this.applyRule({ + info: 'Ensure StepFunctions have a DLQ configured', + explanation: "AWS StepFunctions provides active tracing support for AWS X-Ray. Enable active tracing on your API stages to sample incoming requests and send traces to X-Ray.", + level: NagMessageLevel.ERROR, + rule: StepFunctionStateMachineXray, + node: node, + }); + this.applyRule({ + info: 'Ensure Stepfunctions have been configured to log all events to cloudwatch', + explanation: "When a logging configuration is configured, errors can be captured in CLoudWatch Logs", + level: NagMessageLevel.ERROR, + rule: StepFunctionStateMachineAllLogsToCloudWatch, + node: node, + }) + } +} \ No newline at end of file diff --git a/src/rules/apigw/APIGWStructuredLogging.ts b/src/rules/apigw/APIGWStructuredLogging.ts new file mode 100644 index 0000000000..09ad8040d6 --- /dev/null +++ b/src/rules/apigw/APIGWStructuredLogging.ts @@ -0,0 +1,47 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ +import { parse } from 'path'; +import { CfnResource, Stack } from 'aws-cdk-lib'; +import { NagRuleCompliance } from '../../nag-rules'; +import { CfnApi, CfnHttpApi } from 'aws-cdk-lib/aws-sam'; +import { CfnDeployment, CfnStage } from 'aws-cdk-lib/aws-apigateway'; +import { CfnStage as CfnStageV2 } from 'aws-cdk-lib/aws-apigatewayv2'; + +const isJSON = (str: string) => { + try { + JSON.parse(str); + } catch (e) { + return false; + } + return true; +} + +/** + * Ensure that API Gateway REST and HTTP APIs are using JSON structured logs + * @param node the CfnResource to check + */ +export default Object.defineProperty( + (node: CfnResource): NagRuleCompliance => { + if (node instanceof CfnApi || node instanceof CfnHttpApi || node instanceof CfnStage) { + const accessLogSetting = Stack.of(node).resolve(node.accessLogSetting); + if (!accessLogSetting) return NagRuleCompliance.NOT_APPLICABLE; + if (isJSON(accessLogSetting.format)) return NagRuleCompliance.COMPLIANT; + return NagRuleCompliance.NON_COMPLIANT; + } else if (node instanceof CfnDeployment) { + const stageDescription = Stack.of(node).resolve(node.stageDescription); + const accessLogSetting = stageDescription.accessLogSetting; + if (!accessLogSetting) return NagRuleCompliance.NOT_APPLICABLE; + if (isJSON(accessLogSetting.format)) return NagRuleCompliance.COMPLIANT; + return NagRuleCompliance.NON_COMPLIANT; + } else if (node instanceof CfnStageV2) { + const accessLogSetting = Stack.of(node).resolve(node.accessLogSettings); + if (!accessLogSetting) return NagRuleCompliance.NOT_APPLICABLE; + if (isJSON(accessLogSetting.format)) return NagRuleCompliance.COMPLIANT; + return NagRuleCompliance.NON_COMPLIANT; + } + return NagRuleCompliance.NOT_APPLICABLE; + }, + 'name', { value: parse(__filename).name } +); \ No newline at end of file diff --git a/src/rules/apigw/index.ts b/src/rules/apigw/index.ts index d96825150e..9609c068ca 100644 --- a/src/rules/apigw/index.ts +++ b/src/rules/apigw/index.ts @@ -10,3 +10,4 @@ export { default as APIGWExecutionLoggingEnabled } from './APIGWExecutionLogging export { default as APIGWRequestValidation } from './APIGWRequestValidation'; export { default as APIGWSSLEnabled } from './APIGWSSLEnabled'; export { default as APIGWXrayEnabled } from './APIGWXrayEnabled'; +export { default as APIGWStructuredLogging } from './APIGWStructuredLogging' \ No newline at end of file diff --git a/src/rules/appsync/AppSyncTracing.ts b/src/rules/appsync/AppSyncTracing.ts new file mode 100644 index 0000000000..35ca5d1dd4 --- /dev/null +++ b/src/rules/appsync/AppSyncTracing.ts @@ -0,0 +1,24 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ +import { parse } from 'path'; +import { CfnResource, Stack } from 'aws-cdk-lib'; +import { NagRuleCompliance } from '../../nag-rules'; +import { CfnGraphQLApi } from 'aws-cdk-lib/aws-appsync'; + +/** + * Ensure that AppSync APIs have tracing enabled + * @param node the CfnResource to check + */ +export default Object.defineProperty( + (node: CfnResource): NagRuleCompliance => { + if (node instanceof CfnGraphQLApi) { + const isXrayEnabled = Stack.of(node).resolve(node.xrayEnabled); + if (isXrayEnabled) return NagRuleCompliance.COMPLIANT; + return NagRuleCompliance.NON_COMPLIANT; + } + return NagRuleCompliance.NOT_APPLICABLE; + }, + 'name', { value: parse(__filename).name } +); \ No newline at end of file diff --git a/src/rules/appsync/index.ts b/src/rules/appsync/index.ts index 44999551db..eec8da95e4 100644 --- a/src/rules/appsync/index.ts +++ b/src/rules/appsync/index.ts @@ -3,3 +3,4 @@ Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ export { default as AppSyncGraphQLRequestLogging } from './AppSyncGraphQLRequestLogging'; +export { default as AppSyncTracing } from './AppSyncTracing' diff --git a/src/rules/eventbridge/EventBusDLQ.ts b/src/rules/eventbridge/EventBusDLQ.ts new file mode 100644 index 0000000000..12c10b88f5 --- /dev/null +++ b/src/rules/eventbridge/EventBusDLQ.ts @@ -0,0 +1,24 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ +import { parse } from 'path'; +import { CfnResource, Stack } from 'aws-cdk-lib'; +import { NagRuleCompliance } from '../../nag-rules'; +import { CfnRule } from 'aws-cdk-lib/aws-events'; + +/** + * Ensure that EventBus targets have configure a DLQ + * @param node the CfnResource to check + */ +export default Object.defineProperty( + (node: CfnResource): NagRuleCompliance => { + if (node instanceof CfnRule) { + const targets: CfnRule.TargetProperty[] = Stack.of(node).resolve(node.targets); + if (targets.every((target) => target.deadLetterConfig)) return NagRuleCompliance.COMPLIANT; + return NagRuleCompliance.NON_COMPLIANT; + } + return NagRuleCompliance.NOT_APPLICABLE; + }, + 'name', { value: parse(__filename).name } +); \ No newline at end of file diff --git a/src/rules/eventbridge/index.ts b/src/rules/eventbridge/index.ts index f968a0a84c..a33e921165 100644 --- a/src/rules/eventbridge/index.ts +++ b/src/rules/eventbridge/index.ts @@ -3,3 +3,4 @@ Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ export { default as EventBusOpenAccess } from './EventBusOpenAccess'; +export { default as EventBusDLQ } from './EventBusDLQ' diff --git a/src/rules/lambda/LambdaDefaultMemorySize.ts b/src/rules/lambda/LambdaDefaultMemorySize.ts new file mode 100644 index 0000000000..6b248d68a3 --- /dev/null +++ b/src/rules/lambda/LambdaDefaultMemorySize.ts @@ -0,0 +1,24 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ +import { parse } from 'path'; +import { CfnResource, Stack } from 'aws-cdk-lib'; +import { CfnFunction } from 'aws-cdk-lib/aws-lambda'; +import { NagRuleCompliance } from '../../nag-rules'; + +/** + * Ensure that Lambda functions have an explicit memory value + * @param node the CfnResource to check + */ +export default Object.defineProperty( + (node: CfnResource): NagRuleCompliance => { + if (node instanceof CfnFunction) { + const memorySize = Stack.of(node).resolve(node.memorySize); + if (memorySize) return NagRuleCompliance.COMPLIANT; + return NagRuleCompliance.NON_COMPLIANT; + } + return NagRuleCompliance.NOT_APPLICABLE; + }, + 'name', { value: parse(__filename).name } +); \ No newline at end of file diff --git a/src/rules/lambda/LambdaDefaultTimeout.ts b/src/rules/lambda/LambdaDefaultTimeout.ts new file mode 100644 index 0000000000..524b7851c3 --- /dev/null +++ b/src/rules/lambda/LambdaDefaultTimeout.ts @@ -0,0 +1,24 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ +import { parse } from 'path'; +import { CfnResource, Stack } from 'aws-cdk-lib'; +import { CfnFunction } from 'aws-cdk-lib/aws-lambda'; +import { NagRuleCompliance } from '../../nag-rules'; + +/** + * Ensure that Lambda functions have an explicit timeout value + * @param node the CfnResource to check + */ +export default Object.defineProperty( + (node: CfnResource): NagRuleCompliance => { + if (node instanceof CfnFunction) { + const timeout = Stack.of(node).resolve(node.timeout); + if (timeout) return NagRuleCompliance.COMPLIANT; + return NagRuleCompliance.NON_COMPLIANT; + } + return NagRuleCompliance.NOT_APPLICABLE; + }, + 'name', { value: parse(__filename).name } +); \ No newline at end of file diff --git a/src/rules/lambda/LambdaLogging.ts b/src/rules/lambda/LambdaLogging.ts new file mode 100644 index 0000000000..674a0fc4bc --- /dev/null +++ b/src/rules/lambda/LambdaLogging.ts @@ -0,0 +1,24 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ +import { parse } from 'path'; +import { CfnResource, Stack } from 'aws-cdk-lib'; +import { CfnFunction } from 'aws-cdk-lib/aws-lambda'; +import { NagRuleCompliance } from '../../nag-rules'; + +/** + * Ensure that Lambda functions have a corresponding Log Group + * @param node the CfnResource to check + */ +export default Object.defineProperty( + (node: CfnResource): NagRuleCompliance => { + if (node instanceof CfnFunction) { + const loggingConfig = Stack.of(node).resolve(node.loggingConfig); + if (loggingConfig) return NagRuleCompliance.COMPLIANT; + return NagRuleCompliance.NON_COMPLIANT; + } + return NagRuleCompliance.NOT_APPLICABLE; + }, + 'name', { value: parse(__filename).name } +); \ No newline at end of file diff --git a/src/rules/lambda/LambdaTracing.ts b/src/rules/lambda/LambdaTracing.ts new file mode 100644 index 0000000000..776b0fa347 --- /dev/null +++ b/src/rules/lambda/LambdaTracing.ts @@ -0,0 +1,24 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ +import { parse } from 'path'; +import { CfnResource, Stack, aws_lambda } from 'aws-cdk-lib'; +import { CfnFunction } from 'aws-cdk-lib/aws-lambda'; +import { NagRuleCompliance } from '../../nag-rules'; + +/** + * Ensure Lambda functions have tracing enabled + * @param node the CfnResource to check + */ +export default Object.defineProperty( + (node: CfnResource): NagRuleCompliance => { + if (node instanceof CfnFunction) { + const tracingConfig = Stack.of(node).resolve(node.tracingConfig); + if (tracingConfig === aws_lambda.Tracing.ACTIVE) return NagRuleCompliance.COMPLIANT; + return NagRuleCompliance.NON_COMPLIANT; + } + return NagRuleCompliance.NOT_APPLICABLE; + }, + 'name', { value: parse(__filename).name } +); \ No newline at end of file diff --git a/src/rules/lambda/index.ts b/src/rules/lambda/index.ts index 8d96afe93e..5707088280 100644 --- a/src/rules/lambda/index.ts +++ b/src/rules/lambda/index.ts @@ -9,3 +9,7 @@ export { default as LambdaFunctionPublicAccessProhibited } from './LambdaFunctio export { default as LambdaFunctionUrlAuth } from './LambdaFunctionUrlAuth'; export { default as LambdaInsideVPC } from './LambdaInsideVPC'; export { default as LambdaLatestVersion } from './LambdaLatestVersion'; +export { default as LambdaLogging } from './LambdaLogging' +export { default as LambdaTracing } from './LambdaTracing' +export { default as LambdaDefaultMemorySize } from './LambdaDefaultMemorySize' +export { default as LambdaDefaultTimeout } from './LambdaDefaultTimeout' diff --git a/src/rules/sns/SNSDeadLetterQueue.ts b/src/rules/sns/SNSDeadLetterQueue.ts new file mode 100644 index 0000000000..e88d3bddb9 --- /dev/null +++ b/src/rules/sns/SNSDeadLetterQueue.ts @@ -0,0 +1,25 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ +import { parse } from 'path'; +import { CfnResource } from 'aws-cdk-lib'; +import { NagRuleCompliance } from '../../nag-rules'; +import { CfnSubscription } from 'aws-cdk-lib/aws-sns'; + + +/** + * Ensure that API Gateway REST and HTTP APIs are using JSON structured logs + * @param node the CfnResource to check + */ +export default Object.defineProperty( + (node: CfnResource): NagRuleCompliance => { + if (node instanceof CfnSubscription) { + const redrivePolicy = node.redrivePolicy; + if (redrivePolicy) return NagRuleCompliance.COMPLIANT; + return NagRuleCompliance.NON_COMPLIANT; + } + return NagRuleCompliance.NOT_APPLICABLE; + }, + 'name', { value: parse(__filename).name } +); \ No newline at end of file diff --git a/src/rules/sns/index.ts b/src/rules/sns/index.ts index 4d45acee84..a1c30e6843 100644 --- a/src/rules/sns/index.ts +++ b/src/rules/sns/index.ts @@ -4,3 +4,5 @@ SPDX-License-Identifier: Apache-2.0 */ export { default as SNSEncryptedKMS } from './SNSEncryptedKMS'; export { default as SNSTopicSSLPublishOnly } from './SNSTopicSSLPublishOnly'; +export { default as SNSDeadLetterQueue } from './SNSDeadLetterQueue' + diff --git a/test/rules/Lambda.test.ts b/test/rules/Lambda.test.ts index 1ef1b8c79b..49fa7bf5e9 100644 --- a/test/rules/Lambda.test.ts +++ b/test/rules/Lambda.test.ts @@ -351,4 +351,29 @@ describe('AWS Lambda', () => { validateStack(stack, ruleId, TestType.VALIDATION_FAILURE); }); }); + + describe('LambdaLogging: Ensure that Lambda functions have a corresponding Log Group', () => { + const ruleId = 'LambdaLogging'; + test('Noncompliance 1', () => { + new CfnFunction(stack, 'rFunction', { + code: {}, + role: 'somerole', + }); + validateStack(stack, ruleId, TestType.NON_COMPLIANCE); + }); + + test('Compliance', () => { + new CfnFunction(stack, 'rFunction', { + code: {}, + role: 'somerole', + loggingConfig: { + applicationLogLevel: 'applicationLogLevel', + logFormat: 'logFormat', + logGroup: 'logGroup', + systemLogLevel: 'systemLogLevel', + } + }); + validateStack(stack, ruleId, TestType.COMPLIANCE); + }); + }) }); From 3cd5e1c69ed9cd57842473bc46c378650a76260c Mon Sep 17 00:00:00 2001 From: kevinwochan Date: Mon, 29 Apr 2024 14:21:48 +1000 Subject: [PATCH 02/42] feat: lambda logging rules --- src/rules/lambda/LambdaLogging.ts | 26 ++++++++++++++++++++++++++ src/rules/lambda/index.ts | 1 + test/rules/Lambda.test.ts | 27 +++++++++++++++++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 src/rules/lambda/LambdaLogging.ts diff --git a/src/rules/lambda/LambdaLogging.ts b/src/rules/lambda/LambdaLogging.ts new file mode 100644 index 0000000000..393b812389 --- /dev/null +++ b/src/rules/lambda/LambdaLogging.ts @@ -0,0 +1,26 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ +import { parse } from 'path'; +import { CfnResource, Stack } from 'aws-cdk-lib'; +import { CfnFunction } from 'aws-cdk-lib/aws-lambda'; +import { NagRuleCompliance } from '../../nag-rules'; + +/** + * Ensure that Lambda functions have a corresponding Log Group + * @param node the CfnResource to check + */ +export default Object.defineProperty( + (node: CfnResource): NagRuleCompliance => { + if (node instanceof CfnFunction) { + const loggingConfig = Stack.of(node).resolve(node.loggingConfig); + if (loggingConfig && loggingConfig.logGroup) + return NagRuleCompliance.COMPLIANT; + return NagRuleCompliance.NON_COMPLIANT; + } + return NagRuleCompliance.NOT_APPLICABLE; + }, + 'name', + { value: parse(__filename).name } +); diff --git a/src/rules/lambda/index.ts b/src/rules/lambda/index.ts index 8d96afe93e..92ce59d3e4 100644 --- a/src/rules/lambda/index.ts +++ b/src/rules/lambda/index.ts @@ -9,3 +9,4 @@ export { default as LambdaFunctionPublicAccessProhibited } from './LambdaFunctio export { default as LambdaFunctionUrlAuth } from './LambdaFunctionUrlAuth'; export { default as LambdaInsideVPC } from './LambdaInsideVPC'; export { default as LambdaLatestVersion } from './LambdaLatestVersion'; +export { default as LambdaLogging } from './LambdaLogging' diff --git a/test/rules/Lambda.test.ts b/test/rules/Lambda.test.ts index 1ef1b8c79b..7ea6767973 100644 --- a/test/rules/Lambda.test.ts +++ b/test/rules/Lambda.test.ts @@ -23,6 +23,7 @@ import { LambdaFunctionUrlAuth, LambdaInsideVPC, LambdaLatestVersion, + LambdaLogging, } from '../../src/rules/lambda'; const testPack = new TestPack([ @@ -32,6 +33,7 @@ const testPack = new TestPack([ LambdaFunctionUrlAuth, LambdaInsideVPC, LambdaLatestVersion, + LambdaLogging, ]); let stack: Stack; @@ -351,4 +353,29 @@ describe('AWS Lambda', () => { validateStack(stack, ruleId, TestType.VALIDATION_FAILURE); }); }); + + describe('LambdaLogging: Ensure that Lambda functions have a corresponding Log Group', () => { + const ruleId = 'LambdaLogging'; + test('Noncompliance 1', () => { + new CfnFunction(stack, 'rFunction', { + code: {}, + role: 'somerole', + }); + validateStack(stack, ruleId, TestType.NON_COMPLIANCE); + }); + + test('Compliance', () => { + new CfnFunction(stack, 'rFunction', { + code: {}, + role: 'somerole', + loggingConfig: { + applicationLogLevel: 'applicationLogLevel', + logFormat: 'logFormat', + logGroup: 'logGroup', + systemLogLevel: 'systemLogLevel', + }, + }); + validateStack(stack, ruleId, TestType.COMPLIANCE); + }); + }); }); From a757e4b8d7ee7de93727d00d78f21152f3ff5417 Mon Sep 17 00:00:00 2001 From: Kevin Chan Date: Thu, 9 May 2024 10:20:04 +1000 Subject: [PATCH 03/42] fix: typo in function id Co-authored-by: Arun Donti --- test/rules/Lambda.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/rules/Lambda.test.ts b/test/rules/Lambda.test.ts index 7ea6767973..b98895735b 100644 --- a/test/rules/Lambda.test.ts +++ b/test/rules/Lambda.test.ts @@ -365,7 +365,7 @@ describe('AWS Lambda', () => { }); test('Compliance', () => { - new CfnFunction(stack, 'rFunction', { + new CfnFunction(stack, 'Function', { code: {}, role: 'somerole', loggingConfig: { From 38c23bc41b8965a7114739ea06d452635eaeed53 Mon Sep 17 00:00:00 2001 From: Kevin Chan Date: Thu, 9 May 2024 10:20:18 +1000 Subject: [PATCH 04/42] fix: typo in function id Co-authored-by: Arun Donti --- test/rules/Lambda.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/rules/Lambda.test.ts b/test/rules/Lambda.test.ts index b98895735b..5c27fc37a5 100644 --- a/test/rules/Lambda.test.ts +++ b/test/rules/Lambda.test.ts @@ -357,7 +357,7 @@ describe('AWS Lambda', () => { describe('LambdaLogging: Ensure that Lambda functions have a corresponding Log Group', () => { const ruleId = 'LambdaLogging'; test('Noncompliance 1', () => { - new CfnFunction(stack, 'rFunction', { + new CfnFunction(stack, 'Function', { code: {}, role: 'somerole', }); From 66181a6277c2e338250228986528a846a9b3d7d5 Mon Sep 17 00:00:00 2001 From: Kevin Chan Date: Thu, 9 May 2024 10:21:08 +1000 Subject: [PATCH 05/42] styling: rule description grammar Co-authored-by: Arun Donti --- src/rules/lambda/LambdaLogging.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rules/lambda/LambdaLogging.ts b/src/rules/lambda/LambdaLogging.ts index 393b812389..54c4c7b85c 100644 --- a/src/rules/lambda/LambdaLogging.ts +++ b/src/rules/lambda/LambdaLogging.ts @@ -8,7 +8,7 @@ import { CfnFunction } from 'aws-cdk-lib/aws-lambda'; import { NagRuleCompliance } from '../../nag-rules'; /** - * Ensure that Lambda functions have a corresponding Log Group + * Lambda functions explicitly define their CloudWatch Log Groups * @param node the CfnResource to check */ export default Object.defineProperty( From 71793d0e29ae33e4ee8a78c85f5446d53165c0b5 Mon Sep 17 00:00:00 2001 From: kevinwochan Date: Mon, 17 Jun 2024 13:28:52 +1000 Subject: [PATCH 06/42] docs: added docs for lambda logging --- RULES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RULES.md b/RULES.md index 5d28cd202d..4b942e7175 100644 --- a/RULES.md +++ b/RULES.md @@ -702,6 +702,7 @@ A collection of community rules that are not currently included in any of the pr | Rule ID | Cause | Explanation | | --------------------- | ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | LambdaFunctionUrlAuth | The Lambda Function URL allows for public, unauthenticated access. | AWS Lambda Function URLs allow you to invoke your function via a HTTPS end-point, setting the authentication to NONE allows anyone on the internet to invoke your function. | +| LambdaLogging | The Lambda Function does not define a corresponding log group | Ensure that Lambda functions have a corresponding Log Group | ## Footnotes From bc1d61c68cc9568022dcfc929f6105dd545731b7 Mon Sep 17 00:00:00 2001 From: kevinwochan Date: Mon, 17 Jun 2024 13:29:30 +1000 Subject: [PATCH 07/42] feat: testing L2 constructs --- test/rules/Lambda.test.ts | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/test/rules/Lambda.test.ts b/test/rules/Lambda.test.ts index 5c27fc37a5..2c658e95bf 100644 --- a/test/rules/Lambda.test.ts +++ b/test/rules/Lambda.test.ts @@ -14,7 +14,11 @@ import { Function, FunctionUrlAuthType, Runtime, + LogFormat, + SystemLogLevel, + ApplicationLogLevel, } from 'aws-cdk-lib/aws-lambda'; +import { LogGroup } from 'aws-cdk-lib/aws-logs'; import { TestPack, TestType, validateStack } from './utils'; import { LambdaConcurrency, @@ -356,7 +360,7 @@ describe('AWS Lambda', () => { describe('LambdaLogging: Ensure that Lambda functions have a corresponding Log Group', () => { const ruleId = 'LambdaLogging'; - test('Noncompliance 1', () => { + test('Noncompliance 1 - L1 Construct', () => { new CfnFunction(stack, 'Function', { code: {}, role: 'somerole', @@ -364,7 +368,19 @@ describe('AWS Lambda', () => { validateStack(stack, ruleId, TestType.NON_COMPLIANCE); }); - test('Compliance', () => { + test('Noncompliance 2 - L2 Constructs', () => { + new Function(stack, 'Function', { + handler: '', + runtime: Runtime.NODEJS_LATEST, + code: Code.fromInline('console.log("hello world'), + logFormat: LogFormat.JSON, + applicationLogLevel: ApplicationLogLevel.TRACE, + systemLogLevel: SystemLogLevel.WARN + }); + validateStack(stack, ruleId, TestType.NON_COMPLIANCE); + }); + + test('Compliance - L1 Construct', () => { new CfnFunction(stack, 'Function', { code: {}, role: 'somerole', @@ -377,5 +393,18 @@ describe('AWS Lambda', () => { }); validateStack(stack, ruleId, TestType.COMPLIANCE); }); + + test('Compliance 1 - L2 Constructs', () => { + new Function(stack, 'Function', { + handler: '', + runtime: Runtime.NODEJS_LATEST, + code: Code.fromInline('console.log("hello world'), + logFormat: LogFormat.JSON, + applicationLogLevel: ApplicationLogLevel.TRACE, + systemLogLevel: SystemLogLevel.WARN, + logGroup: new LogGroup(stack, 'LogGroup'), + }); + validateStack(stack, ruleId, TestType.COMPLIANCE); + }); }); }); From 515f898ac13ffdc7872e9b6b150ef0cda85a320e Mon Sep 17 00:00:00 2001 From: kevinwochan Date: Mon, 17 Jun 2024 14:07:48 +1000 Subject: [PATCH 08/42] implemeted all rules, untested and undocumented --- RULES.md | 2 +- src/packs/serverless.ts | 242 ++++++++++++++++++ src/rules/apigw/APIGWStructuredLogging.ts | 52 ++++ src/rules/apigw/index.ts | 1 + src/rules/appsync/AppSyncTracing.ts | 25 ++ src/rules/appsync/index.ts | 1 + src/rules/eventbridge/EventBusDLQ.ts | 28 ++ src/rules/eventbridge/index.ts | 1 + src/rules/lambda/LambdaDefaultMemorySize.ts | 26 ++ src/rules/lambda/LambdaDefaultTimeout.ts | 25 ++ .../{LambdaLogging.ts => LambdaLogLevel.ts} | 2 +- src/rules/lambda/LambdaTracing.ts | 26 ++ src/rules/lambda/index.ts | 2 +- src/rules/sns/SNSDeadLetterQueue.ts | 25 ++ src/rules/sns/index.ts | 1 + test/rules/Lambda.test.ts | 27 +- 16 files changed, 474 insertions(+), 12 deletions(-) create mode 100644 src/packs/serverless.ts create mode 100644 src/rules/apigw/APIGWStructuredLogging.ts create mode 100644 src/rules/appsync/AppSyncTracing.ts create mode 100644 src/rules/eventbridge/EventBusDLQ.ts create mode 100644 src/rules/lambda/LambdaDefaultMemorySize.ts create mode 100644 src/rules/lambda/LambdaDefaultTimeout.ts rename src/rules/lambda/{LambdaLogging.ts => LambdaLogLevel.ts} (73%) create mode 100644 src/rules/lambda/LambdaTracing.ts create mode 100644 src/rules/sns/SNSDeadLetterQueue.ts diff --git a/RULES.md b/RULES.md index 4b942e7175..9647943c26 100644 --- a/RULES.md +++ b/RULES.md @@ -702,7 +702,7 @@ A collection of community rules that are not currently included in any of the pr | Rule ID | Cause | Explanation | | --------------------- | ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | LambdaFunctionUrlAuth | The Lambda Function URL allows for public, unauthenticated access. | AWS Lambda Function URLs allow you to invoke your function via a HTTPS end-point, setting the authentication to NONE allows anyone on the internet to invoke your function. | -| LambdaLogging | The Lambda Function does not define a corresponding log group | Ensure that Lambda functions have a corresponding Log Group | +| LambdaLogLevel | The Lambda Function does not define an explicit Log Level | For cost optimization purposes, you should explicitly define the required log level for cost effective storage of Lambda Logs. | ## Footnotes diff --git a/src/packs/serverless.ts b/src/packs/serverless.ts new file mode 100644 index 0000000000..c8e6ab4be9 --- /dev/null +++ b/src/packs/serverless.ts @@ -0,0 +1,242 @@ +import { CfnResource } from 'aws-cdk-lib'; +import { IConstruct } from 'constructs'; +import { NagPack, NagPackProps } from '../nag-pack'; +import { NagMessageLevel } from '../nag-rules'; +import { + APIGWAccessLogging, + APIGWXrayEnabled, + APIGWStructuredLogging, +} from '../rules/apigw'; +import { AppSyncTracing } from '../rules/appsync'; +import { CloudWatchLogGroupRetentionPeriod } from '../rules/cloudwatch'; +import { EventBusDLQ } from '../rules/eventbridge'; +import { + LambdaDLQ, + LambdaDefaultMemorySize, + LambdaLogLevel, + LambdaDefaultTimeout, + LambdaTracing, +} from '../rules/lambda'; +import { SNSDeadLetterQueue } from '../rules/sns'; +import { SQSQueueDLQ } from '../rules/sqs'; +import { + StepFunctionStateMachineAllLogsToCloudWatch, + StepFunctionStateMachineXray, +} from '../rules/stepfunctions'; + +/** + * Serverless Checks are a compilation of rules to validate infrastructure-as-code template against recommended practices. + * + */ +export class ServerlessChecks extends NagPack { + constructor(props?: NagPackProps) { + super(props); + this.packName = 'Serverless'; + } + public visit(node: IConstruct): void { + if (node instanceof CfnResource) { + this.checkLambda(node); + this.checkCloudwatch(node); + this.checkApiGw(node); + this.checkAppSync(node); + this.checkEventBridge(node); + this.checkSNS(node); + this.checkSQS(node); + this.checkStepFunctions(node); + } + } + + /** + * Check Lambda Resources + * @param node the CfnResource to check + * @param ignores list of ignores for the resource + */ + private checkLambda(node: CfnResource) { + this.applyRule({ + info: 'Ensure that Lambda functions have an explcity timeout value', + explanation: + 'Lambda functions have a default timeout of 3 seconds. If your timeout value is too short, Lambda might terminate invocations prematurely. On the other side, setting the timeout much higher than the average execution may cause functions to execute for longer upon code malfunction, resulting in higher costs and possibly reaching concurrency limits depending on how such functions are invoked. You can also use AWS Lambda Power Tuning to test your function at different timeout settings to find the one that matches your cost and performance requirements the best.', + level: NagMessageLevel.ERROR, + rule: LambdaDefaultTimeout, + node: node, + }); + this.applyRule({ + info: 'Ensure that Lambda functions have an explicit memory value', + explanation: + "Lambda allocates CPU power in proportion to the amount of memory configured. By default, your functions have 128 MB of memory allocated. You can increase that value up to 10 GB. With more CPU resources, your Lambda function's duration might decrease. You can use tools such as AWS Lambda Power Tuning to test your function at different memory settings to find the one that matches your cost and performance requirements the best.", + level: NagMessageLevel.ERROR, + rule: LambdaDefaultMemorySize, + node: node, + }); + this.applyRule({ + info: 'Ensure Lambda functions have a onFaliure destination configured', + explanation: + 'When a lambda function has a onFailure destination configured, failed messages can be temporarily stored to be later reviewed', + level: NagMessageLevel.ERROR, + rule: LambdaDLQ, + node: node, + }); + this.applyRule({ + info: 'The Lambda function should have tracing set to Tracing.ACTIVE', + explanation: + 'When a Lambda function has ACTIVE tracing, Lambda automatically samples invocation requests, based on the sampling algorithm specified by X-Ray.', + level: NagMessageLevel.ERROR, + rule: LambdaTracing, + node: node, + }); + + this.applyRule({ + info: 'Ensure that Lambda functions have a corresponding Log Group', + explanation: + 'Lambda captures logs for all requests handled by your function and sends them to Amazon CloudWatch Logs. You can insert logging statements into your code to help you validate that your code is working as expected. Lambda sends all logs from your code to the CloudWatch logs group associated with a Lambda function.', + level: NagMessageLevel.ERROR, + rule: LambdaLogLevel, + node: node, + }); + + this.applyRule({ + info: 'Ensure that Lambda functions have a defined DLQ', + explanation: + 'When a Dead Letter Queue (DLQ) is specified, messages that fail to deliver to targets are stored in the Dead Letter Queue', + level: NagMessageLevel.ERROR, + rule: LambdaDLQ, + node: node, + }); + } + + /** + * Check Cloudwatch Resources + * @param node the CfnResource to check + * @param ignores list of ignores for the resource + */ + private checkCloudwatch(node: CfnResource) { + this.applyRule({ + info: 'Ensure that CloudWatch Log Groups have an explcity retention policy', + explanation: + 'By default, logs are kept indefinitely and never expire. You can adjust the retention policy for each log group, keeping the indefinite retention, or choosing a retention period between 10 years and one day.', + level: NagMessageLevel.ERROR, + rule: CloudWatchLogGroupRetentionPeriod, + node: node, + }); + } + + /** + * Check API Gateway Resources + * @param node the CfnResource to check + * @param ignores list of ignores for the resource + */ + private checkApiGw(node: CfnResource) { + this.applyRule({ + info: 'Ensure tracing is enabled', + explanation: + 'Amazon API Gateway provides active tracing support for AWS X-Ray. Enable active tracing on your API stages to sample incoming requests and send traces to X-Ray.', + level: NagMessageLevel.ERROR, + rule: APIGWXrayEnabled, + node: node, + }); + this.applyRule({ + info: 'Ensure API Gateway stages have access logging enabled', + explanation: + 'API Gateway provides access logging for API stages. Enable access logging on your API stages to monitor API requests and responses.', + level: NagMessageLevel.ERROR, + rule: APIGWAccessLogging, + node: node, + }); + this.applyRule({ + info: 'Ensure API Gateway stages have access logging enabled', + explanation: + 'API Gateway provides access logging for API stages. Enable access logging on your API stages to monitor API requests and responses.', + level: NagMessageLevel.ERROR, + rule: APIGWStructuredLogging, + node: node, + }); + } + + /** + * Check AppSync Resources + * @param node the CfnResource to check + * @param ignores list of ignores for the resource + */ + private checkAppSync(node: CfnResource) { + this.applyRule({ + info: 'Ensure tracing is enabled', + explanation: + 'AWS AppSync provides active tracing support for AWS X-Ray. Enable active tracing on your API stages to sample incoming requests and send traces to X-Ray.', + level: NagMessageLevel.ERROR, + rule: AppSyncTracing, + node: node, + }); + } + + /** + * Check EventBridge Resources + * @param node the CfnResource to check + * @param ignores list of ignores for the resource + */ + private checkEventBridge(node: CfnResource) { + this.applyRule({ + info: 'Ensure eventbridge targets have a DLQ configured', + explanation: + 'When a Dead Letter Queue (DLQ) is specified, messages that fail to deliver to targets are stored in the Dead Letter Queue', + level: NagMessageLevel.ERROR, + rule: EventBusDLQ, + node: node, + }); + } + + /** + * Check SNS Resources + * @param node the CfnResource to check + * @param ignores list of ignores for the resource + */ + private checkSNS(node: CfnResource) { + this.applyRule({ + info: 'Ensure SNS subscriptions have a DLQ configured', + explanation: + 'When a Dead Letter Queue (DLQ) is specified, messages that fail to deliver to targets are stored in the Dead Letter Queue', + level: NagMessageLevel.ERROR, + rule: SNSDeadLetterQueue, + node: node, + }); + } + + /** + * Check SQS Resources + * @param node the CfnResource to check + * @param ignores list of ignores for the resource + */ + private checkSQS(node: CfnResource) { + this.applyRule({ + info: 'Ensure SQS queues have a DLQ configured', + explanation: + 'When a Dead Letter Queue (DLQ) is specified, messages that fail to deliver to targets are stored in the Dead Letter Queue', + level: NagMessageLevel.ERROR, + rule: SQSQueueDLQ, + node: node, + }); + } + + /** + * Check StepFunctions Resources + * @param node the CfnResource to check + * @param ignores list of ignores for the resource + */ + private checkStepFunctions(node: CfnResource) { + this.applyRule({ + info: 'Ensure StepFunctions have a DLQ configured', + explanation: + 'AWS StepFunctions provides active tracing support for AWS X-Ray. Enable active tracing on your API stages to sample incoming requests and send traces to X-Ray.', + level: NagMessageLevel.ERROR, + rule: StepFunctionStateMachineXray, + node: node, + }); + this.applyRule({ + info: 'Ensure Stepfunctions have been configured to log all events to cloudwatch', + explanation: + 'When a logging configuration is configured, errors can be captured in CLoudWatch Logs', + level: NagMessageLevel.ERROR, + rule: StepFunctionStateMachineAllLogsToCloudWatch, + node: node, + }); + } +} diff --git a/src/rules/apigw/APIGWStructuredLogging.ts b/src/rules/apigw/APIGWStructuredLogging.ts new file mode 100644 index 0000000000..64c9b40660 --- /dev/null +++ b/src/rules/apigw/APIGWStructuredLogging.ts @@ -0,0 +1,52 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ +import { parse } from 'path'; +import { CfnResource, Stack } from 'aws-cdk-lib'; +import { CfnDeployment, CfnStage } from 'aws-cdk-lib/aws-apigateway'; +import { CfnStage as CfnStageV2 } from 'aws-cdk-lib/aws-apigatewayv2'; +import { CfnApi, CfnHttpApi } from 'aws-cdk-lib/aws-sam'; +import { NagRuleCompliance } from '../../nag-rules'; + +const isJSON = (str: string) => { + try { + JSON.parse(str); + } catch (e) { + return false; + } + return true; +}; + +/** + * Ensure that API Gateway REST and HTTP APIs are using JSON structured logs + * @param node the CfnResource to check + */ +export default Object.defineProperty( + (node: CfnResource): NagRuleCompliance => { + if ( + node instanceof CfnApi || + node instanceof CfnHttpApi || + node instanceof CfnStage + ) { + const accessLogSetting = Stack.of(node).resolve(node.accessLogSetting); + if (!accessLogSetting) return NagRuleCompliance.NOT_APPLICABLE; + if (isJSON(accessLogSetting.format)) return NagRuleCompliance.COMPLIANT; + return NagRuleCompliance.NON_COMPLIANT; + } else if (node instanceof CfnDeployment) { + const stageDescription = Stack.of(node).resolve(node.stageDescription); + const accessLogSetting = stageDescription.accessLogSetting; + if (!accessLogSetting) return NagRuleCompliance.NOT_APPLICABLE; + if (isJSON(accessLogSetting.format)) return NagRuleCompliance.COMPLIANT; + return NagRuleCompliance.NON_COMPLIANT; + } else if (node instanceof CfnStageV2) { + const accessLogSetting = Stack.of(node).resolve(node.accessLogSettings); + if (!accessLogSetting) return NagRuleCompliance.NOT_APPLICABLE; + if (isJSON(accessLogSetting.format)) return NagRuleCompliance.COMPLIANT; + return NagRuleCompliance.NON_COMPLIANT; + } + return NagRuleCompliance.NOT_APPLICABLE; + }, + 'name', + { value: parse(__filename).name } +); diff --git a/src/rules/apigw/index.ts b/src/rules/apigw/index.ts index d96825150e..a2fbccfb11 100644 --- a/src/rules/apigw/index.ts +++ b/src/rules/apigw/index.ts @@ -10,3 +10,4 @@ export { default as APIGWExecutionLoggingEnabled } from './APIGWExecutionLogging export { default as APIGWRequestValidation } from './APIGWRequestValidation'; export { default as APIGWSSLEnabled } from './APIGWSSLEnabled'; export { default as APIGWXrayEnabled } from './APIGWXrayEnabled'; +export { default as APIGWStructuredLogging } from './APIGWStructuredLogging'; diff --git a/src/rules/appsync/AppSyncTracing.ts b/src/rules/appsync/AppSyncTracing.ts new file mode 100644 index 0000000000..97ae923bbf --- /dev/null +++ b/src/rules/appsync/AppSyncTracing.ts @@ -0,0 +1,25 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ +import { parse } from 'path'; +import { CfnResource, Stack } from 'aws-cdk-lib'; +import { CfnGraphQLApi } from 'aws-cdk-lib/aws-appsync'; +import { NagRuleCompliance } from '../../nag-rules'; + +/** + * Ensure that AppSync APIs have tracing enabled + * @param node the CfnResource to check + */ +export default Object.defineProperty( + (node: CfnResource): NagRuleCompliance => { + if (node instanceof CfnGraphQLApi) { + const isXrayEnabled = Stack.of(node).resolve(node.xrayEnabled); + if (isXrayEnabled) return NagRuleCompliance.COMPLIANT; + return NagRuleCompliance.NON_COMPLIANT; + } + return NagRuleCompliance.NOT_APPLICABLE; + }, + 'name', + { value: parse(__filename).name } +); diff --git a/src/rules/appsync/index.ts b/src/rules/appsync/index.ts index 44999551db..f8501051ab 100644 --- a/src/rules/appsync/index.ts +++ b/src/rules/appsync/index.ts @@ -3,3 +3,4 @@ Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ export { default as AppSyncGraphQLRequestLogging } from './AppSyncGraphQLRequestLogging'; +export { default as AppSyncTracing } from './AppSyncTracing'; diff --git a/src/rules/eventbridge/EventBusDLQ.ts b/src/rules/eventbridge/EventBusDLQ.ts new file mode 100644 index 0000000000..577af14f88 --- /dev/null +++ b/src/rules/eventbridge/EventBusDLQ.ts @@ -0,0 +1,28 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ +import { parse } from 'path'; +import { CfnResource, Stack } from 'aws-cdk-lib'; +import { CfnRule } from 'aws-cdk-lib/aws-events'; +import { NagRuleCompliance } from '../../nag-rules'; + +/** + * Ensure that EventBus targets have configure a DLQ + * @param node the CfnResource to check + */ +export default Object.defineProperty( + (node: CfnResource): NagRuleCompliance => { + if (node instanceof CfnRule) { + const targets: CfnRule.TargetProperty[] = Stack.of(node).resolve( + node.targets + ); + if (targets.every((target) => target.deadLetterConfig)) + return NagRuleCompliance.COMPLIANT; + return NagRuleCompliance.NON_COMPLIANT; + } + return NagRuleCompliance.NOT_APPLICABLE; + }, + 'name', + { value: parse(__filename).name } +); diff --git a/src/rules/eventbridge/index.ts b/src/rules/eventbridge/index.ts index f968a0a84c..c69fa15a2b 100644 --- a/src/rules/eventbridge/index.ts +++ b/src/rules/eventbridge/index.ts @@ -3,3 +3,4 @@ Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ export { default as EventBusOpenAccess } from './EventBusOpenAccess'; +export { default as EventBusDLQ } from './EventBusDLQ'; diff --git a/src/rules/lambda/LambdaDefaultMemorySize.ts b/src/rules/lambda/LambdaDefaultMemorySize.ts new file mode 100644 index 0000000000..dae25f64ec --- /dev/null +++ b/src/rules/lambda/LambdaDefaultMemorySize.ts @@ -0,0 +1,26 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ +import { parse } from 'path'; +import { CfnResource, Stack } from 'aws-cdk-lib'; +import { CfnFunction } from 'aws-cdk-lib/aws-lambda'; +import { NagRuleCompliance } from '../../nag-rules'; + +/** + * Lambda allocates CPU power in proportion to the amount of memory configured. By default, your functions have 128 MB of memory allocated. You can increase that value up to 10 GB. With more CPU resources, your Lambda function's duration might decrease. + * You can use tools such as AWS Lambda Power Tuning to test your function at different memory settings to find the one that matches your cost and performance requirements the best. + * @param node the CfnResource to check + */ +export default Object.defineProperty( + (node: CfnResource): NagRuleCompliance => { + if (node instanceof CfnFunction) { + const memorySize = Stack.of(node).resolve(node.memorySize); + if (memorySize) return NagRuleCompliance.COMPLIANT; + return NagRuleCompliance.NON_COMPLIANT; + } + return NagRuleCompliance.NOT_APPLICABLE; + }, + 'name', + { value: parse(__filename).name } +); diff --git a/src/rules/lambda/LambdaDefaultTimeout.ts b/src/rules/lambda/LambdaDefaultTimeout.ts new file mode 100644 index 0000000000..1c88c8e2e8 --- /dev/null +++ b/src/rules/lambda/LambdaDefaultTimeout.ts @@ -0,0 +1,25 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ +import { parse } from 'path'; +import { CfnResource, Stack } from 'aws-cdk-lib'; +import { CfnFunction } from 'aws-cdk-lib/aws-lambda'; +import { NagRuleCompliance } from '../../nag-rules'; + +/** + * Ensure that Lambda functions have an explicit timeout value + * @param node the CfnResource to check + */ +export default Object.defineProperty( + (node: CfnResource): NagRuleCompliance => { + if (node instanceof CfnFunction) { + const timeout = Stack.of(node).resolve(node.timeout); + if (timeout) return NagRuleCompliance.COMPLIANT; + return NagRuleCompliance.NON_COMPLIANT; + } + return NagRuleCompliance.NOT_APPLICABLE; + }, + 'name', + { value: parse(__filename).name } +); diff --git a/src/rules/lambda/LambdaLogging.ts b/src/rules/lambda/LambdaLogLevel.ts similarity index 73% rename from src/rules/lambda/LambdaLogging.ts rename to src/rules/lambda/LambdaLogLevel.ts index 54c4c7b85c..0a74c65111 100644 --- a/src/rules/lambda/LambdaLogging.ts +++ b/src/rules/lambda/LambdaLogLevel.ts @@ -8,7 +8,7 @@ import { CfnFunction } from 'aws-cdk-lib/aws-lambda'; import { NagRuleCompliance } from '../../nag-rules'; /** - * Lambda functions explicitly define their CloudWatch Log Groups + * By default, CloudWatch log groups created by Lambda functions have an unlimited retention time. For cost optimization purposes, you should explicitly define a LogGroup which allows for the CloudWatchLogGroupRetentionPeriod rule to detect unspecified log retention periods. * @param node the CfnResource to check */ export default Object.defineProperty( diff --git a/src/rules/lambda/LambdaTracing.ts b/src/rules/lambda/LambdaTracing.ts new file mode 100644 index 0000000000..f248400d5a --- /dev/null +++ b/src/rules/lambda/LambdaTracing.ts @@ -0,0 +1,26 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ +import { parse } from 'path'; +import { CfnResource, Stack, aws_lambda } from 'aws-cdk-lib'; +import { CfnFunction } from 'aws-cdk-lib/aws-lambda'; +import { NagRuleCompliance } from '../../nag-rules'; + +/** + * Ensure Lambda functions have tracing enabled + * @param node the CfnResource to check + */ +export default Object.defineProperty( + (node: CfnResource): NagRuleCompliance => { + if (node instanceof CfnFunction) { + const tracingConfig = Stack.of(node).resolve(node.tracingConfig); + if (tracingConfig === aws_lambda.Tracing.ACTIVE) + return NagRuleCompliance.COMPLIANT; + return NagRuleCompliance.NON_COMPLIANT; + } + return NagRuleCompliance.NOT_APPLICABLE; + }, + 'name', + { value: parse(__filename).name } +); diff --git a/src/rules/lambda/index.ts b/src/rules/lambda/index.ts index 92ce59d3e4..ef3a8a74db 100644 --- a/src/rules/lambda/index.ts +++ b/src/rules/lambda/index.ts @@ -9,4 +9,4 @@ export { default as LambdaFunctionPublicAccessProhibited } from './LambdaFunctio export { default as LambdaFunctionUrlAuth } from './LambdaFunctionUrlAuth'; export { default as LambdaInsideVPC } from './LambdaInsideVPC'; export { default as LambdaLatestVersion } from './LambdaLatestVersion'; -export { default as LambdaLogging } from './LambdaLogging' +export { default as LambdaLogLevel } from './LambdaLogLevel' diff --git a/src/rules/sns/SNSDeadLetterQueue.ts b/src/rules/sns/SNSDeadLetterQueue.ts new file mode 100644 index 0000000000..b6d139bde1 --- /dev/null +++ b/src/rules/sns/SNSDeadLetterQueue.ts @@ -0,0 +1,25 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ +import { parse } from 'path'; +import { CfnResource } from 'aws-cdk-lib'; +import { CfnSubscription } from 'aws-cdk-lib/aws-sns'; +import { NagRuleCompliance } from '../../nag-rules'; + +/** + * Ensure that API Gateway REST and HTTP APIs are using JSON structured logs + * @param node the CfnResource to check + */ +export default Object.defineProperty( + (node: CfnResource): NagRuleCompliance => { + if (node instanceof CfnSubscription) { + const redrivePolicy = node.redrivePolicy; + if (redrivePolicy) return NagRuleCompliance.COMPLIANT; + return NagRuleCompliance.NON_COMPLIANT; + } + return NagRuleCompliance.NOT_APPLICABLE; + }, + 'name', + { value: parse(__filename).name } +); diff --git a/src/rules/sns/index.ts b/src/rules/sns/index.ts index 4d45acee84..2eb744073d 100644 --- a/src/rules/sns/index.ts +++ b/src/rules/sns/index.ts @@ -4,3 +4,4 @@ SPDX-License-Identifier: Apache-2.0 */ export { default as SNSEncryptedKMS } from './SNSEncryptedKMS'; export { default as SNSTopicSSLPublishOnly } from './SNSTopicSSLPublishOnly'; +export { default as SNSDeadLetterQueue } from './SNSDeadLetterQueue'; diff --git a/test/rules/Lambda.test.ts b/test/rules/Lambda.test.ts index 2c658e95bf..bdb1eae08b 100644 --- a/test/rules/Lambda.test.ts +++ b/test/rules/Lambda.test.ts @@ -27,7 +27,7 @@ import { LambdaFunctionUrlAuth, LambdaInsideVPC, LambdaLatestVersion, - LambdaLogging, + LambdaLogLevel, } from '../../src/rules/lambda'; const testPack = new TestPack([ @@ -37,7 +37,7 @@ const testPack = new TestPack([ LambdaFunctionUrlAuth, LambdaInsideVPC, LambdaLatestVersion, - LambdaLogging, + LambdaLogLevel, ]); let stack: Stack; @@ -358,8 +358,8 @@ describe('AWS Lambda', () => { }); }); - describe('LambdaLogging: Ensure that Lambda functions have a corresponding Log Group', () => { - const ruleId = 'LambdaLogging'; + describe('LambdaLogLevel: Lambda functions have a explicit log level', () => { + const ruleId = 'LambdaLogLevel'; test('Noncompliance 1 - L1 Construct', () => { new CfnFunction(stack, 'Function', { code: {}, @@ -368,27 +368,37 @@ describe('AWS Lambda', () => { validateStack(stack, ruleId, TestType.NON_COMPLIANCE); }); - test('Noncompliance 2 - L2 Constructs', () => { + test('Noncompliance 2 - L2 Constructs missing ApplicationLogLevel', () => { new Function(stack, 'Function', { handler: '', runtime: Runtime.NODEJS_LATEST, code: Code.fromInline('console.log("hello world'), logFormat: LogFormat.JSON, - applicationLogLevel: ApplicationLogLevel.TRACE, systemLogLevel: SystemLogLevel.WARN }); validateStack(stack, ruleId, TestType.NON_COMPLIANCE); }); + test('Noncompliance 2 - L2 Constructs missing systemLogLevel', () => { + new Function(stack, 'Function', { + handler: '', + runtime: Runtime.NODEJS_LATEST, + code: Code.fromInline('console.log("hello world'), + logFormat: LogFormat.JSON, + applicationLogLevel: ApplicationLogLevel.TRACE, + }); + validateStack(stack, ruleId, TestType.NON_COMPLIANCE); + }); + test('Compliance - L1 Construct', () => { new CfnFunction(stack, 'Function', { code: {}, role: 'somerole', loggingConfig: { - applicationLogLevel: 'applicationLogLevel', + applicationLogLevel: 'WARN', logFormat: 'logFormat', logGroup: 'logGroup', - systemLogLevel: 'systemLogLevel', + systemLogLevel: 'DEBUG', }, }); validateStack(stack, ruleId, TestType.COMPLIANCE); @@ -402,7 +412,6 @@ describe('AWS Lambda', () => { logFormat: LogFormat.JSON, applicationLogLevel: ApplicationLogLevel.TRACE, systemLogLevel: SystemLogLevel.WARN, - logGroup: new LogGroup(stack, 'LogGroup'), }); validateStack(stack, ruleId, TestType.COMPLIANCE); }); From 9211f0309b039e907a622b0f39d85d8d2e58ef92 Mon Sep 17 00:00:00 2001 From: kevinwochan Date: Mon, 17 Jun 2024 14:20:02 +1000 Subject: [PATCH 09/42] feat: LambdaLogLevels rules --- RULES.md | 2 +- test/rules/Lambda.test.ts | 23 ++++++++++++++++------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/RULES.md b/RULES.md index 4b942e7175..9647943c26 100644 --- a/RULES.md +++ b/RULES.md @@ -702,7 +702,7 @@ A collection of community rules that are not currently included in any of the pr | Rule ID | Cause | Explanation | | --------------------- | ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | LambdaFunctionUrlAuth | The Lambda Function URL allows for public, unauthenticated access. | AWS Lambda Function URLs allow you to invoke your function via a HTTPS end-point, setting the authentication to NONE allows anyone on the internet to invoke your function. | -| LambdaLogging | The Lambda Function does not define a corresponding log group | Ensure that Lambda functions have a corresponding Log Group | +| LambdaLogLevel | The Lambda Function does not define an explicit Log Level | For cost optimization purposes, you should explicitly define the required log level for cost effective storage of Lambda Logs. | ## Footnotes diff --git a/test/rules/Lambda.test.ts b/test/rules/Lambda.test.ts index 2c658e95bf..175869815b 100644 --- a/test/rules/Lambda.test.ts +++ b/test/rules/Lambda.test.ts @@ -358,8 +358,8 @@ describe('AWS Lambda', () => { }); }); - describe('LambdaLogging: Ensure that Lambda functions have a corresponding Log Group', () => { - const ruleId = 'LambdaLogging'; + describe('LambdaLogLevel: Lambda functions have a explicit log level', () => { + const ruleId = 'LambdaLogLevel'; test('Noncompliance 1 - L1 Construct', () => { new CfnFunction(stack, 'Function', { code: {}, @@ -368,27 +368,37 @@ describe('AWS Lambda', () => { validateStack(stack, ruleId, TestType.NON_COMPLIANCE); }); - test('Noncompliance 2 - L2 Constructs', () => { + test('Noncompliance 2 - L2 Constructs missing ApplicationLogLevel', () => { new Function(stack, 'Function', { handler: '', runtime: Runtime.NODEJS_LATEST, code: Code.fromInline('console.log("hello world'), logFormat: LogFormat.JSON, - applicationLogLevel: ApplicationLogLevel.TRACE, systemLogLevel: SystemLogLevel.WARN }); validateStack(stack, ruleId, TestType.NON_COMPLIANCE); }); + test('Noncompliance 2 - L2 Constructs missing systemLogLevel', () => { + new Function(stack, 'Function', { + handler: '', + runtime: Runtime.NODEJS_LATEST, + code: Code.fromInline('console.log("hello world'), + logFormat: LogFormat.JSON, + applicationLogLevel: ApplicationLogLevel.TRACE, + }); + validateStack(stack, ruleId, TestType.NON_COMPLIANCE); + }); + test('Compliance - L1 Construct', () => { new CfnFunction(stack, 'Function', { code: {}, role: 'somerole', loggingConfig: { - applicationLogLevel: 'applicationLogLevel', + applicationLogLevel: 'WARN', logFormat: 'logFormat', logGroup: 'logGroup', - systemLogLevel: 'systemLogLevel', + systemLogLevel: 'DEBUG', }, }); validateStack(stack, ruleId, TestType.COMPLIANCE); @@ -402,7 +412,6 @@ describe('AWS Lambda', () => { logFormat: LogFormat.JSON, applicationLogLevel: ApplicationLogLevel.TRACE, systemLogLevel: SystemLogLevel.WARN, - logGroup: new LogGroup(stack, 'LogGroup'), }); validateStack(stack, ruleId, TestType.COMPLIANCE); }); From 4fb7e60592bdc3a5b29337c9c5ad222fab57f658 Mon Sep 17 00:00:00 2001 From: kevinwochan Date: Mon, 29 Apr 2024 14:21:48 +1000 Subject: [PATCH 10/42] feat: lambda logging rules Co-authored-by: Arun Donti --- RULES.md | 1 + src/rules/lambda/LambdaLogging.ts | 26 +++++++++++++ src/rules/lambda/index.ts | 1 + test/rules/Lambda.test.ts | 65 +++++++++++++++++++++++++++++++ 4 files changed, 93 insertions(+) create mode 100644 src/rules/lambda/LambdaLogging.ts diff --git a/RULES.md b/RULES.md index 5d28cd202d..07149f1d9c 100644 --- a/RULES.md +++ b/RULES.md @@ -702,6 +702,7 @@ A collection of community rules that are not currently included in any of the pr | Rule ID | Cause | Explanation | | --------------------- | ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | LambdaFunctionUrlAuth | The Lambda Function URL allows for public, unauthenticated access. | AWS Lambda Function URLs allow you to invoke your function via a HTTPS end-point, setting the authentication to NONE allows anyone on the internet to invoke your function. | +| LambdaLogging | The Lambda Function does not define an explicit Log Level | For cost optimization purposes, you should explicitly define the required log level for cost effective storage of Lambda Logs. | ## Footnotes diff --git a/src/rules/lambda/LambdaLogging.ts b/src/rules/lambda/LambdaLogging.ts new file mode 100644 index 0000000000..54c4c7b85c --- /dev/null +++ b/src/rules/lambda/LambdaLogging.ts @@ -0,0 +1,26 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ +import { parse } from 'path'; +import { CfnResource, Stack } from 'aws-cdk-lib'; +import { CfnFunction } from 'aws-cdk-lib/aws-lambda'; +import { NagRuleCompliance } from '../../nag-rules'; + +/** + * Lambda functions explicitly define their CloudWatch Log Groups + * @param node the CfnResource to check + */ +export default Object.defineProperty( + (node: CfnResource): NagRuleCompliance => { + if (node instanceof CfnFunction) { + const loggingConfig = Stack.of(node).resolve(node.loggingConfig); + if (loggingConfig && loggingConfig.logGroup) + return NagRuleCompliance.COMPLIANT; + return NagRuleCompliance.NON_COMPLIANT; + } + return NagRuleCompliance.NOT_APPLICABLE; + }, + 'name', + { value: parse(__filename).name } +); diff --git a/src/rules/lambda/index.ts b/src/rules/lambda/index.ts index 8d96afe93e..92ce59d3e4 100644 --- a/src/rules/lambda/index.ts +++ b/src/rules/lambda/index.ts @@ -9,3 +9,4 @@ export { default as LambdaFunctionPublicAccessProhibited } from './LambdaFunctio export { default as LambdaFunctionUrlAuth } from './LambdaFunctionUrlAuth'; export { default as LambdaInsideVPC } from './LambdaInsideVPC'; export { default as LambdaLatestVersion } from './LambdaLatestVersion'; +export { default as LambdaLogging } from './LambdaLogging' diff --git a/test/rules/Lambda.test.ts b/test/rules/Lambda.test.ts index 1ef1b8c79b..9764e7fd1e 100644 --- a/test/rules/Lambda.test.ts +++ b/test/rules/Lambda.test.ts @@ -14,7 +14,11 @@ import { Function, FunctionUrlAuthType, Runtime, + LogFormat, + SystemLogLevel, + ApplicationLogLevel, } from 'aws-cdk-lib/aws-lambda'; +import { LogGroup } from 'aws-cdk-lib/aws-logs'; import { TestPack, TestType, validateStack } from './utils'; import { LambdaConcurrency, @@ -23,6 +27,7 @@ import { LambdaFunctionUrlAuth, LambdaInsideVPC, LambdaLatestVersion, + LambdaLogging, } from '../../src/rules/lambda'; const testPack = new TestPack([ @@ -32,6 +37,7 @@ const testPack = new TestPack([ LambdaFunctionUrlAuth, LambdaInsideVPC, LambdaLatestVersion, + LambdaLogging, ]); let stack: Stack; @@ -351,4 +357,63 @@ describe('AWS Lambda', () => { validateStack(stack, ruleId, TestType.VALIDATION_FAILURE); }); }); + + describe('LambdaLogging: Lambda functions have a explicit log level', () => { + const ruleId = 'LambdaLogging'; + test('Noncompliance 1 - L1 Construct', () => { + new CfnFunction(stack, 'Function', { + code: {}, + role: 'somerole', + }); + validateStack(stack, ruleId, TestType.NON_COMPLIANCE); + }); + + test('Noncompliance 2 - L2 Constructs missing ApplicationLogLevel', () => { + new Function(stack, 'Function', { + handler: '', + runtime: Runtime.NODEJS_LATEST, + code: Code.fromInline('console.log("hello world'), + logFormat: LogFormat.JSON, + systemLogLevel: SystemLogLevel.WARN + }); + validateStack(stack, ruleId, TestType.NON_COMPLIANCE); + }); + + test('Noncompliance 2 - L2 Constructs missing systemLogLevel', () => { + new Function(stack, 'Function', { + handler: '', + runtime: Runtime.NODEJS_LATEST, + code: Code.fromInline('console.log("hello world'), + logFormat: LogFormat.JSON, + applicationLogLevel: ApplicationLogLevel.TRACE, + }); + validateStack(stack, ruleId, TestType.NON_COMPLIANCE); + }); + + test('Compliance - L1 Construct', () => { + new CfnFunction(stack, 'Function', { + code: {}, + role: 'somerole', + loggingConfig: { + applicationLogLevel: 'WARN', + logFormat: 'logFormat', + logGroup: 'logGroup', + systemLogLevel: 'DEBUG', + }, + }); + validateStack(stack, ruleId, TestType.COMPLIANCE); + }); + + test('Compliance 1 - L2 Constructs', () => { + new Function(stack, 'Function', { + handler: '', + runtime: Runtime.NODEJS_LATEST, + code: Code.fromInline('console.log("hello world'), + logFormat: LogFormat.JSON, + applicationLogLevel: ApplicationLogLevel.TRACE, + systemLogLevel: SystemLogLevel.WARN, + }); + validateStack(stack, ruleId, TestType.COMPLIANCE); + }); + }); }); From c9b8332df7b61052b595e64f2221514adb434fc5 Mon Sep 17 00:00:00 2001 From: kevinwochan Date: Thu, 29 Aug 2024 21:12:35 +1000 Subject: [PATCH 11/42] docs(serverless): added example of warning and error --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 30ce17098c..caf95d646c 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ See [RULES](./RULES.md) for more information on all the available packs. 3. [NIST 800-53 rev 4](./RULES.md#nist-800-53-rev-4) 4. [NIST 800-53 rev 5](./RULES.md#nist-800-53-rev-5) 5. [PCI DSS 3.2.1](./RULES.md#pci-dss-321) +6. [Serverless](./RULES.md#serverless) [RULES](./RULES.md) also includes a collection of [additional rules](./RULES.md#additional-rules) that are not currently included in any of the pre-built NagPacks, but are still available for inclusion in custom NagPacks. From 21ab1a2345b0f4143a6a69dbc577b30764ace6bd Mon Sep 17 00:00:00 2001 From: kevinwochan Date: Thu, 29 Aug 2024 21:13:14 +1000 Subject: [PATCH 12/42] docs(serverless): added example warning and error rules --- RULES.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/RULES.md b/RULES.md index 07149f1d9c..4713bdfe4f 100644 --- a/RULES.md +++ b/RULES.md @@ -702,7 +702,25 @@ A collection of community rules that are not currently included in any of the pr | Rule ID | Cause | Explanation | | --------------------- | ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | LambdaFunctionUrlAuth | The Lambda Function URL allows for public, unauthenticated access. | AWS Lambda Function URLs allow you to invoke your function via a HTTPS end-point, setting the authentication to NONE allows anyone on the internet to invoke your function. | -| LambdaLogging | The Lambda Function does not define an explicit Log Level | For cost optimization purposes, you should explicitly define the required log level for cost effective storage of Lambda Logs. | + + +## Serverless + +The [Serverless Rules](https://awslabs.github.io/serverless-rules/) are a compilation of rules to validate infrastructure-as-code template against recommended practices. + +### Warnings + +| Rule ID | Cause | Explanation | +| --------------------------------------------------------------------------------- | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | +| [LambdaTracing](https://awslabs.github.io/serverless-rules/rules/lambda/tracing/) | Lambda function does not have X-Ray tracing enabled. | AWS Lambda can emit traces to AWS X-Ray, which enables visualizing service maps for faster troubleshooting. | + + +### Errors + +| Rule ID | Cause | Explanation | +| ------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| [LambdaDefaultMemorySize](https://awslabs.github.io/serverless-rules/rules/lambda/default_memory_size/) | Lambda function does not specify a memory size. | Lambda CPU power and costs are proportional to the amount of memory configured. You can use tools such as AWS Lambda Power Tuning to test your function at different memory settings to find the one that matches your cost and performance requirements the best. | +| | ## Footnotes From 55144a9282f434324f25bcbb959111181c5e56ad Mon Sep 17 00:00:00 2001 From: kevinwochan Date: Thu, 29 Aug 2024 21:13:49 +1000 Subject: [PATCH 13/42] feat(serverless): remove lambda logging rule --- src/packs/serverless.ts | 394 +++++++++++++----------------- src/rules/lambda/LambdaLogging.ts | 26 -- src/rules/lambda/index.ts | 5 +- 3 files changed, 177 insertions(+), 248 deletions(-) delete mode 100644 src/rules/lambda/LambdaLogging.ts diff --git a/src/packs/serverless.ts b/src/packs/serverless.ts index c8e6ab4be9..3bc7c1b787 100644 --- a/src/packs/serverless.ts +++ b/src/packs/serverless.ts @@ -2,241 +2,193 @@ import { CfnResource } from 'aws-cdk-lib'; import { IConstruct } from 'constructs'; import { NagPack, NagPackProps } from '../nag-pack'; import { NagMessageLevel } from '../nag-rules'; -import { - APIGWAccessLogging, - APIGWXrayEnabled, - APIGWStructuredLogging, -} from '../rules/apigw'; -import { AppSyncTracing } from '../rules/appsync'; -import { CloudWatchLogGroupRetentionPeriod } from '../rules/cloudwatch'; -import { EventBusDLQ } from '../rules/eventbridge'; -import { - LambdaDLQ, - LambdaDefaultMemorySize, - LambdaLogLevel, - LambdaDefaultTimeout, - LambdaTracing, -} from '../rules/lambda'; -import { SNSDeadLetterQueue } from '../rules/sns'; -import { SQSQueueDLQ } from '../rules/sqs'; -import { - StepFunctionStateMachineAllLogsToCloudWatch, - StepFunctionStateMachineXray, -} from '../rules/stepfunctions'; +import { apigw, appsync, cloudwatch, eventbridge, lambda, sns, sqs, stepfunctions } from '../rules'; /** * Serverless Checks are a compilation of rules to validate infrastructure-as-code template against recommended practices. * */ export class ServerlessChecks extends NagPack { - constructor(props?: NagPackProps) { - super(props); - this.packName = 'Serverless'; - } - public visit(node: IConstruct): void { - if (node instanceof CfnResource) { - this.checkLambda(node); - this.checkCloudwatch(node); - this.checkApiGw(node); - this.checkAppSync(node); - this.checkEventBridge(node); - this.checkSNS(node); - this.checkSQS(node); - this.checkStepFunctions(node); + constructor(props?: NagPackProps) { + super(props); + this.packName = 'Serverless'; + } + public visit(node: IConstruct): void { + if (node instanceof CfnResource) { + this.checkLambda(node); + this.checkCloudwatch(node); + this.checkApiGw(node); + this.checkAppSync(node); + this.checkEventBridge(node); + this.checkSNS(node); + this.checkSQS(node); + this.checkStepFunctions(node); + } } - } - /** - * Check Lambda Resources - * @param node the CfnResource to check - * @param ignores list of ignores for the resource - */ - private checkLambda(node: CfnResource) { - this.applyRule({ - info: 'Ensure that Lambda functions have an explcity timeout value', - explanation: - 'Lambda functions have a default timeout of 3 seconds. If your timeout value is too short, Lambda might terminate invocations prematurely. On the other side, setting the timeout much higher than the average execution may cause functions to execute for longer upon code malfunction, resulting in higher costs and possibly reaching concurrency limits depending on how such functions are invoked. You can also use AWS Lambda Power Tuning to test your function at different timeout settings to find the one that matches your cost and performance requirements the best.', - level: NagMessageLevel.ERROR, - rule: LambdaDefaultTimeout, - node: node, - }); - this.applyRule({ - info: 'Ensure that Lambda functions have an explicit memory value', - explanation: - "Lambda allocates CPU power in proportion to the amount of memory configured. By default, your functions have 128 MB of memory allocated. You can increase that value up to 10 GB. With more CPU resources, your Lambda function's duration might decrease. You can use tools such as AWS Lambda Power Tuning to test your function at different memory settings to find the one that matches your cost and performance requirements the best.", - level: NagMessageLevel.ERROR, - rule: LambdaDefaultMemorySize, - node: node, - }); - this.applyRule({ - info: 'Ensure Lambda functions have a onFaliure destination configured', - explanation: - 'When a lambda function has a onFailure destination configured, failed messages can be temporarily stored to be later reviewed', - level: NagMessageLevel.ERROR, - rule: LambdaDLQ, - node: node, - }); - this.applyRule({ - info: 'The Lambda function should have tracing set to Tracing.ACTIVE', - explanation: - 'When a Lambda function has ACTIVE tracing, Lambda automatically samples invocation requests, based on the sampling algorithm specified by X-Ray.', - level: NagMessageLevel.ERROR, - rule: LambdaTracing, - node: node, - }); + /** + * Check Lambda Resources + * @param node the CfnResource to check + * @param ignores list of ignores for the resource + */ + private checkLambda(node: CfnResource) { + this.applyRule({ + info: 'The Lambda function should have tracing set to Tracing.ACTIVE', + explanation: + "When a Lambda function has ACTIVE tracing, Lambda automatically samples invocation requests, based on the sampling algorithm specified by X-Ray.", + level: NagMessageLevel.ERROR, + rule: lambda.LambdaTracing, + node: node, + }); + + this.applyRule({ + info: 'Ensure that Lambda functions have an explcity timeout value', + explanation: "Lambda functions have a default timeout of 3 seconds. If your timeout value is too short, Lambda might terminate invocations prematurely. On the other side, setting the timeout much higher than the average execution may cause functions to execute for longer upon code malfunction, resulting in higher costs and possibly reaching concurrency limits depending on how such functions are invoked. You can also use AWS Lambda Power Tuning to test your function at different timeout settings to find the one that matches your cost and performance requirements the best.", + level: NagMessageLevel.ERROR, + rule: lambda.LambdaDefaultTimeout, + node: node, + }); + this.applyRule({ + info: 'Ensure that Lambda functions have an explicit memory value', + explanation: "Lambda allocates CPU power in proportion to the amount of memory configured. By default, your functions have 128 MB of memory allocated. You can increase that value up to 10 GB. With more CPU resources, your Lambda function's duration might decrease. You can use tools such as AWS Lambda Power Tuning to test your function at different memory settings to find the one that matches your cost and performance requirements the best.", + level: NagMessageLevel.ERROR, + rule: lambda.LambdaDefaultMemorySize, + node: node, + }); + this.applyRule({ + info: 'Ensure Lambda functions have a onFaliure destination configured', + explanation: 'When a lambda function has a onFailure destination configured, failed messages can be temporarily stored to be later reviewed', + level: NagMessageLevel.ERROR, + rule: lambda.LambdaDLQ, + node: node, + }) - this.applyRule({ - info: 'Ensure that Lambda functions have a corresponding Log Group', - explanation: - 'Lambda captures logs for all requests handled by your function and sends them to Amazon CloudWatch Logs. You can insert logging statements into your code to help you validate that your code is working as expected. Lambda sends all logs from your code to the CloudWatch logs group associated with a Lambda function.', - level: NagMessageLevel.ERROR, - rule: LambdaLogLevel, - node: node, - }); + } - this.applyRule({ - info: 'Ensure that Lambda functions have a defined DLQ', - explanation: - 'When a Dead Letter Queue (DLQ) is specified, messages that fail to deliver to targets are stored in the Dead Letter Queue', - level: NagMessageLevel.ERROR, - rule: LambdaDLQ, - node: node, - }); - } + /** + * Check Cloudwatch Resources + * @param node the CfnResource to check + * @param ignores list of ignores for the resource + */ + private checkCloudwatch(node: CfnResource) { + this.applyRule({ + info: 'Ensure that CloudWatch Log Groups have an explcity retention policy', + explanation: "By default, logs are kept indefinitely and never expire. You can adjust the retention policy for each log group, keeping the indefinite retention, or choosing a retention period between 10 years and one day.", + level: NagMessageLevel.ERROR, + rule: cloudwatch.CloudWatchLogGroupRetentionPeriod, + node: node, + }); + } - /** - * Check Cloudwatch Resources - * @param node the CfnResource to check - * @param ignores list of ignores for the resource - */ - private checkCloudwatch(node: CfnResource) { - this.applyRule({ - info: 'Ensure that CloudWatch Log Groups have an explcity retention policy', - explanation: - 'By default, logs are kept indefinitely and never expire. You can adjust the retention policy for each log group, keeping the indefinite retention, or choosing a retention period between 10 years and one day.', - level: NagMessageLevel.ERROR, - rule: CloudWatchLogGroupRetentionPeriod, - node: node, - }); - } + /** + * Check API Gateway Resources + * @param node the CfnResource to check + * @param ignores list of ignores for the resource + */ + private checkApiGw(node: CfnResource) { + this.applyRule({ + info: 'Ensure tracing is enabled', + explanation: "Amazon API Gateway provides active tracing support for AWS X-Ray. Enable active tracing on your API stages to sample incoming requests and send traces to X-Ray.", + level: NagMessageLevel.ERROR, + rule: apigw.APIGWXrayEnabled, + node: node, + }); + this.applyRule({ + info: 'Ensure API Gateway stages have access logging enabled', + explanation: "API Gateway provides access logging for API stages. Enable access logging on your API stages to monitor API requests and responses.", + level: NagMessageLevel.ERROR, + rule: apigw.APIGWAccessLogging, + node: node, + }); + this.applyRule({ + info: 'Ensure API Gateway stages have access logging enabled', + explanation: "API Gateway provides access logging for API stages. Enable access logging on your API stages to monitor API requests and responses.", + level: NagMessageLevel.ERROR, + rule: apigw.APIGWStructuredLogging, + node: node, + }); + } - /** - * Check API Gateway Resources - * @param node the CfnResource to check - * @param ignores list of ignores for the resource - */ - private checkApiGw(node: CfnResource) { - this.applyRule({ - info: 'Ensure tracing is enabled', - explanation: - 'Amazon API Gateway provides active tracing support for AWS X-Ray. Enable active tracing on your API stages to sample incoming requests and send traces to X-Ray.', - level: NagMessageLevel.ERROR, - rule: APIGWXrayEnabled, - node: node, - }); - this.applyRule({ - info: 'Ensure API Gateway stages have access logging enabled', - explanation: - 'API Gateway provides access logging for API stages. Enable access logging on your API stages to monitor API requests and responses.', - level: NagMessageLevel.ERROR, - rule: APIGWAccessLogging, - node: node, - }); - this.applyRule({ - info: 'Ensure API Gateway stages have access logging enabled', - explanation: - 'API Gateway provides access logging for API stages. Enable access logging on your API stages to monitor API requests and responses.', - level: NagMessageLevel.ERROR, - rule: APIGWStructuredLogging, - node: node, - }); - } + /** + * Check AppSync Resources + * @param node the CfnResource to check + * @param ignores list of ignores for the resource + */ + private checkAppSync(node: CfnResource) { + this.applyRule({ + info: 'Ensure tracing is enabled', + explanation: "AWS AppSync provides active tracing support for AWS X-Ray. Enable active tracing on your API stages to sample incoming requests and send traces to X-Ray.", + level: NagMessageLevel.ERROR, + rule: appsync.AppSyncTracing, + node: node, + }); + } - /** - * Check AppSync Resources - * @param node the CfnResource to check - * @param ignores list of ignores for the resource - */ - private checkAppSync(node: CfnResource) { - this.applyRule({ - info: 'Ensure tracing is enabled', - explanation: - 'AWS AppSync provides active tracing support for AWS X-Ray. Enable active tracing on your API stages to sample incoming requests and send traces to X-Ray.', - level: NagMessageLevel.ERROR, - rule: AppSyncTracing, - node: node, - }); - } - /** - * Check EventBridge Resources - * @param node the CfnResource to check - * @param ignores list of ignores for the resource - */ - private checkEventBridge(node: CfnResource) { - this.applyRule({ - info: 'Ensure eventbridge targets have a DLQ configured', - explanation: - 'When a Dead Letter Queue (DLQ) is specified, messages that fail to deliver to targets are stored in the Dead Letter Queue', - level: NagMessageLevel.ERROR, - rule: EventBusDLQ, - node: node, - }); - } + /** + * Check EventBridge Resources + * @param node the CfnResource to check + * @param ignores list of ignores for the resource + */ + private checkEventBridge(node: CfnResource) { + this.applyRule({ + info: 'Ensure eventbridge targets have a DLQ configured', + explanation: "When a Dead Letter Queue (DLQ) is specified, messages that fail to deliver to targets are stored in the Dead Letter Queue", + level: NagMessageLevel.ERROR, + rule: eventbridge.EventBusDLQ, + node: node, + }); + } - /** - * Check SNS Resources - * @param node the CfnResource to check - * @param ignores list of ignores for the resource - */ - private checkSNS(node: CfnResource) { - this.applyRule({ - info: 'Ensure SNS subscriptions have a DLQ configured', - explanation: - 'When a Dead Letter Queue (DLQ) is specified, messages that fail to deliver to targets are stored in the Dead Letter Queue', - level: NagMessageLevel.ERROR, - rule: SNSDeadLetterQueue, - node: node, - }); - } + /** + * Check SNS Resources + * @param node the CfnResource to check + * @param ignores list of ignores for the resource + */ + private checkSNS(node: CfnResource) { + this.applyRule({ + info: 'Ensure SNS subscriptions have a DLQ configured', + explanation: "When a Dead Letter Queue (DLQ) is specified, messages that fail to deliver to targets are stored in the Dead Letter Queue", + level: NagMessageLevel.ERROR, + rule: sns.SNSDeadLetterQueue, + node: node, + }); + } - /** - * Check SQS Resources - * @param node the CfnResource to check - * @param ignores list of ignores for the resource - */ - private checkSQS(node: CfnResource) { - this.applyRule({ - info: 'Ensure SQS queues have a DLQ configured', - explanation: - 'When a Dead Letter Queue (DLQ) is specified, messages that fail to deliver to targets are stored in the Dead Letter Queue', - level: NagMessageLevel.ERROR, - rule: SQSQueueDLQ, - node: node, - }); - } + /** + * Check SQS Resources + * @param node the CfnResource to check + * @param ignores list of ignores for the resource + */ + private checkSQS(node: CfnResource) { + this.applyRule({ + info: 'Ensure SQS queues have a DLQ configured', + explanation: "When a Dead Letter Queue (DLQ) is specified, messages that fail to deliver to targets are stored in the Dead Letter Queue", + level: NagMessageLevel.ERROR, + rule: sqs.SQSQueueDLQ, + node: node, + }); + } - /** - * Check StepFunctions Resources - * @param node the CfnResource to check - * @param ignores list of ignores for the resource - */ - private checkStepFunctions(node: CfnResource) { - this.applyRule({ - info: 'Ensure StepFunctions have a DLQ configured', - explanation: - 'AWS StepFunctions provides active tracing support for AWS X-Ray. Enable active tracing on your API stages to sample incoming requests and send traces to X-Ray.', - level: NagMessageLevel.ERROR, - rule: StepFunctionStateMachineXray, - node: node, - }); - this.applyRule({ - info: 'Ensure Stepfunctions have been configured to log all events to cloudwatch', - explanation: - 'When a logging configuration is configured, errors can be captured in CLoudWatch Logs', - level: NagMessageLevel.ERROR, - rule: StepFunctionStateMachineAllLogsToCloudWatch, - node: node, - }); - } -} + /** + * Check StepFunctions Resources + * @param node the CfnResource to check + * @param ignores list of ignores for the resource + */ + private checkStepFunctions(node: CfnResource) { + this.applyRule({ + info: 'Ensure StepFunctions have a DLQ configured', + explanation: "AWS StepFunctions provides active tracing support for AWS X-Ray. Enable active tracing on your API stages to sample incoming requests and send traces to X-Ray.", + level: NagMessageLevel.ERROR, + rule: stepfunctions.StepFunctionStateMachineXray, + node: node, + }); + this.applyRule({ + info: 'Ensure Stepfunctions have been configured to log all events to cloudwatch', + explanation: "When a logging configuration is configured, errors can be captured in CLoudWatch Logs", + level: NagMessageLevel.ERROR, + rule: stepfunctions.StepFunctionStateMachineAllLogsToCloudWatch, + node: node, + }) + } +} \ No newline at end of file diff --git a/src/rules/lambda/LambdaLogging.ts b/src/rules/lambda/LambdaLogging.ts deleted file mode 100644 index 54c4c7b85c..0000000000 --- a/src/rules/lambda/LambdaLogging.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* -Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -SPDX-License-Identifier: Apache-2.0 -*/ -import { parse } from 'path'; -import { CfnResource, Stack } from 'aws-cdk-lib'; -import { CfnFunction } from 'aws-cdk-lib/aws-lambda'; -import { NagRuleCompliance } from '../../nag-rules'; - -/** - * Lambda functions explicitly define their CloudWatch Log Groups - * @param node the CfnResource to check - */ -export default Object.defineProperty( - (node: CfnResource): NagRuleCompliance => { - if (node instanceof CfnFunction) { - const loggingConfig = Stack.of(node).resolve(node.loggingConfig); - if (loggingConfig && loggingConfig.logGroup) - return NagRuleCompliance.COMPLIANT; - return NagRuleCompliance.NON_COMPLIANT; - } - return NagRuleCompliance.NOT_APPLICABLE; - }, - 'name', - { value: parse(__filename).name } -); diff --git a/src/rules/lambda/index.ts b/src/rules/lambda/index.ts index 92ce59d3e4..7e630e5b79 100644 --- a/src/rules/lambda/index.ts +++ b/src/rules/lambda/index.ts @@ -4,9 +4,12 @@ SPDX-License-Identifier: Apache-2.0 */ export { default as LambdaConcurrency } from './LambdaConcurrency'; +export { default as LambdaDefaultMemorySize } from './LambdaDefaultMemorySize'; +export { default as LambdaDefaultTimeout } from './LambdaDefaultTimeout'; export { default as LambdaDLQ } from './LambdaDLQ'; export { default as LambdaFunctionPublicAccessProhibited } from './LambdaFunctionPublicAccessProhibited'; export { default as LambdaFunctionUrlAuth } from './LambdaFunctionUrlAuth'; export { default as LambdaInsideVPC } from './LambdaInsideVPC'; export { default as LambdaLatestVersion } from './LambdaLatestVersion'; -export { default as LambdaLogging } from './LambdaLogging' +export { default as LambdaTracing } from './LambdaTracing'; + From f8a47b9c0cdf1553cdecd75d148db9f732d333fe Mon Sep 17 00:00:00 2001 From: kevinwochan Date: Thu, 29 Aug 2024 22:06:24 +1000 Subject: [PATCH 14/42] feat(serverless): remove lambda logging rule --- test/rules/Lambda.test.ts | 63 +-------------------------------------- 1 file changed, 1 insertion(+), 62 deletions(-) diff --git a/test/rules/Lambda.test.ts b/test/rules/Lambda.test.ts index 22c81c54b0..edcc400dfd 100644 --- a/test/rules/Lambda.test.ts +++ b/test/rules/Lambda.test.ts @@ -5,7 +5,6 @@ SPDX-License-Identifier: Apache-2.0 import { Aspects, Stack } from 'aws-cdk-lib'; import { Repository } from 'aws-cdk-lib/aws-ecr'; import { - ApplicationLogLevel, CfnFunction, CfnPermission, CfnUrl, @@ -14,9 +13,7 @@ import { DockerImageFunction, Function, FunctionUrlAuthType, - LogFormat, - Runtime, - SystemLogLevel, + Runtime } from 'aws-cdk-lib/aws-lambda'; import { LambdaConcurrency, @@ -357,62 +354,4 @@ describe('AWS Lambda', () => { }); }); - describe('LambdaLogging: Lambda functions have a explicit log level', () => { - const ruleId = 'LambdaLogging'; - test('Noncompliance 1 - L1 Construct', () => { - new CfnFunction(stack, 'Function', { - code: {}, - role: 'somerole', - }); - validateStack(stack, ruleId, TestType.NON_COMPLIANCE); - }); - - test('Noncompliance 2 - L2 Constructs missing ApplicationLogLevel', () => { - new Function(stack, 'Function', { - handler: '', - runtime: Runtime.NODEJS_LATEST, - code: Code.fromInline('console.log("hello world'), - logFormat: LogFormat.JSON, - systemLogLevel: SystemLogLevel.WARN - }); - validateStack(stack, ruleId, TestType.NON_COMPLIANCE); - }); - - test('Noncompliance 2 - L2 Constructs missing systemLogLevel', () => { - new Function(stack, 'Function', { - handler: '', - runtime: Runtime.NODEJS_LATEST, - code: Code.fromInline('console.log("hello world'), - logFormat: LogFormat.JSON, - applicationLogLevel: ApplicationLogLevel.TRACE, - }); - validateStack(stack, ruleId, TestType.NON_COMPLIANCE); - }); - - test('Compliance - L1 Construct', () => { - new CfnFunction(stack, 'Function', { - code: {}, - role: 'somerole', - loggingConfig: { - applicationLogLevel: 'WARN', - logFormat: 'logFormat', - logGroup: 'logGroup', - systemLogLevel: 'DEBUG', - }, - }); - validateStack(stack, ruleId, TestType.COMPLIANCE); - }); - - test('Compliance 1 - L2 Constructs', () => { - new Function(stack, 'Function', { - handler: '', - runtime: Runtime.NODEJS_LATEST, - code: Code.fromInline('console.log("hello world'), - logFormat: LogFormat.JSON, - applicationLogLevel: ApplicationLogLevel.TRACE, - systemLogLevel: SystemLogLevel.WARN, - }); - validateStack(stack, ruleId, TestType.COMPLIANCE); - }); - }); }); From 0ff3d12fb0574d3bfbcc86f13a232d66c68a5dc6 Mon Sep 17 00:00:00 2001 From: kevinwochan Date: Thu, 29 Aug 2024 22:27:37 +1000 Subject: [PATCH 15/42] feat(serverless): unit tests for lambda xray tracing --- test/rules/Lambda.test.ts | 77 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 3 deletions(-) diff --git a/test/rules/Lambda.test.ts b/test/rules/Lambda.test.ts index edcc400dfd..486e000ba0 100644 --- a/test/rules/Lambda.test.ts +++ b/test/rules/Lambda.test.ts @@ -13,8 +13,10 @@ import { DockerImageFunction, Function, FunctionUrlAuthType, - Runtime + Runtime, + Tracing, } from 'aws-cdk-lib/aws-lambda'; +import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs'; import { LambdaConcurrency, LambdaDLQ, @@ -22,7 +24,7 @@ import { LambdaFunctionUrlAuth, LambdaInsideVPC, LambdaLatestVersion, - LambdaLogging + LambdaTracing, } from '../../src/rules/lambda'; import { TestPack, TestType, validateStack } from './utils'; @@ -33,7 +35,7 @@ const testPack = new TestPack([ LambdaFunctionUrlAuth, LambdaInsideVPC, LambdaLatestVersion, - LambdaLogging, + LambdaTracing, ]); let stack: Stack; @@ -354,4 +356,73 @@ describe('AWS Lambda', () => { }); }); + describe('LambdaTracing: Lambda functions have X-Ray tracing enabled', () => { + const ruleId = 'LambdaTracing'; + + test('Noncompliance 1 - Tracing not configured', () => { + new CfnFunction(stack, 'rFunction', { + code: {}, + role: 'somerole', + }); + validateStack(stack, ruleId, TestType.NON_COMPLIANCE); + }); + + test('Noncompliance 2 - Tracing disabled', () => { + new CfnFunction(stack, 'rFunction', { + code: {}, + role: 'somerole', + tracingConfig: { + mode: 'PassThrough', + }, + }); + validateStack(stack, ruleId, TestType.NON_COMPLIANCE); + }); + + test('Compliance - Tracing enabled', () => { + new CfnFunction(stack, 'rFunction', { + code: {}, + role: 'somerole', + tracingConfig: { + mode: 'Active', + }, + }); + validateStack(stack, ruleId, TestType.COMPLIANCE); + }); + + test('Compliance - L2 construct with tracing enabled', () => { + new Function(stack, 'rFunction', { + runtime: Runtime.NODEJS_20_X, + code: Code.fromInline('exports.handler = async () => {};'), + handler: 'index.handler', + tracing: Tracing.ACTIVE, + }); + validateStack(stack, ruleId, TestType.COMPLIANCE); + }); + + test('Noncompliance 3 - L2 construct with tracing disabled', () => { + new Function(stack, 'rFunctionDisabled', { + runtime: Runtime.NODEJS_20_X, + code: Code.fromInline('exports.handler = async () => {};'), + handler: 'index.handler', + tracing: Tracing.DISABLED, + }); + validateStack(stack, ruleId, TestType.NON_COMPLIANCE); + }); + + test('Compliance - NodejsFunction with tracing enabled', () => { + new NodejsFunction(stack, 'rNodejsFunction', { + handler: 'handler', + tracing: Tracing.ACTIVE, + }); + validateStack(stack, ruleId, TestType.COMPLIANCE); + }); + + test('Noncompliance 4 - NodejsFunction with tracing disabled', () => { + new NodejsFunction(stack, 'rNodejsFunctionDisabled', { + handler: 'handler', + tracing: Tracing.DISABLED, + }); + validateStack(stack, ruleId, TestType.NON_COMPLIANCE); + }); + }); }); From 9b4cd49df3b1df6730c7537b3ade1f339865a67f Mon Sep 17 00:00:00 2001 From: kevinwochan Date: Thu, 29 Aug 2024 22:28:55 +1000 Subject: [PATCH 16/42] feat(serverless): updated LambdaTracing to use tracingConfig object --- src/rules/lambda/LambdaTracing.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/rules/lambda/LambdaTracing.ts b/src/rules/lambda/LambdaTracing.ts index f248400d5a..4db789b578 100644 --- a/src/rules/lambda/LambdaTracing.ts +++ b/src/rules/lambda/LambdaTracing.ts @@ -2,9 +2,9 @@ Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ -import { parse } from 'path'; -import { CfnResource, Stack, aws_lambda } from 'aws-cdk-lib'; +import { CfnResource, Stack } from 'aws-cdk-lib'; import { CfnFunction } from 'aws-cdk-lib/aws-lambda'; +import { parse } from 'path'; import { NagRuleCompliance } from '../../nag-rules'; /** @@ -14,9 +14,8 @@ import { NagRuleCompliance } from '../../nag-rules'; export default Object.defineProperty( (node: CfnResource): NagRuleCompliance => { if (node instanceof CfnFunction) { - const tracingConfig = Stack.of(node).resolve(node.tracingConfig); - if (tracingConfig === aws_lambda.Tracing.ACTIVE) - return NagRuleCompliance.COMPLIANT; + const tracingConfig = Stack.of(node).resolve(node.tracingConfig) as CfnFunction.TracingConfigProperty | undefined; + if (tracingConfig?.mode === 'Active') return NagRuleCompliance.COMPLIANT return NagRuleCompliance.NON_COMPLIANT; } return NagRuleCompliance.NOT_APPLICABLE; From d783d825eba2eb1013ecafc1e2af205e13029882 Mon Sep 17 00:00:00 2001 From: kevinwochan Date: Thu, 29 Aug 2024 22:32:05 +1000 Subject: [PATCH 17/42] docs(serverless): updated docs for LambdaTracing rule --- RULES.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/RULES.md b/RULES.md index 4713bdfe4f..b9849bcbeb 100644 --- a/RULES.md +++ b/RULES.md @@ -710,9 +710,9 @@ The [Serverless Rules](https://awslabs.github.io/serverless-rules/) are a compil ### Warnings -| Rule ID | Cause | Explanation | -| --------------------------------------------------------------------------------- | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | -| [LambdaTracing](https://awslabs.github.io/serverless-rules/rules/lambda/tracing/) | Lambda function does not have X-Ray tracing enabled. | AWS Lambda can emit traces to AWS X-Ray, which enables visualizing service maps for faster troubleshooting. | +| Rule ID | Cause | Explanation | +| --------------------------------------------------------------------------------- | ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [LambdaTracing](https://awslabs.github.io/serverless-rules/rules/lambda/tracing/) | Lambda function does not have X-Ray tracing enabled. | AWS Lambda can emit traces to AWS X-Ray, which enables visualizing service maps for faster troubleshooting. X-Ray tracing provides insights into the performance and behavior of your Lambda functions, helping to identify bottlenecks, errors, and dependencies. Enabling tracing allows you to track requests as they traverse through your serverless applications, making it easier to debug and optimize your functions. Consider enabling X-Ray tracing. | ### Errors From 872bdf769215f2218c6b38201fc69f8394347eab Mon Sep 17 00:00:00 2001 From: kevinwochan Date: Thu, 29 Aug 2024 23:58:42 +1000 Subject: [PATCH 18/42] feat(serverless): LambdaEventSourceMappingDestination unit tested --- .../LambdaEventSourceMappingDestination.ts | 24 ++++++ src/rules/lambda/index.ts | 1 + test/rules/Lambda.test.ts | 78 ++++++++++++++++--- 3 files changed, 93 insertions(+), 10 deletions(-) create mode 100644 src/rules/lambda/LambdaEventSourceMappingDestination.ts diff --git a/src/rules/lambda/LambdaEventSourceMappingDestination.ts b/src/rules/lambda/LambdaEventSourceMappingDestination.ts new file mode 100644 index 0000000000..64a9be279a --- /dev/null +++ b/src/rules/lambda/LambdaEventSourceMappingDestination.ts @@ -0,0 +1,24 @@ +import { CfnResource, Stack } from 'aws-cdk-lib'; +import { CfnEventSourceMapping } from 'aws-cdk-lib/aws-lambda'; +import { NagRuleCompliance } from '../../nag-rules'; + +/** + * Lambda event source mappings should have a failure destination configured + * + * @param node - The CfnResource to check + */ +export default function LambdaEventSourceMappingDestination( + node: CfnResource +): NagRuleCompliance { + if (node instanceof CfnEventSourceMapping) { + const destinationConfig = Stack.of(node).resolve(node.destinationConfig); + if ( + destinationConfig?.onFailure && + destinationConfig?.onFailure?.destination + ) + return NagRuleCompliance.COMPLIANT; + + return NagRuleCompliance.NON_COMPLIANT; + } + return NagRuleCompliance.NOT_APPLICABLE; +} diff --git a/src/rules/lambda/index.ts b/src/rules/lambda/index.ts index 7e630e5b79..ee6d4be8eb 100644 --- a/src/rules/lambda/index.ts +++ b/src/rules/lambda/index.ts @@ -7,6 +7,7 @@ export { default as LambdaConcurrency } from './LambdaConcurrency'; export { default as LambdaDefaultMemorySize } from './LambdaDefaultMemorySize'; export { default as LambdaDefaultTimeout } from './LambdaDefaultTimeout'; export { default as LambdaDLQ } from './LambdaDLQ'; +export { default as LambdaEventSourceMappingDestination } from './LambdaEventSourceMappingDestination'; export { default as LambdaFunctionPublicAccessProhibited } from './LambdaFunctionPublicAccessProhibited'; export { default as LambdaFunctionUrlAuth } from './LambdaFunctionUrlAuth'; export { default as LambdaInsideVPC } from './LambdaInsideVPC'; diff --git a/test/rules/Lambda.test.ts b/test/rules/Lambda.test.ts index 486e000ba0..0b070398e4 100644 --- a/test/rules/Lambda.test.ts +++ b/test/rules/Lambda.test.ts @@ -5,28 +5,32 @@ SPDX-License-Identifier: Apache-2.0 import { Aspects, Stack } from 'aws-cdk-lib'; import { Repository } from 'aws-cdk-lib/aws-ecr'; import { + CfnEventSourceMapping, CfnFunction, CfnPermission, CfnUrl, Code, DockerImageCode, DockerImageFunction, + EventSourceMapping, Function, FunctionUrlAuthType, Runtime, Tracing, } from 'aws-cdk-lib/aws-lambda'; -import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs'; +import { SqsDlq } from 'aws-cdk-lib/aws-lambda-event-sources'; +import { Queue } from 'aws-cdk-lib/aws-sqs'; +import { TestPack, TestType, validateStack } from './utils'; import { LambdaConcurrency, LambdaDLQ, + LambdaEventSourceMappingDestination, LambdaFunctionPublicAccessProhibited, LambdaFunctionUrlAuth, LambdaInsideVPC, LambdaLatestVersion, LambdaTracing, } from '../../src/rules/lambda'; -import { TestPack, TestType, validateStack } from './utils'; const testPack = new TestPack([ LambdaConcurrency, @@ -36,6 +40,7 @@ const testPack = new TestPack([ LambdaInsideVPC, LambdaLatestVersion, LambdaTracing, + LambdaEventSourceMappingDestination, ]); let stack: Stack; @@ -408,21 +413,74 @@ describe('AWS Lambda', () => { }); validateStack(stack, ruleId, TestType.NON_COMPLIANCE); }); + }); - test('Compliance - NodejsFunction with tracing enabled', () => { - new NodejsFunction(stack, 'rNodejsFunction', { - handler: 'handler', - tracing: Tracing.ACTIVE, + describe('LambdaEventSourceMappingDestination: Lambda event source mappings should have a failure destination configured', () => { + const ruleId = 'LambdaEventSourceMappingDestination'; + + test('Noncompliance 1 - No destinationConfig', () => { + new CfnEventSourceMapping(stack, 'rEventSourceMapping1', { + functionName: 'myFunction', + eventSourceArn: 'myEventSourceArn', + }); + validateStack(stack, ruleId, TestType.NON_COMPLIANCE); + }); + + test('Noncompliance 2 - onFailure without destination', () => { + new CfnEventSourceMapping(stack, 'rEventSourceMapping4', { + functionName: 'myFunction', + eventSourceArn: 'myEventSourceArn', + destinationConfig: { + onFailure: {}, + }, + }); + validateStack(stack, ruleId, TestType.NON_COMPLIANCE); + }); + + test('Compliance - Proper failure destination configured', () => { + new CfnEventSourceMapping(stack, 'rEventSourceMapping5', { + functionName: 'myFunction', + eventSourceArn: 'myEventSourceArn', + destinationConfig: { + onFailure: { + destination: 'arn:aws:sqs:us-east-1:123456789012:myQueue', + }, + }, }); validateStack(stack, ruleId, TestType.COMPLIANCE); }); - test('Noncompliance 4 - NodejsFunction with tracing disabled', () => { - new NodejsFunction(stack, 'rNodejsFunctionDisabled', { - handler: 'handler', - tracing: Tracing.DISABLED, + test('Noncompliance 3 - L2 construct without onFailure', () => { + const lambdaFunction = new Function(stack, 'MyFunction1', { + runtime: Runtime.NODEJS_20_X, + handler: 'index.handler', + code: Code.fromInline('exports.handler = async () => {};'), + }); + + new EventSourceMapping(stack, 'MyEventSourceMapping1', { + target: lambdaFunction, + eventSourceArn: 'arn:aws:sqs:us-east-1:123456789012:myQueue', }); + validateStack(stack, ruleId, TestType.NON_COMPLIANCE); }); + + test('Compliance - L2 construct with onFailure', () => { + const lambdaFunction = new Function(stack, 'MyFunction2', { + runtime: Runtime.NODEJS_20_X, + handler: 'index.handler', + code: Code.fromInline('exports.handler = async () => {};'), + }); + + const deadLetterQueue = new Queue(stack, 'DeadLetterQueue'); + + new EventSourceMapping(stack, 'MyEventSourceMapping2', { + target: lambdaFunction, + eventSourceArn: 'arn:aws:sqs:us-east-1:123456789012:myQueue', + onFailure: new SqsDlq(deadLetterQueue), + }); + + validateStack(stack, ruleId, TestType.COMPLIANCE); + }); }); }); From 9620a94166baf81c994148934cb13f82161ff43a Mon Sep 17 00:00:00 2001 From: kevinwochan Date: Fri, 30 Aug 2024 00:43:52 +1000 Subject: [PATCH 19/42] feat(serverless): added docs and checks to the serverless nag pack --- RULES.md | 13 +- src/packs/serverless.ts | 403 +++++++++++++++++++++++----------------- src/rules/lambda.ts | 0 3 files changed, 237 insertions(+), 179 deletions(-) create mode 100644 src/rules/lambda.ts diff --git a/RULES.md b/RULES.md index b9849bcbeb..46557775e1 100644 --- a/RULES.md +++ b/RULES.md @@ -710,17 +710,18 @@ The [Serverless Rules](https://awslabs.github.io/serverless-rules/) are a compil ### Warnings -| Rule ID | Cause | Explanation | -| --------------------------------------------------------------------------------- | ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Rule ID | Cause | Explanation | +| --------------------------------------------------------------------------------- | ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [LambdaTracing](https://awslabs.github.io/serverless-rules/rules/lambda/tracing/) | Lambda function does not have X-Ray tracing enabled. | AWS Lambda can emit traces to AWS X-Ray, which enables visualizing service maps for faster troubleshooting. X-Ray tracing provides insights into the performance and behavior of your Lambda functions, helping to identify bottlenecks, errors, and dependencies. Enabling tracing allows you to track requests as they traverse through your serverless applications, making it easier to debug and optimize your functions. Consider enabling X-Ray tracing. | ### Errors -| Rule ID | Cause | Explanation | -| ------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| [LambdaDefaultMemorySize](https://awslabs.github.io/serverless-rules/rules/lambda/default_memory_size/) | Lambda function does not specify a memory size. | Lambda CPU power and costs are proportional to the amount of memory configured. You can use tools such as AWS Lambda Power Tuning to test your function at different memory settings to find the one that matches your cost and performance requirements the best. | -| | +| Rule ID | Cause | Explanation | +| -------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [LambdaEventSourceMappingDestination](https://awslabs.github.io/serverless-rules/rules/lambda/eventsourcemapping_failure_destination/) | The Lambda Event Source Mapping does not have a destination configured for failed invocations. | Configuring a destination for failed invocations in Lambda Event Source Mappings allows you to capture and process events that fail to be processed by your Lambda function. This helps in monitoring, debugging, and implementing retry mechanisms for failed events, improving the reliability and observability of your serverless applications. | +| [LambdaDefaultMemorySize](https://awslabs.github.io/serverless-rules/rules/lambda/default_memory_size/) | Lambda function does not specify a memory size. | Lambda CPU power and costs are proportional to the amount of memory configured. You can use tools such as AWS Lambda Power Tuning to test your function at different memory settings to find the one that matches your cost and performance requirements the best. | +| | ## Footnotes diff --git a/src/packs/serverless.ts b/src/packs/serverless.ts index 3bc7c1b787..d59688fc45 100644 --- a/src/packs/serverless.ts +++ b/src/packs/serverless.ts @@ -2,193 +2,250 @@ import { CfnResource } from 'aws-cdk-lib'; import { IConstruct } from 'constructs'; import { NagPack, NagPackProps } from '../nag-pack'; import { NagMessageLevel } from '../nag-rules'; -import { apigw, appsync, cloudwatch, eventbridge, lambda, sns, sqs, stepfunctions } from '../rules'; +import { + apigw, + appsync, + cloudwatch, + eventbridge, + lambda, + sns, + sqs, + stepfunctions, +} from '../rules'; /** * Serverless Checks are a compilation of rules to validate infrastructure-as-code template against recommended practices. * */ export class ServerlessChecks extends NagPack { - constructor(props?: NagPackProps) { - super(props); - this.packName = 'Serverless'; - } - public visit(node: IConstruct): void { - if (node instanceof CfnResource) { - this.checkLambda(node); - this.checkCloudwatch(node); - this.checkApiGw(node); - this.checkAppSync(node); - this.checkEventBridge(node); - this.checkSNS(node); - this.checkSQS(node); - this.checkStepFunctions(node); - } + constructor(props?: NagPackProps) { + super(props); + this.packName = 'Serverless'; + } + public visit(node: IConstruct): void { + if (node instanceof CfnResource) { + this.checkLambda(node); + this.checkCloudwatch(node); + this.checkApiGw(node); + this.checkAppSync(node); + this.checkEventBridge(node); + this.checkSNS(node); + this.checkSQS(node); + this.checkStepFunctions(node); } + } - /** - * Check Lambda Resources - * @param node the CfnResource to check - * @param ignores list of ignores for the resource - */ - private checkLambda(node: CfnResource) { - this.applyRule({ - info: 'The Lambda function should have tracing set to Tracing.ACTIVE', - explanation: - "When a Lambda function has ACTIVE tracing, Lambda automatically samples invocation requests, based on the sampling algorithm specified by X-Ray.", - level: NagMessageLevel.ERROR, - rule: lambda.LambdaTracing, - node: node, - }); - - this.applyRule({ - info: 'Ensure that Lambda functions have an explcity timeout value', - explanation: "Lambda functions have a default timeout of 3 seconds. If your timeout value is too short, Lambda might terminate invocations prematurely. On the other side, setting the timeout much higher than the average execution may cause functions to execute for longer upon code malfunction, resulting in higher costs and possibly reaching concurrency limits depending on how such functions are invoked. You can also use AWS Lambda Power Tuning to test your function at different timeout settings to find the one that matches your cost and performance requirements the best.", - level: NagMessageLevel.ERROR, - rule: lambda.LambdaDefaultTimeout, - node: node, - }); - this.applyRule({ - info: 'Ensure that Lambda functions have an explicit memory value', - explanation: "Lambda allocates CPU power in proportion to the amount of memory configured. By default, your functions have 128 MB of memory allocated. You can increase that value up to 10 GB. With more CPU resources, your Lambda function's duration might decrease. You can use tools such as AWS Lambda Power Tuning to test your function at different memory settings to find the one that matches your cost and performance requirements the best.", - level: NagMessageLevel.ERROR, - rule: lambda.LambdaDefaultMemorySize, - node: node, - }); - this.applyRule({ - info: 'Ensure Lambda functions have a onFaliure destination configured', - explanation: 'When a lambda function has a onFailure destination configured, failed messages can be temporarily stored to be later reviewed', - level: NagMessageLevel.ERROR, - rule: lambda.LambdaDLQ, - node: node, - }) + /** + * Check Lambda Resources + * @param node the CfnResource to check + * @param ignores list of ignores for the resource + */ + private checkLambda(node: CfnResource) { + this.applyRule({ + info: 'The Lambda function should have tracing set to Tracing.ACTIVE', + explanation: + 'When a Lambda function has ACTIVE tracing, Lambda automatically samples invocation requests, based on the sampling algorithm specified by X-Ray.', + level: NagMessageLevel.WARN, + rule: lambda.LambdaTracing, + node: node, + }); - } + this.applyRule({ + info: 'Ensure Lambda Event Source Mappings have a destination configured for failed invocations', + explanation: + 'Configuring a destination for failed invocations in Lambda Event Source Mappings allows you to capture and process events that fail to be processed by your Lambda function. This helps in monitoring, debugging, and implementing retry mechanisms for failed events, improving the reliability and observability of your serverless applications.', + level: NagMessageLevel.ERROR, + rule: lambda.LambdaEventSourceMappingDestination, + node: node, + }); - /** - * Check Cloudwatch Resources - * @param node the CfnResource to check - * @param ignores list of ignores for the resource - */ - private checkCloudwatch(node: CfnResource) { - this.applyRule({ - info: 'Ensure that CloudWatch Log Groups have an explcity retention policy', - explanation: "By default, logs are kept indefinitely and never expire. You can adjust the retention policy for each log group, keeping the indefinite retention, or choosing a retention period between 10 years and one day.", - level: NagMessageLevel.ERROR, - rule: cloudwatch.CloudWatchLogGroupRetentionPeriod, - node: node, - }); - } - /** - * Check API Gateway Resources - * @param node the CfnResource to check - * @param ignores list of ignores for the resource - */ - private checkApiGw(node: CfnResource) { - this.applyRule({ - info: 'Ensure tracing is enabled', - explanation: "Amazon API Gateway provides active tracing support for AWS X-Ray. Enable active tracing on your API stages to sample incoming requests and send traces to X-Ray.", - level: NagMessageLevel.ERROR, - rule: apigw.APIGWXrayEnabled, - node: node, - }); - this.applyRule({ - info: 'Ensure API Gateway stages have access logging enabled', - explanation: "API Gateway provides access logging for API stages. Enable access logging on your API stages to monitor API requests and responses.", - level: NagMessageLevel.ERROR, - rule: apigw.APIGWAccessLogging, - node: node, - }); - this.applyRule({ - info: 'Ensure API Gateway stages have access logging enabled', - explanation: "API Gateway provides access logging for API stages. Enable access logging on your API stages to monitor API requests and responses.", - level: NagMessageLevel.ERROR, - rule: apigw.APIGWStructuredLogging, - node: node, - }); - } + this.applyRule({ + info: 'Ensure that Lambda functions have an explcity timeout value', + explanation: + 'Lambda functions have a default timeout of 3 seconds. If your timeout value is too short, Lambda might terminate invocations prematurely. On the other side, setting the timeout much higher than the average execution may cause functions to execute for longer upon code malfunction, resulting in higher costs and possibly reaching concurrency limits depending on how such functions are invoked. You can also use AWS Lambda Power Tuning to test your function at different timeout settings to find the one that matches your cost and performance requirements the best.', + level: NagMessageLevel.ERROR, + rule: lambda.LambdaDefaultTimeout, + node: node, + }); + this.applyRule({ + info: 'Ensure that Lambda functions have an explicit memory value', + explanation: + "Lambda allocates CPU power in proportion to the amount of memory configured. By default, your functions have 128 MB of memory allocated. You can increase that value up to 10 GB. With more CPU resources, your Lambda function's duration might decrease. You can use tools such as AWS Lambda Power Tuning to test your function at different memory settings to find the one that matches your cost and performance requirements the best.", + level: NagMessageLevel.ERROR, + rule: lambda.LambdaDefaultMemorySize, + node: node, + }); + this.applyRule({ + info: 'Ensure Lambda functions have a onFaliure destination configured', + explanation: + 'When a lambda function has a onFailure destination configured, failed messages can be temporarily stored to be later reviewed', + level: NagMessageLevel.ERROR, + rule: lambda.LambdaDLQ, + node: node, + }); - /** - * Check AppSync Resources - * @param node the CfnResource to check - * @param ignores list of ignores for the resource - */ - private checkAppSync(node: CfnResource) { - this.applyRule({ - info: 'Ensure tracing is enabled', - explanation: "AWS AppSync provides active tracing support for AWS X-Ray. Enable active tracing on your API stages to sample incoming requests and send traces to X-Ray.", - level: NagMessageLevel.ERROR, - rule: appsync.AppSyncTracing, - node: node, - }); - } + this.applyRule({ + info: 'Ensure Lambda functions are using the latest runtime version', + explanation: + 'Using the latest runtime version ensures that your Lambda function has access to the most recent features, performance improvements, and security updates. It\'s important to regularly update your Lambda functions to use the latest runtime versions to maintain optimal performance and security.', + level: NagMessageLevel.ERROR, + rule: lambda.LambdaLatestVersion, + node: node, + }); + this.applyRule({ + info: 'Ensure Lambda Event Source Mappings have a destination configured for failed invocations', + explanation: + 'Configuring a destination for failed invocations in Lambda Event Source Mappings allows you to capture and process events that fail to be processed by your Lambda function. This helps in monitoring, debugging, and implementing retry mechanisms for failed events, improving the reliability and observability of your serverless applications.', + level: NagMessageLevel.ERROR, + rule: lambda.LambdaEventSourceMappingDestination, + node: node, + }); - /** - * Check EventBridge Resources - * @param node the CfnResource to check - * @param ignores list of ignores for the resource - */ - private checkEventBridge(node: CfnResource) { - this.applyRule({ - info: 'Ensure eventbridge targets have a DLQ configured', - explanation: "When a Dead Letter Queue (DLQ) is specified, messages that fail to deliver to targets are stored in the Dead Letter Queue", - level: NagMessageLevel.ERROR, - rule: eventbridge.EventBusDLQ, - node: node, - }); - } + this.applyRule({ + info: 'Ensure Lambda Event Source Mappings have a destination configured for failed invocations', + explanation: + 'Configuring a destination for failed invocations in Lambda Event Source Mappings allows you to capture and process events that fail to be processed by your Lambda function. This helps in monitoring, debugging, and implementing retry mechanisms for failed events, improving the reliability and observability of your serverless applications.', + level: NagMessageLevel.ERROR, + rule: lambda.LambdaEventSourceMappingDestination, + node: node, + }); + } - /** - * Check SNS Resources - * @param node the CfnResource to check - * @param ignores list of ignores for the resource - */ - private checkSNS(node: CfnResource) { - this.applyRule({ - info: 'Ensure SNS subscriptions have a DLQ configured', - explanation: "When a Dead Letter Queue (DLQ) is specified, messages that fail to deliver to targets are stored in the Dead Letter Queue", - level: NagMessageLevel.ERROR, - rule: sns.SNSDeadLetterQueue, - node: node, - }); - } + /** + * Check Cloudwatch Resources + * @param node the CfnResource to check + * @param ignores list of ignores for the resource + */ + private checkCloudwatch(node: CfnResource) { + this.applyRule({ + info: 'Ensure that CloudWatch Log Groups have an explcity retention policy', + explanation: + 'By default, logs are kept indefinitely and never expire. You can adjust the retention policy for each log group, keeping the indefinite retention, or choosing a retention period between 10 years and one day.', + level: NagMessageLevel.ERROR, + rule: cloudwatch.CloudWatchLogGroupRetentionPeriod, + node: node, + }); + } - /** - * Check SQS Resources - * @param node the CfnResource to check - * @param ignores list of ignores for the resource - */ - private checkSQS(node: CfnResource) { - this.applyRule({ - info: 'Ensure SQS queues have a DLQ configured', - explanation: "When a Dead Letter Queue (DLQ) is specified, messages that fail to deliver to targets are stored in the Dead Letter Queue", - level: NagMessageLevel.ERROR, - rule: sqs.SQSQueueDLQ, - node: node, - }); - } + /** + * Check API Gateway Resources + * @param node the CfnResource to check + * @param ignores list of ignores for the resource + */ + private checkApiGw(node: CfnResource) { + this.applyRule({ + info: 'Ensure tracing is enabled', + explanation: + 'Amazon API Gateway provides active tracing support for AWS X-Ray. Enable active tracing on your API stages to sample incoming requests and send traces to X-Ray.', + level: NagMessageLevel.ERROR, + rule: apigw.APIGWXrayEnabled, + node: node, + }); + this.applyRule({ + info: 'Ensure API Gateway stages have access logging enabled', + explanation: + 'API Gateway provides access logging for API stages. Enable access logging on your API stages to monitor API requests and responses.', + level: NagMessageLevel.ERROR, + rule: apigw.APIGWAccessLogging, + node: node, + }); + this.applyRule({ + info: 'Ensure API Gateway stages have access logging enabled', + explanation: + 'API Gateway provides access logging for API stages. Enable access logging on your API stages to monitor API requests and responses.', + level: NagMessageLevel.ERROR, + rule: apigw.APIGWStructuredLogging, + node: node, + }); + } - /** - * Check StepFunctions Resources - * @param node the CfnResource to check - * @param ignores list of ignores for the resource - */ - private checkStepFunctions(node: CfnResource) { - this.applyRule({ - info: 'Ensure StepFunctions have a DLQ configured', - explanation: "AWS StepFunctions provides active tracing support for AWS X-Ray. Enable active tracing on your API stages to sample incoming requests and send traces to X-Ray.", - level: NagMessageLevel.ERROR, - rule: stepfunctions.StepFunctionStateMachineXray, - node: node, - }); - this.applyRule({ - info: 'Ensure Stepfunctions have been configured to log all events to cloudwatch', - explanation: "When a logging configuration is configured, errors can be captured in CLoudWatch Logs", - level: NagMessageLevel.ERROR, - rule: stepfunctions.StepFunctionStateMachineAllLogsToCloudWatch, - node: node, - }) - } -} \ No newline at end of file + /** + * Check AppSync Resources + * @param node the CfnResource to check + * @param ignores list of ignores for the resource + */ + private checkAppSync(node: CfnResource) { + this.applyRule({ + info: 'Ensure tracing is enabled', + explanation: + 'AWS AppSync provides active tracing support for AWS X-Ray. Enable active tracing on your API stages to sample incoming requests and send traces to X-Ray.', + level: NagMessageLevel.ERROR, + rule: appsync.AppSyncTracing, + node: node, + }); + } + + /** + * Check EventBridge Resources + * @param node the CfnResource to check + * @param ignores list of ignores for the resource + */ + private checkEventBridge(node: CfnResource) { + this.applyRule({ + info: 'Ensure eventbridge targets have a DLQ configured', + explanation: + 'When a Dead Letter Queue (DLQ) is specified, messages that fail to deliver to targets are stored in the Dead Letter Queue', + level: NagMessageLevel.ERROR, + rule: eventbridge.EventBusDLQ, + node: node, + }); + } + + /** + * Check SNS Resources + * @param node the CfnResource to check + * @param ignores list of ignores for the resource + */ + private checkSNS(node: CfnResource) { + this.applyRule({ + info: 'Ensure SNS subscriptions have a DLQ configured', + explanation: + 'When a Dead Letter Queue (DLQ) is specified, messages that fail to deliver to targets are stored in the Dead Letter Queue', + level: NagMessageLevel.ERROR, + rule: sns.SNSDeadLetterQueue, + node: node, + }); + } + + /** + * Check SQS Resources + * @param node the CfnResource to check + * @param ignores list of ignores for the resource + */ + private checkSQS(node: CfnResource) { + this.applyRule({ + info: 'Ensure SQS queues have a DLQ configured', + explanation: + 'When a Dead Letter Queue (DLQ) is specified, messages that fail to deliver to targets are stored in the Dead Letter Queue', + level: NagMessageLevel.ERROR, + rule: sqs.SQSQueueDLQ, + node: node, + }); + } + + /** + * Check StepFunctions Resources + * @param node the CfnResource to check + * @param ignores list of ignores for the resource + */ + private checkStepFunctions(node: CfnResource) { + this.applyRule({ + info: 'Ensure StepFunctions have a DLQ configured', + explanation: + 'AWS StepFunctions provides active tracing support for AWS X-Ray. Enable active tracing on your API stages to sample incoming requests and send traces to X-Ray.', + level: NagMessageLevel.ERROR, + rule: stepfunctions.StepFunctionStateMachineXray, + node: node, + }); + this.applyRule({ + info: 'Ensure Stepfunctions have been configured to log all events to cloudwatch', + explanation: + 'When a logging configuration is configured, errors can be captured in CLoudWatch Logs', + level: NagMessageLevel.ERROR, + rule: stepfunctions.StepFunctionStateMachineAllLogsToCloudWatch, + node: node, + }); + } +} diff --git a/src/rules/lambda.ts b/src/rules/lambda.ts new file mode 100644 index 0000000000..e69de29bb2 From 5d2cb8b9ce4361dc2c5637235448bbec92cf3f7d Mon Sep 17 00:00:00 2001 From: kevinwochan Date: Fri, 30 Aug 2024 00:59:48 +1000 Subject: [PATCH 20/42] feat(serverless): added iam wildcard checks --- RULES.md | 2 +- src/packs/serverless.ts | 22 ++++++++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/RULES.md b/RULES.md index 46557775e1..ccc242d431 100644 --- a/RULES.md +++ b/RULES.md @@ -713,7 +713,7 @@ The [Serverless Rules](https://awslabs.github.io/serverless-rules/) are a compil | Rule ID | Cause | Explanation | | --------------------------------------------------------------------------------- | ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [LambdaTracing](https://awslabs.github.io/serverless-rules/rules/lambda/tracing/) | Lambda function does not have X-Ray tracing enabled. | AWS Lambda can emit traces to AWS X-Ray, which enables visualizing service maps for faster troubleshooting. X-Ray tracing provides insights into the performance and behavior of your Lambda functions, helping to identify bottlenecks, errors, and dependencies. Enabling tracing allows you to track requests as they traverse through your serverless applications, making it easier to debug and optimize your functions. Consider enabling X-Ray tracing. | - +| [StarPermissions](https://awslabs.github.io/serverless-rules/rules/lambda/star_permissions/) | IAM role has overly permissive policies. | IAM roles should follow the principle of least privilege. Avoid using wildcard (*) permissions in IAM policies attached to Lambda roles. Instead, specify only the permissions required for the function to operate. This reduces the potential impact if the function is compromised. Review and tighten the IAM permissions for your Lambda functions. | ### Errors diff --git a/src/packs/serverless.ts b/src/packs/serverless.ts index d59688fc45..4a5a413a3b 100644 --- a/src/packs/serverless.ts +++ b/src/packs/serverless.ts @@ -7,10 +7,11 @@ import { appsync, cloudwatch, eventbridge, + iam, lambda, sns, sqs, - stepfunctions, + stepfunctions } from '../rules'; /** @@ -26,6 +27,7 @@ export class ServerlessChecks extends NagPack { if (node instanceof CfnResource) { this.checkLambda(node); this.checkCloudwatch(node); + this.checkIAM(node); this.checkApiGw(node); this.checkAppSync(node); this.checkEventBridge(node); @@ -35,6 +37,7 @@ export class ServerlessChecks extends NagPack { } } + /** * Check Lambda Resources * @param node the CfnResource to check @@ -59,7 +62,6 @@ export class ServerlessChecks extends NagPack { node: node, }); - this.applyRule({ info: 'Ensure that Lambda functions have an explcity timeout value', explanation: @@ -113,6 +115,22 @@ export class ServerlessChecks extends NagPack { }); } + /** + * Check Lambda Resources + * @param node the CfnResource to check + * @param ignores list of ignores for the resource + */ + private checkIAM(node: CfnResource) { + this.applyRule({ + info: 'Ensure Lambda functions do not have overly permissive IAM roles', + explanation: + 'Lambda functions should follow the principle of least privilege. Avoid using wildcard (*) permissions in IAM roles attached to Lambda functions. Instead, specify only the permissions required for the function to operate.', + level: NagMessageLevel.WARN, + rule: iam.IAMNoWildcardPermissions, + node: node, + }); + } + /** * Check Cloudwatch Resources * @param node the CfnResource to check From ce169fc5f1f37ccab2896d303e96f9a06dcc0a59 Mon Sep 17 00:00:00 2001 From: kevinwochan Date: Fri, 30 Aug 2024 01:06:32 +1000 Subject: [PATCH 21/42] feat(serverless): added cw logs retention checks --- RULES.md | 2 ++ src/packs/serverless.ts | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/RULES.md b/RULES.md index ccc242d431..b5d63fa272 100644 --- a/RULES.md +++ b/RULES.md @@ -714,6 +714,8 @@ The [Serverless Rules](https://awslabs.github.io/serverless-rules/) are a compil | --------------------------------------------------------------------------------- | ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [LambdaTracing](https://awslabs.github.io/serverless-rules/rules/lambda/tracing/) | Lambda function does not have X-Ray tracing enabled. | AWS Lambda can emit traces to AWS X-Ray, which enables visualizing service maps for faster troubleshooting. X-Ray tracing provides insights into the performance and behavior of your Lambda functions, helping to identify bottlenecks, errors, and dependencies. Enabling tracing allows you to track requests as they traverse through your serverless applications, making it easier to debug and optimize your functions. Consider enabling X-Ray tracing. | | [StarPermissions](https://awslabs.github.io/serverless-rules/rules/lambda/star_permissions/) | IAM role has overly permissive policies. | IAM roles should follow the principle of least privilege. Avoid using wildcard (*) permissions in IAM policies attached to Lambda roles. Instead, specify only the permissions required for the function to operate. This reduces the potential impact if the function is compromised. Review and tighten the IAM permissions for your Lambda functions. | +| [CloudWatchLogGroupRetentionPeriod](https://docs.aws.amazon.com/config/latest/developerguide/cw-loggroup-retention-period-check.html) | The CloudWatch Log Group does not have an explicit retention period configured. | By default, CloudWatch log groups created by Lambda functions have an unlimited retention time. For cost optimization purposes, you should set a retention duration on all log groups. For log archival, export and set cost-effective storage classes that best suit your needs. +| ### Errors diff --git a/src/packs/serverless.ts b/src/packs/serverless.ts index 4a5a413a3b..90665d8001 100644 --- a/src/packs/serverless.ts +++ b/src/packs/serverless.ts @@ -138,10 +138,10 @@ export class ServerlessChecks extends NagPack { */ private checkCloudwatch(node: CfnResource) { this.applyRule({ - info: 'Ensure that CloudWatch Log Groups have an explcity retention policy', + info: 'Ensure that CloudWatch Log Groups have an explicit retention policy', explanation: - 'By default, logs are kept indefinitely and never expire. You can adjust the retention policy for each log group, keeping the indefinite retention, or choosing a retention period between 10 years and one day.', - level: NagMessageLevel.ERROR, + 'By default, logs are kept indefinitely and never expire. You can adjust the retention policy for each log group, keeping the indefinite retention, or choosing a retention period between 10 years and one day. For Lambda functions, this applies to their automatically created CloudWatch Log Groups.', + level: NagMessageLevel.WARN, rule: cloudwatch.CloudWatchLogGroupRetentionPeriod, node: node, }); From 554fcede7ba12b3a5a821091f3b4b3beeb38dd55 Mon Sep 17 00:00:00 2001 From: kevinwochan Date: Fri, 30 Aug 2024 01:22:35 +1000 Subject: [PATCH 22/42] feat(serverless): unit tested and documented LambdaDefaultMemorySize rule --- RULES.md | 1 + src/rules/lambda.ts | 0 test/rules/Lambda.test.ts | 42 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+) delete mode 100644 src/rules/lambda.ts diff --git a/RULES.md b/RULES.md index b5d63fa272..041785cc49 100644 --- a/RULES.md +++ b/RULES.md @@ -714,6 +714,7 @@ The [Serverless Rules](https://awslabs.github.io/serverless-rules/) are a compil | --------------------------------------------------------------------------------- | ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [LambdaTracing](https://awslabs.github.io/serverless-rules/rules/lambda/tracing/) | Lambda function does not have X-Ray tracing enabled. | AWS Lambda can emit traces to AWS X-Ray, which enables visualizing service maps for faster troubleshooting. X-Ray tracing provides insights into the performance and behavior of your Lambda functions, helping to identify bottlenecks, errors, and dependencies. Enabling tracing allows you to track requests as they traverse through your serverless applications, making it easier to debug and optimize your functions. Consider enabling X-Ray tracing. | | [StarPermissions](https://awslabs.github.io/serverless-rules/rules/lambda/star_permissions/) | IAM role has overly permissive policies. | IAM roles should follow the principle of least privilege. Avoid using wildcard (*) permissions in IAM policies attached to Lambda roles. Instead, specify only the permissions required for the function to operate. This reduces the potential impact if the function is compromised. Review and tighten the IAM permissions for your Lambda functions. | +| [LambdaDefaultMemorySize](https://awslabs.github.io/serverless-rules/rules/lambda/default_memory_size/) | Lambda function does not specify a memory size. | Lambda CPU power and costs are proportional to the amount of memory configured. You can use tools such as AWS Lambda Power Tuning to test your function at different memory settings to find the one that matches your cost and performance requirements the best. | | [CloudWatchLogGroupRetentionPeriod](https://docs.aws.amazon.com/config/latest/developerguide/cw-loggroup-retention-period-check.html) | The CloudWatch Log Group does not have an explicit retention period configured. | By default, CloudWatch log groups created by Lambda functions have an unlimited retention time. For cost optimization purposes, you should set a retention duration on all log groups. For log archival, export and set cost-effective storage classes that best suit your needs. | diff --git a/src/rules/lambda.ts b/src/rules/lambda.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/test/rules/Lambda.test.ts b/test/rules/Lambda.test.ts index 0b070398e4..f4b9661e8b 100644 --- a/test/rules/Lambda.test.ts +++ b/test/rules/Lambda.test.ts @@ -23,6 +23,7 @@ import { Queue } from 'aws-cdk-lib/aws-sqs'; import { TestPack, TestType, validateStack } from './utils'; import { LambdaConcurrency, + LambdaDefaultMemorySize, LambdaDLQ, LambdaEventSourceMappingDestination, LambdaFunctionPublicAccessProhibited, @@ -40,6 +41,7 @@ const testPack = new TestPack([ LambdaInsideVPC, LambdaLatestVersion, LambdaTracing, + LambdaDefaultMemorySize, LambdaEventSourceMappingDestination, ]); let stack: Stack; @@ -483,4 +485,44 @@ describe('AWS Lambda', () => { validateStack(stack, ruleId, TestType.COMPLIANCE); }); }); + + describe('LambdaDefaultMemorySize: Lambda functions should not use the default memory size', () => { + const ruleId = 'LambdaDefaultMemorySize'; + + test('Noncompliance 1 - Default memory size (128 MB)', () => { + new CfnFunction(stack, 'rFunction', { + code: {}, + role: 'somerole', + }); + validateStack(stack, ruleId, TestType.NON_COMPLIANCE); + }); + + test('Noncompliance 2 - Explicitly set to default memory size', () => { + new Function(stack, 'rFunction', { + runtime: Runtime.NODEJS_20_X, + code: Code.fromInline('exports.handler = async () => {};'), + handler: 'index.handler', + }); + validateStack(stack, ruleId, TestType.NON_COMPLIANCE); + }); + + test('Compliance 1 - L1 construct with non-default memory size', () => { + new CfnFunction(stack, 'rFunction', { + code: {}, + role: 'somerole', + memorySize: 128, + }); + validateStack(stack, ruleId, TestType.COMPLIANCE); + }); + + test('Compliance 2 - L2 construct with non-default memory size', () => { + new Function(stack, 'rFunction', { + runtime: Runtime.NODEJS_20_X, + code: Code.fromInline('exports.handler = async () => {};'), + handler: 'index.handler', + memorySize: 512, + }); + validateStack(stack, ruleId, TestType.COMPLIANCE); + }); + }); }); From 73821c80329ed2ce23bded735ce276185fec975a Mon Sep 17 00:00:00 2001 From: kevinwochan Date: Fri, 30 Aug 2024 01:28:06 +1000 Subject: [PATCH 23/42] feat(serverless): unit tests for Lambda Default Timeout --- test/rules/Lambda.test.ts | 48 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/test/rules/Lambda.test.ts b/test/rules/Lambda.test.ts index f4b9661e8b..87ca9d0d3e 100644 --- a/test/rules/Lambda.test.ts +++ b/test/rules/Lambda.test.ts @@ -2,7 +2,7 @@ Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ -import { Aspects, Stack } from 'aws-cdk-lib'; +import { Aspects, Duration, Stack } from 'aws-cdk-lib'; import { Repository } from 'aws-cdk-lib/aws-ecr'; import { CfnEventSourceMapping, @@ -20,10 +20,10 @@ import { } from 'aws-cdk-lib/aws-lambda'; import { SqsDlq } from 'aws-cdk-lib/aws-lambda-event-sources'; import { Queue } from 'aws-cdk-lib/aws-sqs'; -import { TestPack, TestType, validateStack } from './utils'; import { LambdaConcurrency, LambdaDefaultMemorySize, + LambdaDefaultTimeout, LambdaDLQ, LambdaEventSourceMappingDestination, LambdaFunctionPublicAccessProhibited, @@ -32,6 +32,7 @@ import { LambdaLatestVersion, LambdaTracing, } from '../../src/rules/lambda'; +import { TestPack, TestType, validateStack } from './utils'; const testPack = new TestPack([ LambdaConcurrency, @@ -43,6 +44,7 @@ const testPack = new TestPack([ LambdaTracing, LambdaDefaultMemorySize, LambdaEventSourceMappingDestination, + LambdaDefaultTimeout, ]); let stack: Stack; @@ -497,7 +499,7 @@ describe('AWS Lambda', () => { validateStack(stack, ruleId, TestType.NON_COMPLIANCE); }); - test('Noncompliance 2 - Explicitly set to default memory size', () => { + test('Noncompliance 2 - L2 Construct set to default memory size', () => { new Function(stack, 'rFunction', { runtime: Runtime.NODEJS_20_X, code: Code.fromInline('exports.handler = async () => {};'), @@ -525,4 +527,44 @@ describe('AWS Lambda', () => { validateStack(stack, ruleId, TestType.COMPLIANCE); }); }); + + describe('LambdaDefaultTimeout: Lambda functions should not use the default timeout', () => { + const ruleId = 'LambdaDefaultTimeout'; + + test('Noncompliance 1 - Default timeout (3 seconds)', () => { + new CfnFunction(stack, 'rFunction', { + code: {}, + role: 'somerole', + }); + validateStack(stack, ruleId, TestType.NON_COMPLIANCE); + }); + + test('Noncompliance 2 - L2 construct explicitly using the default timeout', () => { + new Function(stack, 'rFunction', { + runtime: Runtime.NODEJS_20_X, + code: Code.fromInline('exports.handler = async () => {};'), + handler: 'index.handler' + }); + validateStack(stack, ruleId, TestType.NON_COMPLIANCE); + }); + + test('Compliance 1 - L1 construct with non-default timeout', () => { + new CfnFunction(stack, 'rFunction', { + code: {}, + role: 'somerole', + timeout: 10, + }); + validateStack(stack, ruleId, TestType.COMPLIANCE); + }); + + test('Compliance 2 - L2 construct with non-default timeout', () => { + new Function(stack, 'rFunction', { + runtime: Runtime.NODEJS_20_X, + code: Code.fromInline('exports.handler = async () => {};'), + handler: 'index.handler', + timeout: Duration.seconds(30), + }); + validateStack(stack, ruleId, TestType.COMPLIANCE); + }); + }); }); From 3fd72f7912fd347b386cd5b7d8f3f2c18126b389 Mon Sep 17 00:00:00 2001 From: kevinwochan Date: Fri, 30 Aug 2024 01:30:41 +1000 Subject: [PATCH 24/42] feat(serverless): documented LambdaDefaultTimeout in the serverless nag pack --- RULES.md | 4 ++-- src/packs/serverless.ts | 14 ++++++++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/RULES.md b/RULES.md index 041785cc49..2d69659cad 100644 --- a/RULES.md +++ b/RULES.md @@ -715,8 +715,8 @@ The [Serverless Rules](https://awslabs.github.io/serverless-rules/) are a compil | [LambdaTracing](https://awslabs.github.io/serverless-rules/rules/lambda/tracing/) | Lambda function does not have X-Ray tracing enabled. | AWS Lambda can emit traces to AWS X-Ray, which enables visualizing service maps for faster troubleshooting. X-Ray tracing provides insights into the performance and behavior of your Lambda functions, helping to identify bottlenecks, errors, and dependencies. Enabling tracing allows you to track requests as they traverse through your serverless applications, making it easier to debug and optimize your functions. Consider enabling X-Ray tracing. | | [StarPermissions](https://awslabs.github.io/serverless-rules/rules/lambda/star_permissions/) | IAM role has overly permissive policies. | IAM roles should follow the principle of least privilege. Avoid using wildcard (*) permissions in IAM policies attached to Lambda roles. Instead, specify only the permissions required for the function to operate. This reduces the potential impact if the function is compromised. Review and tighten the IAM permissions for your Lambda functions. | | [LambdaDefaultMemorySize](https://awslabs.github.io/serverless-rules/rules/lambda/default_memory_size/) | Lambda function does not specify a memory size. | Lambda CPU power and costs are proportional to the amount of memory configured. You can use tools such as AWS Lambda Power Tuning to test your function at different memory settings to find the one that matches your cost and performance requirements the best. | -| [CloudWatchLogGroupRetentionPeriod](https://docs.aws.amazon.com/config/latest/developerguide/cw-loggroup-retention-period-check.html) | The CloudWatch Log Group does not have an explicit retention period configured. | By default, CloudWatch log groups created by Lambda functions have an unlimited retention time. For cost optimization purposes, you should set a retention duration on all log groups. For log archival, export and set cost-effective storage classes that best suit your needs. -| +| [CloudWatchLogGroupRetentionPeriod](https://docs.aws.amazon.com/config/latest/developerguide/cw-loggroup-retention-period-check.html) | The CloudWatch Log Group does not have an explicit retention period configured. | By default, CloudWatch log groups created by Lambda functions have an unlimited retention time. For cost optimization purposes, you should set a retention duration on all log groups. For log archival, export and set cost-effective storage classes that best suit your needs. | +| [LambdaFunctionDefaultTimeout](https://awslabs.github.io/serverless-rules/rules/lambda/default_timeout/) | Lambda function uses the default timeout. | The default timeout for Lambda functions is 3 seconds, which may not be sufficient for many use cases. Setting an appropriate timeout helps prevent unexpected function terminations and ensures your function has enough time to complete its tasks. Consider the nature of your function's operations and set a timeout that balances between allowing sufficient execution time and avoiding unnecessarily long-running functions. | ### Errors diff --git a/src/packs/serverless.ts b/src/packs/serverless.ts index 90665d8001..aad129e146 100644 --- a/src/packs/serverless.ts +++ b/src/packs/serverless.ts @@ -63,21 +63,23 @@ export class ServerlessChecks extends NagPack { }); this.applyRule({ - info: 'Ensure that Lambda functions have an explcity timeout value', + info: 'Ensure that Lambda functions have an explicit memory value', explanation: - 'Lambda functions have a default timeout of 3 seconds. If your timeout value is too short, Lambda might terminate invocations prematurely. On the other side, setting the timeout much higher than the average execution may cause functions to execute for longer upon code malfunction, resulting in higher costs and possibly reaching concurrency limits depending on how such functions are invoked. You can also use AWS Lambda Power Tuning to test your function at different timeout settings to find the one that matches your cost and performance requirements the best.', + "Lambda allocates CPU power in proportion to the amount of memory configured. By default, your functions have 128 MB of memory allocated. You can increase that value up to 10 GB. With more CPU resources, your Lambda function's duration might decrease. You can use tools such as AWS Lambda Power Tuning to test your function at different memory settings to find the one that matches your cost and performance requirements the best.", level: NagMessageLevel.ERROR, - rule: lambda.LambdaDefaultTimeout, + rule: lambda.LambdaDefaultMemorySize, node: node, }); + this.applyRule({ - info: 'Ensure that Lambda functions have an explicit memory value', + info: 'Ensure that Lambda functions have an explcity timeout value', explanation: - "Lambda allocates CPU power in proportion to the amount of memory configured. By default, your functions have 128 MB of memory allocated. You can increase that value up to 10 GB. With more CPU resources, your Lambda function's duration might decrease. You can use tools such as AWS Lambda Power Tuning to test your function at different memory settings to find the one that matches your cost and performance requirements the best.", + 'Lambda functions have a default timeout of 3 seconds. If your timeout value is too short, Lambda might terminate invocations prematurely. On the other side, setting the timeout much higher than the average execution may cause functions to execute for longer upon code malfunction, resulting in higher costs and possibly reaching concurrency limits depending on how such functions are invoked. You can also use AWS Lambda Power Tuning to test your function at different timeout settings to find the one that matches your cost and performance requirements the best.', level: NagMessageLevel.ERROR, - rule: lambda.LambdaDefaultMemorySize, + rule: lambda.LambdaDefaultTimeout, node: node, }); + this.applyRule({ info: 'Ensure Lambda functions have a onFaliure destination configured', explanation: From 11eb99a9e2f0060eddfa7d8cbfc8bda8b8ca93af Mon Sep 17 00:00:00 2001 From: kevinwochan Date: Fri, 30 Aug 2024 11:41:06 +1000 Subject: [PATCH 25/42] feat(serverless): implemented LambdaAsyncFailureDestination rule --- RULES.md | 1 + src/packs/serverless.ts | 22 +++-- .../lambda/LambdaAsyncFailureDestination.ts | 26 ++++++ src/rules/lambda/index.ts | 2 +- test/rules/Lambda.test.ts | 80 ++++++++++++++++++- 5 files changed, 121 insertions(+), 10 deletions(-) create mode 100644 src/rules/lambda/LambdaAsyncFailureDestination.ts diff --git a/RULES.md b/RULES.md index 2d69659cad..3dcbe5a2f4 100644 --- a/RULES.md +++ b/RULES.md @@ -723,6 +723,7 @@ The [Serverless Rules](https://awslabs.github.io/serverless-rules/) are a compil | Rule ID | Cause | Explanation | | -------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [LambdaEventSourceMappingDestination](https://awslabs.github.io/serverless-rules/rules/lambda/eventsourcemapping_failure_destination/) | The Lambda Event Source Mapping does not have a destination configured for failed invocations. | Configuring a destination for failed invocations in Lambda Event Source Mappings allows you to capture and process events that fail to be processed by your Lambda function. This helps in monitoring, debugging, and implementing retry mechanisms for failed events, improving the reliability and observability of your serverless applications. | +| [LambdaAsyncFailureDestination](https://awslabs.github.io/serverless-rules/rules/lambda/async_failure_destination/) | The Lambda function does not have a failure destination for asynchronous invocations. | When a Lambda function is invoked asynchronously (e.g., by S3, SNS, or EventBridge), it's important to configure a failure destination. This allows you to capture and handle events that fail processing, improving the reliability and observability of your serverless applications. | | [LambdaDefaultMemorySize](https://awslabs.github.io/serverless-rules/rules/lambda/default_memory_size/) | Lambda function does not specify a memory size. | Lambda CPU power and costs are proportional to the amount of memory configured. You can use tools such as AWS Lambda Power Tuning to test your function at different memory settings to find the one that matches your cost and performance requirements the best. | | | diff --git a/src/packs/serverless.ts b/src/packs/serverless.ts index aad129e146..3b4ec9fb40 100644 --- a/src/packs/serverless.ts +++ b/src/packs/serverless.ts @@ -11,7 +11,7 @@ import { lambda, sns, sqs, - stepfunctions + stepfunctions, } from '../rules'; /** @@ -37,7 +37,6 @@ export class ServerlessChecks extends NagPack { } } - /** * Check Lambda Resources * @param node the CfnResource to check @@ -62,6 +61,15 @@ export class ServerlessChecks extends NagPack { node: node, }); + this.applyRule({ + info: 'Ensure Lambda functions have a failure destination for asynchronous invocations', + explanation: + 'When a Lambda function is invoked asynchronously (e.g., by S3, SNS, or EventBridge), it\'s important to configure a failure destination. This allows you to capture and handle events that fail processing, improving the reliability and observability of your serverless applications.', + level: NagMessageLevel.ERROR, + rule: lambda.LambdaAsyncFailureDestination, + node: node, + }); + this.applyRule({ info: 'Ensure that Lambda functions have an explicit memory value', explanation: @@ -92,7 +100,7 @@ export class ServerlessChecks extends NagPack { this.applyRule({ info: 'Ensure Lambda functions are using the latest runtime version', explanation: - 'Using the latest runtime version ensures that your Lambda function has access to the most recent features, performance improvements, and security updates. It\'s important to regularly update your Lambda functions to use the latest runtime versions to maintain optimal performance and security.', + "Using the latest runtime version ensures that your Lambda function has access to the most recent features, performance improvements, and security updates. It's important to regularly update your Lambda functions to use the latest runtime versions to maintain optimal performance and security.", level: NagMessageLevel.ERROR, rule: lambda.LambdaLatestVersion, node: node, @@ -118,10 +126,10 @@ export class ServerlessChecks extends NagPack { } /** - * Check Lambda Resources - * @param node the CfnResource to check - * @param ignores list of ignores for the resource - */ + * Check Lambda Resources + * @param node the CfnResource to check + * @param ignores list of ignores for the resource + */ private checkIAM(node: CfnResource) { this.applyRule({ info: 'Ensure Lambda functions do not have overly permissive IAM roles', diff --git a/src/rules/lambda/LambdaAsyncFailureDestination.ts b/src/rules/lambda/LambdaAsyncFailureDestination.ts new file mode 100644 index 0000000000..7b25ef96ca --- /dev/null +++ b/src/rules/lambda/LambdaAsyncFailureDestination.ts @@ -0,0 +1,26 @@ +/* +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: Apache-2.0 +*/ +import { parse } from 'path'; +import { CfnResource, Stack } from 'aws-cdk-lib'; +import { CfnEventInvokeConfig } from 'aws-cdk-lib/aws-lambda'; +import { NagRuleCompliance } from '../../nag-rules'; + +/** + * Lambda functions with asynchronous invocations should have a failure destination + * @param node the CfnResource to check + */ +export default Object.defineProperty( + (node: CfnResource): NagRuleCompliance => { + if (node instanceof CfnEventInvokeConfig) { + const destinationConfig = Stack.of(node).resolve(node.destinationConfig); + if (destinationConfig?.onFailure?.destination) + return NagRuleCompliance.COMPLIANT; + return NagRuleCompliance.NON_COMPLIANT; + } + return NagRuleCompliance.NOT_APPLICABLE; + }, + 'name', + { value: parse(__filename).name } +); diff --git a/src/rules/lambda/index.ts b/src/rules/lambda/index.ts index ee6d4be8eb..6535aa70ea 100644 --- a/src/rules/lambda/index.ts +++ b/src/rules/lambda/index.ts @@ -3,6 +3,7 @@ Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ +export { default as LambdaAsyncFailureDestination } from './LambdaAsyncFailureDestination'; export { default as LambdaConcurrency } from './LambdaConcurrency'; export { default as LambdaDefaultMemorySize } from './LambdaDefaultMemorySize'; export { default as LambdaDefaultTimeout } from './LambdaDefaultTimeout'; @@ -13,4 +14,3 @@ export { default as LambdaFunctionUrlAuth } from './LambdaFunctionUrlAuth'; export { default as LambdaInsideVPC } from './LambdaInsideVPC'; export { default as LambdaLatestVersion } from './LambdaLatestVersion'; export { default as LambdaTracing } from './LambdaTracing'; - diff --git a/test/rules/Lambda.test.ts b/test/rules/Lambda.test.ts index 87ca9d0d3e..424696005b 100644 --- a/test/rules/Lambda.test.ts +++ b/test/rules/Lambda.test.ts @@ -5,6 +5,7 @@ SPDX-License-Identifier: Apache-2.0 import { Aspects, Duration, Stack } from 'aws-cdk-lib'; import { Repository } from 'aws-cdk-lib/aws-ecr'; import { + CfnEventInvokeConfig, CfnEventSourceMapping, CfnFunction, CfnPermission, @@ -18,9 +19,12 @@ import { Runtime, Tracing, } from 'aws-cdk-lib/aws-lambda'; +import { SqsDestination } from 'aws-cdk-lib/aws-lambda-destinations'; import { SqsDlq } from 'aws-cdk-lib/aws-lambda-event-sources'; import { Queue } from 'aws-cdk-lib/aws-sqs'; +import { TestPack, TestType, validateStack } from './utils'; import { + LambdaAsyncFailureDestination, LambdaConcurrency, LambdaDefaultMemorySize, LambdaDefaultTimeout, @@ -32,7 +36,6 @@ import { LambdaLatestVersion, LambdaTracing, } from '../../src/rules/lambda'; -import { TestPack, TestType, validateStack } from './utils'; const testPack = new TestPack([ LambdaConcurrency, @@ -45,6 +48,7 @@ const testPack = new TestPack([ LambdaDefaultMemorySize, LambdaEventSourceMappingDestination, LambdaDefaultTimeout, + LambdaAsyncFailureDestination, ]); let stack: Stack; @@ -543,7 +547,7 @@ describe('AWS Lambda', () => { new Function(stack, 'rFunction', { runtime: Runtime.NODEJS_20_X, code: Code.fromInline('exports.handler = async () => {};'), - handler: 'index.handler' + handler: 'index.handler', }); validateStack(stack, ruleId, TestType.NON_COMPLIANCE); }); @@ -567,4 +571,76 @@ describe('AWS Lambda', () => { validateStack(stack, ruleId, TestType.COMPLIANCE); }); }); + + describe('LambdaAsyncFailureDestination: Lambda functions with async invocation should have a failure destination', () => { + const ruleId = 'LambdaAsyncFailureDestination'; + + test('Noncompliance 1 - Lambda function with async event invoke but no failure handler', () => { + const lambdaFunction = new CfnFunction(stack, 'rFunction', { + code: {}, + role: 'somerole', + }); + new CfnEventInvokeConfig(stack, 'rEventInvokeConfig', { + functionName: lambdaFunction.ref, + qualifier: '$LATEST', + }); + validateStack(stack, ruleId, TestType.NON_COMPLIANCE); + }); + + test('Noncompliance 2 - Lambda function with async event invoke but no failure handler', () => { + const lambdaFunction = new Function(stack, 'rFunction', { + runtime: Runtime.NODEJS_20_X, + code: Code.fromInline('exports.handler = async () => {};'), + handler: 'index.handler', + }); + const queue = new Queue(stack, 'DestinationQueue'); + lambdaFunction.configureAsyncInvoke({ + onSuccess: new SqsDestination(queue), + }); + validateStack(stack, ruleId, TestType.NON_COMPLIANCE); + }); + + test('Noncompliance 3 - L2 Lambda function with async invocation handler for successes but no failure handler', () => { + const lambdaFunction = new Function(stack, 'rFunction', { + runtime: Runtime.NODEJS_20_X, + code: Code.fromInline('exports.handler = async () => {};'), + handler: 'index.handler', + }); + const queue = new Queue(stack, 'DestinationQueue'); + lambdaFunction.configureAsyncInvoke({ + onSuccess: new SqsDestination(queue), + }); + validateStack(stack, ruleId, TestType.NON_COMPLIANCE); + }); + + test('Compliance - Lambda function with proper async failure destination', () => { + const lambdaFunction = new CfnFunction(stack, 'rFunction', { + code: {}, + role: 'somerole', + }); + new CfnEventInvokeConfig(stack, 'rEventInvokeConfig', { + functionName: lambdaFunction.ref, + qualifier: '$LATEST', + destinationConfig: { + onFailure: { + destination: 'arn:aws:sqs:us-east-1:123456789012:myQueue', + }, + }, + }); + validateStack(stack, ruleId, TestType.COMPLIANCE); + }); + + test('Compliance - L2 construct with async failure destination', () => { + const lambdaFunction = new Function(stack, 'rFunction', { + runtime: Runtime.NODEJS_20_X, + code: Code.fromInline('exports.handler = async () => {};'), + handler: 'index.handler', + }); + const queue = new Queue(stack, 'DestinationQueue'); + lambdaFunction.configureAsyncInvoke({ + onFailure: new SqsDestination(queue), + }); + validateStack(stack, ruleId, TestType.COMPLIANCE); + }); + }); }); From 901b4232536a8fe19906ef2008ba79c1211fe7f1 Mon Sep 17 00:00:00 2001 From: kevinwochan Date: Fri, 30 Aug 2024 11:57:51 +1000 Subject: [PATCH 26/42] feat(serverless): added latestlambdaversion check to serverless nag pack --- RULES.md | 18 +++++++++--------- src/lambda/index.ts | 0 src/packs/serverless.ts | 9 +++++++++ 3 files changed, 18 insertions(+), 9 deletions(-) create mode 100644 src/lambda/index.ts diff --git a/RULES.md b/RULES.md index 3dcbe5a2f4..b28d5ceee9 100644 --- a/RULES.md +++ b/RULES.md @@ -710,13 +710,14 @@ The [Serverless Rules](https://awslabs.github.io/serverless-rules/) are a compil ### Warnings -| Rule ID | Cause | Explanation | -| --------------------------------------------------------------------------------- | ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [LambdaTracing](https://awslabs.github.io/serverless-rules/rules/lambda/tracing/) | Lambda function does not have X-Ray tracing enabled. | AWS Lambda can emit traces to AWS X-Ray, which enables visualizing service maps for faster troubleshooting. X-Ray tracing provides insights into the performance and behavior of your Lambda functions, helping to identify bottlenecks, errors, and dependencies. Enabling tracing allows you to track requests as they traverse through your serverless applications, making it easier to debug and optimize your functions. Consider enabling X-Ray tracing. | -| [StarPermissions](https://awslabs.github.io/serverless-rules/rules/lambda/star_permissions/) | IAM role has overly permissive policies. | IAM roles should follow the principle of least privilege. Avoid using wildcard (*) permissions in IAM policies attached to Lambda roles. Instead, specify only the permissions required for the function to operate. This reduces the potential impact if the function is compromised. Review and tighten the IAM permissions for your Lambda functions. | -| [LambdaDefaultMemorySize](https://awslabs.github.io/serverless-rules/rules/lambda/default_memory_size/) | Lambda function does not specify a memory size. | Lambda CPU power and costs are proportional to the amount of memory configured. You can use tools such as AWS Lambda Power Tuning to test your function at different memory settings to find the one that matches your cost and performance requirements the best. | -| [CloudWatchLogGroupRetentionPeriod](https://docs.aws.amazon.com/config/latest/developerguide/cw-loggroup-retention-period-check.html) | The CloudWatch Log Group does not have an explicit retention period configured. | By default, CloudWatch log groups created by Lambda functions have an unlimited retention time. For cost optimization purposes, you should set a retention duration on all log groups. For log archival, export and set cost-effective storage classes that best suit your needs. | -| [LambdaFunctionDefaultTimeout](https://awslabs.github.io/serverless-rules/rules/lambda/default_timeout/) | Lambda function uses the default timeout. | The default timeout for Lambda functions is 3 seconds, which may not be sufficient for many use cases. Setting an appropriate timeout helps prevent unexpected function terminations and ensures your function has enough time to complete its tasks. Consider the nature of your function's operations and set a timeout that balances between allowing sufficient execution time and avoiding unnecessarily long-running functions. | +| Rule ID | Cause | Explanation | +| ------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [LambdaTracing](https://awslabs.github.io/serverless-rules/rules/lambda/tracing/) | Lambda function does not have X-Ray tracing enabled. | AWS Lambda can emit traces to AWS X-Ray, which enables visualizing service maps for faster troubleshooting. X-Ray tracing provides insights into the performance and behavior of your Lambda functions, helping to identify bottlenecks, errors, and dependencies. Enabling tracing allows you to track requests as they traverse through your serverless applications, making it easier to debug and optimize your functions. Consider enabling X-Ray tracing. | +| [StarPermissions](https://awslabs.github.io/serverless-rules/rules/lambda/star_permissions/) | IAM role has overly permissive policies. | IAM roles should follow the principle of least privilege. Avoid using wildcard (*) permissions in IAM policies attached to Lambda roles. Instead, specify only the permissions required for the function to operate. This reduces the potential impact if the function is compromised. Review and tighten the IAM permissions for your Lambda functions. | +| [LambdaDefaultMemorySize](https://awslabs.github.io/serverless-rules/rules/lambda/default_memory_size/) | Lambda function does not specify a memory size. | Lambda CPU power and costs are proportional to the amount of memory configured. You can use tools such as AWS Lambda Power Tuning to test your function at different memory settings to find the one that matches your cost and performance requirements the best. | +| [CloudWatchLogGroupRetentionPeriod](https://docs.aws.amazon.com/config/latest/developerguide/cw-loggroup-retention-period-check.html) | The CloudWatch Log Group does not have an explicit retention period configured. | By default, CloudWatch log groups created by Lambda functions have an unlimited retention time. For cost optimization purposes, you should set a retention duration on all log groups. For log archival, export and set cost-effective storage classes that best suit your needs. | +| [LambdaFunctionDefaultTimeout](https://awslabs.github.io/serverless-rules/rules/lambda/default_timeout/) | Lambda function uses the default timeout. | The default timeout for Lambda functions is 3 seconds, which may not be sufficient for many use cases. Setting an appropriate timeout helps prevent unexpected function terminations and ensures your function has enough time to complete its tasks. Consider the nature of your function's operations and set a timeout that balances between allowing sufficient execution time and avoiding unnecessarily long-running functions. | +| [LambdaLatestVersion](https://awslabs.github.io/serverless-rules/rules/lambda/end_of_life_runtime/) | Lambda function is not using the latest runtime version. | Using the latest runtime version ensures that your Lambda function has access to the most recent features, performance improvements, and security updates. It's important to regularly update your Lambda functions to use the latest runtime versions to maintain optimal performance and security. | ### Errors @@ -724,8 +725,7 @@ The [Serverless Rules](https://awslabs.github.io/serverless-rules/) are a compil | -------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [LambdaEventSourceMappingDestination](https://awslabs.github.io/serverless-rules/rules/lambda/eventsourcemapping_failure_destination/) | The Lambda Event Source Mapping does not have a destination configured for failed invocations. | Configuring a destination for failed invocations in Lambda Event Source Mappings allows you to capture and process events that fail to be processed by your Lambda function. This helps in monitoring, debugging, and implementing retry mechanisms for failed events, improving the reliability and observability of your serverless applications. | | [LambdaAsyncFailureDestination](https://awslabs.github.io/serverless-rules/rules/lambda/async_failure_destination/) | The Lambda function does not have a failure destination for asynchronous invocations. | When a Lambda function is invoked asynchronously (e.g., by S3, SNS, or EventBridge), it's important to configure a failure destination. This allows you to capture and handle events that fail processing, improving the reliability and observability of your serverless applications. | -| [LambdaDefaultMemorySize](https://awslabs.github.io/serverless-rules/rules/lambda/default_memory_size/) | Lambda function does not specify a memory size. | Lambda CPU power and costs are proportional to the amount of memory configured. You can use tools such as AWS Lambda Power Tuning to test your function at different memory settings to find the one that matches your cost and performance requirements the best. | -| | +| [LambdaDefaultMemorySize](https://awslabs.github.io/serverless-rules/rules/lambda/default_memory_size/) | Lambda function does not specify a memory size. | Lambda CPU power and costs are proportional to the amount of memory configured. You can use tools such as AWS Lambda Power Tuning to test your function at different memory settings to find the one that matches your cost and performance requirements the best. | | ## Footnotes diff --git a/src/lambda/index.ts b/src/lambda/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/packs/serverless.ts b/src/packs/serverless.ts index 3b4ec9fb40..a145efaa94 100644 --- a/src/packs/serverless.ts +++ b/src/packs/serverless.ts @@ -61,6 +61,15 @@ export class ServerlessChecks extends NagPack { node: node, }); + this.applyRule({ + info: 'Ensure Lambda functions are using the latest runtime version', + explanation: + "Using the latest runtime version ensures that your Lambda function has access to the most recent features, performance improvements, and security updates. It's important to regularly update your Lambda functions to use the latest runtime versions to maintain optimal performance and security.", + level: NagMessageLevel.WARN, + rule: lambda.LambdaLatestVersion, + node: node, + }); + this.applyRule({ info: 'Ensure Lambda functions have a failure destination for asynchronous invocations', explanation: From 47091d2d07b93e73818c4321915e7a94a13deecc Mon Sep 17 00:00:00 2001 From: kevinwochan Date: Fri, 30 Aug 2024 12:07:01 +1000 Subject: [PATCH 27/42] feat(serverless): added APIGW access logging check --- RULES.md | 1 + src/packs/serverless.ts | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/RULES.md b/RULES.md index b28d5ceee9..2d767ef63e 100644 --- a/RULES.md +++ b/RULES.md @@ -726,6 +726,7 @@ The [Serverless Rules](https://awslabs.github.io/serverless-rules/) are a compil | [LambdaEventSourceMappingDestination](https://awslabs.github.io/serverless-rules/rules/lambda/eventsourcemapping_failure_destination/) | The Lambda Event Source Mapping does not have a destination configured for failed invocations. | Configuring a destination for failed invocations in Lambda Event Source Mappings allows you to capture and process events that fail to be processed by your Lambda function. This helps in monitoring, debugging, and implementing retry mechanisms for failed events, improving the reliability and observability of your serverless applications. | | [LambdaAsyncFailureDestination](https://awslabs.github.io/serverless-rules/rules/lambda/async_failure_destination/) | The Lambda function does not have a failure destination for asynchronous invocations. | When a Lambda function is invoked asynchronously (e.g., by S3, SNS, or EventBridge), it's important to configure a failure destination. This allows you to capture and handle events that fail processing, improving the reliability and observability of your serverless applications. | | [LambdaDefaultMemorySize](https://awslabs.github.io/serverless-rules/rules/lambda/default_memory_size/) | Lambda function does not specify a memory size. | Lambda CPU power and costs are proportional to the amount of memory configured. You can use tools such as AWS Lambda Power Tuning to test your function at different memory settings to find the one that matches your cost and performance requirements the best. | | +| [APIGWAccessLogging](https://docs.aws.amazon.com/config/latest/developerguide/api-gw-execution-logging-enabled.html) | The API Gateway stage does not have access logging enabled. | API Gateway provides access logging for API stages. Enable access logging on your API stages to monitor API requests and responses. | ## Footnotes diff --git a/src/packs/serverless.ts b/src/packs/serverless.ts index a145efaa94..4f3c4ed1e5 100644 --- a/src/packs/serverless.ts +++ b/src/packs/serverless.ts @@ -173,19 +173,19 @@ export class ServerlessChecks extends NagPack { */ private checkApiGw(node: CfnResource) { this.applyRule({ - info: 'Ensure tracing is enabled', + info: 'Ensure API Gateway stages have access logging enabled', explanation: - 'Amazon API Gateway provides active tracing support for AWS X-Ray. Enable active tracing on your API stages to sample incoming requests and send traces to X-Ray.', + 'API Gateway provides access logging for API stages. Enable access logging on your API stages to monitor API requests and responses.', level: NagMessageLevel.ERROR, - rule: apigw.APIGWXrayEnabled, + rule: apigw.APIGWAccessLogging, node: node, }); this.applyRule({ - info: 'Ensure API Gateway stages have access logging enabled', + info: 'Ensure tracing is enabled', explanation: - 'API Gateway provides access logging for API stages. Enable access logging on your API stages to monitor API requests and responses.', + 'Amazon API Gateway provides active tracing support for AWS X-Ray. Enable active tracing on your API stages to sample incoming requests and send traces to X-Ray.', level: NagMessageLevel.ERROR, - rule: apigw.APIGWAccessLogging, + rule: apigw.APIGWXrayEnabled, node: node, }); this.applyRule({ From eac6ce22afc19a04aa5a54920016e25183a95c90 Mon Sep 17 00:00:00 2001 From: kevinwochan Date: Fri, 30 Aug 2024 12:33:31 +1000 Subject: [PATCH 28/42] feat(serverless): unit tests for apigw structured logging --- test/rules/APIGW.test.ts | 126 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/test/rules/APIGW.test.ts b/test/rules/APIGW.test.ts index d45ef110f5..fabd54064c 100644 --- a/test/rules/APIGW.test.ts +++ b/test/rules/APIGW.test.ts @@ -5,6 +5,7 @@ SPDX-License-Identifier: Apache-2.0 import { AuthorizationType, CfnClientCertificate, + CfnDeployment, CfnRequestValidator, CfnRestApi, CfnStage, @@ -12,6 +13,7 @@ import { RestApi, } from 'aws-cdk-lib/aws-apigateway'; import { CfnRoute, CfnStage as CfnV2Stage } from 'aws-cdk-lib/aws-apigatewayv2'; +import { CfnApi, CfnHttpApi } from 'aws-cdk-lib/aws-sam'; import { CfnWebACLAssociation } from 'aws-cdk-lib/aws-wafv2'; import { Aspects, Stack } from 'aws-cdk-lib/core'; import { TestPack, TestType, validateStack } from './utils'; @@ -23,6 +25,7 @@ import { APIGWExecutionLoggingEnabled, APIGWRequestValidation, APIGWSSLEnabled, + APIGWStructuredLogging, APIGWXrayEnabled, } from '../../src/rules/apigw'; @@ -35,6 +38,7 @@ const testPack = new TestPack([ APIGWRequestValidation, APIGWSSLEnabled, APIGWXrayEnabled, + APIGWStructuredLogging, ]); let stack: Stack; @@ -318,4 +322,126 @@ describe('Amazon API Gateway', () => { validateStack(stack, ruleId, TestType.COMPLIANCE); }); }); + + describe('APIGWStructuredLogging: API Gateway stages use JSON-formatted structured logging', () => { + const ruleId = 'APIGWStructuredLogging'; + + test('Noncompliance 1: Non-JSON format (CfnStage)', () => { + new CfnStage(stack, 'rRestApiStageNonJsonFormat', { + restApiId: 'foo', + stageName: 'prod', + accessLogSetting: { + destinationArn: + 'arn:aws:logs:us-east-1:123456789012:log-group:API-Gateway-Execution-Logs_abc123/prod', + format: + '$context.identity.sourceIp $context.identity.caller $context.identity.user [$context.requestTime] "$context.httpMethod $context.resourcePath $context.protocol" $context.status $context.responseLength $context.requestId', + }, + }); + validateStack(stack, ruleId, TestType.NON_COMPLIANCE); + }); + + test('Compliance 1: JSON-formatted log (CfnStage)', () => { + new CfnStage(stack, 'rRestApiStageJsonFormat', { + restApiId: 'foo', + stageName: 'prod', + accessLogSetting: { + destinationArn: + 'arn:aws:logs:us-east-1:123456789012:log-group:API-Gateway-Execution-Logs_abc123/prod', + format: + '{"requestId":"$context.requestId", "ip": "$context.identity.sourceIp", "caller":"$context.identity.caller", "user":"$context.identity.user","requestTime":"$context.requestTime", "httpMethod":"$context.httpMethod","resourcePath":"$context.resourcePath", "status":"$context.status","protocol":"$context.protocol", "responseLength":"$context.responseLength"}', + }, + }); + validateStack(stack, ruleId, TestType.COMPLIANCE); + }); + + test('Compliance 2: HTTP API with JSON-formatted log (CfnStageV2)', () => { + new CfnV2Stage(stack, 'rHttpApiStageJsonFormat', { + apiId: 'bar', + stageName: 'prod', + accessLogSettings: { + destinationArn: + 'arn:aws:logs:us-east-1:123456789012:log-group:API-Gateway-Execution-Logs_abc123/prod', + format: + '{"requestId":"$context.requestId", "ip": "$context.identity.sourceIp", "requestTime":"$context.requestTime", "httpMethod":"$context.httpMethod","routeKey":"$context.routeKey", "status":"$context.status","protocol":"$context.protocol", "responseLength":"$context.responseLength"}', + }, + }); + validateStack(stack, ruleId, TestType.COMPLIANCE); + }); + + test('Noncompliance 2: No access log settings (CfnDeployment)', () => { + new CfnDeployment(stack, 'rRestApiDeploymentNoLogs', { + restApiId: 'foo', + stageDescription: { + accessLogSetting: { + destinationArn: + 'arn:aws:logs:us-east-1:123456789012:log-group:API-Gateway-Execution-Logs_abc123/prod', + }, + }, + }); + validateStack(stack, ruleId, TestType.NON_COMPLIANCE); + }); + + test('Compliance 3: JSON-formatted log (CfnDeployment)', () => { + new CfnDeployment(stack, 'rRestApiDeploymentJsonFormat', { + restApiId: 'foo', + stageDescription: { + accessLogSetting: { + destinationArn: + 'arn:aws:logs:us-east-1:123456789012:log-group:API-Gateway-Execution-Logs_abc123/prod', + format: + '{"requestId":"$context.requestId", "ip": "$context.identity.sourceIp", "caller":"$context.identity.caller", "user":"$context.identity.user","requestTime":"$context.requestTime", "httpMethod":"$context.httpMethod","resourcePath":"$context.resourcePath", "status":"$context.status","protocol":"$context.protocol", "responseLength":"$context.responseLength"}', + }, + }, + }); + validateStack(stack, ruleId, TestType.COMPLIANCE); + }); + + test('Noncompliance 3: No access log settings (CfnApi)', () => { + new CfnApi(stack, 'rSamApiNoLogs', { + stageName: 'MyApi', + accessLogSetting: { + destinationArn: + 'arn:aws:logs:us-east-1:123456789012:log-group:API-Gateway-Execution-Logs_abc123/prod', + }, + }); + validateStack(stack, ruleId, TestType.NON_COMPLIANCE); + }); + + test('Compliance 4: JSON-formatted log (CfnApi)', () => { + new CfnApi(stack, 'rSamApiJsonFormat', { + stageName: 'MyApi', + accessLogSetting: { + destinationArn: + 'arn:aws:logs:us-east-1:123456789012:log-group:API-Gateway-Execution-Logs_abc123/prod', + format: + '{"requestId":"$context.requestId", "ip": "$context.identity.sourceIp", "caller":"$context.identity.caller", "user":"$context.identity.user","requestTime":"$context.requestTime", "httpMethod":"$context.httpMethod","resourcePath":"$context.resourcePath", "status":"$context.status","protocol":"$context.protocol", "responseLength":"$context.responseLength"}', + }, + }); + validateStack(stack, ruleId, TestType.COMPLIANCE); + }); + + test('Noncompliance 4: No access log settings (CfnHttpApi)', () => { + new CfnHttpApi(stack, 'rSamHttpApiNoLogs', { + stageName: 'MyApi', + accessLogSetting: { + destinationArn: + 'arn:aws:logs:us-east-1:123456789012:log-group:API-Gateway-Execution-Logs_abc123/prod', + }, + }); + validateStack(stack, ruleId, TestType.NON_COMPLIANCE); + }); + + test('Compliance 5: JSON-formatted log (CfnHttpApi)', () => { + new CfnHttpApi(stack, 'rSamHttpApiJsonFormat', { + stageName: 'MyApi', + accessLogSetting: { + destinationArn: + 'arn:aws:logs:us-east-1:123456789012:log-group:API-Gateway-Execution-Logs_abc123/prod', + format: + '{"requestId":"$context.requestId", "ip": "$context.identity.sourceIp", "requestTime":"$context.requestTime", "httpMethod":"$context.httpMethod","routeKey":"$context.routeKey", "status":"$context.status","protocol":"$context.protocol", "responseLength":"$context.responseLength"}', + }, + }); + validateStack(stack, ruleId, TestType.COMPLIANCE); + }); + }); }); From 58dde6536b6af1ea8022eb54fe8f1faf0de1dd16 Mon Sep 17 00:00:00 2001 From: kevinwochan Date: Fri, 30 Aug 2024 12:39:33 +1000 Subject: [PATCH 29/42] feat(serverless): implemented and tested APIGWStructuredLogging --- RULES.md | 1 + src/packs/serverless.ts | 15 +++------------ 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/RULES.md b/RULES.md index 2d767ef63e..48f28de1b3 100644 --- a/RULES.md +++ b/RULES.md @@ -718,6 +718,7 @@ The [Serverless Rules](https://awslabs.github.io/serverless-rules/) are a compil | [CloudWatchLogGroupRetentionPeriod](https://docs.aws.amazon.com/config/latest/developerguide/cw-loggroup-retention-period-check.html) | The CloudWatch Log Group does not have an explicit retention period configured. | By default, CloudWatch log groups created by Lambda functions have an unlimited retention time. For cost optimization purposes, you should set a retention duration on all log groups. For log archival, export and set cost-effective storage classes that best suit your needs. | | [LambdaFunctionDefaultTimeout](https://awslabs.github.io/serverless-rules/rules/lambda/default_timeout/) | Lambda function uses the default timeout. | The default timeout for Lambda functions is 3 seconds, which may not be sufficient for many use cases. Setting an appropriate timeout helps prevent unexpected function terminations and ensures your function has enough time to complete its tasks. Consider the nature of your function's operations and set a timeout that balances between allowing sufficient execution time and avoiding unnecessarily long-running functions. | | [LambdaLatestVersion](https://awslabs.github.io/serverless-rules/rules/lambda/end_of_life_runtime/) | Lambda function is not using the latest runtime version. | Using the latest runtime version ensures that your Lambda function has access to the most recent features, performance improvements, and security updates. It's important to regularly update your Lambda functions to use the latest runtime versions to maintain optimal performance and security. | +| [APIGWStructuredLogging](https://awslabs.github.io/serverless-rules/rules/apigateway/structured_logging/) | The API Gateway stage does not have structured logging enabled. | API Gateway can emit structured logs to CloudWatch Logs. Structured logging provides a consistent and machine-readable format for log entries, making it easier to search, analyze, and process log data. Enable structured logging to improve the observability and troubleshooting capabilities of your API Gateway deployments. | ### Errors diff --git a/src/packs/serverless.ts b/src/packs/serverless.ts index 4f3c4ed1e5..87e159f9e8 100644 --- a/src/packs/serverless.ts +++ b/src/packs/serverless.ts @@ -73,7 +73,7 @@ export class ServerlessChecks extends NagPack { this.applyRule({ info: 'Ensure Lambda functions have a failure destination for asynchronous invocations', explanation: - 'When a Lambda function is invoked asynchronously (e.g., by S3, SNS, or EventBridge), it\'s important to configure a failure destination. This allows you to capture and handle events that fail processing, improving the reliability and observability of your serverless applications.', + "When a Lambda function is invoked asynchronously (e.g., by S3, SNS, or EventBridge), it's important to configure a failure destination. This allows you to capture and handle events that fail processing, improving the reliability and observability of your serverless applications.", level: NagMessageLevel.ERROR, rule: lambda.LambdaAsyncFailureDestination, node: node, @@ -123,15 +123,6 @@ export class ServerlessChecks extends NagPack { rule: lambda.LambdaEventSourceMappingDestination, node: node, }); - - this.applyRule({ - info: 'Ensure Lambda Event Source Mappings have a destination configured for failed invocations', - explanation: - 'Configuring a destination for failed invocations in Lambda Event Source Mappings allows you to capture and process events that fail to be processed by your Lambda function. This helps in monitoring, debugging, and implementing retry mechanisms for failed events, improving the reliability and observability of your serverless applications.', - level: NagMessageLevel.ERROR, - rule: lambda.LambdaEventSourceMappingDestination, - node: node, - }); } /** @@ -189,9 +180,9 @@ export class ServerlessChecks extends NagPack { node: node, }); this.applyRule({ - info: 'Ensure API Gateway stages have access logging enabled', + info: 'Ensure API Gateway logs are JSON structured', explanation: - 'API Gateway provides access logging for API stages. Enable access logging on your API stages to monitor API requests and responses.', + 'You can customize the log format that Amazon API Gateway uses to send logs. JSON Structured logging makes it easier to derive queries to answer arbitrary questions about the health of your application.', level: NagMessageLevel.ERROR, rule: apigw.APIGWStructuredLogging, node: node, From 2debbe967f83ab72383a06854c8897b0158b5f05 Mon Sep 17 00:00:00 2001 From: kevinwochan Date: Fri, 30 Aug 2024 12:44:01 +1000 Subject: [PATCH 30/42] feat(serverless): Stepfunctions rules --- RULES.md | 1 + src/packs/serverless.ts | 8 -------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/RULES.md b/RULES.md index 48f28de1b3..1b6fc42859 100644 --- a/RULES.md +++ b/RULES.md @@ -719,6 +719,7 @@ The [Serverless Rules](https://awslabs.github.io/serverless-rules/) are a compil | [LambdaFunctionDefaultTimeout](https://awslabs.github.io/serverless-rules/rules/lambda/default_timeout/) | Lambda function uses the default timeout. | The default timeout for Lambda functions is 3 seconds, which may not be sufficient for many use cases. Setting an appropriate timeout helps prevent unexpected function terminations and ensures your function has enough time to complete its tasks. Consider the nature of your function's operations and set a timeout that balances between allowing sufficient execution time and avoiding unnecessarily long-running functions. | | [LambdaLatestVersion](https://awslabs.github.io/serverless-rules/rules/lambda/end_of_life_runtime/) | Lambda function is not using the latest runtime version. | Using the latest runtime version ensures that your Lambda function has access to the most recent features, performance improvements, and security updates. It's important to regularly update your Lambda functions to use the latest runtime versions to maintain optimal performance and security. | | [APIGWStructuredLogging](https://awslabs.github.io/serverless-rules/rules/apigateway/structured_logging/) | The API Gateway stage does not have structured logging enabled. | API Gateway can emit structured logs to CloudWatch Logs. Structured logging provides a consistent and machine-readable format for log entries, making it easier to search, analyze, and process log data. Enable structured logging to improve the observability and troubleshooting capabilities of your API Gateway deployments. | +| [StepFunctionStateMachineXray](https://awslabs.github.io/serverless-rules/rules/stepfunctions/xray/) | The Step Functions state machine does not have X-Ray tracing enabled. | AWS Step Functions can emit traces to AWS X-Ray, which enables visualizing service maps for faster troubleshooting. X-Ray tracing provides insights into the execution of your state machines, helping to identify bottlenecks, errors, and dependencies. Enabling tracing allows you to track executions as they traverse through your serverless workflows, making it easier to debug and optimize your state machines. Consider enabling X-Ray tracing. | ### Errors diff --git a/src/packs/serverless.ts b/src/packs/serverless.ts index 87e159f9e8..5c4fd6e73d 100644 --- a/src/packs/serverless.ts +++ b/src/packs/serverless.ts @@ -267,13 +267,5 @@ export class ServerlessChecks extends NagPack { rule: stepfunctions.StepFunctionStateMachineXray, node: node, }); - this.applyRule({ - info: 'Ensure Stepfunctions have been configured to log all events to cloudwatch', - explanation: - 'When a logging configuration is configured, errors can be captured in CLoudWatch Logs', - level: NagMessageLevel.ERROR, - rule: stepfunctions.StepFunctionStateMachineAllLogsToCloudWatch, - node: node, - }); } } From 37beb39e94e8ed6cefee3dd12c179afce2e1b0da Mon Sep 17 00:00:00 2001 From: kevinwochan Date: Fri, 30 Aug 2024 14:10:52 +1000 Subject: [PATCH 31/42] feat(serverless): implemented and unit tested redrive policy --- src/rules/sqs/SQSRedrivePolicy.ts | 25 ++++++++++++++++ src/rules/sqs/index.ts | 1 + test/rules/SQS.test.ts | 48 ++++++++++++++++++++++++++++++- 3 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 src/rules/sqs/SQSRedrivePolicy.ts diff --git a/src/rules/sqs/SQSRedrivePolicy.ts b/src/rules/sqs/SQSRedrivePolicy.ts new file mode 100644 index 0000000000..05aaa64479 --- /dev/null +++ b/src/rules/sqs/SQSRedrivePolicy.ts @@ -0,0 +1,25 @@ +import { CfnResource, Stack } from 'aws-cdk-lib'; +import { CfnQueue, Queue } from 'aws-cdk-lib/aws-sqs'; +import { NagRuleCompliance } from '../../nag-rules'; + +/** + * SQS queues should have a redrive policy configured + * + * @param node - the CfnResource to check + */ +export default function SQSRedrivePolicy(node: CfnResource): NagRuleCompliance { + if (node instanceof CfnQueue) { + const redrivePolicy = Stack.of(node).resolve(node.redrivePolicy); + if (redrivePolicy !== undefined) return NagRuleCompliance.COMPLIANT; + return NagRuleCompliance.NON_COMPLIANT; + } else if (node instanceof Queue) { + // For L2 constructs, we need to check the underlying CfnQueue + console.log(node); + const dlqConfig = Stack.of(node).resolve(node.deadLetterQueue); + console.log(dlqConfig); + if (dlqConfig?.maxReceiveCount) return NagRuleCompliance.COMPLIANT; + return NagRuleCompliance.NON_COMPLIANT; + } + + return NagRuleCompliance.NOT_APPLICABLE; +} diff --git a/src/rules/sqs/index.ts b/src/rules/sqs/index.ts index 5e6f9a2a9e..fb561e2d30 100644 --- a/src/rules/sqs/index.ts +++ b/src/rules/sqs/index.ts @@ -5,3 +5,4 @@ SPDX-License-Identifier: Apache-2.0 export { default as SQSQueueDLQ } from './SQSQueueDLQ'; export { default as SQSQueueSSE } from './SQSQueueSSE'; export { default as SQSQueueSSLRequestsOnly } from './SQSQueueSSLRequestsOnly'; +export { default as SQSRedrivePolicy } from './SQSRedrivePolicy'; diff --git a/test/rules/SQS.test.ts b/test/rules/SQS.test.ts index 4896a33977..e990716fa4 100644 --- a/test/rules/SQS.test.ts +++ b/test/rules/SQS.test.ts @@ -11,19 +11,26 @@ import { } from 'aws-cdk-lib/aws-iam'; import { Key } from 'aws-cdk-lib/aws-kms'; import { Code, Function, Runtime } from 'aws-cdk-lib/aws-lambda'; -import { CfnQueuePolicy, Queue, QueueEncryption } from 'aws-cdk-lib/aws-sqs'; +import { + CfnQueue, + CfnQueuePolicy, + Queue, + QueueEncryption, +} from 'aws-cdk-lib/aws-sqs'; import { Aspects, Stack } from 'aws-cdk-lib/core'; import { TestPack, TestType, validateStack } from './utils'; import { SQSQueueDLQ, SQSQueueSSE, SQSQueueSSLRequestsOnly, + SQSRedrivePolicy, } from '../../src/rules/sqs'; const testPack = new TestPack([ SQSQueueDLQ, SQSQueueSSE, SQSQueueSSLRequestsOnly, + SQSRedrivePolicy, ]); let stack: Stack; @@ -204,4 +211,43 @@ describe('Amazon Simple Queue Service (SQS)', () => { validateStack(stack, ruleId, TestType.COMPLIANCE); }); }); + + describe('SQSRedrivePolicy: SQS queues should have a redrive policy configured', () => { + const ruleId = 'SQSRedrivePolicy'; + + test('Noncompliance: L2 construct without redrive policy', () => { + new Queue(stack, 'QueueWithoutRedrive'); + validateStack(stack, ruleId, TestType.NON_COMPLIANCE); + }); + + test('Noncompliance: L1 construct without redrive policy', () => { + new CfnQueue(stack, 'L1QueueWithoutRedrive', {}); + validateStack(stack, ruleId, TestType.NON_COMPLIANCE); + }); + + test('Compliance: L1 construct with redrive policy', () => { + new CfnQueue(stack, 'L1QueueWithRedrive', { + redrivePolicy: { + deadLetterTargetArn: + 'arn:aws:sqs:us-east-1:123456789012:DeadLetterQueue', + maxReceiveCount: 3, + }, + }); + validateStack(stack, ruleId, TestType.COMPLIANCE); + }); + + test('Compliance: L2 construct with redrive policy', () => { + new Queue(stack, 'QueueWithRedrive', { + deadLetterQueue: { + queue: Queue.fromQueueArn( + stack, + 'Dlq2', + `arn:aws:sqs:${stack.region}:${stack.account}:foo2` + ), + maxReceiveCount: 3, + }, + }); + validateStack(stack, ruleId, TestType.COMPLIANCE); + }); + }); }); From 156a3e3fe7579c08d0d754cc2e130f875181ea32 Mon Sep 17 00:00:00 2001 From: kevinwochan Date: Fri, 30 Aug 2024 14:15:43 +1000 Subject: [PATCH 32/42] feat(serverless): added sqs redrive policy to serverless nag pack --- RULES.md | 1 + src/packs/serverless.ts | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/RULES.md b/RULES.md index 1b6fc42859..959f56f2e0 100644 --- a/RULES.md +++ b/RULES.md @@ -729,6 +729,7 @@ The [Serverless Rules](https://awslabs.github.io/serverless-rules/) are a compil | [LambdaAsyncFailureDestination](https://awslabs.github.io/serverless-rules/rules/lambda/async_failure_destination/) | The Lambda function does not have a failure destination for asynchronous invocations. | When a Lambda function is invoked asynchronously (e.g., by S3, SNS, or EventBridge), it's important to configure a failure destination. This allows you to capture and handle events that fail processing, improving the reliability and observability of your serverless applications. | | [LambdaDefaultMemorySize](https://awslabs.github.io/serverless-rules/rules/lambda/default_memory_size/) | Lambda function does not specify a memory size. | Lambda CPU power and costs are proportional to the amount of memory configured. You can use tools such as AWS Lambda Power Tuning to test your function at different memory settings to find the one that matches your cost and performance requirements the best. | | | [APIGWAccessLogging](https://docs.aws.amazon.com/config/latest/developerguide/api-gw-execution-logging-enabled.html) | The API Gateway stage does not have access logging enabled. | API Gateway provides access logging for API stages. Enable access logging on your API stages to monitor API requests and responses. | +| [SQSRedrivePolicy](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-dead-letter-queues.html) | The SQS queue does not have a redrive policy configured. | Configuring a redrive policy on an SQS queue allows you to define how many times SQS will make messages available for consumers before sending them to a dead-letter queue. This helps in managing message processing failures and provides a mechanism for handling problematic messages. | ## Footnotes diff --git a/src/packs/serverless.ts b/src/packs/serverless.ts index 5c4fd6e73d..4f7624a18f 100644 --- a/src/packs/serverless.ts +++ b/src/packs/serverless.ts @@ -11,7 +11,7 @@ import { lambda, sns, sqs, - stepfunctions, + stepfunctions } from '../rules'; /** @@ -251,6 +251,14 @@ export class ServerlessChecks extends NagPack { rule: sqs.SQSQueueDLQ, node: node, }); + + this.applyRule({ + info: 'SQS queues should have a redrive policy configured', + explanation: 'Configuring a redrive policy on an SQS queue allows you to define how many times SQS will make messages available for consumers before sending them to a dead-letter queue. This helps in managing message processing failures and provides a mechanism for handling problematic messages.', + level: NagMessageLevel.ERROR, + rule: sqs.SQSRedrivePolicy, + node: node, + }); } /** From c22ee0590e512a1bf557586db57308bf2c3d5330 Mon Sep 17 00:00:00 2001 From: kevinwochan Date: Fri, 30 Aug 2024 14:45:57 +1000 Subject: [PATCH 33/42] feat(serverless): removing redundant implementation --- src/rules/sqs/SQSRedrivePolicy.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/rules/sqs/SQSRedrivePolicy.ts b/src/rules/sqs/SQSRedrivePolicy.ts index 05aaa64479..b689198b5b 100644 --- a/src/rules/sqs/SQSRedrivePolicy.ts +++ b/src/rules/sqs/SQSRedrivePolicy.ts @@ -1,5 +1,5 @@ import { CfnResource, Stack } from 'aws-cdk-lib'; -import { CfnQueue, Queue } from 'aws-cdk-lib/aws-sqs'; +import { CfnQueue } from 'aws-cdk-lib/aws-sqs'; import { NagRuleCompliance } from '../../nag-rules'; /** @@ -12,14 +12,6 @@ export default function SQSRedrivePolicy(node: CfnResource): NagRuleCompliance { const redrivePolicy = Stack.of(node).resolve(node.redrivePolicy); if (redrivePolicy !== undefined) return NagRuleCompliance.COMPLIANT; return NagRuleCompliance.NON_COMPLIANT; - } else if (node instanceof Queue) { - // For L2 constructs, we need to check the underlying CfnQueue - console.log(node); - const dlqConfig = Stack.of(node).resolve(node.deadLetterQueue); - console.log(dlqConfig); - if (dlqConfig?.maxReceiveCount) return NagRuleCompliance.COMPLIANT; - return NagRuleCompliance.NON_COMPLIANT; } - return NagRuleCompliance.NOT_APPLICABLE; } From d083e17fdee5ee93b1a26bc2c7065569ff078636 Mon Sep 17 00:00:00 2001 From: kevinwochan Date: Fri, 30 Aug 2024 14:55:36 +1000 Subject: [PATCH 34/42] feat(serverless): sns redrive policy rule --- RULES.md | 1 + src/packs/serverless.ts | 20 ++---- ...DeadLetterQueue.ts => SNSRedrivePolicy.ts} | 15 ++--- src/rules/sns/index.ts | 2 +- test/rules/SNS.test.ts | 66 +++++++++++++++++-- 5 files changed, 74 insertions(+), 30 deletions(-) rename src/rules/sns/{SNSDeadLetterQueue.ts => SNSRedrivePolicy.ts} (52%) diff --git a/RULES.md b/RULES.md index 959f56f2e0..05bd269aec 100644 --- a/RULES.md +++ b/RULES.md @@ -730,6 +730,7 @@ The [Serverless Rules](https://awslabs.github.io/serverless-rules/) are a compil | [LambdaDefaultMemorySize](https://awslabs.github.io/serverless-rules/rules/lambda/default_memory_size/) | Lambda function does not specify a memory size. | Lambda CPU power and costs are proportional to the amount of memory configured. You can use tools such as AWS Lambda Power Tuning to test your function at different memory settings to find the one that matches your cost and performance requirements the best. | | | [APIGWAccessLogging](https://docs.aws.amazon.com/config/latest/developerguide/api-gw-execution-logging-enabled.html) | The API Gateway stage does not have access logging enabled. | API Gateway provides access logging for API stages. Enable access logging on your API stages to monitor API requests and responses. | | [SQSRedrivePolicy](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-dead-letter-queues.html) | The SQS queue does not have a redrive policy configured. | Configuring a redrive policy on an SQS queue allows you to define how many times SQS will make messages available for consumers before sending them to a dead-letter queue. This helps in managing message processing failures and provides a mechanism for handling problematic messages. | +| [SNSRedrivePolicy](https://docs.aws.amazon.com/sns/latest/dg/sns-dead-letter-queues.html) | The SNS subscription does not have a redrive policy specified. | Configuring a redrive policy for SNS subscriptions helps manage message delivery failures by specifying how many times SNS will retry undeliverable messages before sending them to a dead-letter queue. This improves the reliability of your messaging system by providing a mechanism to handle and retry failed message deliveries. | ## Footnotes diff --git a/src/packs/serverless.ts b/src/packs/serverless.ts index 4f7624a18f..512f170d45 100644 --- a/src/packs/serverless.ts +++ b/src/packs/serverless.ts @@ -11,7 +11,7 @@ import { lambda, sns, sqs, - stepfunctions + stepfunctions, } from '../rules'; /** @@ -228,11 +228,11 @@ export class ServerlessChecks extends NagPack { */ private checkSNS(node: CfnResource) { this.applyRule({ - info: 'Ensure SNS subscriptions have a DLQ configured', + info: 'SNS subscriptions should specify a redrive policy.', explanation: - 'When a Dead Letter Queue (DLQ) is specified, messages that fail to deliver to targets are stored in the Dead Letter Queue', + 'Configuring a redrive policy helps manage message delivery failures by sending undeliverable messages to a dead-letter queue.', level: NagMessageLevel.ERROR, - rule: sns.SNSDeadLetterQueue, + rule: sns.SNSRedrivePolicy, node: node, }); } @@ -243,18 +243,10 @@ export class ServerlessChecks extends NagPack { * @param ignores list of ignores for the resource */ private checkSQS(node: CfnResource) { - this.applyRule({ - info: 'Ensure SQS queues have a DLQ configured', - explanation: - 'When a Dead Letter Queue (DLQ) is specified, messages that fail to deliver to targets are stored in the Dead Letter Queue', - level: NagMessageLevel.ERROR, - rule: sqs.SQSQueueDLQ, - node: node, - }); - this.applyRule({ info: 'SQS queues should have a redrive policy configured', - explanation: 'Configuring a redrive policy on an SQS queue allows you to define how many times SQS will make messages available for consumers before sending them to a dead-letter queue. This helps in managing message processing failures and provides a mechanism for handling problematic messages.', + explanation: + 'Configuring a redrive policy on an SQS queue allows you to define how many times SQS will make messages available for consumers before sending them to a dead-letter queue. This helps in managing message processing failures and provides a mechanism for handling problematic messages.', level: NagMessageLevel.ERROR, rule: sqs.SQSRedrivePolicy, node: node, diff --git a/src/rules/sns/SNSDeadLetterQueue.ts b/src/rules/sns/SNSRedrivePolicy.ts similarity index 52% rename from src/rules/sns/SNSDeadLetterQueue.ts rename to src/rules/sns/SNSRedrivePolicy.ts index b6d139bde1..3a63119ca2 100644 --- a/src/rules/sns/SNSDeadLetterQueue.ts +++ b/src/rules/sns/SNSRedrivePolicy.ts @@ -1,22 +1,19 @@ -/* -Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -SPDX-License-Identifier: Apache-2.0 -*/ import { parse } from 'path'; import { CfnResource } from 'aws-cdk-lib'; import { CfnSubscription } from 'aws-cdk-lib/aws-sns'; import { NagRuleCompliance } from '../../nag-rules'; /** - * Ensure that API Gateway REST and HTTP APIs are using JSON structured logs - * @param node the CfnResource to check + * SNS subscriptions should specify a redrive policy + * + * @see https://docs.aws.amazon.com/sns/latest/dg/sns-dead-letter-queues.html */ export default Object.defineProperty( (node: CfnResource): NagRuleCompliance => { if (node instanceof CfnSubscription) { - const redrivePolicy = node.redrivePolicy; - if (redrivePolicy) return NagRuleCompliance.COMPLIANT; - return NagRuleCompliance.NON_COMPLIANT; + if (node.redrivePolicy === undefined) + return NagRuleCompliance.NON_COMPLIANT; + return NagRuleCompliance.COMPLIANT; } return NagRuleCompliance.NOT_APPLICABLE; }, diff --git a/src/rules/sns/index.ts b/src/rules/sns/index.ts index 2eb744073d..535b24b3d4 100644 --- a/src/rules/sns/index.ts +++ b/src/rules/sns/index.ts @@ -3,5 +3,5 @@ Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ export { default as SNSEncryptedKMS } from './SNSEncryptedKMS'; +export { default as SNSRedrivePolicy } from './SNSRedrivePolicy'; export { default as SNSTopicSSLPublishOnly } from './SNSTopicSSLPublishOnly'; -export { default as SNSDeadLetterQueue } from './SNSDeadLetterQueue'; diff --git a/test/rules/SNS.test.ts b/test/rules/SNS.test.ts index 2e6c13add3..884dd48792 100644 --- a/test/rules/SNS.test.ts +++ b/test/rules/SNS.test.ts @@ -3,19 +3,29 @@ Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ import { + AnyPrincipal, + Effect, PolicyDocument, PolicyStatement, - Effect, - AnyPrincipal, StarPrincipal, } from 'aws-cdk-lib/aws-iam'; import { Key } from 'aws-cdk-lib/aws-kms'; -import { CfnTopicPolicy, Topic } from 'aws-cdk-lib/aws-sns'; +import { CfnSubscription, CfnTopicPolicy, Topic } from 'aws-cdk-lib/aws-sns'; +import { SqsSubscription } from 'aws-cdk-lib/aws-sns-subscriptions'; +import { Queue } from 'aws-cdk-lib/aws-sqs'; import { Aspects, Stack } from 'aws-cdk-lib/core'; -import { validateStack, TestType, TestPack } from './utils'; -import { SNSEncryptedKMS, SNSTopicSSLPublishOnly } from '../../src/rules/sns'; +import { TestPack, TestType, validateStack } from './utils'; +import { + SNSEncryptedKMS, + SNSRedrivePolicy, + SNSTopicSSLPublishOnly, +} from '../../src/rules/sns'; -const testPack = new TestPack([SNSEncryptedKMS, SNSTopicSSLPublishOnly]); +const testPack = new TestPack([ + SNSEncryptedKMS, + SNSTopicSSLPublishOnly, + SNSRedrivePolicy, +]); let stack: Stack; beforeEach(() => { @@ -89,4 +99,48 @@ describe('Amazon Simple Notification Service (Amazon SNS)', () => { validateStack(stack, ruleId, TestType.COMPLIANCE); }); }); + + describe('SNSRedrivePolicy: SNS subscriptions specify a redrive policy', () => { + const ruleId = 'SNSRedrivePolicy'; + + test('Noncompliance: CfnSubscription without redrive policy', () => { + new CfnSubscription(stack, 'rSubscription', { + topicArn: 'arn:aws:sns:us-east-1:123456789012:MyTopic', + protocol: 'sqs', + endpoint: 'arn:aws:sqs:us-east-1:123456789012:MyQueue', + }); + validateStack(stack, ruleId, TestType.NON_COMPLIANCE); + }); + + test('Noncompliance: Subscription without redrive policy', () => { + const topic = new Topic(stack, 'rTopic'); + const queue = new Queue(stack, 'rQueue'); + topic.addSubscription(new SqsSubscription(queue)); + validateStack(stack, ruleId, TestType.NON_COMPLIANCE); + }); + + test('Compliance: CfnSubscription with redrive policy', () => { + new CfnSubscription(stack, 'rSubscription', { + topicArn: 'arn:aws:sns:us-east-1:123456789012:MyTopic', + protocol: 'sqs', + endpoint: 'arn:aws:sqs:us-east-1:123456789012:MyQueue', + redrivePolicy: { + deadLetterTargetArn: 'arn:aws:sqs:us-east-1:123456789012:MyDLQ', + }, + }); + validateStack(stack, ruleId, TestType.COMPLIANCE); + }); + + test('Compliance: Subscription with redrive policy', () => { + const topic = new Topic(stack, 'rTopic'); + const queue = new Queue(stack, 'rQueue'); + const dlq = new Queue(stack, 'rDLQ'); + topic.addSubscription( + new SqsSubscription(queue, { + deadLetterQueue: dlq, + }) + ); + validateStack(stack, ruleId, TestType.COMPLIANCE); + }); + }); }); From 9a8af8cbb17ec8d4d30c97a517aa5d672256880f Mon Sep 17 00:00:00 2001 From: kevinwochan Date: Fri, 30 Aug 2024 15:13:12 +1000 Subject: [PATCH 35/42] feat(serverless): eventbridge dlq rules --- RULES.md | 7 +- src/packs/serverless.ts | 2 +- src/rules/eventbridge/EventBusDLQ.ts | 2 +- test/rules/EventBridge.test.ts | 208 ++++++++++++++++++++++++++- 4 files changed, 210 insertions(+), 9 deletions(-) diff --git a/RULES.md b/RULES.md index 05bd269aec..5fb4c56efc 100644 --- a/RULES.md +++ b/RULES.md @@ -728,9 +728,10 @@ The [Serverless Rules](https://awslabs.github.io/serverless-rules/) are a compil | [LambdaEventSourceMappingDestination](https://awslabs.github.io/serverless-rules/rules/lambda/eventsourcemapping_failure_destination/) | The Lambda Event Source Mapping does not have a destination configured for failed invocations. | Configuring a destination for failed invocations in Lambda Event Source Mappings allows you to capture and process events that fail to be processed by your Lambda function. This helps in monitoring, debugging, and implementing retry mechanisms for failed events, improving the reliability and observability of your serverless applications. | | [LambdaAsyncFailureDestination](https://awslabs.github.io/serverless-rules/rules/lambda/async_failure_destination/) | The Lambda function does not have a failure destination for asynchronous invocations. | When a Lambda function is invoked asynchronously (e.g., by S3, SNS, or EventBridge), it's important to configure a failure destination. This allows you to capture and handle events that fail processing, improving the reliability and observability of your serverless applications. | | [LambdaDefaultMemorySize](https://awslabs.github.io/serverless-rules/rules/lambda/default_memory_size/) | Lambda function does not specify a memory size. | Lambda CPU power and costs are proportional to the amount of memory configured. You can use tools such as AWS Lambda Power Tuning to test your function at different memory settings to find the one that matches your cost and performance requirements the best. | | -| [APIGWAccessLogging](https://docs.aws.amazon.com/config/latest/developerguide/api-gw-execution-logging-enabled.html) | The API Gateway stage does not have access logging enabled. | API Gateway provides access logging for API stages. Enable access logging on your API stages to monitor API requests and responses. | -| [SQSRedrivePolicy](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-dead-letter-queues.html) | The SQS queue does not have a redrive policy configured. | Configuring a redrive policy on an SQS queue allows you to define how many times SQS will make messages available for consumers before sending them to a dead-letter queue. This helps in managing message processing failures and provides a mechanism for handling problematic messages. | -| [SNSRedrivePolicy](https://docs.aws.amazon.com/sns/latest/dg/sns-dead-letter-queues.html) | The SNS subscription does not have a redrive policy specified. | Configuring a redrive policy for SNS subscriptions helps manage message delivery failures by specifying how many times SNS will retry undeliverable messages before sending them to a dead-letter queue. This improves the reliability of your messaging system by providing a mechanism to handle and retry failed message deliveries. | +| [APIGWAccessLogging](https://awslabs.github.io/serverless-rules/rules/api_gateway/logging/) | The API Gateway stage does not have access logging enabled. | API Gateway provides access logging for API stages. Enable access logging on your API stages to monitor API requests and responses. | +| [SQSRedrivePolicy](https://awslabs.github.io/serverless-rules/rules/sqs/redrive_policy/) | The SQS queue does not have a redrive policy configured. | Configuring a redrive policy on an SQS queue allows you to define how many times SQS will make messages available for consumers before sending them to a dead-letter queue. This helps in managing message processing failures and provides a mechanism for handling problematic messages. | +| [SNSRedrivePolicy](https://awslabs.github.io/serverless-rules/rules/sns/sns-subscription-dlq/) | The SNS subscription does not have a redrive policy specified. | Configuring a redrive policy for SNS subscriptions helps manage message delivery failures by specifying how many times SNS will retry undeliverable messages before sending them to a dead-letter queue. This improves the reliability of your messaging system by providing a mechanism to handle and retry failed message deliveries. | +| [EventBusDLQ](https://awslabs.github.io/serverless-rules/rules/eventbridge/eventbridge-dlq/) | The EventBridge rule does not have a Dead-Letter Queue (DLQ) configured. | Configuring a Dead-Letter Queue (DLQ) for EventBridge rules helps manage failed event deliveries. When a rule's target fails to process an event, the DLQ captures these failed events, allowing for analysis, troubleshooting, and potential reprocessing. This improves the reliability and observability of your event-driven architectures by providing a safety net for handling delivery failures. | ## Footnotes diff --git a/src/packs/serverless.ts b/src/packs/serverless.ts index 512f170d45..ce8777bbd5 100644 --- a/src/packs/serverless.ts +++ b/src/packs/serverless.ts @@ -214,7 +214,7 @@ export class ServerlessChecks extends NagPack { this.applyRule({ info: 'Ensure eventbridge targets have a DLQ configured', explanation: - 'When a Dead Letter Queue (DLQ) is specified, messages that fail to deliver to targets are stored in the Dead Letter Queue', + "Configuring a Dead-Letter Queue (DLQ) for EventBridge rules helps manage failed event deliveries. When a rule's target fails to process an event, the DLQ captures these failed events, allowing for analysis, troubleshooting, and potential reprocessing. This improves the reliability and observability of your event-driven architectures by providing a safety net for handling delivery failures.", level: NagMessageLevel.ERROR, rule: eventbridge.EventBusDLQ, node: node, diff --git a/src/rules/eventbridge/EventBusDLQ.ts b/src/rules/eventbridge/EventBusDLQ.ts index 577af14f88..86ca2d2008 100644 --- a/src/rules/eventbridge/EventBusDLQ.ts +++ b/src/rules/eventbridge/EventBusDLQ.ts @@ -17,7 +17,7 @@ export default Object.defineProperty( const targets: CfnRule.TargetProperty[] = Stack.of(node).resolve( node.targets ); - if (targets.every((target) => target.deadLetterConfig)) + if (targets.every((target) => target.deadLetterConfig !== undefined)) return NagRuleCompliance.COMPLIANT; return NagRuleCompliance.NON_COMPLIANT; } diff --git a/test/rules/EventBridge.test.ts b/test/rules/EventBridge.test.ts index 995af739f5..abb1b36ec7 100644 --- a/test/rules/EventBridge.test.ts +++ b/test/rules/EventBridge.test.ts @@ -3,11 +3,15 @@ Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ import { Aspects, Stack } from 'aws-cdk-lib'; -import { CfnEventBusPolicy } from 'aws-cdk-lib/aws-events'; -import { validateStack, TestType, TestPack } from './utils'; -import { EventBusOpenAccess } from '../../src/rules/eventbridge'; +import * as events from 'aws-cdk-lib/aws-events'; +import { CfnEventBusPolicy, CfnRule } from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import { TestPack, TestType, validateStack } from './utils'; +import { EventBusDLQ, EventBusOpenAccess } from '../../src/rules/eventbridge'; -const testPack = new TestPack([EventBusOpenAccess]); +const testPack = new TestPack([EventBusOpenAccess, EventBusDLQ]); let stack: Stack; beforeEach(() => { @@ -15,6 +19,202 @@ beforeEach(() => { Aspects.of(stack).add(testPack); }); +describe('EventBusDLQ: EventBridge rules should have a Dead Letter Queue', () => { + const ruleId = 'EventBusDLQ'; + + test('Noncompliance: Rule without DLQ', () => { + new CfnRule(stack, 'RuleWithoutDLQ', { + eventPattern: { + source: ['aws.ec2'], + }, + targets: [ + { + arn: 'arn:aws:lambda:us-east-1:111122223333:function:MyFunction', + id: 'Target1', + }, + ], + }); + validateStack(stack, ruleId, TestType.NON_COMPLIANCE); + }); + + test('Compliance: Rule with DLQ', () => { + new CfnRule(stack, 'RuleWithDLQ', { + eventPattern: { + source: ['aws.ec2'], + }, + targets: [ + { + arn: 'arn:aws:lambda:us-east-1:111122223333:function:MyFunction', + id: 'Target1', + deadLetterConfig: { + arn: 'arn:aws:sqs:us-east-1:111122223333:MyDLQ', + }, + }, + ], + }); + validateStack(stack, ruleId, TestType.COMPLIANCE); + }); + + test('Compliance: Rule with multiple targets, all having DLQs', () => { + new CfnRule(stack, 'RuleWithMultipleTargetsAllDLQ', { + eventPattern: { + source: ['aws.ec2'], + }, + targets: [ + { + arn: 'arn:aws:lambda:us-east-1:111122223333:function:Function1', + id: 'Target1', + deadLetterConfig: { + arn: 'arn:aws:sqs:us-east-1:111122223333:DLQ1', + }, + }, + { + arn: 'arn:aws:lambda:us-east-1:111122223333:function:Function2', + id: 'Target2', + deadLetterConfig: { + arn: 'arn:aws:sqs:us-east-1:111122223333:DLQ2', + }, + }, + ], + }); + validateStack(stack, ruleId, TestType.COMPLIANCE); + }); + + test('Noncompliance: Rule with multiple targets, one missing DLQ', () => { + new CfnRule(stack, 'RuleWithMultipleTargetsOneMissingDLQ', { + eventPattern: { + source: ['aws.ec2'], + }, + targets: [ + { + arn: 'arn:aws:lambda:us-east-1:111122223333:function:Function1', + id: 'Target1', + deadLetterConfig: { + arn: 'arn:aws:sqs:us-east-1:111122223333:DLQ1', + }, + }, + { + arn: 'arn:aws:lambda:us-east-1:111122223333:function:Function2', + id: 'Target2', + }, + ], + }); + validateStack(stack, ruleId, TestType.NON_COMPLIANCE); + }); + + describe('L2 Construct Tests', () => { + test('Noncompliance: L2 Rule without DLQ', () => { + const rule = new events.Rule(stack, 'L2RuleWithoutDLQ', { + eventPattern: { + source: ['aws.ec2'], + }, + }); + rule.addTarget( + new targets.LambdaFunction( + new lambda.Function(stack, 'MyLambda', { + runtime: lambda.Runtime.NODEJS_20_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async () => {};'), + }) + ) + ); + validateStack(stack, ruleId, TestType.NON_COMPLIANCE); + }); + + test('Compliance: L2 Rule with DLQ', () => { + const dlq = new sqs.Queue(stack, 'MyDLQ'); + const rule = new events.Rule(stack, 'L2RuleWithDLQ', { + eventPattern: { + source: ['aws.ec2'], + }, + }); + rule.addTarget( + new targets.LambdaFunction( + new lambda.Function(stack, 'MyLambda', { + runtime: lambda.Runtime.NODEJS_20_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async () => {};'), + }), + { + deadLetterQueue: dlq, + } + ) + ); + validateStack(stack, ruleId, TestType.COMPLIANCE); + }); + + test('Compliance: L2 Rule with multiple targets, all having DLQs', () => { + const dlq1 = new sqs.Queue(stack, 'MyDLQ1'); + const dlq2 = new sqs.Queue(stack, 'MyDLQ2'); + const rule = new events.Rule(stack, 'L2RuleWithMultipleTargetsAllDLQ', { + eventPattern: { + source: ['aws.ec2'], + }, + }); + rule.addTarget( + new targets.LambdaFunction( + new lambda.Function(stack, 'MyLambda1', { + runtime: lambda.Runtime.NODEJS_20_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async () => {};'), + }), + { + deadLetterQueue: dlq1, + } + ) + ); + rule.addTarget( + new targets.LambdaFunction( + new lambda.Function(stack, 'MyLambda2', { + runtime: lambda.Runtime.NODEJS_20_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async () => {};'), + }), + { + deadLetterQueue: dlq2, + } + ) + ); + validateStack(stack, ruleId, TestType.COMPLIANCE); + }); + + test('Noncompliance: L2 Rule with multiple targets, one missing DLQ', () => { + const dlq = new sqs.Queue(stack, 'MyDLQ'); + const rule = new events.Rule( + stack, + 'L2RuleWithMultipleTargetsOneMissingDLQ', + { + eventPattern: { + source: ['aws.ec2'], + }, + } + ); + rule.addTarget( + new targets.LambdaFunction( + new lambda.Function(stack, 'MyLambda1', { + runtime: lambda.Runtime.NODEJS_20_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async () => {};'), + }), + { + deadLetterQueue: dlq, + } + ) + ); + rule.addTarget( + new targets.LambdaFunction( + new lambda.Function(stack, 'MyLambda2', { + runtime: lambda.Runtime.NODEJS_20_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async () => {};'), + }) + ) + ); + validateStack(stack, ruleId, TestType.NON_COMPLIANCE); + }); + }); +}); + describe('Amazon EventBridge', () => { describe('EventBusOpenAccess: DMS replication instances are not public', () => { const ruleId = 'EventBusOpenAccess'; From 256ebfa0618346a4ddd58a20917ec5d8dc8acc05 Mon Sep 17 00:00:00 2001 From: kevinwochan Date: Fri, 30 Aug 2024 15:32:55 +1000 Subject: [PATCH 36/42] feat(serverless): appsync tracing rule --- RULES.md | 45 +++++++++++++++++++------------------- src/packs/serverless.ts | 4 ++-- test/rules/AppSync.test.ts | 29 +++++++++++++++++++++--- 3 files changed, 51 insertions(+), 27 deletions(-) diff --git a/RULES.md b/RULES.md index 5fb4c56efc..2566f51f49 100644 --- a/RULES.md +++ b/RULES.md @@ -319,7 +319,7 @@ The [Operational Best Practices for NIST 800-53 rev 4](https://docs.aws.amazon.c | [NIST.800.53.R4-ALBWAFEnabled](https://docs.aws.amazon.com/config/latest/developerguide/alb-waf-enabled.html) | The ALB is not associated with AWS WAFv2 web ACL. | A WAF helps to protect your web applications or APIs against common web exploits. These web exploits may affect availability, compromise security, or consume excessive resources within your environment. | SC-7, SI-4(a)(b)(c) | | [NIST.800.53.R4-APIGWCacheEnabledAndEncrypted](https://docs.aws.amazon.com/config/latest/developerguide/api-gw-cache-enabled-and-encrypted.html) | The API Gateway stage does not have caching enabled and encrypted for all methods. | To help protect data at rest, ensure encryption is enabled for your API Gateway stage's cache. Because sensitive data can be captured for the API method, enable encryption at rest to help protect that data. | SC-13, SC-28 | | [NIST.800.53.R4-APIGWExecutionLoggingEnabled](https://docs.aws.amazon.com/config/latest/developerguide/api-gw-execution-logging-enabled.html) | The API Gateway stage does not have execution logging enabled for all methods. | API Gateway logging displays detailed views of users who accessed the API and the way they accessed the API. This insight enables visibility of user activities. | AU-2(a)(d), AU-3, AU-12(a)(c) | -| [NIST.800.53.R4-AutoScalingGroupELBHealthCheckRequired](https://docs.aws.amazon.com/config/latest/developerguide/autoscaling-group-elb-healthcheck-required.html) | The Auto Scaling group (which is associated with a load balancer) does not utilize ELB health checks. | The Elastic Load Balancer (ELB) health checks for Amazon Elastic Compute Cloud (Amazon EC2) Auto Scaling groups support maintenance of adequate capacity and availability. | SC-5 | +| [NIST.800.53.R4-AutoScalingGroupELBHealthCheckRequired](https://docs.aws.amazon.com/config/latest/developerguide/autoscaling-group-elb-healthcheck-required.html) | The Auto Scaling group (which is associated with a load balancer) does not utilize ELB health checks. | The Elastic Load Balancer (ELB) health checks for Amazon Elastic Compute Cloud (Amazon EC2) Auto Scaling groups support maintenance of adequate capacity and availability. | SC-5 | | [NIST.800.53.R4-CloudTrailCloudWatchLogsEnabled](https://docs.aws.amazon.com/config/latest/developerguide/cloud-trail-cloud-watch-logs-enabled.html) | The trail does not have CloudWatch logs enabled. | Use Amazon CloudWatch to centrally collect and manage log event activity. Inclusion of AWS CloudTrail data provides details of API call activity within your AWS account. | AC-2(4), AC-2(g), AU-2(a)(d), AU-3, AU-6(1)(3), AU-7(1), AU-12(a)(c), CA-7(a)(b), SI-4(2), SI-4(4), SI-4(5), SI-4(a)(b)(c) | | [NIST.800.53.R4-CloudTrailEncryptionEnabled](https://docs.aws.amazon.com/config/latest/developerguide/cloud-trail-encryption-enabled.html) | The trail does not have encryption enabled. | Because sensitive data may exist and to help protect data at rest, ensure encryption is enabled for your AWS CloudTrail trails. | AU-9, SC-13, SC-28 | | [NIST.800.53.R4-CloudTrailLogFileValidationEnabled](https://docs.aws.amazon.com/config/latest/developerguide/cloud-trail-log-file-validation-enabled.html) | The trail does not have log file validation enabled. | Utilize AWS CloudTrail log file validation to check the integrity of CloudTrail logs. Log file validation helps determine if a log file was modified or deleted or unchanged after CloudTrail delivered it. This feature is built using industry standard algorithms: SHA-256 for hashing and SHA-256 with RSA for digital signing. This makes it computationally infeasible to modify, delete or forge CloudTrail log files without detection. | AU-9, SC-13, SC-28 | @@ -447,7 +447,7 @@ The [Operational Best Practices for NIST 800-53 rev 5](https://docs.aws.amazon.c | [NIST.800.53.R5-APIGWCacheEnabledAndEncrypted](https://docs.aws.amazon.com/config/latest/developerguide/api-gw-cache-enabled-and-encrypted.html) | The API Gateway stage does not have caching enabled and encrypted for all methods. | To help protect data at rest, ensure encryption is enabled for your API Gateway stage's cache. Because sensitive data can be captured for the API method, enable encryption at rest to help protect that data. | AU-9(3), CP-9d, SC-8(3), SC-8(4), SC-13a, SC-28(1), SI-19(4) | | [NIST.800.53.R5-APIGWExecutionLoggingEnabled](https://docs.aws.amazon.com/config/latest/developerguide/api-gw-execution-logging-enabled.html) | The API Gateway stage does not have execution logging enabled for all methods. | API Gateway logging displays detailed views of users who accessed the API and the way they accessed the API. This insight enables visibility of user activities. | AC-4(26), AU-2b, AU-3a, AU-3b, AU-3c, AU-3d, AU-3e, AU-3f, AU-6(3), AU-6(4), AU-6(6), AU-6(9), AU-8b, AU-10, AU-12a, AU-12c, AU-12(1), AU-12(2), AU-12(3), AU-12(4), AU-14a, AU-14b, AU-14b, AU-14(3), CA-7b, CM-5(1)(b), IA-3(3)(b), MA-4(1)(a), PM-14a.1, PM-14b, PM-31, SC-7(9)(b), SI-4(17), SI-7(8) | | [NIST.800.53.R5-APIGWSSLEnabled](https://docs.aws.amazon.com/config/latest/developerguide/api-gw-ssl-enabled.html) | The API Gateway REST API stage is not configured with SSL certificates. | Ensure Amazon API Gateway REST API stages are configured with SSL certificates to allow backend systems to authenticate that requests originate from API Gateway. | AC-4, AC-4(22), AC-17(2), AC-24(1), AU-9(3), CA-9b, IA-5(1)(c), PM-17b, SC-7(4)(b), SC-7(4)(g), SC-8, SC-8(1), SC-8(2), SC-8(3), SC-8(4), SC-8(5), SC-13a, SC-23, SI-1a.2, SI-1a.2, SI-1c.2 | -| [NIST.800.53.R5-AutoScalingGroupELBHealthCheckRequired](https://docs.aws.amazon.com/config/latest/developerguide/autoscaling-group-elb-healthcheck-required.html) | The Auto Scaling group (which is associated with a load balancer) does not utilize ELB health checks. | The Elastic Load Balancer (ELB) health checks for Amazon Elastic Compute Cloud (Amazon EC2) Auto Scaling groups support maintenance of adequate capacity and availability. The load balancer periodically sends pings, attempts connections, or sends requests to test Amazon EC2 instances health in an auto-scaling group. If an instance is not reporting back, traffic is sent to a new Amazon EC2 instance. | AU-12(3), AU-14a, AU-14b, CA-2(2), CA-7, CA-7b, CM-6a, CM-9b, PM-14a.1, PM-14b, PM-31, SC-6, SC-36(1)(a), SI-2a | +| [NIST.800.53.R5-AutoScalingGroupELBHealthCheckRequired](https://docs.aws.amazon.com/config/latest/developerguide/autoscaling-group-elb-healthcheck-required.html) | The Auto Scaling group (which is associated with a load balancer) does not utilize ELB health checks. | The Elastic Load Balancer (ELB) health checks for Amazon Elastic Compute Cloud (Amazon EC2) Auto Scaling groups support maintenance of adequate capacity and availability. The load balancer periodically sends pings, attempts connections, or sends requests to test Amazon EC2 instances health in an auto-scaling group. If an instance is not reporting back, traffic is sent to a new Amazon EC2 instance. | AU-12(3), AU-14a, AU-14b, CA-2(2), CA-7, CA-7b, CM-6a, CM-9b, PM-14a.1, PM-14b, PM-31, SC-6, SC-36(1)(a), SI-2a | | [NIST.800.53.R5-AutoScalingLaunchConfigPublicIpDisabled](https://docs.aws.amazon.com/config/latest/developerguide/autoscaling-launch-config-public-ip-disabled.html) | The Auto Scaling launch configuration does not have public IP addresses disabled. | If you configure your Network Interfaces with a public IP address, then the associated resources to those Network Interfaces are reachable from the internet. EC2 resources should not be publicly accessible, as this may allow unintended access to your applications or servers. | AC-3, AC-4(21), CM-6a, SC-7(3) | | [NIST.800.53.R5-CloudTrailCloudWatchLogsEnabled](https://docs.aws.amazon.com/config/latest/developerguide/cloud-trail-cloud-watch-logs-enabled.html) | The trail does not have CloudWatch logs enabled. | Use Amazon CloudWatch to centrally collect and manage log event activity. Inclusion of AWS CloudTrail data provides details of API call activity within your AWS account. | AC-2(4), AC-3(1), AC-3(10), AC-4(26), AC-6(9), AU-2b, AU-3a, AU-3b, AU-3c, AU-3d, AU-3e, AU-3f, AU-4(1), AU-6(1), AU-6(3), AU-6(4), AU-6(5), AU-6(6), AU-6(9), AU-7(1), AU-8b, AU-9(7), AU-10, AU-12a, AU-12c, AU-12(1), AU-12(2), AU-12(3), AU-12(4), AU-14a, AU-14b, AU-14b, AU-14(3), AU-16, CA-7b, CM-5(1)(b), CM-6a, CM-9b, IA-3(3)(b), MA-4(1)(a), PM-14a.1, PM-14b, PM-31, SC-7(9)(b), SI-1(1)(c), SI-3(8)(b), SI-4(2), SI-4(17), SI-4(20), SI-7(8), SI-10(1)(c) | | [NIST.800.53.R5-CloudTrailEncryptionEnabled](https://docs.aws.amazon.com/config/latest/developerguide/cloud-trail-encryption-enabled.html) | The trail does not have encryption enabled. | Because sensitive data may exist and to help protect data at rest, ensure encryption is enabled for your AWS CloudTrail trails. | AU-9(3), CM-6a, CM-9b, CP-9d, SC-8(3), SC-8(4), SC-13a, SC-28(1), SI-19(4) | @@ -592,7 +592,7 @@ The [Operational Best Practices for PCI DSS 3.2.1](https://docs.aws.amazon.com/c | [PCI.DSS.321-APIGWCacheEnabledAndEncrypted](https://docs.aws.amazon.com/config/latest/developerguide/api-gw-cache-enabled-and-encrypted.html) | The API Gateway stage does not have caching enabled and encrypted for all methods. | To help protect data at rest, ensure encryption is enabled for your API Gateway stage's cache. Because sensitive data can be captured for the API method, enable encryption at rest to help protect that data. | 3.4 | | [PCI.DSS.321-APIGWExecutionLoggingEnabled](https://docs.aws.amazon.com/config/latest/developerguide/api-gw-execution-logging-enabled.html) | The API Gateway stage does not have execution logging enabled for all methods. | API Gateway logging displays detailed views of users who accessed the API and the way they accessed the API. This insight enables visibility of user activities. | 10.1, 10.3.1, 10.3.2, 10.3.3, 10.3.4, 10.3.5, 10.3.6, 10.5.4 | | [PCI.DSS.321-APIGWSSLEnabled](https://docs.aws.amazon.com/config/latest/developerguide/api-gw-ssl-enabled.html) | The API Gateway REST API stage is not configured with SSL certificates. | Ensure Amazon API Gateway REST API stages are configured with SSL certificates to allow backend systems to authenticate that requests originate from API Gateway. | 2.3, 4.1, 8.2.1 | -| [PCI.DSS.321-AutoScalingGroupELBHealthCheckRequired](https://docs.aws.amazon.com/config/latest/developerguide/autoscaling-group-elb-healthcheck-required.html) | The Auto Scaling group (which is associated with a load balancer) does not utilize ELB health checks. | The Elastic Load Balancer (ELB) health checks for Amazon Elastic Compute Cloud (Amazon EC2) Auto Scaling groups support maintenance of adequate capacity and availability. The load balancer periodically sends pings, attempts connections, or sends requests to test Amazon EC2 instances health in an auto-scaling group. If an instance is not reporting back, traffic is sent to a new Amazon EC2 instance. | 2.2 | +| [PCI.DSS.321-AutoScalingGroupELBHealthCheckRequired](https://docs.aws.amazon.com/config/latest/developerguide/autoscaling-group-elb-healthcheck-required.html) | The Auto Scaling group (which is associated with a load balancer) does not utilize ELB health checks. | The Elastic Load Balancer (ELB) health checks for Amazon Elastic Compute Cloud (Amazon EC2) Auto Scaling groups support maintenance of adequate capacity and availability. The load balancer periodically sends pings, attempts connections, or sends requests to test Amazon EC2 instances health in an auto-scaling group. If an instance is not reporting back, traffic is sent to a new Amazon EC2 instance. | 2.2 | | [PCI.DSS.321-AutoScalingLaunchConfigPublicIpDisabled](https://docs.aws.amazon.com/config/latest/developerguide/autoscaling-launch-config-public-ip-disabled.html) | The Auto Scaling launch configuration does not have public IP addresses disabled. | If you configure your Network Interfaces with a public IP address, then the associated resources to those Network Interfaces are reachable from the internet. EC2 resources should not be publicly accessible, as this may allow unintended access to your applications or servers. | 1.2, 1.2.1, 1.3, 1.3.1, 1.3.2, 1.3.4, 1.3.6, 2.2.2 | | [PCI.DSS.321-CloudTrailCloudWatchLogsEnabled](https://docs.aws.amazon.com/config/latest/developerguide/cloud-trail-cloud-watch-logs-enabled.html) | The trail does not have CloudWatch logs enabled. | Use Amazon CloudWatch to centrally collect and manage log event activity. Inclusion of AWS CloudTrail data provides details of API call activity within your AWS account. | 2.2, 10.1, 10.2.1, 10.2.2, 10.2.3, 10.2.5, 10.3.1, 10.3.2, 10.3.3, 10.3.4, 10.3.5, 10.3.6, 10.5.3, 10.5.4 | | [PCI.DSS.321-CloudTrailEncryptionEnabled](https://docs.aws.amazon.com/config/latest/developerguide/cloud-trail-encryption-enabled.html) | The trail does not have encryption enabled. | Because sensitive data may exist and to help protect data at rest, ensure encryption is enabled for your AWS CloudTrail trails. | 2.2, 3.4, 10.5 | @@ -710,28 +710,29 @@ The [Serverless Rules](https://awslabs.github.io/serverless-rules/) are a compil ### Warnings -| Rule ID | Cause | Explanation | -| ------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [LambdaTracing](https://awslabs.github.io/serverless-rules/rules/lambda/tracing/) | Lambda function does not have X-Ray tracing enabled. | AWS Lambda can emit traces to AWS X-Ray, which enables visualizing service maps for faster troubleshooting. X-Ray tracing provides insights into the performance and behavior of your Lambda functions, helping to identify bottlenecks, errors, and dependencies. Enabling tracing allows you to track requests as they traverse through your serverless applications, making it easier to debug and optimize your functions. Consider enabling X-Ray tracing. | -| [StarPermissions](https://awslabs.github.io/serverless-rules/rules/lambda/star_permissions/) | IAM role has overly permissive policies. | IAM roles should follow the principle of least privilege. Avoid using wildcard (*) permissions in IAM policies attached to Lambda roles. Instead, specify only the permissions required for the function to operate. This reduces the potential impact if the function is compromised. Review and tighten the IAM permissions for your Lambda functions. | -| [LambdaDefaultMemorySize](https://awslabs.github.io/serverless-rules/rules/lambda/default_memory_size/) | Lambda function does not specify a memory size. | Lambda CPU power and costs are proportional to the amount of memory configured. You can use tools such as AWS Lambda Power Tuning to test your function at different memory settings to find the one that matches your cost and performance requirements the best. | -| [CloudWatchLogGroupRetentionPeriod](https://docs.aws.amazon.com/config/latest/developerguide/cw-loggroup-retention-period-check.html) | The CloudWatch Log Group does not have an explicit retention period configured. | By default, CloudWatch log groups created by Lambda functions have an unlimited retention time. For cost optimization purposes, you should set a retention duration on all log groups. For log archival, export and set cost-effective storage classes that best suit your needs. | -| [LambdaFunctionDefaultTimeout](https://awslabs.github.io/serverless-rules/rules/lambda/default_timeout/) | Lambda function uses the default timeout. | The default timeout for Lambda functions is 3 seconds, which may not be sufficient for many use cases. Setting an appropriate timeout helps prevent unexpected function terminations and ensures your function has enough time to complete its tasks. Consider the nature of your function's operations and set a timeout that balances between allowing sufficient execution time and avoiding unnecessarily long-running functions. | -| [LambdaLatestVersion](https://awslabs.github.io/serverless-rules/rules/lambda/end_of_life_runtime/) | Lambda function is not using the latest runtime version. | Using the latest runtime version ensures that your Lambda function has access to the most recent features, performance improvements, and security updates. It's important to regularly update your Lambda functions to use the latest runtime versions to maintain optimal performance and security. | -| [APIGWStructuredLogging](https://awslabs.github.io/serverless-rules/rules/apigateway/structured_logging/) | The API Gateway stage does not have structured logging enabled. | API Gateway can emit structured logs to CloudWatch Logs. Structured logging provides a consistent and machine-readable format for log entries, making it easier to search, analyze, and process log data. Enable structured logging to improve the observability and troubleshooting capabilities of your API Gateway deployments. | -| [StepFunctionStateMachineXray](https://awslabs.github.io/serverless-rules/rules/stepfunctions/xray/) | The Step Functions state machine does not have X-Ray tracing enabled. | AWS Step Functions can emit traces to AWS X-Ray, which enables visualizing service maps for faster troubleshooting. X-Ray tracing provides insights into the execution of your state machines, helping to identify bottlenecks, errors, and dependencies. Enabling tracing allows you to track executions as they traverse through your serverless workflows, making it easier to debug and optimize your state machines. Consider enabling X-Ray tracing. | +| Rule ID | Cause | Explanation | +| ------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [LambdaTracing](https://awslabs.github.io/serverless-rules/rules/lambda/tracing/) | Lambda function does not have X-Ray tracing enabled. | AWS Lambda can emit traces to AWS X-Ray, which enables visualizing service maps for faster troubleshooting. X-Ray tracing provides insights into the performance and behavior of your Lambda functions, helping to identify bottlenecks, errors, and dependencies. Enabling tracing allows you to track requests as they traverse through your serverless applications, making it easier to debug and optimize your functions. Consider enabling X-Ray tracing. | +| [StarPermissions](https://awslabs.github.io/serverless-rules/rules/lambda/star_permissions/) | IAM role has overly permissive policies. | IAM roles should follow the principle of least privilege. Avoid using wildcard (*) permissions in IAM policies attached to Lambda roles. Instead, specify only the permissions required for the function to operate. This reduces the potential impact if the function is compromised. Review and tighten the IAM permissions for your Lambda functions. | +| [LambdaDefaultMemorySize](https://awslabs.github.io/serverless-rules/rules/lambda/default_memory_size/) | Lambda function does not specify a memory size. | Lambda CPU power and costs are proportional to the amount of memory configured. You can use tools such as AWS Lambda Power Tuning to test your function at different memory settings to find the one that matches your cost and performance requirements the best. | +| [CloudWatchLogGroupRetentionPeriod](https://docs.aws.amazon.com/config/latest/developerguide/cw-loggroup-retention-period-check.html) | The CloudWatch Log Group does not have an explicit retention period configured. | By default, CloudWatch log groups created by Lambda functions have an unlimited retention time. For cost optimization purposes, you should set a retention duration on all log groups. For log archival, export and set cost-effective storage classes that best suit your needs. | +| [LambdaFunctionDefaultTimeout](https://awslabs.github.io/serverless-rules/rules/lambda/default_timeout/) | Lambda function uses the default timeout. | The default timeout for Lambda functions is 3 seconds, which may not be sufficient for many use cases. Setting an appropriate timeout helps prevent unexpected function terminations and ensures your function has enough time to complete its tasks. Consider the nature of your function's operations and set a timeout that balances between allowing sufficient execution time and avoiding unnecessarily long-running functions. | +| [LambdaLatestVersion](https://awslabs.github.io/serverless-rules/rules/lambda/end_of_life_runtime/) | Lambda function is not using the latest runtime version. | Using the latest runtime version ensures that your Lambda function has access to the most recent features, performance improvements, and security updates. It's important to regularly update your Lambda functions to use the latest runtime versions to maintain optimal performance and security. | +| [APIGWStructuredLogging](https://awslabs.github.io/serverless-rules/rules/apigateway/structured_logging/) | The API Gateway stage does not have structured logging enabled. | API Gateway can emit structured logs to CloudWatch Logs. Structured logging provides a consistent and machine-readable format for log entries, making it easier to search, analyze, and process log data. Enable structured logging to improve the observability and troubleshooting capabilities of your API Gateway deployments. | +| [StepFunctionStateMachineXray](https://awslabs.github.io/serverless-rules/rules/stepfunctions/xray/) | The Step Functions state machine does not have X-Ray tracing enabled. | AWS Step Functions can emit traces to AWS X-Ray, which enables visualizing service maps for faster troubleshooting. X-Ray tracing provides insights into the execution of your state machines, helping to identify bottlenecks, errors, and dependencies. Enabling tracing allows you to track executions as they traverse through your serverless workflows, making it easier to debug and optimize your state machines. Consider enabling X-Ray tracing. | +| [AppSyncTracing](https://awslabs.github.io/serverless-rules/rules/appsync/tracing/) | The AppSync API does not have X-Ray tracing enabled. | AWS AppSync can emit traces to AWS X-Ray, which enables visualizing service maps for faster troubleshooting. X-Ray tracing provides insights into the execution of your GraphQL APIs, helping to identify bottlenecks, errors, and dependencies. Enabling tracing allows you to track requests as they traverse through your serverless applications, making it easier to debug and optimize your AppSync APIs. Consider enabling X-Ray tracing for improved observability. | ### Errors -| Rule ID | Cause | Explanation | -| -------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [LambdaEventSourceMappingDestination](https://awslabs.github.io/serverless-rules/rules/lambda/eventsourcemapping_failure_destination/) | The Lambda Event Source Mapping does not have a destination configured for failed invocations. | Configuring a destination for failed invocations in Lambda Event Source Mappings allows you to capture and process events that fail to be processed by your Lambda function. This helps in monitoring, debugging, and implementing retry mechanisms for failed events, improving the reliability and observability of your serverless applications. | -| [LambdaAsyncFailureDestination](https://awslabs.github.io/serverless-rules/rules/lambda/async_failure_destination/) | The Lambda function does not have a failure destination for asynchronous invocations. | When a Lambda function is invoked asynchronously (e.g., by S3, SNS, or EventBridge), it's important to configure a failure destination. This allows you to capture and handle events that fail processing, improving the reliability and observability of your serverless applications. | -| [LambdaDefaultMemorySize](https://awslabs.github.io/serverless-rules/rules/lambda/default_memory_size/) | Lambda function does not specify a memory size. | Lambda CPU power and costs are proportional to the amount of memory configured. You can use tools such as AWS Lambda Power Tuning to test your function at different memory settings to find the one that matches your cost and performance requirements the best. | | -| [APIGWAccessLogging](https://awslabs.github.io/serverless-rules/rules/api_gateway/logging/) | The API Gateway stage does not have access logging enabled. | API Gateway provides access logging for API stages. Enable access logging on your API stages to monitor API requests and responses. | -| [SQSRedrivePolicy](https://awslabs.github.io/serverless-rules/rules/sqs/redrive_policy/) | The SQS queue does not have a redrive policy configured. | Configuring a redrive policy on an SQS queue allows you to define how many times SQS will make messages available for consumers before sending them to a dead-letter queue. This helps in managing message processing failures and provides a mechanism for handling problematic messages. | -| [SNSRedrivePolicy](https://awslabs.github.io/serverless-rules/rules/sns/sns-subscription-dlq/) | The SNS subscription does not have a redrive policy specified. | Configuring a redrive policy for SNS subscriptions helps manage message delivery failures by specifying how many times SNS will retry undeliverable messages before sending them to a dead-letter queue. This improves the reliability of your messaging system by providing a mechanism to handle and retry failed message deliveries. | -| [EventBusDLQ](https://awslabs.github.io/serverless-rules/rules/eventbridge/eventbridge-dlq/) | The EventBridge rule does not have a Dead-Letter Queue (DLQ) configured. | Configuring a Dead-Letter Queue (DLQ) for EventBridge rules helps manage failed event deliveries. When a rule's target fails to process an event, the DLQ captures these failed events, allowing for analysis, troubleshooting, and potential reprocessing. This improves the reliability and observability of your event-driven architectures by providing a safety net for handling delivery failures. | +| Rule ID | Cause | Explanation | +| -------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [LambdaEventSourceMappingDestination](https://awslabs.github.io/serverless-rules/rules/lambda/eventsourcemapping_failure_destination/) | The Lambda Event Source Mapping does not have a destination configured for failed invocations. | Configuring a destination for failed invocations in Lambda Event Source Mappings allows you to capture and process events that fail to be processed by your Lambda function. This helps in monitoring, debugging, and implementing retry mechanisms for failed events, improving the reliability and observability of your serverless applications. | +| [LambdaAsyncFailureDestination](https://awslabs.github.io/serverless-rules/rules/lambda/async_failure_destination/) | The Lambda function does not have a failure destination for asynchronous invocations. | When a Lambda function is invoked asynchronously (e.g., by S3, SNS, or EventBridge), it's important to configure a failure destination. This allows you to capture and handle events that fail processing, improving the reliability and observability of your serverless applications. | +| [LambdaDefaultMemorySize](https://awslabs.github.io/serverless-rules/rules/lambda/default_memory_size/) | Lambda function does not specify a memory size. | Lambda CPU power and costs are proportional to the amount of memory configured. You can use tools such as AWS Lambda Power Tuning to test your function at different memory settings to find the one that matches your cost and performance requirements the best. | | +| [APIGWAccessLogging](https://awslabs.github.io/serverless-rules/rules/api_gateway/logging/) | The API Gateway stage does not have access logging enabled. | API Gateway provides access logging for API stages. Enable access logging on your API stages to monitor API requests and responses. | +| [SQSRedrivePolicy](https://awslabs.github.io/serverless-rules/rules/sqs/redrive_policy/) | The SQS queue does not have a redrive policy configured. | Configuring a redrive policy on an SQS queue allows you to define how many times SQS will make messages available for consumers before sending them to a dead-letter queue. This helps in managing message processing failures and provides a mechanism for handling problematic messages. | +| [SNSRedrivePolicy](https://awslabs.github.io/serverless-rules/rules/sns/sns-subscription-dlq/) | The SNS subscription does not have a redrive policy specified. | Configuring a redrive policy for SNS subscriptions helps manage message delivery failures by specifying how many times SNS will retry undeliverable messages before sending them to a dead-letter queue. This improves the reliability of your messaging system by providing a mechanism to handle and retry failed message deliveries. | +| [EventBusDLQ](https://awslabs.github.io/serverless-rules/rules/eventbridge/eventbridge-dlq/) | The EventBridge rule does not have a Dead-Letter Queue (DLQ) configured. | Configuring a Dead-Letter Queue (DLQ) for EventBridge rules helps manage failed event deliveries. When a rule's target fails to process an event, the DLQ captures these failed events, allowing for analysis, troubleshooting, and potential reprocessing. This improves the reliability and observability of your event-driven architectures by providing a safety net for handling delivery failures. | ## Footnotes diff --git a/src/packs/serverless.ts b/src/packs/serverless.ts index ce8777bbd5..b8671ba199 100644 --- a/src/packs/serverless.ts +++ b/src/packs/serverless.ts @@ -198,8 +198,8 @@ export class ServerlessChecks extends NagPack { this.applyRule({ info: 'Ensure tracing is enabled', explanation: - 'AWS AppSync provides active tracing support for AWS X-Ray. Enable active tracing on your API stages to sample incoming requests and send traces to X-Ray.', - level: NagMessageLevel.ERROR, + 'AWS AppSync can emit traces to AWS X-Ray, which enables visualizing service maps for faster troubleshooting.', + level: NagMessageLevel.WARN, rule: appsync.AppSyncTracing, node: node, }); diff --git a/test/rules/AppSync.test.ts b/test/rules/AppSync.test.ts index f91dfa2188..e0057977c9 100644 --- a/test/rules/AppSync.test.ts +++ b/test/rules/AppSync.test.ts @@ -4,10 +4,13 @@ SPDX-License-Identifier: Apache-2.0 */ import { CfnGraphQLApi } from 'aws-cdk-lib/aws-appsync'; import { Aspects, Stack } from 'aws-cdk-lib/core'; -import { validateStack, TestType, TestPack } from './utils'; -import { AppSyncGraphQLRequestLogging } from '../../src/rules/appsync'; +import { TestPack, TestType, validateStack } from './utils'; +import { + AppSyncGraphQLRequestLogging, + AppSyncTracing, +} from '../../src/rules/appsync'; -const testPack = new TestPack([AppSyncGraphQLRequestLogging]); +const testPack = new TestPack([AppSyncGraphQLRequestLogging, AppSyncTracing]); let stack: Stack; beforeEach(() => { @@ -42,4 +45,24 @@ describe('AWS AppSync', () => { validateStack(stack, ruleId, TestType.COMPLIANCE); }); }); + + describe('AppSyncTracing: GraphQL APIs have X-Ray tracing enabled', () => { + const ruleId = 'AppSyncTracing'; + test('Noncompliance 1', () => { + new CfnGraphQLApi(stack, 'rGraphqlApi', { + authenticationType: 'AMAZON_COGNITO_USER_POOLS', + name: 'foo', + }); + validateStack(stack, ruleId, TestType.NON_COMPLIANCE); + }); + + test('Compliance - L1 Construct', () => { + new CfnGraphQLApi(stack, 'rGraphqlApi', { + authenticationType: 'AMAZON_COGNITO_USER_POOLS', + name: 'foo', + xrayEnabled: true, + }); + validateStack(stack, ruleId, TestType.COMPLIANCE); + }); + }); }); From f8cb04a37ef5aa78fc4c6ef6debfe3a3f29fd249 Mon Sep 17 00:00:00 2001 From: kevinwochan Date: Mon, 2 Sep 2024 13:17:18 +1000 Subject: [PATCH 37/42] feat(serverless): http and rest api throttling rule --- src/rules/apigw/APIGWDefaultThrottling.ts | 46 ++++++++++++++++ src/rules/apigw/index.ts | 3 +- test/rules/APIGW.test.ts | 65 +++++++++++++++++++++++ 3 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 src/rules/apigw/APIGWDefaultThrottling.ts diff --git a/src/rules/apigw/APIGWDefaultThrottling.ts b/src/rules/apigw/APIGWDefaultThrottling.ts new file mode 100644 index 0000000000..31e56c0831 --- /dev/null +++ b/src/rules/apigw/APIGWDefaultThrottling.ts @@ -0,0 +1,46 @@ +import { CfnResource, Stack } from 'aws-cdk-lib'; +import { CfnStage } from 'aws-cdk-lib/aws-apigateway'; +import { CfnStage as CfnHttpStage } from 'aws-cdk-lib/aws-apigatewayv2'; +import { NagRuleCompliance } from '../../nag-rules'; + +/** + * API Gateway stages have default throttling configured + * @param node The CfnStage or CfnHttpStage to check + */ +export default Object.defineProperty( + (node: CfnResource): NagRuleCompliance => { + if (node instanceof CfnStage) { + // Check REST API + const methodSettings = Stack.of(node).resolve(node.methodSettings); + if ( + Array.isArray(methodSettings) && + methodSettings.some( + (setting) => + setting.throttlingBurstLimit !== undefined && + setting.throttlingRateLimit !== undefined && + setting.httpMethod === '*' && + setting.resourcePath === '/*' + ) + ) { + return NagRuleCompliance.COMPLIANT; + } + return NagRuleCompliance.NON_COMPLIANT; + } else if (node instanceof CfnHttpStage) { + // Check HTTP API + const defaultRouteSettings = Stack.of(node).resolve( + node.defaultRouteSettings + ); + if ( + defaultRouteSettings && + defaultRouteSettings.throttlingBurstLimit !== undefined && + defaultRouteSettings.throttlingRateLimit !== undefined + ) { + return NagRuleCompliance.COMPLIANT; + } + return NagRuleCompliance.NON_COMPLIANT; + } + return NagRuleCompliance.NOT_APPLICABLE; + }, + 'name', + { value: 'APIGWDefaultThrottling' } +); diff --git a/src/rules/apigw/index.ts b/src/rules/apigw/index.ts index a2fbccfb11..2e365eb804 100644 --- a/src/rules/apigw/index.ts +++ b/src/rules/apigw/index.ts @@ -6,8 +6,9 @@ export { default as APIGWAccessLogging } from './APIGWAccessLogging'; export { default as APIGWAssociatedWithWAF } from './APIGWAssociatedWithWAF'; export { default as APIGWAuthorization } from './APIGWAuthorization'; export { default as APIGWCacheEnabledAndEncrypted } from './APIGWCacheEnabledAndEncrypted'; +export { default as APIGWDefaultThrottling } from './APIGWDefaultThrottling'; export { default as APIGWExecutionLoggingEnabled } from './APIGWExecutionLoggingEnabled'; export { default as APIGWRequestValidation } from './APIGWRequestValidation'; export { default as APIGWSSLEnabled } from './APIGWSSLEnabled'; -export { default as APIGWXrayEnabled } from './APIGWXrayEnabled'; export { default as APIGWStructuredLogging } from './APIGWStructuredLogging'; +export { default as APIGWXrayEnabled } from './APIGWXrayEnabled'; diff --git a/test/rules/APIGW.test.ts b/test/rules/APIGW.test.ts index fabd54064c..1da5dfba12 100644 --- a/test/rules/APIGW.test.ts +++ b/test/rules/APIGW.test.ts @@ -22,6 +22,7 @@ import { APIGWAssociatedWithWAF, APIGWAuthorization, APIGWCacheEnabledAndEncrypted, + APIGWDefaultThrottling, APIGWExecutionLoggingEnabled, APIGWRequestValidation, APIGWSSLEnabled, @@ -39,6 +40,7 @@ const testPack = new TestPack([ APIGWSSLEnabled, APIGWXrayEnabled, APIGWStructuredLogging, + APIGWDefaultThrottling, ]); let stack: Stack; @@ -444,4 +446,67 @@ describe('Amazon API Gateway', () => { validateStack(stack, ruleId, TestType.COMPLIANCE); }); }); + + describe('APIGWDefaultThrottling: API Gateway REST and HTTP APIs have default throttling enabled', () => { + const ruleId = 'APIGWDefaultThrottling'; + + test('Noncompliance 1: REST API without throttling', () => { + new CfnStage(stack, 'rRestApiStageNoThrottling', { + restApiId: 'foo', + stageName: 'prod', + }); + validateStack(stack, ruleId, TestType.NON_COMPLIANCE); + }); + + test('Noncompliance 2: HTTP API without throttling', () => { + new CfnV2Stage(stack, 'rHttpApiStageNoThrottling', { + apiId: 'bar', + stageName: 'prod', + }); + validateStack(stack, ruleId, TestType.NON_COMPLIANCE); + }); + + test('Noncompliance 3: REST API with incomplete throttling', () => { + new CfnStage(stack, 'rRestApiStageIncompleteThrottling', { + restApiId: 'foo', + stageName: 'prod', + methodSettings: [ + { + httpMethod: '*', + resourcePath: '/*', + throttlingRateLimit: 100, + }, + ], + }); + validateStack(stack, ruleId, TestType.NON_COMPLIANCE); + }); + + test('Compliance 1: REST API with complete throttling', () => { + new CfnStage(stack, 'rRestApiStageCompliantThrottling', { + restApiId: 'foo', + stageName: 'prod', + methodSettings: [ + { + httpMethod: '*', + resourcePath: '/*', + throttlingRateLimit: 100, + throttlingBurstLimit: 50, + }, + ], + }); + validateStack(stack, ruleId, TestType.COMPLIANCE); + }); + + test('Compliance 2: HTTP API with throttling', () => { + new CfnV2Stage(stack, 'rHttpApiStageCompliantThrottling', { + apiId: 'bar', + stageName: 'prod', + defaultRouteSettings: { + throttlingRateLimit: 100, + throttlingBurstLimit: 50, + }, + }); + validateStack(stack, ruleId, TestType.COMPLIANCE); + }); + }); }); From 760975e1c3510e0283f77f325b94cd22bd10acdc Mon Sep 17 00:00:00 2001 From: kevinwochan Date: Mon, 2 Sep 2024 13:23:07 +1000 Subject: [PATCH 38/42] feat(serverless): added apigw throttling rule --- RULES.md | 2 ++ src/packs/serverless.ts | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/RULES.md b/RULES.md index 2566f51f49..fd94fb623d 100644 --- a/RULES.md +++ b/RULES.md @@ -733,6 +733,8 @@ The [Serverless Rules](https://awslabs.github.io/serverless-rules/) are a compil | [SQSRedrivePolicy](https://awslabs.github.io/serverless-rules/rules/sqs/redrive_policy/) | The SQS queue does not have a redrive policy configured. | Configuring a redrive policy on an SQS queue allows you to define how many times SQS will make messages available for consumers before sending them to a dead-letter queue. This helps in managing message processing failures and provides a mechanism for handling problematic messages. | | [SNSRedrivePolicy](https://awslabs.github.io/serverless-rules/rules/sns/sns-subscription-dlq/) | The SNS subscription does not have a redrive policy specified. | Configuring a redrive policy for SNS subscriptions helps manage message delivery failures by specifying how many times SNS will retry undeliverable messages before sending them to a dead-letter queue. This improves the reliability of your messaging system by providing a mechanism to handle and retry failed message deliveries. | | [EventBusDLQ](https://awslabs.github.io/serverless-rules/rules/eventbridge/eventbridge-dlq/) | The EventBridge rule does not have a Dead-Letter Queue (DLQ) configured. | Configuring a Dead-Letter Queue (DLQ) for EventBridge rules helps manage failed event deliveries. When a rule's target fails to process an event, the DLQ captures these failed events, allowing for analysis, troubleshooting, and potential reprocessing. This improves the reliability and observability of your event-driven architectures by providing a safety net for handling delivery failures. | +| [APIGWDefaultThrottling](https://awslabs.github.io/serverless-rules/rules/api_gateway/default_throttling/) | The API Gateway stage is using default throttling settings. | API Gateway default throttling settings may not be suitable for all applications. Custom throttling limits help protect your backend systems from being overwhelmed with requests, ensure consistent performance. | +c ## Footnotes diff --git a/src/packs/serverless.ts b/src/packs/serverless.ts index b8671ba199..69b058d39e 100644 --- a/src/packs/serverless.ts +++ b/src/packs/serverless.ts @@ -187,6 +187,14 @@ export class ServerlessChecks extends NagPack { rule: apigw.APIGWStructuredLogging, node: node, }); + this.applyRule({ + info: 'Ensure API Gateway stages are not using default throttling settings', + explanation: + 'API Gateway default throttling settings may not be suitable for all applications. Custom throttling limits help protect your backend systems from being overwhelmed with requests, ensure consistent performance, and can be tailored to your specific use case.', + level: NagMessageLevel.ERROR, + rule: apigw.APIGWDefaultThrottling, + node: node, + }); } /** From e21a7d4d10efe1804de46347acf15b5476bd7561 Mon Sep 17 00:00:00 2001 From: kevinwochan Date: Mon, 2 Sep 2024 13:28:10 +1000 Subject: [PATCH 39/42] feat(serverless): remove unused file --- src/lambda/index.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/lambda/index.ts diff --git a/src/lambda/index.ts b/src/lambda/index.ts deleted file mode 100644 index e69de29bb2..0000000000 From 04524130feee41b217f2adc067695070917aebfd Mon Sep 17 00:00:00 2001 From: Kevin Chan Date: Thu, 17 Oct 2024 16:28:43 +1100 Subject: [PATCH 40/42] feat(serverless): declerative grammar Co-authored-by: Arun Donti --- src/packs/serverless.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/packs/serverless.ts b/src/packs/serverless.ts index 69b058d39e..f509f1a374 100644 --- a/src/packs/serverless.ts +++ b/src/packs/serverless.ts @@ -44,7 +44,7 @@ export class ServerlessChecks extends NagPack { */ private checkLambda(node: CfnResource) { this.applyRule({ - info: 'The Lambda function should have tracing set to Tracing.ACTIVE', + info: 'The Lambda function does not have tracing set to Tracing.ACTIVE', explanation: 'When a Lambda function has ACTIVE tracing, Lambda automatically samples invocation requests, based on the sampling algorithm specified by X-Ray.', level: NagMessageLevel.WARN, From 8308a3d1dc57cddd1a5bd6d15e5b249b0d264e85 Mon Sep 17 00:00:00 2001 From: Kevin Chan Date: Thu, 17 Oct 2024 16:28:56 +1100 Subject: [PATCH 41/42] feat(serverless): declarative grammar Co-authored-by: Arun Donti --- src/rules/apigw/APIGWStructuredLogging.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rules/apigw/APIGWStructuredLogging.ts b/src/rules/apigw/APIGWStructuredLogging.ts index 64c9b40660..af210e2421 100644 --- a/src/rules/apigw/APIGWStructuredLogging.ts +++ b/src/rules/apigw/APIGWStructuredLogging.ts @@ -19,7 +19,7 @@ const isJSON = (str: string) => { }; /** - * Ensure that API Gateway REST and HTTP APIs are using JSON structured logs + * API Gateway REST and HTTP APIs use JSON structured logs * @param node the CfnResource to check */ export default Object.defineProperty( From 84f7b7ac07e9395d5b539f382249425dd09f2554 Mon Sep 17 00:00:00 2001 From: adrianjhunter <> Date: Fri, 8 Nov 2024 20:42:45 +1000 Subject: [PATCH 42/42] feat(serverless): update to use declarative grammar --- src/packs/serverless.ts | 44 +++++++++---------- src/rules/apigw/APIGWAccessLogging.ts | 2 +- src/rules/apigw/APIGWDefaultThrottling.ts | 2 +- src/rules/apigw/APIGWStructuredLogging.ts | 2 +- src/rules/appsync/AppSyncTracing.ts | 2 +- src/rules/eventbridge/EventBusDLQ.ts | 2 +- src/rules/lambda/LambdaDLQ.ts | 2 +- src/rules/lambda/LambdaDefaultMemorySize.ts | 2 +- src/rules/lambda/LambdaDefaultTimeout.ts | 2 +- .../LambdaEventSourceMappingDestination.ts | 2 +- src/rules/sns/SNSRedrivePolicy.ts | 2 +- src/rules/sqs/SQSRedrivePolicy.ts | 2 +- .../StepFunctionStateMachineXray.ts | 2 +- test/rules/EventBridge.test.ts | 2 +- test/rules/Lambda.test.ts | 2 +- test/rules/SNS.test.ts | 2 +- 16 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/packs/serverless.ts b/src/packs/serverless.ts index f509f1a374..13cf025578 100644 --- a/src/packs/serverless.ts +++ b/src/packs/serverless.ts @@ -44,7 +44,7 @@ export class ServerlessChecks extends NagPack { */ private checkLambda(node: CfnResource) { this.applyRule({ - info: 'The Lambda function does not have tracing set to Tracing.ACTIVE', + info: 'The Lambda function should have tracing set to Tracing.ACTIVE.', explanation: 'When a Lambda function has ACTIVE tracing, Lambda automatically samples invocation requests, based on the sampling algorithm specified by X-Ray.', level: NagMessageLevel.WARN, @@ -53,7 +53,7 @@ export class ServerlessChecks extends NagPack { }); this.applyRule({ - info: 'Ensure Lambda Event Source Mappings have a destination configured for failed invocations', + info: 'Lambda Event Source Mappings have a destination configured for failed invocations.', explanation: 'Configuring a destination for failed invocations in Lambda Event Source Mappings allows you to capture and process events that fail to be processed by your Lambda function. This helps in monitoring, debugging, and implementing retry mechanisms for failed events, improving the reliability and observability of your serverless applications.', level: NagMessageLevel.ERROR, @@ -62,7 +62,7 @@ export class ServerlessChecks extends NagPack { }); this.applyRule({ - info: 'Ensure Lambda functions are using the latest runtime version', + info: 'Lambda functions utilize the latest available runtime version.', explanation: "Using the latest runtime version ensures that your Lambda function has access to the most recent features, performance improvements, and security updates. It's important to regularly update your Lambda functions to use the latest runtime versions to maintain optimal performance and security.", level: NagMessageLevel.WARN, @@ -71,7 +71,7 @@ export class ServerlessChecks extends NagPack { }); this.applyRule({ - info: 'Ensure Lambda functions have a failure destination for asynchronous invocations', + info: 'Lambda functions have a configured failure destination for asynchronous invocations.', explanation: "When a Lambda function is invoked asynchronously (e.g., by S3, SNS, or EventBridge), it's important to configure a failure destination. This allows you to capture and handle events that fail processing, improving the reliability and observability of your serverless applications.", level: NagMessageLevel.ERROR, @@ -80,7 +80,7 @@ export class ServerlessChecks extends NagPack { }); this.applyRule({ - info: 'Ensure that Lambda functions have an explicit memory value', + info: 'Lambda functions have an explicit memory value configured', explanation: "Lambda allocates CPU power in proportion to the amount of memory configured. By default, your functions have 128 MB of memory allocated. You can increase that value up to 10 GB. With more CPU resources, your Lambda function's duration might decrease. You can use tools such as AWS Lambda Power Tuning to test your function at different memory settings to find the one that matches your cost and performance requirements the best.", level: NagMessageLevel.ERROR, @@ -89,7 +89,7 @@ export class ServerlessChecks extends NagPack { }); this.applyRule({ - info: 'Ensure that Lambda functions have an explcity timeout value', + info: 'Lambda functions must have an explicitly defined timeout value.', explanation: 'Lambda functions have a default timeout of 3 seconds. If your timeout value is too short, Lambda might terminate invocations prematurely. On the other side, setting the timeout much higher than the average execution may cause functions to execute for longer upon code malfunction, resulting in higher costs and possibly reaching concurrency limits depending on how such functions are invoked. You can also use AWS Lambda Power Tuning to test your function at different timeout settings to find the one that matches your cost and performance requirements the best.', level: NagMessageLevel.ERROR, @@ -98,16 +98,16 @@ export class ServerlessChecks extends NagPack { }); this.applyRule({ - info: 'Ensure Lambda functions have a onFaliure destination configured', + info: 'Lambda functions have a dead letter target configured.', explanation: - 'When a lambda function has a onFailure destination configured, failed messages can be temporarily stored to be later reviewed', + 'When a lambda function has the DeadLetterConfig property set, failed messages can be temporarily stored for review in an SQS queue or an SNS topic.', level: NagMessageLevel.ERROR, rule: lambda.LambdaDLQ, node: node, }); this.applyRule({ - info: 'Ensure Lambda functions are using the latest runtime version', + info: 'Lambda functions are required to use the latest runtime version.', explanation: "Using the latest runtime version ensures that your Lambda function has access to the most recent features, performance improvements, and security updates. It's important to regularly update your Lambda functions to use the latest runtime versions to maintain optimal performance and security.", level: NagMessageLevel.ERROR, @@ -116,7 +116,7 @@ export class ServerlessChecks extends NagPack { }); this.applyRule({ - info: 'Ensure Lambda Event Source Mappings have a destination configured for failed invocations', + info: 'Lambda Event Source Mappings must have a destination configured for failed invocations.', explanation: 'Configuring a destination for failed invocations in Lambda Event Source Mappings allows you to capture and process events that fail to be processed by your Lambda function. This helps in monitoring, debugging, and implementing retry mechanisms for failed events, improving the reliability and observability of your serverless applications.', level: NagMessageLevel.ERROR, @@ -132,7 +132,7 @@ export class ServerlessChecks extends NagPack { */ private checkIAM(node: CfnResource) { this.applyRule({ - info: 'Ensure Lambda functions do not have overly permissive IAM roles', + info: 'Lambda functions do not have overly permissive IAM roles.', explanation: 'Lambda functions should follow the principle of least privilege. Avoid using wildcard (*) permissions in IAM roles attached to Lambda functions. Instead, specify only the permissions required for the function to operate.', level: NagMessageLevel.WARN, @@ -148,9 +148,9 @@ export class ServerlessChecks extends NagPack { */ private checkCloudwatch(node: CfnResource) { this.applyRule({ - info: 'Ensure that CloudWatch Log Groups have an explicit retention policy', + info: 'CloudWatch Log Groups must have an explicit retention policy defined.', explanation: - 'By default, logs are kept indefinitely and never expire. You can adjust the retention policy for each log group, keeping the indefinite retention, or choosing a retention period between 10 years and one day. For Lambda functions, this applies to their automatically created CloudWatch Log Groups.', + 'By default, logs are kept indefinitely and never expire. You can adjust the retention policy for each log group, keeping the indefinite retention, or choosing a retention period between one day and 10 years. For Lambda functions, this applies to their automatically created CloudWatch Log Groups.', level: NagMessageLevel.WARN, rule: cloudwatch.CloudWatchLogGroupRetentionPeriod, node: node, @@ -164,7 +164,7 @@ export class ServerlessChecks extends NagPack { */ private checkApiGw(node: CfnResource) { this.applyRule({ - info: 'Ensure API Gateway stages have access logging enabled', + info: 'API Gateway stages have access logging enabled.', explanation: 'API Gateway provides access logging for API stages. Enable access logging on your API stages to monitor API requests and responses.', level: NagMessageLevel.ERROR, @@ -172,7 +172,7 @@ export class ServerlessChecks extends NagPack { node: node, }); this.applyRule({ - info: 'Ensure tracing is enabled', + info: 'API Gateways have Tracing enabled.', explanation: 'Amazon API Gateway provides active tracing support for AWS X-Ray. Enable active tracing on your API stages to sample incoming requests and send traces to X-Ray.', level: NagMessageLevel.ERROR, @@ -180,7 +180,7 @@ export class ServerlessChecks extends NagPack { node: node, }); this.applyRule({ - info: 'Ensure API Gateway logs are JSON structured', + info: 'API Gateway logs are configured for the JSON format.', explanation: 'You can customize the log format that Amazon API Gateway uses to send logs. JSON Structured logging makes it easier to derive queries to answer arbitrary questions about the health of your application.', level: NagMessageLevel.ERROR, @@ -188,7 +188,7 @@ export class ServerlessChecks extends NagPack { node: node, }); this.applyRule({ - info: 'Ensure API Gateway stages are not using default throttling settings', + info: 'API Gateway stages are not using default throttling setting.s', explanation: 'API Gateway default throttling settings may not be suitable for all applications. Custom throttling limits help protect your backend systems from being overwhelmed with requests, ensure consistent performance, and can be tailored to your specific use case.', level: NagMessageLevel.ERROR, @@ -204,7 +204,7 @@ export class ServerlessChecks extends NagPack { */ private checkAppSync(node: CfnResource) { this.applyRule({ - info: 'Ensure tracing is enabled', + info: 'AppSync APIs have tracing enabled', explanation: 'AWS AppSync can emit traces to AWS X-Ray, which enables visualizing service maps for faster troubleshooting.', level: NagMessageLevel.WARN, @@ -220,7 +220,7 @@ export class ServerlessChecks extends NagPack { */ private checkEventBridge(node: CfnResource) { this.applyRule({ - info: 'Ensure eventbridge targets have a DLQ configured', + info: 'EventBridge targets have a DLQ configured.', explanation: "Configuring a Dead-Letter Queue (DLQ) for EventBridge rules helps manage failed event deliveries. When a rule's target fails to process an event, the DLQ captures these failed events, allowing for analysis, troubleshooting, and potential reprocessing. This improves the reliability and observability of your event-driven architectures by providing a safety net for handling delivery failures.", level: NagMessageLevel.ERROR, @@ -236,7 +236,7 @@ export class ServerlessChecks extends NagPack { */ private checkSNS(node: CfnResource) { this.applyRule({ - info: 'SNS subscriptions should specify a redrive policy.', + info: 'SNS subscriptions have a redrive policy configured.', explanation: 'Configuring a redrive policy helps manage message delivery failures by sending undeliverable messages to a dead-letter queue.', level: NagMessageLevel.ERROR, @@ -252,7 +252,7 @@ export class ServerlessChecks extends NagPack { */ private checkSQS(node: CfnResource) { this.applyRule({ - info: 'SQS queues should have a redrive policy configured', + info: 'SQS queues have a redrive policy configured.', explanation: 'Configuring a redrive policy on an SQS queue allows you to define how many times SQS will make messages available for consumers before sending them to a dead-letter queue. This helps in managing message processing failures and provides a mechanism for handling problematic messages.', level: NagMessageLevel.ERROR, @@ -268,7 +268,7 @@ export class ServerlessChecks extends NagPack { */ private checkStepFunctions(node: CfnResource) { this.applyRule({ - info: 'Ensure StepFunctions have a DLQ configured', + info: 'StepFunctions have X-Ray tracing configured.', explanation: 'AWS StepFunctions provides active tracing support for AWS X-Ray. Enable active tracing on your API stages to sample incoming requests and send traces to X-Ray.', level: NagMessageLevel.ERROR, diff --git a/src/rules/apigw/APIGWAccessLogging.ts b/src/rules/apigw/APIGWAccessLogging.ts index 02e4386e72..ef68527bcc 100644 --- a/src/rules/apigw/APIGWAccessLogging.ts +++ b/src/rules/apigw/APIGWAccessLogging.ts @@ -8,7 +8,7 @@ import { CfnStage } from 'aws-cdk-lib/aws-apigateway'; import { CfnStage as CfnV2Stage } from 'aws-cdk-lib/aws-apigatewayv2'; import { NagRuleCompliance } from '../../nag-rules'; /** - * APIs have access logging enabled + * API Gateway stages have access logging enabled * @param node the CfnResource to check */ export default Object.defineProperty( diff --git a/src/rules/apigw/APIGWDefaultThrottling.ts b/src/rules/apigw/APIGWDefaultThrottling.ts index 31e56c0831..2a775bd36e 100644 --- a/src/rules/apigw/APIGWDefaultThrottling.ts +++ b/src/rules/apigw/APIGWDefaultThrottling.ts @@ -4,7 +4,7 @@ import { CfnStage as CfnHttpStage } from 'aws-cdk-lib/aws-apigatewayv2'; import { NagRuleCompliance } from '../../nag-rules'; /** - * API Gateway stages have default throttling configured + * API Gateway stages are not using default throttling settings * @param node The CfnStage or CfnHttpStage to check */ export default Object.defineProperty( diff --git a/src/rules/apigw/APIGWStructuredLogging.ts b/src/rules/apigw/APIGWStructuredLogging.ts index af210e2421..93ebe31c01 100644 --- a/src/rules/apigw/APIGWStructuredLogging.ts +++ b/src/rules/apigw/APIGWStructuredLogging.ts @@ -19,7 +19,7 @@ const isJSON = (str: string) => { }; /** - * API Gateway REST and HTTP APIs use JSON structured logs + * API Gateway logs are configured in JSON format. * @param node the CfnResource to check */ export default Object.defineProperty( diff --git a/src/rules/appsync/AppSyncTracing.ts b/src/rules/appsync/AppSyncTracing.ts index 97ae923bbf..1d2188c7bb 100644 --- a/src/rules/appsync/AppSyncTracing.ts +++ b/src/rules/appsync/AppSyncTracing.ts @@ -8,7 +8,7 @@ import { CfnGraphQLApi } from 'aws-cdk-lib/aws-appsync'; import { NagRuleCompliance } from '../../nag-rules'; /** - * Ensure that AppSync APIs have tracing enabled + * AppSync APIs have tracing enabled * @param node the CfnResource to check */ export default Object.defineProperty( diff --git a/src/rules/eventbridge/EventBusDLQ.ts b/src/rules/eventbridge/EventBusDLQ.ts index 86ca2d2008..6c1ed80b21 100644 --- a/src/rules/eventbridge/EventBusDLQ.ts +++ b/src/rules/eventbridge/EventBusDLQ.ts @@ -8,7 +8,7 @@ import { CfnRule } from 'aws-cdk-lib/aws-events'; import { NagRuleCompliance } from '../../nag-rules'; /** - * Ensure that EventBus targets have configure a DLQ + * EventBridge targets have a Dead Letter Queue configured. * @param node the CfnResource to check */ export default Object.defineProperty( diff --git a/src/rules/lambda/LambdaDLQ.ts b/src/rules/lambda/LambdaDLQ.ts index 04dfe8c70b..cb57699b5d 100644 --- a/src/rules/lambda/LambdaDLQ.ts +++ b/src/rules/lambda/LambdaDLQ.ts @@ -17,7 +17,7 @@ export default Object.defineProperty( const deadLetterConfig = Stack.of(node).resolve(node.deadLetterConfig); if ( deadLetterConfig == undefined || - deadLetterConfig.targetArn == undefined + deadLetterConfig.targetArn == undefined ) { return NagRuleCompliance.NON_COMPLIANT; } diff --git a/src/rules/lambda/LambdaDefaultMemorySize.ts b/src/rules/lambda/LambdaDefaultMemorySize.ts index dae25f64ec..7f4d4067bb 100644 --- a/src/rules/lambda/LambdaDefaultMemorySize.ts +++ b/src/rules/lambda/LambdaDefaultMemorySize.ts @@ -8,7 +8,7 @@ import { CfnFunction } from 'aws-cdk-lib/aws-lambda'; import { NagRuleCompliance } from '../../nag-rules'; /** - * Lambda allocates CPU power in proportion to the amount of memory configured. By default, your functions have 128 MB of memory allocated. You can increase that value up to 10 GB. With more CPU resources, your Lambda function's duration might decrease. + * Lambda allocates CPU power in proportion to the amount of memory configured. By default, your functions have 128 MB of memory allocated. You can increase that value up to 10 GB. With more CPU resources, your Lambda function's duration might decrease. Lambda functions should have an explicit memory value configured rather than using the default value. * You can use tools such as AWS Lambda Power Tuning to test your function at different memory settings to find the one that matches your cost and performance requirements the best. * @param node the CfnResource to check */ diff --git a/src/rules/lambda/LambdaDefaultTimeout.ts b/src/rules/lambda/LambdaDefaultTimeout.ts index 1c88c8e2e8..bcb06f9fcc 100644 --- a/src/rules/lambda/LambdaDefaultTimeout.ts +++ b/src/rules/lambda/LambdaDefaultTimeout.ts @@ -8,7 +8,7 @@ import { CfnFunction } from 'aws-cdk-lib/aws-lambda'; import { NagRuleCompliance } from '../../nag-rules'; /** - * Ensure that Lambda functions have an explicit timeout value + * Lambda functions must have an explicitly defined timeout value. * @param node the CfnResource to check */ export default Object.defineProperty( diff --git a/src/rules/lambda/LambdaEventSourceMappingDestination.ts b/src/rules/lambda/LambdaEventSourceMappingDestination.ts index 64a9be279a..3406d759db 100644 --- a/src/rules/lambda/LambdaEventSourceMappingDestination.ts +++ b/src/rules/lambda/LambdaEventSourceMappingDestination.ts @@ -3,7 +3,7 @@ import { CfnEventSourceMapping } from 'aws-cdk-lib/aws-lambda'; import { NagRuleCompliance } from '../../nag-rules'; /** - * Lambda event source mappings should have a failure destination configured + * Lambda Event Source Mappings must have a destination configured for failed invocations. * * @param node - The CfnResource to check */ diff --git a/src/rules/sns/SNSRedrivePolicy.ts b/src/rules/sns/SNSRedrivePolicy.ts index 3a63119ca2..75886eb8ab 100644 --- a/src/rules/sns/SNSRedrivePolicy.ts +++ b/src/rules/sns/SNSRedrivePolicy.ts @@ -4,7 +4,7 @@ import { CfnSubscription } from 'aws-cdk-lib/aws-sns'; import { NagRuleCompliance } from '../../nag-rules'; /** - * SNS subscriptions should specify a redrive policy + * SNS subscriptions have a redrive policy configured. * * @see https://docs.aws.amazon.com/sns/latest/dg/sns-dead-letter-queues.html */ diff --git a/src/rules/sqs/SQSRedrivePolicy.ts b/src/rules/sqs/SQSRedrivePolicy.ts index b689198b5b..a9f52fe67a 100644 --- a/src/rules/sqs/SQSRedrivePolicy.ts +++ b/src/rules/sqs/SQSRedrivePolicy.ts @@ -3,7 +3,7 @@ import { CfnQueue } from 'aws-cdk-lib/aws-sqs'; import { NagRuleCompliance } from '../../nag-rules'; /** - * SQS queues should have a redrive policy configured + * SQS queues have a redrive policy configured * * @param node - the CfnResource to check */ diff --git a/src/rules/stepfunctions/StepFunctionStateMachineXray.ts b/src/rules/stepfunctions/StepFunctionStateMachineXray.ts index 6459cea39d..d1cb79b5a6 100644 --- a/src/rules/stepfunctions/StepFunctionStateMachineXray.ts +++ b/src/rules/stepfunctions/StepFunctionStateMachineXray.ts @@ -8,7 +8,7 @@ import { CfnStateMachine } from 'aws-cdk-lib/aws-stepfunctions'; import { NagRuleCompliance, NagRules } from '../../nag-rules'; /** - * Step Function have X-Ray tracing enabled + * StepFunctions have X-Ray tracing configured. * @param node the CfnResource to check */ export default Object.defineProperty( diff --git a/test/rules/EventBridge.test.ts b/test/rules/EventBridge.test.ts index abb1b36ec7..73b9854b5c 100644 --- a/test/rules/EventBridge.test.ts +++ b/test/rules/EventBridge.test.ts @@ -19,7 +19,7 @@ beforeEach(() => { Aspects.of(stack).add(testPack); }); -describe('EventBusDLQ: EventBridge rules should have a Dead Letter Queue', () => { +describe('EventBusDLQ: EventBridge rules have a Dead Letter Queue configured.', () => { const ruleId = 'EventBusDLQ'; test('Noncompliance: Rule without DLQ', () => { diff --git a/test/rules/Lambda.test.ts b/test/rules/Lambda.test.ts index 424696005b..fd7f223fa4 100644 --- a/test/rules/Lambda.test.ts +++ b/test/rules/Lambda.test.ts @@ -423,7 +423,7 @@ describe('AWS Lambda', () => { }); }); - describe('LambdaEventSourceMappingDestination: Lambda event source mappings should have a failure destination configured', () => { + describe('LambdaEventSourceMappingDestination: Lambda event source mappings have a failure destination configured', () => { const ruleId = 'LambdaEventSourceMappingDestination'; test('Noncompliance 1 - No destinationConfig', () => { diff --git a/test/rules/SNS.test.ts b/test/rules/SNS.test.ts index 884dd48792..131202fd35 100644 --- a/test/rules/SNS.test.ts +++ b/test/rules/SNS.test.ts @@ -100,7 +100,7 @@ describe('Amazon Simple Notification Service (Amazon SNS)', () => { }); }); - describe('SNSRedrivePolicy: SNS subscriptions specify a redrive policy', () => { + describe('SNSRedrivePolicy: SNS subscriptions have a redrive policy configured.', () => { const ruleId = 'SNSRedrivePolicy'; test('Noncompliance: CfnSubscription without redrive policy', () => {