Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Serverless Nag Pack #1793

Open
wants to merge 46 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
6b112a8
feat: implemented rules and 1 test
Kevinwochan Apr 29, 2024
3cd5e1c
feat: lambda logging rules
Kevinwochan Apr 29, 2024
a757e4b
fix: typo in function id
Kevinwochan May 9, 2024
38c23bc
fix: typo in function id
Kevinwochan May 9, 2024
66181a6
styling: rule description grammar
Kevinwochan May 9, 2024
71793d0
docs: added docs for lambda logging
Kevinwochan Jun 17, 2024
bc1d61c
feat: testing L2 constructs
Kevinwochan Jun 17, 2024
515f898
implemeted all rules, untested and undocumented
Kevinwochan Jun 17, 2024
9211f03
feat: LambdaLogLevels rules
Kevinwochan Jun 17, 2024
1e0d6ce
Merge branch 'feat/serverless-nag-pack' into feat/serverless-nag-pack…
Kevinwochan Jun 17, 2024
4fb7e60
feat: lambda logging rules
Kevinwochan Apr 29, 2024
e61f13c
Merge branch 'feat/serverless-nag-pack' into feat/serverless-nag-pack…
Kevinwochan Aug 19, 2024
c9b8332
docs(serverless): added example of warning and error
Kevinwochan Aug 29, 2024
21ab1a2
docs(serverless): added example warning and error rules
Kevinwochan Aug 29, 2024
55144a9
feat(serverless): remove lambda logging rule
Kevinwochan Aug 29, 2024
f8a47b9
feat(serverless): remove lambda logging rule
Kevinwochan Aug 29, 2024
0ff3d12
feat(serverless): unit tests for lambda xray tracing
Kevinwochan Aug 29, 2024
9b4cd49
feat(serverless): updated LambdaTracing to use tracingConfig object
Kevinwochan Aug 29, 2024
d783d82
docs(serverless): updated docs for LambdaTracing rule
Kevinwochan Aug 29, 2024
872bdf7
feat(serverless): LambdaEventSourceMappingDestination unit tested
Kevinwochan Aug 29, 2024
9620a94
feat(serverless): added docs and checks to the serverless nag pack
Kevinwochan Aug 29, 2024
5d2cb8b
feat(serverless): added iam wildcard checks
Kevinwochan Aug 29, 2024
ce169fc
feat(serverless): added cw logs retention checks
Kevinwochan Aug 29, 2024
554fced
feat(serverless): unit tested and documented LambdaDefaultMemorySize …
Kevinwochan Aug 29, 2024
73821c8
feat(serverless): unit tests for Lambda Default Timeout
Kevinwochan Aug 29, 2024
3fd72f7
feat(serverless): documented LambdaDefaultTimeout in the serverless n…
Kevinwochan Aug 29, 2024
11eb99a
feat(serverless): implemented LambdaAsyncFailureDestination rule
Kevinwochan Aug 30, 2024
901b423
feat(serverless): added latestlambdaversion check to serverless nag pack
Kevinwochan Aug 30, 2024
47091d2
feat(serverless): added APIGW access logging check
Kevinwochan Aug 30, 2024
eac6ce2
feat(serverless): unit tests for apigw structured logging
Kevinwochan Aug 30, 2024
58dde65
feat(serverless): implemented and tested APIGWStructuredLogging
Kevinwochan Aug 30, 2024
2debbe9
feat(serverless): Stepfunctions rules
Kevinwochan Aug 30, 2024
37beb39
feat(serverless): implemented and unit tested redrive policy
Kevinwochan Aug 30, 2024
156a3e3
feat(serverless): added sqs redrive policy to serverless nag pack
Kevinwochan Aug 30, 2024
c22ee05
feat(serverless): removing redundant implementation
Kevinwochan Aug 30, 2024
d083e17
feat(serverless): sns redrive policy rule
Kevinwochan Aug 30, 2024
9a8af8c
feat(serverless): eventbridge dlq rules
Kevinwochan Aug 30, 2024
256ebfa
feat(serverless): appsync tracing rule
Kevinwochan Aug 30, 2024
f8cb04a
feat(serverless): http and rest api throttling rule
Kevinwochan Sep 2, 2024
760975e
feat(serverless): added apigw throttling rule
Kevinwochan Sep 2, 2024
e21a7d4
feat(serverless): remove unused file
Kevinwochan Sep 2, 2024
1069abb
Merge branch 'cdklabs:main' into main
Kevinwochan Sep 2, 2024
3fd6a50
Merge branch 'main' into feat/serverless-nag-pack-complete
Kevinwochan Sep 2, 2024
0452413
feat(serverless): declerative grammar
Kevinwochan Oct 17, 2024
8308a3d
feat(serverless): declarative grammar
Kevinwochan Oct 17, 2024
84f7b7a
feat(serverless): update to use declarative grammar
Nov 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
39 changes: 36 additions & 3 deletions RULES.md

Large diffs are not rendered by default.

279 changes: 279 additions & 0 deletions src/packs/serverless.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
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,
iam,
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.checkIAM(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.',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
info: 'The Lambda function should have tracing set to Tracing.ACTIVE.',
info: 'The Lambda function does not have tracing set to Tracing.ACTIVE.',

does not was appropriate wording

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: 'Lambda Event Source Mappings have a destination configured for failed invocations.',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The info on applyRule needs to declare what's wrong with the specific, singular resource that violated the rule.

Suggested change
info: 'Lambda Event Source Mappings have a destination configured for failed invocations.',
info: 'The Lambda Event Source Mapping does not 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,
});

this.applyRule({
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,
rule: lambda.LambdaLatestVersion,
node: node,
});

this.applyRule({
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,
rule: lambda.LambdaAsyncFailureDestination,
node: node,
});

this.applyRule({
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,
rule: lambda.LambdaDefaultMemorySize,
node: node,
});

this.applyRule({
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,
rule: lambda.LambdaDefaultTimeout,
node: node,
});

this.applyRule({
info: 'Lambda functions have a dead letter target configured.',
explanation:
'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: '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,
rule: lambda.LambdaLatestVersion,
node: node,
});

this.applyRule({
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,
rule: lambda.LambdaEventSourceMappingDestination,
node: node,
});
}

/**
* Check Lambda Resources
* @param node the CfnResource to check
* @param ignores list of ignores for the resource
*/
private checkIAM(node: CfnResource) {
this.applyRule({
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,
rule: iam.IAMNoWildcardPermissions,
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: '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 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,
});
}

/**
* 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: '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: '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,
rule: apigw.APIGWXrayEnabled,
node: node,
});
this.applyRule({
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,
rule: apigw.APIGWStructuredLogging,
node: node,
});
this.applyRule({
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,
rule: apigw.APIGWDefaultThrottling,
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: '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,
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: '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,
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: '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,
rule: sns.SNSRedrivePolicy,
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: '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,
rule: sqs.SQSRedrivePolicy,
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: '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,
rule: stepfunctions.StepFunctionStateMachineXray,
node: node,
});
}
}
2 changes: 1 addition & 1 deletion src/rules/apigw/APIGWAccessLogging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
46 changes: 46 additions & 0 deletions src/rules/apigw/APIGWDefaultThrottling.ts
Original file line number Diff line number Diff line change
@@ -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 are not using default throttling settings
* @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' }
);
52 changes: 52 additions & 0 deletions src/rules/apigw/APIGWStructuredLogging.ts
Original file line number Diff line number Diff line change
@@ -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;
};

/**
* API Gateway logs are configured in JSON format.
* @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 }
);
Loading