diff --git a/RULES.md b/RULES.md index 562dc1bfbe..18ef21f445 100644 --- a/RULES.md +++ b/RULES.md @@ -80,6 +80,7 @@ The [AWS Solutions Library](https://aws.amazon.com/solutions/) offers a collecti | AwsSolutions-ECS2 | The ECS Task Definition includes a container definition that directly specifies environment variables. | Use secrets to inject environment variables during container startup from AWS Systems Manager Parameter Store or Secrets Manager instead of directly specifying plaintext environment variables. Updates to direct environment variables require operators to change task definitions and perform new deployments. | | AwsSolutions-ECS4 | The ECS Cluster has CloudWatch Container Insights disabled. | CloudWatch Container Insights allow operators to gain a better perspective on how the cluster’s applications and microservices are performing. | | AwsSolutions-ECS7 | One or more containers in the ECS Task Definition do not have container logging enabled. | Container logging allows operators to view and aggregate the logs from the container. Containers should use the 'awslogs' driver at a minimum. | +| AwsSolutions-ECS8 | One or more containers in the ECS Task Definition are using the 'awslogs' driver in blocking mode. | Containers using 'awslogs' in blocking mode will be interupted if logs can't be immediately sent to Amazon CloudWatch Logs. | | AwsSolutions-EFS1 | The EFS is not configured for encryption at rest. | By using an encrypted file system, data and metadata are automatically encrypted before being written to the file system. Similarly, as data and metadata are read, they are automatically decrypted before being presented to the application. These processes are handled transparently by EFS without requiring modification of applications. | | AwsSolutions-EKS1 | The EKS cluster's Kubernetes API server endpoint has public access enabled. | A cluster's Kubernetes API server endpoint should not be publicly accessible from the Internet in order to avoid exposing private data and minimizing security risks. The API server endpoints should only be accessible from within a AWS Virtual Private Cloud (VPC). | | AwsSolutions-EKS2 | The EKS Cluster does not publish 'api', 'audit', 'authenticator, 'controllerManager', and 'scheduler' control plane logs. | EKS control plane logging provides audit and diagnostic logs directly from the Amazon EKS control plane to CloudWatch Logs in your account. These logs make it easy for you to secure and run your clusters. This is a granular rule that returns individual findings that can be suppressed with `appliesTo`. The findings are in the format `LogExport::` for exported logs. Example: `appliesTo: ['LogExport::authenticate']`. | diff --git a/src/packs/aws-solutions.ts b/src/packs/aws-solutions.ts index 856cb9e5c0..5646abea1f 100644 --- a/src/packs/aws-solutions.ts +++ b/src/packs/aws-solutions.ts @@ -62,6 +62,7 @@ import { ECSClusterCloudWatchContainerInsights, ECSTaskDefinitionContainerLogging, ECSTaskDefinitionNoEnvironmentVariables, + ECSTaskDefinitionAwslogsDriverNotBlocking, } from '../rules/ecs'; import { EFSEncrypted } from '../rules/efs'; import { @@ -328,6 +329,15 @@ export class AwsSolutionsChecks extends NagPack { rule: ECSTaskDefinitionContainerLogging, node: node, }); + this.applyRule({ + ruleSuffixOverride: 'ECS8', + info: "One or more containers in the ECS Task Definition are using the 'awslogs' driver in blocking mode.", + explanation: + "Containers using 'awslogs' in blocking mode will be interupted if logs can't be immediately sent to Amazon CloudWatch Logs.", + level: NagMessageLevel.ERROR, + rule: ECSTaskDefinitionAwslogsDriverNotBlocking, + node: node, + }); this.applyRule({ ruleSuffixOverride: 'EKS1', info: "The EKS cluster's Kubernetes API server endpoint has public access enabled.", diff --git a/src/rules/ecs/ECSTaskDefinitionAwslogsDriverNotBlocking.ts b/src/rules/ecs/ECSTaskDefinitionAwslogsDriverNotBlocking.ts new file mode 100644 index 0000000000..c498d0810a --- /dev/null +++ b/src/rules/ecs/ECSTaskDefinitionAwslogsDriverNotBlocking.ts @@ -0,0 +1,50 @@ +/* +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 { CfnTaskDefinition } from 'aws-cdk-lib/aws-ecs'; +import { NagRuleCompliance } from '../../nag-rules'; + +/** + * Containers in ECS Task Definitions do not use the awslogs in blocking mode + * @param node the CfnResource to check + */ +export default Object.defineProperty( + (node: CfnResource): NagRuleCompliance => { + if (node instanceof CfnTaskDefinition) { + const containerDefinitions = Stack.of(node).resolve( + node.containerDefinitions + ); + if (containerDefinitions === undefined) { + return NagRuleCompliance.NOT_APPLICABLE; + } + + let isUsingAwslogsDriver = false; + for (const containerDefinition of containerDefinitions) { + const resolvedDefinition = Stack.of(node).resolve(containerDefinition); + const logConfiguration = Stack.of(node).resolve( + resolvedDefinition.logConfiguration + ); + if (logConfiguration?.logDriver === 'awslogs') { + isUsingAwslogsDriver = true; + if (logConfiguration?.options?.mode === undefined) { + return NagRuleCompliance.NON_COMPLIANT; + } else if (logConfiguration?.options?.mode === 'blocking') { + return NagRuleCompliance.NON_COMPLIANT; + } + } + } + if (isUsingAwslogsDriver) { + return NagRuleCompliance.COMPLIANT; + } else { + return NagRuleCompliance.NOT_APPLICABLE; + } + } else { + return NagRuleCompliance.NOT_APPLICABLE; + } + }, + 'name', + { value: parse(__filename).name } +); diff --git a/src/rules/ecs/index.ts b/src/rules/ecs/index.ts index e86458c10d..1d73783855 100644 --- a/src/rules/ecs/index.ts +++ b/src/rules/ecs/index.ts @@ -6,3 +6,4 @@ export { default as ECSClusterCloudWatchContainerInsights } from './ECSClusterCl export { default as ECSTaskDefinitionContainerLogging } from './ECSTaskDefinitionContainerLogging'; export { default as ECSTaskDefinitionNoEnvironmentVariables } from './ECSTaskDefinitionNoEnvironmentVariables'; export { default as ECSTaskDefinitionUserForHostMode } from './ECSTaskDefinitionUserForHostMode'; +export { default as ECSTaskDefinitionAwslogsDriverNotBlocking } from './ECSTaskDefinitionAwslogsDriverNotBlocking'; diff --git a/test/Packs.test.ts b/test/Packs.test.ts index c2655d7eee..f277602626 100644 --- a/test/Packs.test.ts +++ b/test/Packs.test.ts @@ -100,6 +100,7 @@ describe('Check NagPack Details', () => { 'AwsSolutions-ECS2', 'AwsSolutions-ECS4', 'AwsSolutions-ECS7', + 'AwsSolutions-ECS8', 'AwsSolutions-EFS1', 'AwsSolutions-EKS1', 'AwsSolutions-EKS2', diff --git a/test/rules/ECS.test.ts b/test/rules/ECS.test.ts index 5f364e8442..109f095131 100644 --- a/test/rules/ECS.test.ts +++ b/test/rules/ECS.test.ts @@ -9,6 +9,7 @@ import { ContainerImage, Cluster, LogDriver, + AwsLogDriverMode, } from 'aws-cdk-lib/aws-ecs'; import { Aspects, Stack } from 'aws-cdk-lib/core'; import { validateStack, TestType, TestPack } from './utils'; @@ -17,6 +18,7 @@ import { ECSTaskDefinitionContainerLogging, ECSTaskDefinitionNoEnvironmentVariables, ECSTaskDefinitionUserForHostMode, + ECSTaskDefinitionAwslogsDriverNotBlocking, } from '../../src/rules/ecs'; const testPack = new TestPack([ @@ -24,6 +26,7 @@ const testPack = new TestPack([ ECSTaskDefinitionContainerLogging, ECSTaskDefinitionNoEnvironmentVariables, ECSTaskDefinitionUserForHostMode, + ECSTaskDefinitionAwslogsDriverNotBlocking, ]); let stack: Stack; @@ -202,3 +205,64 @@ describe('Amazon Elastic Container Service (Amazon ECS)', () => { }); }); }); + +describe('ECSTaskDefinitionAwslogsDriverNotBlocking: Containers in ECS Task Definitions have logging enabled', () => { + const ruleId = 'ECSTaskDefinitionAwslogsDriverNotBlocking'; + test('Noncompliance 1', () => { + new TaskDefinition(stack, 'rTaskDef', { + compatibility: Compatibility.EC2, + }).addContainer('rContainer', { + image: ContainerImage.fromRegistry('imageName'), + memoryReservationMiB: 42, + logging: LogDriver.awsLogs({ + streamPrefix: 'foo', + mode: AwsLogDriverMode.BLOCKING, + }), + }); + validateStack(stack, ruleId, TestType.NON_COMPLIANCE); + }); + test('Noncompliance 2', () => { + const taskDef = new TaskDefinition(stack, 'rTaskDef', { + compatibility: Compatibility.EC2, + }); + taskDef.addContainer('rContainer', { + image: ContainerImage.fromRegistry('imageName'), + memoryReservationMiB: 42, + logging: LogDriver.awsLogs({ + streamPrefix: 'foo', + mode: AwsLogDriverMode.NON_BLOCKING, + }), + }); + taskDef.addContainer('rContainer2', { + image: ContainerImage.fromRegistry('imageName'), + memoryReservationMiB: 42, + logging: LogDriver.awsLogs({ + streamPrefix: 'bar', + // mode is implicitly AwsLogDriverMode.BLOCKING + }), + }); + validateStack(stack, ruleId, TestType.NON_COMPLIANCE); + }); + test('Compliance', () => { + const taskDef = new TaskDefinition(stack, 'rTaskDef', { + compatibility: Compatibility.EC2, + }); + taskDef.addContainer('rContainer', { + image: ContainerImage.fromRegistry('imageName'), + memoryReservationMiB: 42, + logging: LogDriver.awsLogs({ + streamPrefix: 'foo', + mode: AwsLogDriverMode.NON_BLOCKING, + }), + }); + taskDef.addContainer('rContainer2', { + image: ContainerImage.fromRegistry('imageName'), + memoryReservationMiB: 42, + logging: LogDriver.awsLogs({ + streamPrefix: 'bar', + mode: AwsLogDriverMode.NON_BLOCKING, + }), + }); + validateStack(stack, ruleId, TestType.COMPLIANCE); + }); +});