From 64cc86a8fd1992636600aeea36d36194d19c6193 Mon Sep 17 00:00:00 2001 From: Hsing-Hui Hsu Date: Thu, 18 Mar 2021 11:57:04 -0700 Subject: [PATCH 01/19] feat(aws-ecs): add enableExecuteCommand to Service --- .../@aws-cdk/aws-ecs/lib/base/base-service.ts | 9 +++ .../aws-ecs/test/ec2/ec2-service.test.ts | 41 +++++++++++++ .../test/fargate/fargate-service.test.ts | 57 +++++++++++++++++++ 3 files changed, 107 insertions(+) diff --git a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts index 306b2bced3477..83cc198312c8a 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts @@ -189,6 +189,14 @@ export interface BaseServiceOptions { * */ readonly capacityProviderStrategies?: CapacityProviderStrategy[]; + + + /** + * Whether to enable ability to exec into a container + * + * @default - undefined + */ + readonly enableExecuteCommand?: boolean; } /** @@ -391,6 +399,7 @@ export abstract class BaseService extends Resource type: DeploymentControllerType.ECS, } : props.deploymentController, launchType: launchType, + enableExecuteCommand: props.enableExecuteCommand, capacityProviderStrategy: props.capacityProviderStrategies, healthCheckGracePeriodSeconds: this.evaluateHealthGracePeriod(props.healthCheckGracePeriod), /* role: never specified, supplanted by Service Linked Role */ diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts b/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts index 945832a09b869..cfb8826063ad2 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts @@ -49,6 +49,47 @@ nodeunitShim({ test.done(); }, + 'allows setting enable execute command'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'MyVpc', {}); + const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); + + taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + memoryLimitMiB: 512, + }); + + new ecs.Ec2Service(stack, 'Ec2Service', { + cluster, + taskDefinition, + enableExecuteCommand: true, + }); + + // THEN + expect(stack).to(haveResource('AWS::ECS::Service', { + TaskDefinition: { + Ref: 'Ec2TaskDef0226F28C', + }, + Cluster: { + Ref: 'EcsCluster97242B84', + }, + DeploymentConfiguration: { + MaximumPercent: 200, + MinimumHealthyPercent: 50, + }, + LaunchType: LaunchType.EC2, + SchedulingStrategy: 'REPLICA', + EnableECSManagedTags: false, + EnableExecuteCommand: true, + })); + + test.done(); + }, + + 'with custom cloudmap namespace'(test: Test) { // GIVEN const stack = new cdk.Stack(); diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts b/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts index cbd43a8d44f67..b5f941e2fae71 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts @@ -195,6 +195,63 @@ nodeunitShim({ test.done(); }, + 'allows setting enable execute command'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'MyVpc', {}); + const cluster = new ecs.Cluster(stack, 'EcsCluster', { vpc }); + const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); + + taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + }); + + new ecs.FargateService(stack, 'FargateService', { + cluster, + taskDefinition, + enableExecuteCommand: true, + }); + + // THEN + expect(stack).to(haveResource('AWS::ECS::Service', { + TaskDefinition: { + Ref: 'FargateTaskDefC6FB60B4', + }, + Cluster: { + Ref: 'EcsCluster97242B84', + }, + DeploymentConfiguration: { + MaximumPercent: 200, + MinimumHealthyPercent: 50, + }, + LaunchType: LaunchType.FARGATE, + EnableECSManagedTags: false, + EnableExecuteCommand: true, + NetworkConfiguration: { + AwsvpcConfiguration: { + AssignPublicIp: 'DISABLED', + SecurityGroups: [ + { + 'Fn::GetAtt': [ + 'FargateServiceSecurityGroup0A0E79CB', + 'GroupId', + ], + }, + ], + Subnets: [ + { + Ref: 'MyVpcPrivateSubnet1Subnet5057CF7E', + }, + { + Ref: 'MyVpcPrivateSubnet2Subnet0040C983', + }, + ], + }, + }, + })); + test.done(); + }, + 'with custom cloudmap namespace'(test: Test) { // GIVEN const stack = new cdk.Stack(); From 74f51c1da3f8fecdee490247198826f8bf270fe8 Mon Sep 17 00:00:00 2001 From: UnnatiParekh05 Date: Mon, 10 May 2021 10:42:45 -0700 Subject: [PATCH 02/19] Allowing exec without logging or encryption --- .../@aws-cdk/aws-ecs/lib/base/base-service.ts | 16 ++++++++++++++++ .../aws-ecs/test/ec2/ec2-service.test.ts | 18 ++++++++++++++++++ .../test/fargate/fargate-service.test.ts | 19 +++++++++++++++++++ 3 files changed, 53 insertions(+) diff --git a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts index 903a0fd1df69b..b1414d7fc8bff 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts @@ -424,6 +424,10 @@ export abstract class BaseService extends Resource if (props.cloudMapOptions) { this.enableCloudMap(props.cloudMapOptions); } + + if (props.enableExecuteCommand) { + this.enableExecuteCommand(); + } } /** @@ -797,6 +801,18 @@ export abstract class BaseService extends Resource produce: () => providedHealthCheckGracePeriod?.toSeconds() ?? (this.loadBalancers.length > 0 ? 60 : undefined), }); } + + private enableExecuteCommand() { + this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ + actions: [ + 'ssmmessages:CreateControlChannel', + 'ssmmessages:CreateDataChannel', + 'ssmmessages:OpenControlChannel', + 'ssmmessages:OpenDataChannel', + ], + resources: ['*'], + })); + } } /** diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts b/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts index c47c43db235f9..60edae4fda8ff 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts @@ -86,6 +86,24 @@ nodeunitShim({ EnableExecuteCommand: true, })); + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'ssmmessages:CreateControlChannel', + 'ssmmessages:CreateDataChannel', + 'ssmmessages:OpenControlChannel', + 'ssmmessages:OpenDataChannel', + ], + Effect: 'Allow', + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + })); + test.done(); }, diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts b/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts index ea1ccdd317e3f..52914faa952ba 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts @@ -249,6 +249,25 @@ nodeunitShim({ }, }, })); + + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'ssmmessages:CreateControlChannel', + 'ssmmessages:CreateDataChannel', + 'ssmmessages:OpenControlChannel', + 'ssmmessages:OpenDataChannel', + ], + Effect: 'Allow', + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + })); + test.done(); }, From ad9673113b1f7b3c99c3d5bc630f5563e2e7b895 Mon Sep 17 00:00:00 2001 From: UnnatiParekh05 Date: Wed, 12 May 2021 17:30:17 -0700 Subject: [PATCH 03/19] In-progress: Cluster configuration for exec command --- .../@aws-cdk/aws-ecs/lib/base/base-service.ts | 65 +++++++- packages/@aws-cdk/aws-ecs/lib/cluster.ts | 151 ++++++++++++++++++ 2 files changed, 214 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts index b1414d7fc8bff..a7da5053bfb65 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts @@ -8,7 +8,7 @@ import * as cloudmap from '@aws-cdk/aws-servicediscovery'; import { Annotations, Duration, IResolvable, IResource, Lazy, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { LoadBalancerTargetOptions, NetworkMode, TaskDefinition } from '../base/task-definition'; -import { ICluster, CapacityProviderStrategy } from '../cluster'; +import { ICluster, CapacityProviderStrategy, EcsOptimizedAmi, ExecuteCommandLogging, ExecuteCommandLogConfiguration } from '../cluster'; import { ContainerDefinition, Protocol } from '../container-definition'; import { CfnService } from '../ecs.generated'; import { ScalableTaskCount } from './scalable-task-count'; @@ -427,8 +427,24 @@ export abstract class BaseService extends Resource if (props.enableExecuteCommand) { this.enableExecuteCommand(); - } + + const logging = this.cluster.executeCommandConfiguration?.logging || ExecuteCommandLogging.DEFAULT; + if (logging === ExecuteCommandLogging.OVERRIDE) { + this.executeCommandLogConfiguration(logging, this.cluster.executeCommandConfiguration?.logConfiguration); + } if (logging === ExecuteCommandLogging.DEFAULT) { + this.executeCommandLogConfiguration(logging); + } + + if (this.cluster.executeCommandConfiguration?.kmsKeyID) { + this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ + actions: [ + 'kms:Decrypt', + ], + resources: [`arn:aws:kms:${this.stack.region}:${this.stack.account}:key/${this.cluster.executeCommandConfiguration.kmsKeyID}`], + })); + } } +} /** * The CloudMap service created for this service, if any. @@ -437,6 +453,51 @@ export abstract class BaseService extends Resource return this.cloudmapService; } + private executeCommandLogConfiguration(logging?: ExecuteCommandLogging, logConfiguration?: ExecuteCommandLogConfiguration) { + this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ + actions: [ + 'logs:DescribeLogGroups', + ], + resources: ['*'], + })); + + if (logConfiguration?.cloudWatchLogGroupName) { + this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ + actions: [ + 'logs:CreateLogStream', + 'logs:DescribeLogStreams', + 'logs:PutLogEvents', + ], + resources: [`arn:aws:logs:${this.stack.region}:${this.stack.account}:log-group:${logConfiguration.cloudWatchLogGroupName}:*`], + })); + } else { + this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ + actions: [ + 'logs:CreateLogStream', + 'logs:DescribeLogStreams', + 'logs:PutLogEvents', + ], + resources: ['*'], + })); + } + + if (logConfiguration?.s3BucketName) { + this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ + actions: [ + 's3:GetBucketLocation', + ], + resources: ['*'], + })); + this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ + actions: [ + 's3:GetEncryptionConfiguration', + 's3:PutObject', + ], + resources: [`arn:aws:s3:::${logConfiguration.s3BucketName}/*`], + })); + } + } + /** * This method is called to attach this service to an Application Load Balancer. * diff --git a/packages/@aws-cdk/aws-ecs/lib/cluster.ts b/packages/@aws-cdk/aws-ecs/lib/cluster.ts index c60fc9f4b1dee..467c4ad5d46ee 100644 --- a/packages/@aws-cdk/aws-ecs/lib/cluster.ts +++ b/packages/@aws-cdk/aws-ecs/lib/cluster.ts @@ -61,6 +61,13 @@ export interface ClusterProps { * @default - Container Insights will be disabled for this cluser. */ readonly containerInsights?: boolean; + + /** + * The execute command configuration for the cluster + * + * @default - no configuration will be provided. + */ + readonly executeCommandConfiguration?: ExecuteCommandConfiguration; } /** @@ -130,6 +137,11 @@ export class Cluster extends Resource implements ICluster { */ private _autoscalingGroup?: autoscaling.IAutoScalingGroup; + /** + * The execute command configuration for the cluster + */ + private _executeCommandConfiguration?: ExecuteCommandConfiguration; + /** * Constructs a new instance of the Cluster class. */ @@ -154,6 +166,7 @@ export class Cluster extends Resource implements ICluster { clusterName: this.physicalName, clusterSettings, capacityProviders: Lazy.list({ produce: () => this._capacityProviders }, { omitEmpty: true }), + configuration: props.executeCommandConfiguration ? this.renderConfiguration(props.executeCommandConfiguration) : undefined, }); this.clusterArn = this.getResourceArnAttribute(cluster.attrArn, { @@ -173,6 +186,36 @@ export class Cluster extends Resource implements ICluster { this._autoscalingGroup = props.capacity !== undefined ? this.addCapacity('DefaultAutoScalingGroup', props.capacity) : undefined; + + this._executeCommandConfiguration = props.executeCommandConfiguration; + } + + private renderConfiguration(executeCommandConfig: ExecuteCommandConfiguration) : CfnCluster.ClusterConfigurationProperty { + return { + executeCommandConfiguration: this.renderExecuteCommandConfiguration(executeCommandConfig), + }; + } + + private renderExecuteCommandConfiguration(executeCommandConfig: ExecuteCommandConfiguration): CfnCluster.ExecuteCommandConfigurationProperty { + return { + kmsKeyId: executeCommandConfig.kmsKeyID || undefined, + logConfiguration: this.renderExecuteCommandLogConfiguration(executeCommandConfig.logConfiguration), + logging: executeCommandConfig.logging || undefined, + }; + } + + private renderExecuteCommandLogConfiguration(logConfiguration: ExecuteCommandLogConfiguration | undefined): + CfnCluster.ExecuteCommandLogConfigurationProperty | undefined { + if (!logConfiguration) { + return undefined; + } + return { + cloudWatchEncryptionEnabled: logConfiguration?.cloudWatchEncryptionEnabled, + cloudWatchLogGroupName: logConfiguration?.cloudWatchLogGroupName, + s3BucketName: logConfiguration?.s3BucketName, + s3EncryptionEnabled: logConfiguration?.s3EncryptionEnabled, + s3KeyPrefix: logConfiguration?.s3KeyPrefix, + }; } /** @@ -397,6 +440,13 @@ export class Cluster extends Resource implements ICluster { return this._hasEc2Capacity; } + /** + * Getter for execute command configuration associated with the cluster + */ + public get executeCommandConfiguration(): ExecuteCommandConfiguration | undefined { + return this._executeCommandConfiguration; + } + /** * This method returns the CloudWatch metric for this clusters CPU reservation. * @@ -729,6 +779,11 @@ export interface ICluster extends IResource { * The autoscaling group added to the cluster if capacity is associated to the cluster */ readonly autoscalingGroup?: autoscaling.IAutoScalingGroup; + + /** + * The execute command configuration for the cluster + */ + readonly executeCommandConfiguration?: ExecuteCommandConfiguration; } /** @@ -777,6 +832,13 @@ export interface ClusterAttributes { * @default - No default autoscaling group */ readonly autoscalingGroup?: autoscaling.IAutoScalingGroup; + + /** + * The execute command configuration for the cluster + * + * @default - no configuration will be provided. + */ + readonly executeCommandConfiguration?: ExecuteCommandConfiguration; } /** @@ -997,3 +1059,92 @@ capacity provider. The weight value is taken into consideration after the base v */ readonly weight?: number; } + +/** + * The details of the execute command configuration. For more information, see + * [ExecuteCommandConfiguration] https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-cluster-executecommandconfiguration.html + */ +export interface ExecuteCommandConfiguration { + /** + * The AWS Key Management Service key ID to encrypt the data between the local client and the container. + * + * @default - none + */ + readonly kmsKeyID?: string, + + /** + * The log configuration for the results of the execute command actions. The logs can be sent to CloudWatch Logs or an Amazon S3 bucket. + * + * @default - No logs provided + */ + readonly logConfiguration?: ExecuteCommandLogConfiguration, + + /** + * The log setting to use for redirecting logs for the execute command results. + * + * @default - 'DEFAULT' + */ + readonly logging?: ExecuteCommandLogging, +} + +/** + * The log setting to use for redirecting logs for the execute command results. For more information, see + * [Logging] https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-cluster-executecommandconfiguration.html#cfn-ecs-cluster-executecommandconfiguration-logging + */ +export enum ExecuteCommandLogging { + /** + * The execute command session is not logged. + */ + NONE = 'NONE', + + /** + * The awslogs configuration in the task definition is used. If no logging parameter is specified, it defaults to this value. If no awslogs log driver is configured in the task definition, the output won't be logged. + */ + DEFAULT = 'DEFAULT', + + /** + * Specify the logging details as a part of logConfiguration. + */ + OVERRIDE = 'OVERRIDE', +} + +/** + * The log configuration for the results of the execute command actions. The logs can be sent to CloudWatch Logs and/ or an Amazon S3 bucket. + * For more information, see [ExecuteCommandLogConfiguration] https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-cluster-executecommandlogconfiguration.html + */ +export interface ExecuteCommandLogConfiguration { + /** + * Whether or not to enable encryption on the CloudWatch logs. + * + * @default - encryption will be disabled. + */ + readonly cloudWatchEncryptionEnabled?: boolean, + + /** + * The name of the CloudWatch log group to send logs to. + * + * @default - none + */ + readonly cloudWatchLogGroupName?: string, + + /** + * The name of the S3 bucket to send logs to. + * + * @default - none + */ + readonly s3BucketName?: string, + + /** + * Whether or not to enable encryption on the CloudWatch logs. + * + * @default - encryption will be disabled. + */ + readonly s3EncryptionEnabled?: boolean, + + /** + * An optional folder in the S3 bucket to place logs in. + * + * @default - none + */ + readonly s3KeyPrefix?: string +} \ No newline at end of file From 4d0373c146d66e9408a3521dfcad5ff1d830be4a Mon Sep 17 00:00:00 2001 From: UnnatiParekh05 Date: Fri, 14 May 2021 12:18:21 -0700 Subject: [PATCH 04/19] Resolve conflicts after updating branch --- packages/@aws-cdk/aws-ecs/lib/base/base-service.ts | 8 ++++---- packages/@aws-cdk/aws-ecs/lib/cluster.ts | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts index c41df68415deb..13ced31a4476c 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts @@ -8,7 +8,7 @@ import * as cloudmap from '@aws-cdk/aws-servicediscovery'; import { Annotations, Duration, IResolvable, IResource, Lazy, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { LoadBalancerTargetOptions, NetworkMode, TaskDefinition } from '../base/task-definition'; -import { ICluster, CapacityProviderStrategy, EcsOptimizedAmi, ExecuteCommandLogging, ExecuteCommandLogConfiguration } from '../cluster'; +import { ICluster, CapacityProviderStrategy, ExecuteCommandLogging, ExecuteCommandLogConfiguration } from '../cluster'; import { ContainerDefinition, Protocol } from '../container-definition'; import { CfnService } from '../ecs.generated'; import { ScalableTaskCount } from './scalable-task-count'; @@ -430,9 +430,9 @@ export abstract class BaseService extends Resource const logging = this.cluster.executeCommandConfiguration?.logging || ExecuteCommandLogging.DEFAULT; if (logging === ExecuteCommandLogging.OVERRIDE) { - this.executeCommandLogConfiguration(logging, this.cluster.executeCommandConfiguration?.logConfiguration); + this.executeCommandLogConfiguration(this.cluster.executeCommandConfiguration?.logConfiguration); } if (logging === ExecuteCommandLogging.DEFAULT) { - this.executeCommandLogConfiguration(logging); + this.executeCommandLogConfiguration(); } if (this.cluster.executeCommandConfiguration?.kmsKeyID) { @@ -453,7 +453,7 @@ export abstract class BaseService extends Resource return this.cloudmapService; } - private executeCommandLogConfiguration(logging?: ExecuteCommandLogging, logConfiguration?: ExecuteCommandLogConfiguration) { + private executeCommandLogConfiguration(logConfiguration?: ExecuteCommandLogConfiguration) { this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ actions: [ 'logs:DescribeLogGroups', diff --git a/packages/@aws-cdk/aws-ecs/lib/cluster.ts b/packages/@aws-cdk/aws-ecs/lib/cluster.ts index 5d00a609ab88a..c898d92be5f9b 100644 --- a/packages/@aws-cdk/aws-ecs/lib/cluster.ts +++ b/packages/@aws-cdk/aws-ecs/lib/cluster.ts @@ -179,7 +179,7 @@ export class Cluster extends Resource implements ICluster { const cluster = new CfnCluster(this, 'Resource', { clusterName: this.physicalName, clusterSettings, - capacityProviders: Lazy.list({ produce: () => this._capacityProviders }, { omitEmpty: true }), + capacityProviders: Lazy.list({ produce: () => this._fargateCapacityProviders }, { omitEmpty: true }), configuration: props.executeCommandConfiguration ? this.renderConfiguration(props.executeCommandConfiguration) : undefined, }); @@ -1211,6 +1211,7 @@ export interface ExecuteCommandLogConfiguration { readonly s3KeyPrefix?: string } +/** * The options for creating an Auto Scaling Group Capacity Provider. */ export interface AsgCapacityProviderProps extends AddAutoScalingGroupCapacityOptions { From 1d32595884a50a5eebced0d2e64b72da079c82db Mon Sep 17 00:00:00 2001 From: UnnatiParekh05 Date: Fri, 14 May 2021 13:07:22 -0700 Subject: [PATCH 05/19] Resolved linter errors --- packages/@aws-cdk/aws-ecs/lib/base/base-service.ts | 2 +- packages/@aws-cdk/aws-ecs/lib/cluster.ts | 4 ++-- .../@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts index 13ced31a4476c..59eab03e54956 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts @@ -443,8 +443,8 @@ export abstract class BaseService extends Resource resources: [`arn:aws:kms:${this.stack.region}:${this.stack.account}:key/${this.cluster.executeCommandConfiguration.kmsKeyID}`], })); } + } } -} /** * The CloudMap service created for this service, if any. diff --git a/packages/@aws-cdk/aws-ecs/lib/cluster.ts b/packages/@aws-cdk/aws-ecs/lib/cluster.ts index c898d92be5f9b..f657a522f96b7 100644 --- a/packages/@aws-cdk/aws-ecs/lib/cluster.ts +++ b/packages/@aws-cdk/aws-ecs/lib/cluster.ts @@ -202,7 +202,7 @@ export class Cluster extends Resource implements ICluster { : undefined; this._executeCommandConfiguration = props.executeCommandConfiguration; - + // Only create cluster capacity provider associations if there are any EC2 // capacity providers. Ordinarily we'd just add the construct to the tree // since it's harmless, but we'd prefer not to add unexpected new @@ -221,7 +221,7 @@ export class Cluster extends Resource implements ICluster { } } } - + private renderConfiguration(executeCommandConfig: ExecuteCommandConfiguration) : CfnCluster.ClusterConfigurationProperty { return { executeCommandConfiguration: this.renderExecuteCommandConfiguration(executeCommandConfig), diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts b/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts index 1b30d74412e43..50a15489eba82 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts @@ -278,7 +278,7 @@ nodeunitShim({ }, }, })); - + test.done(); }, @@ -2197,7 +2197,7 @@ nodeunitShim({ test.done(); }, - + 'allows setting enable execute command'(test: Test) { // GIVEN const stack = new cdk.Stack(); From 56a1eacea3356b533e8f7ba4f9c4d0ae9b31129b Mon Sep 17 00:00:00 2001 From: UnnatiParekh05 Date: Mon, 17 May 2021 10:23:18 -0700 Subject: [PATCH 06/19] Logging for CloudWatch and S3 and Encryption for S3 --- .../@aws-cdk/aws-ecs/lib/base/base-service.ts | 39 +++++++++---------- packages/@aws-cdk/aws-ecs/lib/cluster.ts | 16 ++++++-- .../aws-ecs/test/ec2/ec2-service.test.ts | 14 +++++++ .../test/fargate/fargate-service.test.ts | 14 +++++++ 4 files changed, 59 insertions(+), 24 deletions(-) diff --git a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts index 59eab03e54956..65e64265039cc 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts @@ -435,12 +435,13 @@ export abstract class BaseService extends Resource this.executeCommandLogConfiguration(); } - if (this.cluster.executeCommandConfiguration?.kmsKeyID) { + if (this.cluster.executeCommandConfiguration?.kmsKey) { this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ actions: [ 'kms:Decrypt', + 'kms:GenerateDataKey', ], - resources: [`arn:aws:kms:${this.stack.region}:${this.stack.account}:key/${this.cluster.executeCommandConfiguration.kmsKeyID}`], + resources: [`${this.cluster.executeCommandConfiguration.kmsKey.keyArn}`], })); } } @@ -461,25 +462,15 @@ export abstract class BaseService extends Resource resources: ['*'], })); - if (logConfiguration?.cloudWatchLogGroupName) { - this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ - actions: [ - 'logs:CreateLogStream', - 'logs:DescribeLogStreams', - 'logs:PutLogEvents', - ], - resources: [`arn:aws:logs:${this.stack.region}:${this.stack.account}:log-group:${logConfiguration.cloudWatchLogGroupName}:*`], - })); - } else { - this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ - actions: [ - 'logs:CreateLogStream', - 'logs:DescribeLogStreams', - 'logs:PutLogEvents', - ], - resources: ['*'], - })); - } + const logGroupArn = logConfiguration?.cloudWatchLogGroupName ? `arn:aws:logs:${this.stack.region}:${this.stack.account}:log-group:${logConfiguration.cloudWatchLogGroupName}:*` : '*'; + this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ + actions: [ + 'logs:CreateLogStream', + 'logs:DescribeLogStream', + 'logs:PutLogEvents', + ], + resources: [logGroupArn], + })); if (logConfiguration?.s3BucketName) { this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ @@ -491,6 +482,11 @@ export abstract class BaseService extends Resource this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ actions: [ 's3:GetEncryptionConfiguration', + ], + resources: [`arn:aws:s3:::${logConfiguration.s3BucketName}`], + })); + this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ + actions: [ 's3:PutObject', ], resources: [`arn:aws:s3:::${logConfiguration.s3BucketName}/*`], @@ -873,6 +869,7 @@ export abstract class BaseService extends Resource ], resources: ['*'], })); + this.taskDefinition.obtainExecutionRole().addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonECSTaskExecutionRolePolicy')); } } diff --git a/packages/@aws-cdk/aws-ecs/lib/cluster.ts b/packages/@aws-cdk/aws-ecs/lib/cluster.ts index f657a522f96b7..e92c2422d714b 100644 --- a/packages/@aws-cdk/aws-ecs/lib/cluster.ts +++ b/packages/@aws-cdk/aws-ecs/lib/cluster.ts @@ -230,9 +230,9 @@ export class Cluster extends Resource implements ICluster { private renderExecuteCommandConfiguration(executeCommandConfig: ExecuteCommandConfiguration): CfnCluster.ExecuteCommandConfigurationProperty { return { - kmsKeyId: executeCommandConfig.kmsKeyID || undefined, + kmsKeyId: executeCommandConfig.kmsKey ? executeCommandConfig.kmsKey.keyArn : undefined, logConfiguration: this.renderExecuteCommandLogConfiguration(executeCommandConfig.logConfiguration), - logging: executeCommandConfig.logging || undefined, + logging: executeCommandConfig.logging ? executeCommandConfig.logging : undefined, }; } @@ -936,6 +936,11 @@ class ImportedCluster extends Resource implements ICluster { */ private _defaultCloudMapNamespace?: cloudmap.INamespace; + /** + * The execute command configuration for the cluster + */ + private _executeCommandConfiguration?: ExecuteCommandConfiguration; + /** * Constructs a new instance of the ImportedCluster class. */ @@ -945,6 +950,7 @@ class ImportedCluster extends Resource implements ICluster { this.vpc = props.vpc; this.hasEc2Capacity = props.hasEc2Capacity !== false; this._defaultCloudMapNamespace = props.defaultCloudMapNamespace; + this._executeCommandConfiguration = props.executeCommandConfiguration; this.clusterArn = props.clusterArn ?? Stack.of(this).formatArn({ service: 'ecs', @@ -960,6 +966,10 @@ class ImportedCluster extends Resource implements ICluster { public get defaultCloudMapNamespace(): cloudmap.INamespace | undefined { return this._defaultCloudMapNamespace; } + + public get executeCommandConfiguration(): ExecuteCommandConfiguration | undefined { + return this._executeCommandConfiguration; + } } /** @@ -1132,7 +1142,7 @@ export interface ExecuteCommandConfiguration { * * @default - none */ - readonly kmsKeyID?: string, + readonly kmsKey?: kms.IKey, /** * The log configuration for the results of the execute command actions. The logs can be sent to CloudWatch Logs or an Amazon S3 bucket. diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts b/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts index 3e2887f05c397..3883228ec9169 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts @@ -100,6 +100,20 @@ nodeunitShim({ Effect: 'Allow', Resource: '*', }, + { + Action: 'logs:DescribeLogGroups', + Effect: 'Allow', + Resource: '*', + }, + { + Action: [ + 'logs:CreateLogStream', + 'logs:DescribeLogStream', + 'logs:PutLogEvents', + ], + Effect: 'Allow', + Resource: '*', + }, ], Version: '2012-10-17', }, diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts b/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts index 50a15489eba82..6395157f692e8 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts @@ -2266,6 +2266,20 @@ nodeunitShim({ Effect: 'Allow', Resource: '*', }, + { + Action: 'logs:DescribeLogGroups', + Effect: 'Allow', + Resource: '*', + }, + { + Action: [ + 'logs:CreateLogStream', + 'logs:DescribeLogStream', + 'logs:PutLogEvents', + ], + Effect: 'Allow', + Resource: '*', + }, ], Version: '2012-10-17', }, From a0b14ef4f2f4d249e8d065e480d0fc675c343287 Mon Sep 17 00:00:00 2001 From: UnnatiParekh05 Date: Mon, 17 May 2021 10:23:42 -0700 Subject: [PATCH 07/19] Updated README --- packages/@aws-cdk/aws-ecs/README.md | 48 +++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/packages/@aws-cdk/aws-ecs/README.md b/packages/@aws-cdk/aws-ecs/README.md index 5e63747de137f..e5ce7bcd16132 100644 --- a/packages/@aws-cdk/aws-ecs/README.md +++ b/packages/@aws-cdk/aws-ecs/README.md @@ -850,3 +850,51 @@ taskDefinition.addContainer('cont', { inferenceAcceleratorResources, }); ``` + +## ECS Exec command + +Please note, ECS Exec leverages AWS Systems Manager (SSM). So as a prerequisite for the exec command +to work you need to install SSM plugin for the AWS CLI. For more information, see +[Install Session Manager plugin for AWS CLI] (https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html). + +To enable ECS Exec feature for your containers, set the boolean flag `enableExecuteCommand` to `true` in +your EC2Service or FargateService. + +```ts +const service = new ecs.Ec2Service(stack, 'Service', { + cluster, + taskDefinition, + enableExecuteCommand: true, +}); +``` + +Further, enable logging of the execute session results to a CloudWatch Logs log group or S3 Bucket by setting +the `executeCommandConfiguration` property for your cluster accordingly. The default configuration will send the +logs to the CloudWatch Logs using the `awslogs` log driver that is configured in your task definition. Please note, +when using your own `logConfiguration` the log group and/ or S3 Bucket specified must already be created. + +To encrypt data using your own KMS Customer Key (CMK), you must create a CMK and provide the Key ARN in the `kmsKeyId` field +of the `executeCommandConfiguration`. + +```ts +const logGroup = new logs.LogGroup(stack, 'LogGroup'); + +const kmsKey = new kms.Key(stack, 'KmsKey'); + +const execBucket = new s3.Bucket(stack, 'EcsExecBucket'); + +const cluster = new ecs.Cluster(stack, 'Cluster', { + vpc, + executeCommandConfiguration: { + kmsKeyId: kmsKey.keyArn, + logConfiguration: { + cloudWatchLogGroupName: logGroup.logGroupName, + cloudWatchEncryptionEnabled: true, + s3BucketName: execBucket.bucketName, + s3EncryptionEnabled: true, + s3KeyPrefix: 'exec-command-output', + }, + logging: ecs.ExecuteCommandLogging.OVERRIDE, + }, +}); +``` From e2bf45a5d2e68504608e7a909f2edb238f704c0b Mon Sep 17 00:00:00 2001 From: UnnatiParekh05 Date: Mon, 17 May 2021 17:04:05 -0700 Subject: [PATCH 08/19] Ecryption for CloudWatch Logs enabled --- .../@aws-cdk/aws-ecs/lib/base/base-service.ts | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts index 76461aed8ae3f..60f04a4521cb7 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts @@ -443,6 +443,29 @@ export abstract class BaseService extends Resource ], resources: [`${this.cluster.executeCommandConfiguration.kmsKey.keyArn}`], })); + + this.cluster.executeCommandConfiguration.kmsKey.addToResourcePolicy(new iam.PolicyStatement({ + actions: [ + 'kms:*', + ], + resources: ['*'], + principals: [new iam.ArnPrincipal(`arn:aws:iam::${this.stack.account}:root`)], + })); + + this.cluster.executeCommandConfiguration.kmsKey.addToResourcePolicy(new iam.PolicyStatement({ + actions: [ + 'kms:Encrypt*', + 'kms:Decrypt*', + 'kms:ReEncrypt*', + 'kms:GenerateDataKey*', + 'kms:Describe*', + ], + resources: ['*'], + principals: [new iam.ServicePrincipal(`logs.${this.stack.region}.amazonaws.com`)], + conditions: { + ArnLike: { 'kms:EncryptionContext:aws:logs:arn': `arn:aws:logs:${this.stack.region}:${this.stack.account}:*` }, + }, + })); } } this.node.defaultChild = this.resource; @@ -467,7 +490,7 @@ export abstract class BaseService extends Resource this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ actions: [ 'logs:CreateLogStream', - 'logs:DescribeLogStream', + 'logs:DescribeLogStreams', 'logs:PutLogEvents', ], resources: [logGroupArn], From 6c02f67b215cbad6c5ee2ce6f820d811f99c1c10 Mon Sep 17 00:00:00 2001 From: UnnatiParekh05 Date: Wed, 19 May 2021 14:36:53 -0700 Subject: [PATCH 09/19] Modified permissions and added errors --- .../@aws-cdk/aws-ecs/lib/base/base-service.ts | 120 +++++++++--------- packages/@aws-cdk/aws-ecs/lib/cluster.ts | 48 ++++--- 2 files changed, 92 insertions(+), 76 deletions(-) diff --git a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts index 60f04a4521cb7..3fc6c29a05756 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts @@ -429,43 +429,41 @@ export abstract class BaseService extends Resource this.enableExecuteCommand(); const logging = this.cluster.executeCommandConfiguration?.logging || ExecuteCommandLogging.DEFAULT; - if (logging === ExecuteCommandLogging.OVERRIDE) { - this.executeCommandLogConfiguration(this.cluster.executeCommandConfiguration?.logConfiguration); - } if (logging === ExecuteCommandLogging.DEFAULT) { - this.executeCommandLogConfiguration(); - } - - if (this.cluster.executeCommandConfiguration?.kmsKey) { - this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ - actions: [ - 'kms:Decrypt', - 'kms:GenerateDataKey', - ], - resources: [`${this.cluster.executeCommandConfiguration.kmsKey.keyArn}`], - })); - - this.cluster.executeCommandConfiguration.kmsKey.addToResourcePolicy(new iam.PolicyStatement({ - actions: [ - 'kms:*', - ], - resources: ['*'], - principals: [new iam.ArnPrincipal(`arn:aws:iam::${this.stack.account}:root`)], - })); - - this.cluster.executeCommandConfiguration.kmsKey.addToResourcePolicy(new iam.PolicyStatement({ - actions: [ - 'kms:Encrypt*', - 'kms:Decrypt*', - 'kms:ReEncrypt*', - 'kms:GenerateDataKey*', - 'kms:Describe*', - ], - resources: ['*'], - principals: [new iam.ServicePrincipal(`logs.${this.stack.region}.amazonaws.com`)], - conditions: { - ArnLike: { 'kms:EncryptionContext:aws:logs:arn': `arn:aws:logs:${this.stack.region}:${this.stack.account}:*` }, - }, - })); + if (logging !== ExecuteCommandLogging.NONE) { + this.executeCommandLogConfiguration(logging, this.cluster.executeCommandConfiguration?.logConfiguration); + + if (this.cluster.executeCommandConfiguration?.kmsKey) { + this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ + actions: [ + 'kms:Decrypt', + 'kms:GenerateDataKey', + ], + resources: [`${this.cluster.executeCommandConfiguration.kmsKey.keyArn}`], + })); + + this.cluster.executeCommandConfiguration.kmsKey.addToResourcePolicy(new iam.PolicyStatement({ + actions: [ + 'kms:*', + ], + resources: ['*'], + principals: [new iam.ArnPrincipal(`arn:aws:iam::${this.stack.account}:root`)], + })); + + this.cluster.executeCommandConfiguration.kmsKey.addToResourcePolicy(new iam.PolicyStatement({ + actions: [ + 'kms:Encrypt*', + 'kms:Decrypt*', + 'kms:ReEncrypt*', + 'kms:GenerateDataKey*', + 'kms:Describe*', + ], + resources: ['*'], + principals: [new iam.ServicePrincipal(`logs.${this.stack.region}.amazonaws.com`)], + conditions: { + ArnLike: { 'kms:EncryptionContext:aws:logs:arn': `arn:aws:logs:${this.stack.region}:${this.stack.account}:*` }, + }, + })); + } } } this.node.defaultChild = this.resource; @@ -478,36 +476,32 @@ export abstract class BaseService extends Resource return this.cloudmapService; } - private executeCommandLogConfiguration(logConfiguration?: ExecuteCommandLogConfiguration) { - this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ - actions: [ - 'logs:DescribeLogGroups', - ], - resources: ['*'], - })); - - const logGroupArn = logConfiguration?.cloudWatchLogGroupName ? `arn:aws:logs:${this.stack.region}:${this.stack.account}:log-group:${logConfiguration.cloudWatchLogGroupName}:*` : '*'; - this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ - actions: [ - 'logs:CreateLogStream', - 'logs:DescribeLogStreams', - 'logs:PutLogEvents', - ], - resources: [logGroupArn], - })); - - if (logConfiguration?.s3BucketName) { + private executeCommandLogConfiguration(logging: ExecuteCommandLogging, logConfiguration?: ExecuteCommandLogConfiguration) { + if (logging == ExecuteCommandLogging.DEFAULT || (logging == ExecuteCommandLogging.OVERRIDE && logConfiguration?.cloudWatchLogGroupName)) { this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ actions: [ - 's3:GetBucketLocation', + 'logs:DescribeLogGroups', ], resources: ['*'], })); + + const logGroupArn = logConfiguration?.cloudWatchLogGroupName ? `arn:aws:logs:${this.stack.region}:${this.stack.account}:log-group:${logConfiguration.cloudWatchLogGroupName}:*` : '*'; this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ actions: [ - 's3:GetEncryptionConfiguration', + 'logs:CreateLogStream', + 'logs:DescribeLogStreams', + 'logs:PutLogEvents', ], - resources: [`arn:aws:s3:::${logConfiguration.s3BucketName}`], + resources: [logGroupArn], + })); + } + + if (logConfiguration?.s3BucketName) { + this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ + actions: [ + 's3:GetBucketLocation', + ], + resources: ['*'], })); this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ actions: [ @@ -515,6 +509,14 @@ export abstract class BaseService extends Resource ], resources: [`arn:aws:s3:::${logConfiguration.s3BucketName}/*`], })); + if (logConfiguration.s3EncryptionEnabled) { + this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ + actions: [ + 's3:GetEncryptionConfiguration', + ], + resources: [`arn:aws:s3:::${logConfiguration.s3BucketName}`], + })); + } } } diff --git a/packages/@aws-cdk/aws-ecs/lib/cluster.ts b/packages/@aws-cdk/aws-ecs/lib/cluster.ts index e92c2422d714b..92e1bb235b18e 100644 --- a/packages/@aws-cdk/aws-ecs/lib/cluster.ts +++ b/packages/@aws-cdk/aws-ecs/lib/cluster.ts @@ -176,11 +176,23 @@ export class Cluster extends Resource implements ICluster { this.enableFargateCapacityProviders(); } + if (props.executeCommandConfiguration) { + if (props.executeCommandConfiguration.logging) { + if (props.executeCommandConfiguration.logging === ExecuteCommandLogging.OVERRIDE && !props.executeCommandConfiguration.logConfiguration) { + throw new Error('Need to provide log configuration when setting execute command logging field to OVERRIDE'); + } + if (props.executeCommandConfiguration.logging !== ExecuteCommandLogging.OVERRIDE && props.executeCommandConfiguration.logConfiguration) { + throw new Error('Log configuration provided but execute command logging field not set to OVERRIDE. Please set logging to OVERRIDE.'); + } + } + this._executeCommandConfiguration = props.executeCommandConfiguration; + } + const cluster = new CfnCluster(this, 'Resource', { clusterName: this.physicalName, clusterSettings, capacityProviders: Lazy.list({ produce: () => this._fargateCapacityProviders }, { omitEmpty: true }), - configuration: props.executeCommandConfiguration ? this.renderConfiguration(props.executeCommandConfiguration) : undefined, + configuration: this._executeCommandConfiguration ? this.renderConfiguration() : undefined, }); this.clusterArn = this.getResourceArnAttribute(cluster.attrArn, { @@ -201,8 +213,6 @@ export class Cluster extends Resource implements ICluster { ? this.addCapacity('DefaultAutoScalingGroup', props.capacity) : undefined; - this._executeCommandConfiguration = props.executeCommandConfiguration; - // Only create cluster capacity provider associations if there are any EC2 // capacity providers. Ordinarily we'd just add the construct to the tree // since it's harmless, but we'd prefer not to add unexpected new @@ -222,31 +232,35 @@ export class Cluster extends Resource implements ICluster { } } - private renderConfiguration(executeCommandConfig: ExecuteCommandConfiguration) : CfnCluster.ClusterConfigurationProperty { + private renderConfiguration() : CfnCluster.ClusterConfigurationProperty { return { - executeCommandConfiguration: this.renderExecuteCommandConfiguration(executeCommandConfig), + executeCommandConfiguration: this.renderExecuteCommandConfiguration(), }; } - private renderExecuteCommandConfiguration(executeCommandConfig: ExecuteCommandConfiguration): CfnCluster.ExecuteCommandConfigurationProperty { + private renderExecuteCommandConfiguration() : CfnCluster.ExecuteCommandConfigurationProperty { return { - kmsKeyId: executeCommandConfig.kmsKey ? executeCommandConfig.kmsKey.keyArn : undefined, - logConfiguration: this.renderExecuteCommandLogConfiguration(executeCommandConfig.logConfiguration), - logging: executeCommandConfig.logging ? executeCommandConfig.logging : undefined, + kmsKeyId: this._executeCommandConfiguration?.kmsKey ? this._executeCommandConfiguration?.kmsKey.keyArn : undefined, + logConfiguration: this._executeCommandConfiguration?.logConfiguration ? + this.renderExecuteCommandLogConfiguration(this._executeCommandConfiguration?.logConfiguration) : undefined, + logging: this._executeCommandConfiguration?.logging ? this._executeCommandConfiguration?.logging : undefined, }; } - private renderExecuteCommandLogConfiguration(logConfiguration: ExecuteCommandLogConfiguration | undefined): + private renderExecuteCommandLogConfiguration(logConfiguration: ExecuteCommandLogConfiguration): CfnCluster.ExecuteCommandLogConfigurationProperty | undefined { - if (!logConfiguration) { - return undefined; + if (logConfiguration.s3EncryptionEnabled && !logConfiguration.s3BucketName) { + throw new Error('S3EncryptionEnabled without specifying an S3 Bucket in execute command log configuration.'); + } + if (logConfiguration.cloudWatchEncryptionEnabled && !logConfiguration.cloudWatchLogGroupName) { + throw new Error('CloudWatchEncryptionEnabled without specifying a CloudWatch Logs log group in execute command log configuration.'); } return { - cloudWatchEncryptionEnabled: logConfiguration?.cloudWatchEncryptionEnabled, - cloudWatchLogGroupName: logConfiguration?.cloudWatchLogGroupName, - s3BucketName: logConfiguration?.s3BucketName, - s3EncryptionEnabled: logConfiguration?.s3EncryptionEnabled, - s3KeyPrefix: logConfiguration?.s3KeyPrefix, + cloudWatchEncryptionEnabled: logConfiguration.cloudWatchEncryptionEnabled, + cloudWatchLogGroupName: logConfiguration.cloudWatchLogGroupName, + s3BucketName: logConfiguration.s3BucketName, + s3EncryptionEnabled: logConfiguration.s3EncryptionEnabled, + s3KeyPrefix: logConfiguration.s3KeyPrefix, }; } From ad3261d15186e414ac98873c6748396b0ca6b83b Mon Sep 17 00:00:00 2001 From: UnnatiParekh05 Date: Wed, 19 May 2021 14:37:50 -0700 Subject: [PATCH 10/19] Added unit tests --- .../@aws-cdk/aws-ecs/test/cluster.test.ts | 141 ++++++ .../aws-ecs/test/ec2/ec2-service.test.ts | 453 ++++++++++++++++- .../test/fargate/fargate-service.test.ts | 454 +++++++++++++++++- 3 files changed, 1046 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-ecs/test/cluster.test.ts b/packages/@aws-cdk/aws-ecs/test/cluster.test.ts index a76c98377c1db..dbe6cfc1fecb6 100644 --- a/packages/@aws-cdk/aws-ecs/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/cluster.test.ts @@ -9,6 +9,8 @@ import { import * as autoscaling from '@aws-cdk/aws-autoscaling'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as kms from '@aws-cdk/aws-kms'; +import * as logs from '@aws-cdk/aws-logs'; +import * as s3 from '@aws-cdk/aws-s3'; import * as cloudmap from '@aws-cdk/aws-servicediscovery'; import * as cdk from '@aws-cdk/core'; import { nodeunitShim, Test } from 'nodeunit-shim'; @@ -1947,4 +1949,143 @@ nodeunitShim({ })); test.done(); }, + + 'correctly sets log configuration for execute command'(test: Test) { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'test'); + + const kmsKey = new kms.Key(stack, 'KmsKey'); + + const logGroup = new logs.LogGroup(stack, 'LogGroup', { + encryptionKey: kmsKey, + }); + + const execBucket = new s3.Bucket(stack, 'EcsExecBucket', { + encryptionKey: kmsKey, + }); + + // WHEN + new ecs.Cluster(stack, 'EcsCluster', { + executeCommandConfiguration: { + kmsKey: kmsKey, + logConfiguration: { + cloudWatchLogGroupName: logGroup.logGroupName, + cloudWatchEncryptionEnabled: true, + s3BucketName: execBucket.bucketName, + s3EncryptionEnabled: true, + s3KeyPrefix: 'exec-output', + }, + logging: ecs.ExecuteCommandLogging.OVERRIDE, + }, + }); + + // THEN + expect(stack).to(haveResource('AWS::ECS::Cluster', { + Configuration: { + ExecuteCommandConfiguration: { + KmsKeyId: { + 'Fn::GetAtt': [ + 'KmsKey46693ADD', + 'Arn', + ], + }, + LogConfiguration: { + CloudWatchEncryptionEnabled: true, + CloudWatchLogGroupName: { + Ref: 'LogGroupF5B46931', + }, + S3BucketName: { + Ref: 'EcsExecBucket4F468651', + }, + S3EncryptionEnabled: true, + S3KeyPrefix: 'exec-output', + }, + Logging: 'OVERRIDE', + }, + }, + })); + + test.done(); + }, + + 'throws when no log configuration is provided when logging is set to OVERRIDE'(test: Test) { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'test'); + + // THEN + test.throws(() => { + new ecs.Cluster(stack, 'EcsCluster', { + executeCommandConfiguration: { + logging: ecs.ExecuteCommandLogging.OVERRIDE, + }, + }); + }, /Need to provide log configuration when setting execute command logging field to OVERRIDE/); + + test.done(); + }, + + 'throws when log configuration provided but logging is set to DEFAULT'(test: Test) { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'test'); + + const logGroup = new logs.LogGroup(stack, 'LogGroup'); + + // THEN + test.throws(() => { + new ecs.Cluster(stack, 'EcsCluster', { + executeCommandConfiguration: { + logConfiguration: { + cloudWatchLogGroupName: logGroup.logGroupName, + }, + logging: ecs.ExecuteCommandLogging.DEFAULT, + }, + }); + }, /Log configuration provided but execute command logging field not set to OVERRIDE. Please set logging to OVERRIDE./); + + test.done(); + }, + + + 'throws when CloudWatchEncryptionEnabled without providing CloudWatch Logs log group name'(test: Test) { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'test'); + + // THEN + test.throws(() => { + new ecs.Cluster(stack, 'EcsCluster', { + executeCommandConfiguration: { + logConfiguration: { + cloudWatchEncryptionEnabled: true, + }, + logging: ecs.ExecuteCommandLogging.OVERRIDE, + }, + }); + }, /CloudWatchEncryptionEnabled without specifying a CloudWatch Logs log group in execute command log configuration./); + + test.done(); + }, + + 'throws when S3EncryptionEnabled without providing S3 Bucket name'(test: Test) { + // GIVEN + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'test'); + + // THEN + test.throws(() => { + new ecs.Cluster(stack, 'EcsCluster', { + executeCommandConfiguration: { + logConfiguration: { + s3EncryptionEnabled: true, + }, + logging: ecs.ExecuteCommandLogging.OVERRIDE, + }, + }); + }, /S3EncryptionEnabled without specifying an S3 Bucket in execute command log configuration./); + + test.done(); + }, }); diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts b/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts index c6b80344c6d93..ca00d35155289 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts @@ -3,6 +3,9 @@ import * as autoscaling from '@aws-cdk/aws-autoscaling'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as elb from '@aws-cdk/aws-elasticloadbalancing'; import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; +import * as kms from '@aws-cdk/aws-kms'; +import * as logs from '@aws-cdk/aws-logs'; +import * as s3 from '@aws-cdk/aws-s3'; import * as cloudmap from '@aws-cdk/aws-servicediscovery'; import * as cdk from '@aws-cdk/core'; import { nodeunitShim, Test } from 'nodeunit-shim'; @@ -110,7 +113,7 @@ nodeunitShim({ { Action: [ 'logs:CreateLogStream', - 'logs:DescribeLogStream', + 'logs:DescribeLogStreams', 'logs:PutLogEvents', ], Effect: 'Allow', @@ -119,11 +122,459 @@ nodeunitShim({ ], Version: '2012-10-17', }, + PolicyName: 'Ec2TaskDefTaskRoleDefaultPolicyA24FB970', + Roles: [ + { + Ref: 'Ec2TaskDefTaskRole400FA349', + }, + ], + })); + + test.done(); + }, + + 'no logging enabled when logging field is set to NONE'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'MyVpc', {}); + + // WHEN + const cluster = new ecs.Cluster(stack, 'EcsCluster', { + vpc, + executeCommandConfiguration: { + logging: ecs.ExecuteCommandLogging.NONE, + }, + }); + cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); + + const logGroup = new logs.LogGroup(stack, 'LogGroup'); + + taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + logging: ecs.LogDrivers.awsLogs({ + logGroup, + streamPrefix: 'log-group', + }), + memoryLimitMiB: 512, + }); + + new ecs.Ec2Service(stack, 'Ec2Service', { + cluster, + taskDefinition, + enableExecuteCommand: true, + }); + + // THEN + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'ssmmessages:CreateControlChannel', + 'ssmmessages:CreateDataChannel', + 'ssmmessages:OpenControlChannel', + 'ssmmessages:OpenDataChannel', + ], + Effect: 'Allow', + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'Ec2TaskDefTaskRoleDefaultPolicyA24FB970', + Roles: [ + { + Ref: 'Ec2TaskDefTaskRole400FA349', + }, + ], + })); + + test.done(); + }, + + 'enables execute command logging when logging field is set to OVERRIDE'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'MyVpc', {}); + + const logGroup = new logs.LogGroup(stack, 'LogGroup'); + + const execBucket = new s3.Bucket(stack, 'ExecBucket'); + + // WHEN + const cluster = new ecs.Cluster(stack, 'EcsCluster', { + vpc, + executeCommandConfiguration: { + logConfiguration: { + cloudWatchLogGroupName: logGroup.logGroupName, + s3BucketName: execBucket.bucketName, + s3KeyPrefix: 'exec-output', + }, + logging: ecs.ExecuteCommandLogging.OVERRIDE, + }, + }); + cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); + + taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + memoryLimitMiB: 512, + }); + + new ecs.Ec2Service(stack, 'Ec2Service', { + cluster, + taskDefinition, + enableExecuteCommand: true, + }); + + // THEN + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'ssmmessages:CreateControlChannel', + 'ssmmessages:CreateDataChannel', + 'ssmmessages:OpenControlChannel', + 'ssmmessages:OpenDataChannel', + ], + Effect: 'Allow', + Resource: '*', + }, + { + Action: 'logs:DescribeLogGroups', + Effect: 'Allow', + Resource: '*', + }, + { + Action: [ + 'logs:CreateLogStream', + 'logs:DescribeLogStreams', + 'logs:PutLogEvents', + ], + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:aws:logs:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':log-group:', + { + Ref: 'LogGroupF5B46931', + }, + ':*', + ], + ], + }, + }, + { + Action: 's3:GetBucketLocation', + Effect: 'Allow', + Resource: '*', + }, + { + Action: 's3:PutObject', + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:aws:s3:::', + { + Ref: 'ExecBucket29559356', + }, + '/*', + ], + ], + }, + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'Ec2TaskDefTaskRoleDefaultPolicyA24FB970', + Roles: [ + { + Ref: 'Ec2TaskDefTaskRole400FA349', + }, + ], })); test.done(); }, + 'enables encryption for execute command logging'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'MyVpc', {}); + + const kmsKey = new kms.Key(stack, 'KmsKey'); + + const logGroup = new logs.LogGroup(stack, 'LogGroup', { + encryptionKey: kmsKey, + }); + + const execBucket = new s3.Bucket(stack, 'EcsExecBucket', { + encryptionKey: kmsKey, + }); + + // WHEN + const cluster = new ecs.Cluster(stack, 'EcsCluster', { + vpc, + executeCommandConfiguration: { + kmsKey, + logConfiguration: { + cloudWatchLogGroupName: logGroup.logGroupName, + cloudWatchEncryptionEnabled: true, + s3BucketName: execBucket.bucketName, + s3EncryptionEnabled: true, + s3KeyPrefix: 'exec-output', + }, + logging: ecs.ExecuteCommandLogging.OVERRIDE, + }, + }); + + cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); + + taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + memoryLimitMiB: 512, + }); + + new ecs.Ec2Service(stack, 'Ec2Service', { + cluster, + taskDefinition, + enableExecuteCommand: true, + }); + + // THEN + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'ssmmessages:CreateControlChannel', + 'ssmmessages:CreateDataChannel', + 'ssmmessages:OpenControlChannel', + 'ssmmessages:OpenDataChannel', + ], + Effect: 'Allow', + Resource: '*', + }, + { + Action: 'logs:DescribeLogGroups', + Effect: 'Allow', + Resource: '*', + }, + { + Action: [ + 'logs:CreateLogStream', + 'logs:DescribeLogStreams', + 'logs:PutLogEvents', + ], + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:aws:logs:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':log-group:', + { + Ref: 'LogGroupF5B46931', + }, + ':*', + ], + ], + }, + }, + { + Action: 's3:GetBucketLocation', + Effect: 'Allow', + Resource: '*', + }, + { + Action: 's3:PutObject', + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:aws:s3:::', + { + Ref: 'EcsExecBucket4F468651', + }, + '/*', + ], + ], + }, + }, + { + Action: 's3:GetEncryptionConfiguration', + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:aws:s3:::', + { + Ref: 'EcsExecBucket4F468651', + }, + ], + ], + }, + }, + { + Action: [ + 'kms:Decrypt', + 'kms:GenerateDataKey', + ], + Effect: 'Allow', + Resource: { + 'Fn::GetAtt': [ + 'KmsKey46693ADD', + 'Arn', + ], + }, + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'Ec2TaskDefTaskRoleDefaultPolicyA24FB970', + Roles: [ + { + Ref: 'Ec2TaskDefTaskRole400FA349', + }, + ], + })); + + expect(stack).to(haveResource('AWS::KMS::Key', { + KeyPolicy: { + Statement: [ + { + Action: [ + 'kms:Create*', + 'kms:Describe*', + 'kms:Enable*', + 'kms:List*', + 'kms:Put*', + 'kms:Update*', + 'kms:Revoke*', + 'kms:Disable*', + 'kms:Get*', + 'kms:Delete*', + 'kms:ScheduleKeyDeletion', + 'kms:CancelKeyDeletion', + 'kms:GenerateDataKey', + 'kms:TagResource', + 'kms:UntagResource', + ], + Effect: 'Allow', + Principal: { + AWS: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':iam::', + { + Ref: 'AWS::AccountId', + }, + ':root', + ], + ], + }, + }, + Resource: '*', + }, + { + Action: 'kms:*', + Effect: 'Allow', + Principal: { + AWS: { + 'Fn::Join': [ + '', + [ + 'arn:aws:iam::', + { + Ref: 'AWS::AccountId', + }, + ':root', + ], + ], + }, + }, + Resource: '*', + }, + { + Action: [ + 'kms:Encrypt*', + 'kms:Decrypt*', + 'kms:ReEncrypt*', + 'kms:GenerateDataKey*', + 'kms:Describe*', + ], + Condition: { + ArnLike: { + 'kms:EncryptionContext:aws:logs:arn': { + 'Fn::Join': [ + '', + [ + 'arn:aws:logs:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':*', + ], + ], + }, + }, + }, + Effect: 'Allow', + Principal: { + Service: { + 'Fn::Join': [ + '', + [ + 'logs.', + { + Ref: 'AWS::Region', + }, + '.amazonaws.com', + ], + ], + }, + }, + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + })); + + test.done(); + }, 'with custom cloudmap namespace'(test: Test) { // GIVEN diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts b/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts index 8dae8ccc1db79..01992d5ce54e2 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts @@ -3,6 +3,9 @@ import * as appscaling from '@aws-cdk/aws-applicationautoscaling'; import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2'; +import * as kms from '@aws-cdk/aws-kms'; +import * as logs from '@aws-cdk/aws-logs'; +import * as s3 from '@aws-cdk/aws-s3'; import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; import * as cloudmap from '@aws-cdk/aws-servicediscovery'; import * as cdk from '@aws-cdk/core'; @@ -2276,7 +2279,7 @@ nodeunitShim({ { Action: [ 'logs:CreateLogStream', - 'logs:DescribeLogStream', + 'logs:DescribeLogStreams', 'logs:PutLogEvents', ], Effect: 'Allow', @@ -2285,6 +2288,455 @@ nodeunitShim({ ], Version: '2012-10-17', }, + PolicyName: 'FargateTaskDefTaskRoleDefaultPolicy8EB25BBD', + Roles: [ + { + Ref: 'FargateTaskDefTaskRole0B257552', + }, + ], + })); + + test.done(); + }, + + 'no logging enabled when logging field is set to NONE'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'MyVpc', {}); + + // WHEN + const cluster = new ecs.Cluster(stack, 'EcsCluster', { + vpc, + executeCommandConfiguration: { + logging: ecs.ExecuteCommandLogging.NONE, + }, + }); + cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); + + const logGroup = new logs.LogGroup(stack, 'LogGroup'); + + taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + logging: ecs.LogDrivers.awsLogs({ + logGroup, + streamPrefix: 'log-group', + }), + memoryLimitMiB: 512, + }); + + new ecs.FargateService(stack, 'FargateService', { + cluster, + taskDefinition, + enableExecuteCommand: true, + }); + + // THEN + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'ssmmessages:CreateControlChannel', + 'ssmmessages:CreateDataChannel', + 'ssmmessages:OpenControlChannel', + 'ssmmessages:OpenDataChannel', + ], + Effect: 'Allow', + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'FargateTaskDefTaskRoleDefaultPolicy8EB25BBD', + Roles: [ + { + Ref: 'FargateTaskDefTaskRole0B257552', + }, + ], + })); + + test.done(); + }, + + 'enables execute command logging with logging field set to OVERRIDE'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'MyVpc', {}); + + const logGroup = new logs.LogGroup(stack, 'LogGroup'); + + const execBucket = new s3.Bucket(stack, 'ExecBucket'); + + // WHEN + const cluster = new ecs.Cluster(stack, 'EcsCluster', { + vpc, + executeCommandConfiguration: { + logConfiguration: { + cloudWatchLogGroupName: logGroup.logGroupName, + s3BucketName: execBucket.bucketName, + s3KeyPrefix: 'exec-output', + }, + logging: ecs.ExecuteCommandLogging.OVERRIDE, + }, + }); + cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); + + taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + memoryLimitMiB: 512, + }); + + new ecs.FargateService(stack, 'FargateService', { + cluster, + taskDefinition, + enableExecuteCommand: true, + }); + + // THEN + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'ssmmessages:CreateControlChannel', + 'ssmmessages:CreateDataChannel', + 'ssmmessages:OpenControlChannel', + 'ssmmessages:OpenDataChannel', + ], + Effect: 'Allow', + Resource: '*', + }, + { + Action: 'logs:DescribeLogGroups', + Effect: 'Allow', + Resource: '*', + }, + { + Action: [ + 'logs:CreateLogStream', + 'logs:DescribeLogStreams', + 'logs:PutLogEvents', + ], + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:aws:logs:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':log-group:', + { + Ref: 'LogGroupF5B46931', + }, + ':*', + ], + ], + }, + }, + { + Action: 's3:GetBucketLocation', + Effect: 'Allow', + Resource: '*', + }, + { + Action: 's3:PutObject', + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:aws:s3:::', + { + Ref: 'ExecBucket29559356', + }, + '/*', + ], + ], + }, + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'FargateTaskDefTaskRoleDefaultPolicy8EB25BBD', + Roles: [ + { + Ref: 'FargateTaskDefTaskRole0B257552', + }, + ], + })); + + test.done(); + }, + + 'enables encryption for execute command logging'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'MyVpc', {}); + + const kmsKey = new kms.Key(stack, 'KmsKey'); + + const logGroup = new logs.LogGroup(stack, 'LogGroup', { + encryptionKey: kmsKey, + }); + + const execBucket = new s3.Bucket(stack, 'EcsExecBucket', { + encryptionKey: kmsKey, + }); + + // WHEN + const cluster = new ecs.Cluster(stack, 'EcsCluster', { + vpc, + executeCommandConfiguration: { + kmsKey, + logConfiguration: { + cloudWatchLogGroupName: logGroup.logGroupName, + cloudWatchEncryptionEnabled: true, + s3BucketName: execBucket.bucketName, + s3EncryptionEnabled: true, + s3KeyPrefix: 'exec-output', + }, + logging: ecs.ExecuteCommandLogging.OVERRIDE, + }, + }); + + cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); + + taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + memoryLimitMiB: 512, + }); + + new ecs.FargateService(stack, 'FargateService', { + cluster, + taskDefinition, + enableExecuteCommand: true, + }); + + // THEN + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'ssmmessages:CreateControlChannel', + 'ssmmessages:CreateDataChannel', + 'ssmmessages:OpenControlChannel', + 'ssmmessages:OpenDataChannel', + ], + Effect: 'Allow', + Resource: '*', + }, + { + Action: 'logs:DescribeLogGroups', + Effect: 'Allow', + Resource: '*', + }, + { + Action: [ + 'logs:CreateLogStream', + 'logs:DescribeLogStreams', + 'logs:PutLogEvents', + ], + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:aws:logs:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':log-group:', + { + Ref: 'LogGroupF5B46931', + }, + ':*', + ], + ], + }, + }, + { + Action: 's3:GetBucketLocation', + Effect: 'Allow', + Resource: '*', + }, + { + Action: 's3:PutObject', + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:aws:s3:::', + { + Ref: 'EcsExecBucket4F468651', + }, + '/*', + ], + ], + }, + }, + { + Action: 's3:GetEncryptionConfiguration', + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:aws:s3:::', + { + Ref: 'EcsExecBucket4F468651', + }, + ], + ], + }, + }, + { + Action: [ + 'kms:Decrypt', + 'kms:GenerateDataKey', + ], + Effect: 'Allow', + Resource: { + 'Fn::GetAtt': [ + 'KmsKey46693ADD', + 'Arn', + ], + }, + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'FargateTaskDefTaskRoleDefaultPolicy8EB25BBD', + Roles: [ + { + Ref: 'FargateTaskDefTaskRole0B257552', + }, + ], + })); + + expect(stack).to(haveResource('AWS::KMS::Key', { + KeyPolicy: { + Statement: [ + { + Action: [ + 'kms:Create*', + 'kms:Describe*', + 'kms:Enable*', + 'kms:List*', + 'kms:Put*', + 'kms:Update*', + 'kms:Revoke*', + 'kms:Disable*', + 'kms:Get*', + 'kms:Delete*', + 'kms:ScheduleKeyDeletion', + 'kms:CancelKeyDeletion', + 'kms:GenerateDataKey', + 'kms:TagResource', + 'kms:UntagResource', + ], + Effect: 'Allow', + Principal: { + AWS: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':iam::', + { + Ref: 'AWS::AccountId', + }, + ':root', + ], + ], + }, + }, + Resource: '*', + }, + { + Action: 'kms:*', + Effect: 'Allow', + Principal: { + AWS: { + 'Fn::Join': [ + '', + [ + 'arn:aws:iam::', + { + Ref: 'AWS::AccountId', + }, + ':root', + ], + ], + }, + }, + Resource: '*', + }, + { + Action: [ + 'kms:Encrypt*', + 'kms:Decrypt*', + 'kms:ReEncrypt*', + 'kms:GenerateDataKey*', + 'kms:Describe*', + ], + Condition: { + ArnLike: { + 'kms:EncryptionContext:aws:logs:arn': { + 'Fn::Join': [ + '', + [ + 'arn:aws:logs:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':*', + ], + ], + }, + }, + }, + Effect: 'Allow', + Principal: { + Service: { + 'Fn::Join': [ + '', + [ + 'logs.', + { + Ref: 'AWS::Region', + }, + '.amazonaws.com', + ], + ], + }, + }, + Resource: '*', + }, + ], + Version: '2012-10-17', + }, })); test.done(); From eb0fb697ab7df68b790a99824996c24d27c54ae7 Mon Sep 17 00:00:00 2001 From: UnnatiParekh05 Date: Wed, 19 May 2021 14:39:32 -0700 Subject: [PATCH 11/19] Added integ tests for exec command --- .../test/ec2/integ.exec-command.expected.json | 1208 +++++++++++++++++ .../aws-ecs/test/ec2/integ.exec-command.ts | 54 + .../fargate/integ.exec-command.expected.json | 759 +++++++++++ .../test/fargate/integ.exec-command.ts | 50 + 4 files changed, 2071 insertions(+) create mode 100644 packages/@aws-cdk/aws-ecs/test/ec2/integ.exec-command.expected.json create mode 100644 packages/@aws-cdk/aws-ecs/test/ec2/integ.exec-command.ts create mode 100644 packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.expected.json create mode 100644 packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.ts diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.exec-command.expected.json b/packages/@aws-cdk/aws-ecs/test/ec2/integ.exec-command.expected.json new file mode 100644 index 0000000000000..afeb22712be14 --- /dev/null +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.exec-command.expected.json @@ -0,0 +1,1208 @@ +{ + "Resources": { + "Vpc8378EB38": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc" + } + ] + } + }, + "VpcPublicSubnet1Subnet5C2D37C4": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/18", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1RouteTable6C95E38E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1RouteTableAssociation97140677": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + } + } + }, + "VpcPublicSubnet1DefaultRoute3DA9E72A": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + } + }, + "DependsOn": [ + "VpcVPCGWBF912B6E" + ] + }, + "VpcPublicSubnet1EIPD7E02669": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1NATGateway4D7517AA": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet1EIPD7E02669", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet2Subnet691E08A3": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/18", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2RouteTable94F7E489": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2RouteTableAssociationDD5762D8": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + } + } + }, + "VpcPublicSubnet2DefaultRoute97F91067": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + } + }, + "DependsOn": [ + "VpcVPCGWBF912B6E" + ] + }, + "VpcPublicSubnet2EIP3C605A87": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2NATGateway9182C01D": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet2EIP3C605A87", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPrivateSubnet1Subnet536B997A": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/18", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc/PrivateSubnet1" + } + ] + } + }, + "VpcPrivateSubnet1RouteTableB2C5B500": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc/PrivateSubnet1" + } + ] + } + }, + "VpcPrivateSubnet1RouteTableAssociation70C59FA6": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + "SubnetId": { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + } + } + }, + "VpcPrivateSubnet1DefaultRouteBE02A9ED": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VpcPublicSubnet1NATGateway4D7517AA" + } + } + }, + "VpcPrivateSubnet2Subnet3788AAA1": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.192.0/18", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc/PrivateSubnet2" + } + ] + } + }, + "VpcPrivateSubnet2RouteTableA678073B": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc/PrivateSubnet2" + } + ] + } + }, + "VpcPrivateSubnet2RouteTableAssociationA89CAD56": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet2RouteTableA678073B" + }, + "SubnetId": { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + } + } + }, + "VpcPrivateSubnet2DefaultRoute060D2087": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet2RouteTableA678073B" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VpcPublicSubnet2NATGateway9182C01D" + } + } + }, + "VpcIGWD7BA715C": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc" + } + ] + } + }, + "VpcVPCGWBF912B6E": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "InternetGatewayId": { + "Ref": "VpcIGWD7BA715C" + } + } + }, + "LogGroupF5B46931": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "RetentionInDays": "731", + "KmsKeyId": { + "Fn::GetAtt": [ + "KmsKey46693ADD", + "Arn" + ] + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "KmsKey46693ADD": { + "Type": "AWS::KMS::Key", + "Properties": { + "KeyPolicy": { + "Statement": [ + { + "Action": "kms:*", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": "*" + }, + { + "Action": "kms:*", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:aws:iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": "*" + }, + { + "Action": [ + "kms:Encrypt*", + "kms:Decrypt*", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + "kms:Describe*" + ], + "Condition": { + "ArnLike": { + "kms:EncryptionContext:aws:logs:arn": { + "Fn::Join": [ + "", + [ + "arn:aws:logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":*" + ] + ] + } + } + }, + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "logs.", + { + "Ref": "AWS::Region" + }, + ".amazonaws.com" + ] + ] + } + }, + "Resource": "*" + } + ], + "Version": "2012-10-17" + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "EcsExecBucket4F468651": { + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "KMSMasterKeyID": { + "Fn::GetAtt": [ + "KmsKey46693ADD", + "Arn" + ] + }, + "SSEAlgorithm": "aws:kms" + } + } + ] + } + } + }, + "Ec2ClusterEE43E89D": { + "Type": "AWS::ECS::Cluster", + "Properties": { + "Configuration": { + "ExecuteCommandConfiguration": { + "KmsKeyId": { + "Fn::GetAtt": [ + "KmsKey46693ADD", + "Arn" + ] + }, + "LogConfiguration": { + "CloudWatchEncryptionEnabled": true, + "CloudWatchLogGroupName": { + "Ref": "LogGroupF5B46931" + }, + "S3BucketName": { + "Ref": "EcsExecBucket4F468651" + }, + "S3EncryptionEnabled": true, + "S3KeyPrefix": "exec-output" + }, + "Logging": "OVERRIDE" + } + } + } + }, + "Ec2ClusterDefaultAutoScalingGroupInstanceSecurityGroup149B0A9E": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "aws-ecs-integ-exec-command/Ec2Cluster/DefaultAutoScalingGroup/InstanceSecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Ec2Cluster/DefaultAutoScalingGroup" + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + } + }, + "Ec2ClusterDefaultAutoScalingGroupInstanceRole73D80898": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "ec2.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Ec2Cluster/DefaultAutoScalingGroup" + } + ] + } + }, + "Ec2ClusterDefaultAutoScalingGroupInstanceRoleDefaultPolicy6D2DC2FD": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ecs:DeregisterContainerInstance", + "ecs:RegisterContainerInstance", + "ecs:Submit*" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "Ec2ClusterEE43E89D", + "Arn" + ] + } + }, + { + "Action": [ + "ecs:Poll", + "ecs:StartTelemetrySession" + ], + "Condition": { + "ArnEquals": { + "ecs:cluster": { + "Fn::GetAtt": [ + "Ec2ClusterEE43E89D", + "Arn" + ] + } + } + }, + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "ecs:DiscoverPollEndpoint", + "ecr:GetAuthorizationToken", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "Ec2ClusterDefaultAutoScalingGroupInstanceRoleDefaultPolicy6D2DC2FD", + "Roles": [ + { + "Ref": "Ec2ClusterDefaultAutoScalingGroupInstanceRole73D80898" + } + ] + } + }, + "Ec2ClusterDefaultAutoScalingGroupInstanceProfileDB232471": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": { + "Roles": [ + { + "Ref": "Ec2ClusterDefaultAutoScalingGroupInstanceRole73D80898" + } + ] + } + }, + "Ec2ClusterDefaultAutoScalingGroupLaunchConfig7B2FED3A": { + "Type": "AWS::AutoScaling::LaunchConfiguration", + "Properties": { + "ImageId": { + "Ref": "SsmParameterValueawsserviceecsoptimizedamiamazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter" + }, + "InstanceType": "t2.micro", + "IamInstanceProfile": { + "Ref": "Ec2ClusterDefaultAutoScalingGroupInstanceProfileDB232471" + }, + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "Ec2ClusterDefaultAutoScalingGroupInstanceSecurityGroup149B0A9E", + "GroupId" + ] + } + ], + "UserData": { + "Fn::Base64": { + "Fn::Join": [ + "", + [ + "#!/bin/bash\necho ECS_CLUSTER=", + { + "Ref": "Ec2ClusterEE43E89D" + }, + " >> /etc/ecs/ecs.config\nsudo iptables --insert FORWARD 1 --in-interface docker+ --destination 169.254.169.254/32 --jump DROP\nsudo service iptables save\necho ECS_AWSVPC_BLOCK_IMDS=true >> /etc/ecs/ecs.config" + ] + ] + } + } + }, + "DependsOn": [ + "Ec2ClusterDefaultAutoScalingGroupInstanceRoleDefaultPolicy6D2DC2FD", + "Ec2ClusterDefaultAutoScalingGroupInstanceRole73D80898" + ] + }, + "Ec2ClusterDefaultAutoScalingGroupASGC5A6D4C0": { + "Type": "AWS::AutoScaling::AutoScalingGroup", + "Properties": { + "MaxSize": "1", + "MinSize": "1", + "LaunchConfigurationName": { + "Ref": "Ec2ClusterDefaultAutoScalingGroupLaunchConfig7B2FED3A" + }, + "Tags": [ + { + "Key": "Name", + "PropagateAtLaunch": true, + "Value": "aws-ecs-integ-exec-command/Ec2Cluster/DefaultAutoScalingGroup" + } + ], + "VPCZoneIdentifier": [ + { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + }, + { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + } + ] + }, + "UpdatePolicy": { + "AutoScalingReplacingUpdate": { + "WillReplace": true + }, + "AutoScalingScheduledAction": { + "IgnoreUnmodifiedGroupSizeProperties": true + } + } + }, + "Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole23116FA3": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ], + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Ec2Cluster/DefaultAutoScalingGroup" + } + ] + } + }, + "Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRoleDefaultPolicy638C9E33": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ec2:DescribeInstances", + "ec2:DescribeInstanceAttribute", + "ec2:DescribeInstanceStatus", + "ec2:DescribeHosts" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "autoscaling:CompleteLifecycleAction", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":autoscaling:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":autoScalingGroup:*:autoScalingGroupName/", + { + "Ref": "Ec2ClusterDefaultAutoScalingGroupASGC5A6D4C0" + } + ] + ] + } + }, + { + "Action": [ + "ecs:DescribeContainerInstances", + "ecs:DescribeTasks" + ], + "Condition": { + "ArnEquals": { + "ecs:cluster": { + "Fn::GetAtt": [ + "Ec2ClusterEE43E89D", + "Arn" + ] + } + } + }, + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "ecs:ListContainerInstances", + "ecs:SubmitContainerStateChange", + "ecs:SubmitTaskStateChange" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "Ec2ClusterEE43E89D", + "Arn" + ] + } + }, + { + "Action": [ + "ecs:UpdateContainerInstancesState", + "ecs:ListTasks" + ], + "Condition": { + "ArnEquals": { + "ecs:cluster": { + "Fn::GetAtt": [ + "Ec2ClusterEE43E89D", + "Arn" + ] + } + } + }, + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRoleDefaultPolicy638C9E33", + "Roles": [ + { + "Ref": "Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole23116FA3" + } + ] + } + }, + "Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionE0DEFB31": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "import boto3, json, os, time\n\necs = boto3.client('ecs')\nautoscaling = boto3.client('autoscaling')\n\n\ndef lambda_handler(event, context):\n print(json.dumps(event))\n cluster = os.environ['CLUSTER']\n snsTopicArn = event['Records'][0]['Sns']['TopicArn']\n lifecycle_event = json.loads(event['Records'][0]['Sns']['Message'])\n instance_id = lifecycle_event.get('EC2InstanceId')\n if not instance_id:\n print('Got event without EC2InstanceId: %s', json.dumps(event))\n return\n\n instance_arn = container_instance_arn(cluster, instance_id)\n print('Instance %s has container instance ARN %s' % (lifecycle_event['EC2InstanceId'], instance_arn))\n\n if not instance_arn:\n return\n\n task_arns = container_instance_task_arns(cluster, instance_arn)\n \n if task_arns:\n print('Instance ARN %s has task ARNs %s' % (instance_arn, ', '.join(task_arns)))\n\n while has_tasks(cluster, instance_arn, task_arns):\n time.sleep(10)\n\n try:\n print('Terminating instance %s' % instance_id)\n autoscaling.complete_lifecycle_action(\n LifecycleActionResult='CONTINUE',\n **pick(lifecycle_event, 'LifecycleHookName', 'LifecycleActionToken', 'AutoScalingGroupName'))\n except Exception as e:\n # Lifecycle action may have already completed.\n print(str(e))\n\n\ndef container_instance_arn(cluster, instance_id):\n \"\"\"Turn an instance ID into a container instance ARN.\"\"\"\n arns = ecs.list_container_instances(cluster=cluster, filter='ec2InstanceId==' + instance_id)['containerInstanceArns']\n if not arns:\n return None\n return arns[0]\n\ndef container_instance_task_arns(cluster, instance_arn):\n \"\"\"Fetch tasks for a container instance ARN.\"\"\"\n arns = ecs.list_tasks(cluster=cluster, containerInstance=instance_arn)['taskArns']\n return arns\n\ndef has_tasks(cluster, instance_arn, task_arns):\n \"\"\"Return True if the instance is running tasks for the given cluster.\"\"\"\n instances = ecs.describe_container_instances(cluster=cluster, containerInstances=[instance_arn])['containerInstances']\n if not instances:\n return False\n instance = instances[0]\n\n if instance['status'] == 'ACTIVE':\n # Start draining, then try again later\n set_container_instance_to_draining(cluster, instance_arn)\n return True\n\n task_count = None\n\n if task_arns:\n # Fetch details for tasks running on the container instance\n tasks = ecs.describe_tasks(cluster=cluster, tasks=task_arns)['tasks']\n if tasks:\n # Consider any non-stopped tasks as running\n task_count = sum(task['lastStatus'] != 'STOPPED' for task in tasks) + instance['pendingTasksCount']\n \n if not task_count:\n # Fallback to instance task counts if detailed task information is unavailable\n task_count = instance['runningTasksCount'] + instance['pendingTasksCount']\n \n print('Instance %s has %s tasks' % (instance_arn, task_count))\n\n return task_count > 0\n\ndef set_container_instance_to_draining(cluster, instance_arn):\n ecs.update_container_instances_state(\n cluster=cluster,\n containerInstances=[instance_arn], status='DRAINING')\n\n\ndef pick(dct, *keys):\n \"\"\"Pick a subset of a dict.\"\"\"\n return {k: v for k, v in dct.items() if k in keys}\n" + }, + "Role": { + "Fn::GetAtt": [ + "Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole23116FA3", + "Arn" + ] + }, + "Environment": { + "Variables": { + "CLUSTER": { + "Ref": "Ec2ClusterEE43E89D" + } + } + }, + "Handler": "index.lambda_handler", + "Runtime": "python3.6", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Ec2Cluster/DefaultAutoScalingGroup" + } + ], + "Timeout": 310 + }, + "DependsOn": [ + "Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRoleDefaultPolicy638C9E33", + "Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionServiceRole23116FA3" + ] + }, + "Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionAllowInvokeawsecsintegexeccommandEc2ClusterDefaultAutoScalingGroupLifecycleHookDrainHookTopic05F8C92983E1AD32": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "Action": "lambda:InvokeFunction", + "FunctionName": { + "Fn::GetAtt": [ + "Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionE0DEFB31", + "Arn" + ] + }, + "Principal": "sns.amazonaws.com", + "SourceArn": { + "Ref": "Ec2ClusterDefaultAutoScalingGroupLifecycleHookDrainHookTopicF7263B30" + } + } + }, + "Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionTopic4795E0F6": { + "Type": "AWS::SNS::Subscription", + "Properties": { + "Protocol": "lambda", + "TopicArn": { + "Ref": "Ec2ClusterDefaultAutoScalingGroupLifecycleHookDrainHookTopicF7263B30" + }, + "Endpoint": { + "Fn::GetAtt": [ + "Ec2ClusterDefaultAutoScalingGroupDrainECSHookFunctionE0DEFB31", + "Arn" + ] + } + } + }, + "Ec2ClusterDefaultAutoScalingGroupLifecycleHookDrainHookRole71045ED7": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "autoscaling.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Ec2Cluster/DefaultAutoScalingGroup" + } + ] + } + }, + "Ec2ClusterDefaultAutoScalingGroupLifecycleHookDrainHookRoleDefaultPolicyE499974B": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "sns:Publish", + "Effect": "Allow", + "Resource": { + "Ref": "Ec2ClusterDefaultAutoScalingGroupLifecycleHookDrainHookTopicF7263B30" + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "Ec2ClusterDefaultAutoScalingGroupLifecycleHookDrainHookRoleDefaultPolicyE499974B", + "Roles": [ + { + "Ref": "Ec2ClusterDefaultAutoScalingGroupLifecycleHookDrainHookRole71045ED7" + } + ] + } + }, + "Ec2ClusterDefaultAutoScalingGroupLifecycleHookDrainHookTopicF7263B30": { + "Type": "AWS::SNS::Topic", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Ec2Cluster/DefaultAutoScalingGroup" + } + ] + } + }, + "Ec2ClusterDefaultAutoScalingGroupLifecycleHookDrainHook5CB1467E": { + "Type": "AWS::AutoScaling::LifecycleHook", + "Properties": { + "AutoScalingGroupName": { + "Ref": "Ec2ClusterDefaultAutoScalingGroupASGC5A6D4C0" + }, + "LifecycleTransition": "autoscaling:EC2_INSTANCE_TERMINATING", + "DefaultResult": "CONTINUE", + "HeartbeatTimeout": 300, + "NotificationTargetARN": { + "Ref": "Ec2ClusterDefaultAutoScalingGroupLifecycleHookDrainHookTopicF7263B30" + }, + "RoleARN": { + "Fn::GetAtt": [ + "Ec2ClusterDefaultAutoScalingGroupLifecycleHookDrainHookRole71045ED7", + "Arn" + ] + } + }, + "DependsOn": [ + "Ec2ClusterDefaultAutoScalingGroupLifecycleHookDrainHookRoleDefaultPolicyE499974B", + "Ec2ClusterDefaultAutoScalingGroupLifecycleHookDrainHookRole71045ED7" + ] + }, + "TaskDefExecutionRoleB4775C97": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [{ + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" + ] + ] + }] + } + }, + "TaskDefTaskRole1EDB4A67": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "TaskDefTaskRoleDefaultPolicyA592CB18": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ssmmessages:CreateControlChannel", + "ssmmessages:CreateDataChannel", + "ssmmessages:OpenControlChannel", + "ssmmessages:OpenDataChannel" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "logs:DescribeLogGroups", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "logs:CreateLogStream", + "logs:DescribeLogStreams", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:", + { + "Ref": "LogGroupF5B46931" + }, + ":*" + ] + ] + } + }, + { + "Action": "s3:GetBucketLocation", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "s3:PutObject", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:s3:::", + { + "Ref": "EcsExecBucket4F468651" + }, + "/*" + ] + ] + } + }, + { + "Action": "s3:GetEncryptionConfiguration", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:s3:::", + { + "Ref": "EcsExecBucket4F468651" + } + ] + ] + } + }, + { + "Action": [ + "kms:Decrypt", + "kms:GenerateDataKey" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "KmsKey46693ADD", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "TaskDefTaskRoleDefaultPolicyA592CB18", + "Roles": [{ + "Ref": "TaskDefTaskRole1EDB4A67" + }] + } + }, + "TaskDef54694570": { + "Type": "AWS::ECS::TaskDefinition", + "Properties": { + "ContainerDefinitions": [ + { + "Essential": true, + "Image": "amazon/amazon-ecs-sample", + "Memory": "256", + "Name": "web" + } + ], + "Family": "awsecsintegexeccommandTaskDef44709274", + "NetworkMode": "bridge", + "RequiresCompatibilities": [ + "EC2" + ], + "TaskRoleArn": { + "Fn::GetAtt": [ + "TaskDefTaskRole1EDB4A67", + "Arn" + ] + }, + "ExecutionRoleArn": { + "Fn::GetAtt": [ + "TaskDefExecutionRoleB4775C97", + "Arn" + ] + } + } + }, + "Ec2Service04A33183": { + "Type": "AWS::ECS::Service", + "Properties": { + "Cluster": { + "Ref": "Ec2ClusterEE43E89D" + }, + "DeploymentConfiguration": { + "MaximumPercent": 200, + "MinimumHealthyPercent": 50 + }, + "LaunchType": "EC2", + "EnableECSManagedTags": false, + "EnableExecuteCommand": true, + "SchedulingStrategy": "REPLICA", + "TaskDefinition": { + "Ref": "TaskDef54694570" + } + } + } + }, + "Parameters": { + "SsmParameterValueawsserviceecsoptimizedamiamazonlinux2recommendedimageidC96584B6F00A464EAD1953AFF4B05118Parameter": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/aws/service/ecs/optimized-ami/amazon-linux-2/recommended/image_id" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.exec-command.ts b/packages/@aws-cdk/aws-ecs/test/ec2/integ.exec-command.ts new file mode 100644 index 0000000000000..805be661dc4c7 --- /dev/null +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.exec-command.ts @@ -0,0 +1,54 @@ +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as kms from '@aws-cdk/aws-kms'; +import * as logs from '@aws-cdk/aws-logs'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as cdk from '@aws-cdk/core'; +import * as ecs from '../../lib'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'aws-ecs-integ-exec-command'); + +const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 2 }); + +const kmsKey = new kms.Key(stack, 'KmsKey'); + +const logGroup = new logs.LogGroup(stack, 'LogGroup', { + encryptionKey: kmsKey, +}); + +const execBucket = new s3.Bucket(stack, 'EcsExecBucket', { + encryptionKey: kmsKey, +}); + +const cluster = new ecs.Cluster(stack, 'Ec2Cluster', { + vpc, + executeCommandConfiguration: { + kmsKey, + logConfiguration: { + cloudWatchLogGroupName: logGroup.logGroupName, + cloudWatchEncryptionEnabled: true, + s3BucketName: execBucket.bucketName, + s3EncryptionEnabled: true, + s3KeyPrefix: 'exec-output', + }, + logging: ecs.ExecuteCommandLogging.OVERRIDE, + }, +}); +cluster.addCapacity('DefaultAutoScalingGroup', { + instanceType: new ec2.InstanceType('t2.micro'), +}); + +const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef'); + +taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + memoryLimitMiB: 256, +}); + +new ecs.Ec2Service(stack, 'Ec2Service', { + cluster, + taskDefinition, + enableExecuteCommand: true, +}); + +app.synth(); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.expected.json b/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.expected.json new file mode 100644 index 0000000000000..ce72a3d2ce0f8 --- /dev/null +++ b/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.expected.json @@ -0,0 +1,759 @@ +{ + "Resources": { + "Vpc8378EB38": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc" + } + ] + } + }, + "VpcPublicSubnet1Subnet5C2D37C4": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/18", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1RouteTable6C95E38E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1RouteTableAssociation97140677": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + } + } + }, + "VpcPublicSubnet1DefaultRoute3DA9E72A": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet1RouteTable6C95E38E" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + } + }, + "DependsOn": [ + "VpcVPCGWBF912B6E" + ] + }, + "VpcPublicSubnet1EIPD7E02669": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet1NATGateway4D7517AA": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet1EIPD7E02669", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VpcPublicSubnet1Subnet5C2D37C4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc/PublicSubnet1" + } + ] + } + }, + "VpcPublicSubnet2Subnet691E08A3": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/18", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2RouteTable94F7E489": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2RouteTableAssociationDD5762D8": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + }, + "SubnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + } + } + }, + "VpcPublicSubnet2DefaultRoute97F91067": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPublicSubnet2RouteTable94F7E489" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VpcIGWD7BA715C" + } + }, + "DependsOn": [ + "VpcVPCGWBF912B6E" + ] + }, + "VpcPublicSubnet2EIP3C605A87": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPublicSubnet2NATGateway9182C01D": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VpcPublicSubnet2EIP3C605A87", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VpcPublicSubnet2Subnet691E08A3" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc/PublicSubnet2" + } + ] + } + }, + "VpcPrivateSubnet1Subnet536B997A": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/18", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc/PrivateSubnet1" + } + ] + } + }, + "VpcPrivateSubnet1RouteTableB2C5B500": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc/PrivateSubnet1" + } + ] + } + }, + "VpcPrivateSubnet1RouteTableAssociation70C59FA6": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + "SubnetId": { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + } + } + }, + "VpcPrivateSubnet1DefaultRouteBE02A9ED": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet1RouteTableB2C5B500" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VpcPublicSubnet1NATGateway4D7517AA" + } + } + }, + "VpcPrivateSubnet2Subnet3788AAA1": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.192.0/18", + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc/PrivateSubnet2" + } + ] + } + }, + "VpcPrivateSubnet2RouteTableA678073B": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc/PrivateSubnet2" + } + ] + } + }, + "VpcPrivateSubnet2RouteTableAssociationA89CAD56": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet2RouteTableA678073B" + }, + "SubnetId": { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + } + } + }, + "VpcPrivateSubnet2DefaultRoute060D2087": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VpcPrivateSubnet2RouteTableA678073B" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VpcPublicSubnet2NATGateway9182C01D" + } + } + }, + "VpcIGWD7BA715C": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "aws-ecs-integ-exec-command/Vpc" + } + ] + } + }, + "VpcVPCGWBF912B6E": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "Vpc8378EB38" + }, + "InternetGatewayId": { + "Ref": "VpcIGWD7BA715C" + } + } + }, + "LogGroupF5B46931": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "RetentionInDays": "731", + "KmsKeyId": { + "Fn::GetAtt": [ + "KmsKey46693ADD", + "Arn" + ] + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "KmsKey46693ADD": { + "Type": "AWS::KMS::Key", + "Properties": { + "KeyPolicy": { + "Statement": [ + { + "Action": "kms:*", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": "*" + }, + { + "Action": "kms:*", + "Effect": "Allow", + "Principal": { + "AWS": { + "Fn::Join": [ + "", + [ + "arn:aws:iam::", + { + "Ref": "AWS::AccountId" + }, + ":root" + ] + ] + } + }, + "Resource": "*" + }, + { + "Action": [ + "kms:Encrypt*", + "kms:Decrypt*", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + "kms:Describe*" + ], + "Condition": { + "ArnLike": { + "kms:EncryptionContext:aws:logs:arn": { + "Fn::Join": [ + "", + [ + "arn:aws:logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":*" + ] + ] + } + } + }, + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "logs.", + { + "Ref": "AWS::Region" + }, + ".amazonaws.com" + ] + ] + } + }, + "Resource": "*" + } + ], + "Version": "2012-10-17" + } + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, + "EcsExecBucket4F468651": { + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain", + "Properties": { + "BucketEncryption": { + "ServerSideEncryptionConfiguration": [ + { + "ServerSideEncryptionByDefault": { + "KMSMasterKeyID": { + "Fn::GetAtt": [ + "KmsKey46693ADD", + "Arn" + ] + }, + "SSEAlgorithm": "aws:kms" + } + } + ] + } + } + }, + "FargateCluster7CCD5F93": { + "Type": "AWS::ECS::Cluster", + "Properties": { + "Configuration": { + "ExecuteCommandConfiguration": { + "KmsKeyId": { + "Fn::GetAtt": [ + "KmsKey46693ADD", + "Arn" + ] + }, + "LogConfiguration": { + "CloudWatchEncryptionEnabled": true, + "CloudWatchLogGroupName": { + "Ref": "LogGroupF5B46931" + }, + "S3BucketName": { + "Ref": "EcsExecBucket4F468651" + }, + "S3EncryptionEnabled": true, + "S3KeyPrefix": "exec-output" + }, + "Logging": "OVERRIDE" + } + } + } + }, + "TaskDefExecutionRoleB4775C97": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [{ + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" + ] + ] + }] + } + }, + "TaskDefTaskRole1EDB4A67": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "TaskDefTaskRoleDefaultPolicyA592CB18": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ssmmessages:CreateControlChannel", + "ssmmessages:CreateDataChannel", + "ssmmessages:OpenControlChannel", + "ssmmessages:OpenDataChannel" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "logs:DescribeLogGroups", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "logs:CreateLogStream", + "logs:DescribeLogStreams", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:logs:", + { + "Ref": "AWS::Region" + }, + ":", + { + "Ref": "AWS::AccountId" + }, + ":log-group:", + { + "Ref": "LogGroupF5B46931" + }, + ":*" + ] + ] + } + }, + { + "Action": "s3:GetBucketLocation", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "s3:PutObject", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:s3:::", + { + "Ref": "EcsExecBucket4F468651" + }, + "/*" + ] + ] + } + }, + { + "Action": "s3:GetEncryptionConfiguration", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:s3:::", + { + "Ref": "EcsExecBucket4F468651" + } + ] + ] + } + }, + { + "Action": [ + "kms:Decrypt", + "kms:GenerateDataKey" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "KmsKey46693ADD", + "Arn" + ] + } + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "TaskDefTaskRoleDefaultPolicyA592CB18", + "Roles": [{ + "Ref": "TaskDefTaskRole1EDB4A67" + }] + } + }, + "TaskDef54694570": { + "Type": "AWS::ECS::TaskDefinition", + "Properties": { + "ContainerDefinitions": [ + { + "Essential": true, + "Image": "amazon/amazon-ecs-sample", + "Name": "web" + } + ], + "Cpu": "256", + "Family": "awsecsintegexeccommandTaskDef44709274", + "Memory": "512", + "NetworkMode": "awsvpc", + "RequiresCompatibilities": [ + "FARGATE" + ], + "TaskRoleArn": { + "Fn::GetAtt": [ + "TaskDefTaskRole1EDB4A67", + "Arn" + ] + }, + "ExecutionRoleArn": { + "Fn::GetAtt": [ + "TaskDefExecutionRoleB4775C97", + "Arn" + ] + } + } + }, + "FargateServiceAC2B3B85": { + "Type": "AWS::ECS::Service", + "Properties": { + "Cluster": { + "Ref": "FargateCluster7CCD5F93" + }, + "DeploymentConfiguration": { + "MaximumPercent": 200, + "MinimumHealthyPercent": 50 + }, + "LaunchType": "FARGATE", + "EnableECSManagedTags": false, + "EnableExecuteCommand": true, + "NetworkConfiguration": { + "AwsvpcConfiguration": { + "AssignPublicIp": "DISABLED", + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "FargateServiceSecurityGroup0A0E79CB", + "GroupId" + ] + } + ], + "Subnets": [ + { + "Ref": "VpcPrivateSubnet1Subnet536B997A" + }, + { + "Ref": "VpcPrivateSubnet2Subnet3788AAA1" + } + ] + } + }, + "TaskDefinition": { + "Ref": "TaskDef54694570" + } + } + }, + "FargateServiceSecurityGroup0A0E79CB": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "aws-ecs-integ-exec-command/FargateService/SecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "VpcId": { + "Ref": "Vpc8378EB38" + } + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.ts b/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.ts new file mode 100644 index 0000000000000..c40a04e05be79 --- /dev/null +++ b/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.ts @@ -0,0 +1,50 @@ +import * as ec2 from '@aws-cdk/aws-ec2'; +import * as kms from '@aws-cdk/aws-kms'; +import * as logs from '@aws-cdk/aws-logs'; +import * as s3 from '@aws-cdk/aws-s3'; +import * as cdk from '@aws-cdk/core'; +import * as ecs from '../../lib'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'aws-ecs-integ-exec-command'); + +const vpc = new ec2.Vpc(stack, 'Vpc', { maxAzs: 2 }); + +const kmsKey = new kms.Key(stack, 'KmsKey'); + +const logGroup = new logs.LogGroup(stack, 'LogGroup', { + encryptionKey: kmsKey, +}); + +const execBucket = new s3.Bucket(stack, 'EcsExecBucket', { + encryptionKey: kmsKey, +}); + +const cluster = new ecs.Cluster(stack, 'FargateCluster', { + vpc, + executeCommandConfiguration: { + kmsKey, + logConfiguration: { + cloudWatchLogGroupName: logGroup.logGroupName, + cloudWatchEncryptionEnabled: true, + s3BucketName: execBucket.bucketName, + s3EncryptionEnabled: true, + s3KeyPrefix: 'exec-output', + }, + logging: ecs.ExecuteCommandLogging.OVERRIDE, + }, +}); + +const taskDefinition = new ecs.FargateTaskDefinition(stack, 'TaskDef'); + +taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), +}); + +new ecs.FargateService(stack, 'FargateService', { + cluster, + taskDefinition, + enableExecuteCommand: true, +}); + +app.synth(); \ No newline at end of file From 967f6b5ec58c6ffe939e654d868588629d2bde60 Mon Sep 17 00:00:00 2001 From: UnnatiParekh05 Date: Thu, 20 May 2021 09:26:05 -0700 Subject: [PATCH 12/19] Enabling only session encryption --- .../@aws-cdk/aws-ecs/lib/base/base-service.ts | 30 +-- .../aws-ecs/test/ec2/ec2-service.test.ts | 199 ++++++++++++++++++ .../test/fargate/fargate-service.test.ts | 199 ++++++++++++++++++ 3 files changed, 414 insertions(+), 14 deletions(-) diff --git a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts index 3fc6c29a05756..873115b502c23 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts @@ -449,20 +449,22 @@ export abstract class BaseService extends Resource principals: [new iam.ArnPrincipal(`arn:aws:iam::${this.stack.account}:root`)], })); - this.cluster.executeCommandConfiguration.kmsKey.addToResourcePolicy(new iam.PolicyStatement({ - actions: [ - 'kms:Encrypt*', - 'kms:Decrypt*', - 'kms:ReEncrypt*', - 'kms:GenerateDataKey*', - 'kms:Describe*', - ], - resources: ['*'], - principals: [new iam.ServicePrincipal(`logs.${this.stack.region}.amazonaws.com`)], - conditions: { - ArnLike: { 'kms:EncryptionContext:aws:logs:arn': `arn:aws:logs:${this.stack.region}:${this.stack.account}:*` }, - }, - })); + if (this.cluster.executeCommandConfiguration.logConfiguration?.cloudWatchEncryptionEnabled) { + this.cluster.executeCommandConfiguration.kmsKey.addToResourcePolicy(new iam.PolicyStatement({ + actions: [ + 'kms:Encrypt*', + 'kms:Decrypt*', + 'kms:ReEncrypt*', + 'kms:GenerateDataKey*', + 'kms:Describe*', + ], + resources: ['*'], + principals: [new iam.ServicePrincipal(`logs.${this.stack.region}.amazonaws.com`)], + conditions: { + ArnLike: { 'kms:EncryptionContext:aws:logs:arn': `arn:aws:logs:${this.stack.region}:${this.stack.account}:*` }, + }, + })); + } } } } diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts b/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts index ca00d35155289..5d5b8726c1319 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts @@ -310,6 +310,205 @@ nodeunitShim({ test.done(); }, + 'enables only execute command session encryption'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'MyVpc', {}); + + const kmsKey = new kms.Key(stack, 'KmsKey'); + + const logGroup = new logs.LogGroup(stack, 'LogGroup'); + + const execBucket = new s3.Bucket(stack, 'EcsExecBucket'); + + // WHEN + const cluster = new ecs.Cluster(stack, 'EcsCluster', { + vpc, + executeCommandConfiguration: { + kmsKey, + logConfiguration: { + cloudWatchLogGroupName: logGroup.logGroupName, + s3BucketName: execBucket.bucketName, + }, + logging: ecs.ExecuteCommandLogging.OVERRIDE, + }, + }); + + cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef'); + + taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + memoryLimitMiB: 512, + }); + + new ecs.Ec2Service(stack, 'Ec2Service', { + cluster, + taskDefinition, + enableExecuteCommand: true, + }); + + // THEN + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'ssmmessages:CreateControlChannel', + 'ssmmessages:CreateDataChannel', + 'ssmmessages:OpenControlChannel', + 'ssmmessages:OpenDataChannel', + ], + Effect: 'Allow', + Resource: '*', + }, + { + Action: 'logs:DescribeLogGroups', + Effect: 'Allow', + Resource: '*', + }, + { + Action: [ + 'logs:CreateLogStream', + 'logs:DescribeLogStreams', + 'logs:PutLogEvents', + ], + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:aws:logs:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':log-group:', + { + Ref: 'LogGroupF5B46931', + }, + ':*', + ], + ], + }, + }, + { + Action: 's3:GetBucketLocation', + Effect: 'Allow', + Resource: '*', + }, + { + Action: 's3:PutObject', + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:aws:s3:::', + { + Ref: 'EcsExecBucket4F468651', + }, + '/*', + ], + ], + }, + }, + { + Action: [ + 'kms:Decrypt', + 'kms:GenerateDataKey', + ], + Effect: 'Allow', + Resource: { + 'Fn::GetAtt': [ + 'KmsKey46693ADD', + 'Arn', + ], + }, + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'Ec2TaskDefTaskRoleDefaultPolicyA24FB970', + Roles: [ + { + Ref: 'Ec2TaskDefTaskRole400FA349', + }, + ], + })); + + expect(stack).to(haveResource('AWS::KMS::Key', { + KeyPolicy: { + Statement: [ + { + Action: [ + 'kms:Create*', + 'kms:Describe*', + 'kms:Enable*', + 'kms:List*', + 'kms:Put*', + 'kms:Update*', + 'kms:Revoke*', + 'kms:Disable*', + 'kms:Get*', + 'kms:Delete*', + 'kms:ScheduleKeyDeletion', + 'kms:CancelKeyDeletion', + 'kms:GenerateDataKey', + 'kms:TagResource', + 'kms:UntagResource', + ], + Effect: 'Allow', + Principal: { + AWS: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':iam::', + { + Ref: 'AWS::AccountId', + }, + ':root', + ], + ], + }, + }, + Resource: '*', + }, + { + Action: 'kms:*', + Effect: 'Allow', + Principal: { + AWS: { + 'Fn::Join': [ + '', + [ + 'arn:aws:iam::', + { + Ref: 'AWS::AccountId', + }, + ':root', + ], + ], + }, + }, + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + })); + + test.done(); + }, + 'enables encryption for execute command logging'(test: Test) { // GIVEN const stack = new cdk.Stack(); diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts b/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts index 01992d5ce54e2..004161cd27236 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts @@ -2476,6 +2476,205 @@ nodeunitShim({ test.done(); }, + 'enables only execute command session encryption'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'MyVpc', {}); + + const kmsKey = new kms.Key(stack, 'KmsKey'); + + const logGroup = new logs.LogGroup(stack, 'LogGroup'); + + const execBucket = new s3.Bucket(stack, 'EcsExecBucket'); + + // WHEN + const cluster = new ecs.Cluster(stack, 'EcsCluster', { + vpc, + executeCommandConfiguration: { + kmsKey, + logConfiguration: { + cloudWatchLogGroupName: logGroup.logGroupName, + s3BucketName: execBucket.bucketName, + }, + logging: ecs.ExecuteCommandLogging.OVERRIDE, + }, + }); + + cluster.addCapacity('DefaultAutoScalingGroup', { instanceType: new ec2.InstanceType('t2.micro') }); + const taskDefinition = new ecs.FargateTaskDefinition(stack, 'FargateTaskDef'); + + taskDefinition.addContainer('web', { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), + memoryLimitMiB: 512, + }); + + new ecs.FargateService(stack, 'Ec2Service', { + cluster, + taskDefinition, + enableExecuteCommand: true, + }); + + // THEN + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: [ + 'ssmmessages:CreateControlChannel', + 'ssmmessages:CreateDataChannel', + 'ssmmessages:OpenControlChannel', + 'ssmmessages:OpenDataChannel', + ], + Effect: 'Allow', + Resource: '*', + }, + { + Action: 'logs:DescribeLogGroups', + Effect: 'Allow', + Resource: '*', + }, + { + Action: [ + 'logs:CreateLogStream', + 'logs:DescribeLogStreams', + 'logs:PutLogEvents', + ], + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:aws:logs:', + { + Ref: 'AWS::Region', + }, + ':', + { + Ref: 'AWS::AccountId', + }, + ':log-group:', + { + Ref: 'LogGroupF5B46931', + }, + ':*', + ], + ], + }, + }, + { + Action: 's3:GetBucketLocation', + Effect: 'Allow', + Resource: '*', + }, + { + Action: 's3:PutObject', + Effect: 'Allow', + Resource: { + 'Fn::Join': [ + '', + [ + 'arn:aws:s3:::', + { + Ref: 'EcsExecBucket4F468651', + }, + '/*', + ], + ], + }, + }, + { + Action: [ + 'kms:Decrypt', + 'kms:GenerateDataKey', + ], + Effect: 'Allow', + Resource: { + 'Fn::GetAtt': [ + 'KmsKey46693ADD', + 'Arn', + ], + }, + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'FargateTaskDefTaskRoleDefaultPolicy8EB25BBD', + Roles: [ + { + Ref: 'FargateTaskDefTaskRole0B257552', + }, + ], + })); + + expect(stack).to(haveResource('AWS::KMS::Key', { + KeyPolicy: { + Statement: [ + { + Action: [ + 'kms:Create*', + 'kms:Describe*', + 'kms:Enable*', + 'kms:List*', + 'kms:Put*', + 'kms:Update*', + 'kms:Revoke*', + 'kms:Disable*', + 'kms:Get*', + 'kms:Delete*', + 'kms:ScheduleKeyDeletion', + 'kms:CancelKeyDeletion', + 'kms:GenerateDataKey', + 'kms:TagResource', + 'kms:UntagResource', + ], + Effect: 'Allow', + Principal: { + AWS: { + 'Fn::Join': [ + '', + [ + 'arn:', + { + Ref: 'AWS::Partition', + }, + ':iam::', + { + Ref: 'AWS::AccountId', + }, + ':root', + ], + ], + }, + }, + Resource: '*', + }, + { + Action: 'kms:*', + Effect: 'Allow', + Principal: { + AWS: { + 'Fn::Join': [ + '', + [ + 'arn:aws:iam::', + { + Ref: 'AWS::AccountId', + }, + ':root', + ], + ], + }, + }, + Resource: '*', + }, + ], + Version: '2012-10-17', + }, + })); + + test.done(); + }, + 'enables encryption for execute command logging'(test: Test) { // GIVEN const stack = new cdk.Stack(); From 4dc4db5b6bd6ebac6cd27ae0e662c05ed76e4c91 Mon Sep 17 00:00:00 2001 From: Unnati Parekh <80710604+upparekh@users.noreply.github.com> Date: Thu, 20 May 2021 15:46:26 -0700 Subject: [PATCH 13/19] Documentation suggestions from review Co-authored-by: Penghao He --- packages/@aws-cdk/aws-ecs/README.md | 8 ++++---- packages/@aws-cdk/aws-ecs/lib/base/base-service.ts | 3 +-- packages/@aws-cdk/aws-ecs/lib/cluster.ts | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/@aws-cdk/aws-ecs/README.md b/packages/@aws-cdk/aws-ecs/README.md index e5ce7bcd16132..7d19a63adf4b2 100644 --- a/packages/@aws-cdk/aws-ecs/README.md +++ b/packages/@aws-cdk/aws-ecs/README.md @@ -858,7 +858,7 @@ to work you need to install SSM plugin for the AWS CLI. For more information, se [Install Session Manager plugin for AWS CLI] (https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html). To enable ECS Exec feature for your containers, set the boolean flag `enableExecuteCommand` to `true` in -your EC2Service or FargateService. +your `Ec2Service` or `FargateService`. ```ts const service = new ecs.Ec2Service(stack, 'Service', { @@ -868,12 +868,12 @@ const service = new ecs.Ec2Service(stack, 'Service', { }); ``` -Further, enable logging of the execute session results to a CloudWatch Logs log group or S3 Bucket by setting +Further, enable logging of the execute session results to a CloudWatch log group or S3 bucket by setting the `executeCommandConfiguration` property for your cluster accordingly. The default configuration will send the logs to the CloudWatch Logs using the `awslogs` log driver that is configured in your task definition. Please note, -when using your own `logConfiguration` the log group and/ or S3 Bucket specified must already be created. +when using your own `logConfiguration` the log group or S3 Bucket specified must already be created. -To encrypt data using your own KMS Customer Key (CMK), you must create a CMK and provide the Key ARN in the `kmsKeyId` field +To encrypt data using your own KMS Customer Key (CMK), you must create a CMK and provide the key ARN in the `kmsKeyId` field of the `executeCommandConfiguration`. ```ts diff --git a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts index 873115b502c23..17c295e3014e0 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts @@ -190,9 +190,8 @@ export interface BaseServiceOptions { */ readonly capacityProviderStrategies?: CapacityProviderStrategy[]; - /** - * Whether to enable ability to exec into a container + * Whether to enable the ability to execute into a container * * @default - undefined */ diff --git a/packages/@aws-cdk/aws-ecs/lib/cluster.ts b/packages/@aws-cdk/aws-ecs/lib/cluster.ts index 92e1bb235b18e..663a70adf1ae8 100644 --- a/packages/@aws-cdk/aws-ecs/lib/cluster.ts +++ b/packages/@aws-cdk/aws-ecs/lib/cluster.ts @@ -1161,7 +1161,7 @@ export interface ExecuteCommandConfiguration { /** * The log configuration for the results of the execute command actions. The logs can be sent to CloudWatch Logs or an Amazon S3 bucket. * - * @default - No logs provided + * @default - No log configuration */ readonly logConfiguration?: ExecuteCommandLogConfiguration, From 9e824d7b6e8a45eb83a931a23a8c6eeeebf0d8fa Mon Sep 17 00:00:00 2001 From: UnnatiParekh05 Date: Fri, 21 May 2021 14:40:13 -0700 Subject: [PATCH 14/19] Updates after review --- packages/@aws-cdk/aws-ecs/README.md | 2 +- .../@aws-cdk/aws-ecs/lib/base/base-service.ts | 110 +++++++++--------- packages/@aws-cdk/aws-ecs/lib/cluster.ts | 52 +++++---- .../@aws-cdk/aws-ecs/test/cluster.test.ts | 9 +- .../aws-ecs/test/ec2/ec2-service.test.ts | 52 ++++----- .../test/ec2/integ.exec-command.expected.json | 61 +++------- .../test/fargate/fargate-service.test.ts | 52 ++++----- .../fargate/integ.exec-command.expected.json | 61 +++------- 8 files changed, 166 insertions(+), 233 deletions(-) diff --git a/packages/@aws-cdk/aws-ecs/README.md b/packages/@aws-cdk/aws-ecs/README.md index 7d19a63adf4b2..127f10680ed5c 100644 --- a/packages/@aws-cdk/aws-ecs/README.md +++ b/packages/@aws-cdk/aws-ecs/README.md @@ -886,7 +886,7 @@ const execBucket = new s3.Bucket(stack, 'EcsExecBucket'); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc, executeCommandConfiguration: { - kmsKeyId: kmsKey.keyArn, + kmsKeyId: kmsKey, logConfiguration: { cloudWatchLogGroupName: logGroup.logGroupName, cloudWatchEncryptionEnabled: true, diff --git a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts index 17c295e3014e0..f7b61e68cbf22 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts @@ -8,7 +8,7 @@ import * as cloudmap from '@aws-cdk/aws-servicediscovery'; import { Annotations, Duration, IResolvable, IResource, Lazy, Resource, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { LoadBalancerTargetOptions, NetworkMode, TaskDefinition } from '../base/task-definition'; -import { ICluster, CapacityProviderStrategy, ExecuteCommandLogging, ExecuteCommandLogConfiguration } from '../cluster'; +import { ICluster, CapacityProviderStrategy, ExecuteCommandLogging } from '../cluster'; import { ContainerDefinition, Protocol } from '../container-definition'; import { CfnService } from '../ecs.generated'; import { ScalableTaskCount } from './scalable-task-count'; @@ -428,43 +428,12 @@ export abstract class BaseService extends Resource this.enableExecuteCommand(); const logging = this.cluster.executeCommandConfiguration?.logging || ExecuteCommandLogging.DEFAULT; - if (logging !== ExecuteCommandLogging.NONE) { - this.executeCommandLogConfiguration(logging, this.cluster.executeCommandConfiguration?.logConfiguration); + if (logging !== ExecuteCommandLogging.NONE) { if (this.cluster.executeCommandConfiguration?.kmsKey) { - this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ - actions: [ - 'kms:Decrypt', - 'kms:GenerateDataKey', - ], - resources: [`${this.cluster.executeCommandConfiguration.kmsKey.keyArn}`], - })); - - this.cluster.executeCommandConfiguration.kmsKey.addToResourcePolicy(new iam.PolicyStatement({ - actions: [ - 'kms:*', - ], - resources: ['*'], - principals: [new iam.ArnPrincipal(`arn:aws:iam::${this.stack.account}:root`)], - })); - - if (this.cluster.executeCommandConfiguration.logConfiguration?.cloudWatchEncryptionEnabled) { - this.cluster.executeCommandConfiguration.kmsKey.addToResourcePolicy(new iam.PolicyStatement({ - actions: [ - 'kms:Encrypt*', - 'kms:Decrypt*', - 'kms:ReEncrypt*', - 'kms:GenerateDataKey*', - 'kms:Describe*', - ], - resources: ['*'], - principals: [new iam.ServicePrincipal(`logs.${this.stack.region}.amazonaws.com`)], - conditions: { - ArnLike: { 'kms:EncryptionContext:aws:logs:arn': `arn:aws:logs:${this.stack.region}:${this.stack.account}:*` }, - }, - })); - } + this.enableExecuteCommandEncryption(); } + this.executeCommandLogConfiguration(); } } this.node.defaultChild = this.resource; @@ -477,25 +446,24 @@ export abstract class BaseService extends Resource return this.cloudmapService; } - private executeCommandLogConfiguration(logging: ExecuteCommandLogging, logConfiguration?: ExecuteCommandLogConfiguration) { - if (logging == ExecuteCommandLogging.DEFAULT || (logging == ExecuteCommandLogging.OVERRIDE && logConfiguration?.cloudWatchLogGroupName)) { - this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ - actions: [ - 'logs:DescribeLogGroups', - ], - resources: ['*'], - })); + private executeCommandLogConfiguration() { + const logConfiguration = this.cluster.executeCommandConfiguration?.logConfiguration; + this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ + actions: [ + 'logs:DescribeLogGroups', + ], + resources: ['*'], + })); - const logGroupArn = logConfiguration?.cloudWatchLogGroupName ? `arn:aws:logs:${this.stack.region}:${this.stack.account}:log-group:${logConfiguration.cloudWatchLogGroupName}:*` : '*'; - this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ - actions: [ - 'logs:CreateLogStream', - 'logs:DescribeLogStreams', - 'logs:PutLogEvents', - ], - resources: [logGroupArn], - })); - } + const logGroupArn = logConfiguration?.cloudWatchLogGroupName ? `arn:aws:logs:${this.stack.region}:${this.stack.account}:log-group:${logConfiguration.cloudWatchLogGroupName}:*` : '*'; + this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ + actions: [ + 'logs:CreateLogStream', + 'logs:DescribeLogStreams', + 'logs:PutLogEvents', + ], + resources: [logGroupArn], + })); if (logConfiguration?.s3BucketName) { this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ @@ -521,6 +489,41 @@ export abstract class BaseService extends Resource } } + private enableExecuteCommandEncryption() { + this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ + actions: [ + 'kms:Decrypt', + 'kms:GenerateDataKey', + ], + resources: [`${this.cluster.executeCommandConfiguration?.kmsKey?.keyArn}`], + })); + + this.cluster.executeCommandConfiguration?.kmsKey?.addToResourcePolicy(new iam.PolicyStatement({ + actions: [ + 'kms:*', + ], + resources: ['*'], + principals: [new iam.ArnPrincipal(`arn:aws:iam::${this.stack.account}:root`)], + })); + + if (this.cluster.executeCommandConfiguration?.logConfiguration?.cloudWatchEncryptionEnabled) { + this.cluster.executeCommandConfiguration.kmsKey?.addToResourcePolicy(new iam.PolicyStatement({ + actions: [ + 'kms:Encrypt*', + 'kms:Decrypt*', + 'kms:ReEncrypt*', + 'kms:GenerateDataKey*', + 'kms:Describe*', + ], + resources: ['*'], + principals: [new iam.ServicePrincipal(`logs.${this.stack.region}.amazonaws.com`)], + conditions: { + ArnLike: { 'kms:EncryptionContext:aws:logs:arn': `arn:aws:logs:${this.stack.region}:${this.stack.account}:*` }, + }, + })); + } + } + /** * This method is called to attach this service to an Application Load Balancer. * @@ -896,7 +899,6 @@ export abstract class BaseService extends Resource ], resources: ['*'], })); - this.taskDefinition.obtainExecutionRole().addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonECSTaskExecutionRolePolicy')); } } diff --git a/packages/@aws-cdk/aws-ecs/lib/cluster.ts b/packages/@aws-cdk/aws-ecs/lib/cluster.ts index 663a70adf1ae8..16b4ef6bad2a1 100644 --- a/packages/@aws-cdk/aws-ecs/lib/cluster.ts +++ b/packages/@aws-cdk/aws-ecs/lib/cluster.ts @@ -179,10 +179,10 @@ export class Cluster extends Resource implements ICluster { if (props.executeCommandConfiguration) { if (props.executeCommandConfiguration.logging) { if (props.executeCommandConfiguration.logging === ExecuteCommandLogging.OVERRIDE && !props.executeCommandConfiguration.logConfiguration) { - throw new Error('Need to provide log configuration when setting execute command logging field to OVERRIDE'); + throw new Error('Execute command log configuration needs to be provided when setting the logging to OVERRIDE.'); } if (props.executeCommandConfiguration.logging !== ExecuteCommandLogging.OVERRIDE && props.executeCommandConfiguration.logConfiguration) { - throw new Error('Log configuration provided but execute command logging field not set to OVERRIDE. Please set logging to OVERRIDE.'); + throw new Error('Execute command log configuration must only be specified when log configuration is OVERRIDE.'); } } this._executeCommandConfiguration = props.executeCommandConfiguration; @@ -192,7 +192,7 @@ export class Cluster extends Resource implements ICluster { clusterName: this.physicalName, clusterSettings, capacityProviders: Lazy.list({ produce: () => this._fargateCapacityProviders }, { omitEmpty: true }), - configuration: this._executeCommandConfiguration ? this.renderConfiguration() : undefined, + configuration: this.renderConfiguration(), }); this.clusterArn = this.getResourceArnAttribute(cluster.attrArn, { @@ -232,35 +232,37 @@ export class Cluster extends Resource implements ICluster { } } - private renderConfiguration() : CfnCluster.ClusterConfigurationProperty { - return { - executeCommandConfiguration: this.renderExecuteCommandConfiguration(), - }; + private renderConfiguration() : CfnCluster.ClusterConfigurationProperty | undefined { + if (this._executeCommandConfiguration) { + return { + executeCommandConfiguration: this.renderExecuteCommandConfiguration(), + }; + } + return undefined; } private renderExecuteCommandConfiguration() : CfnCluster.ExecuteCommandConfigurationProperty { return { - kmsKeyId: this._executeCommandConfiguration?.kmsKey ? this._executeCommandConfiguration?.kmsKey.keyArn : undefined, - logConfiguration: this._executeCommandConfiguration?.logConfiguration ? - this.renderExecuteCommandLogConfiguration(this._executeCommandConfiguration?.logConfiguration) : undefined, - logging: this._executeCommandConfiguration?.logging ? this._executeCommandConfiguration?.logging : undefined, + kmsKeyId: this._executeCommandConfiguration?.kmsKey?.keyArn, + logConfiguration: this._executeCommandConfiguration?.logConfiguration ? this.renderExecuteCommandLogConfiguration() : undefined, + logging: this._executeCommandConfiguration?.logging, }; } - private renderExecuteCommandLogConfiguration(logConfiguration: ExecuteCommandLogConfiguration): - CfnCluster.ExecuteCommandLogConfigurationProperty | undefined { - if (logConfiguration.s3EncryptionEnabled && !logConfiguration.s3BucketName) { - throw new Error('S3EncryptionEnabled without specifying an S3 Bucket in execute command log configuration.'); + private renderExecuteCommandLogConfiguration(): CfnCluster.ExecuteCommandLogConfigurationProperty { + const logConfiguration = this._executeCommandConfiguration?.logConfiguration; + if (logConfiguration?.s3EncryptionEnabled && !logConfiguration?.s3BucketName) { + throw new Error('S3EncryptionEnabled cannot be specified without an S3 bucket name in execute command log configuration.'); } - if (logConfiguration.cloudWatchEncryptionEnabled && !logConfiguration.cloudWatchLogGroupName) { - throw new Error('CloudWatchEncryptionEnabled without specifying a CloudWatch Logs log group in execute command log configuration.'); + if (logConfiguration?.cloudWatchEncryptionEnabled && !logConfiguration?.cloudWatchLogGroupName) { + throw new Error('CloudWatchEncryptionEnabled cannot be specified without a CloudWatch log group in execute command log configuration.'); } return { - cloudWatchEncryptionEnabled: logConfiguration.cloudWatchEncryptionEnabled, - cloudWatchLogGroupName: logConfiguration.cloudWatchLogGroupName, - s3BucketName: logConfiguration.s3BucketName, - s3EncryptionEnabled: logConfiguration.s3EncryptionEnabled, - s3KeyPrefix: logConfiguration.s3KeyPrefix, + cloudWatchEncryptionEnabled: logConfiguration?.cloudWatchEncryptionEnabled, + cloudWatchLogGroupName: logConfiguration?.cloudWatchLogGroupName, + s3BucketName: logConfiguration?.s3BucketName, + s3EncryptionEnabled: logConfiguration?.s3EncryptionEnabled, + s3KeyPrefix: logConfiguration?.s3KeyPrefix, }; } @@ -1168,7 +1170,7 @@ export interface ExecuteCommandConfiguration { /** * The log setting to use for redirecting logs for the execute command results. * - * @default - 'DEFAULT' + * @default - No log setting */ readonly logging?: ExecuteCommandLogging, } @@ -1207,14 +1209,14 @@ export interface ExecuteCommandLogConfiguration { readonly cloudWatchEncryptionEnabled?: boolean, /** - * The name of the CloudWatch log group to send logs to. + * The name of the CloudWatch log group to send logs to. The CloudWatch log group must already be created. * * @default - none */ readonly cloudWatchLogGroupName?: string, /** - * The name of the S3 bucket to send logs to. + * The name of the S3 bucket to send logs to. The S3 bucket must already be created. * * @default - none */ diff --git a/packages/@aws-cdk/aws-ecs/test/cluster.test.ts b/packages/@aws-cdk/aws-ecs/test/cluster.test.ts index dbe6cfc1fecb6..79acdc4e1e005 100644 --- a/packages/@aws-cdk/aws-ecs/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/cluster.test.ts @@ -2021,7 +2021,7 @@ nodeunitShim({ logging: ecs.ExecuteCommandLogging.OVERRIDE, }, }); - }, /Need to provide log configuration when setting execute command logging field to OVERRIDE/); + }, /Execute command log configuration needs to be provided when setting the logging to OVERRIDE./); test.done(); }, @@ -2043,12 +2043,11 @@ nodeunitShim({ logging: ecs.ExecuteCommandLogging.DEFAULT, }, }); - }, /Log configuration provided but execute command logging field not set to OVERRIDE. Please set logging to OVERRIDE./); + }, /Execute command log configuration must only be specified when log configuration is OVERRIDE./); test.done(); }, - 'throws when CloudWatchEncryptionEnabled without providing CloudWatch Logs log group name'(test: Test) { // GIVEN const app = new cdk.App(); @@ -2064,7 +2063,7 @@ nodeunitShim({ logging: ecs.ExecuteCommandLogging.OVERRIDE, }, }); - }, /CloudWatchEncryptionEnabled without specifying a CloudWatch Logs log group in execute command log configuration./); + }, /CloudWatchEncryptionEnabled cannot be specified without a CloudWatch log group in execute command log configuration./); test.done(); }, @@ -2084,7 +2083,7 @@ nodeunitShim({ logging: ecs.ExecuteCommandLogging.OVERRIDE, }, }); - }, /S3EncryptionEnabled without specifying an S3 Bucket in execute command log configuration./); + }, /S3EncryptionEnabled cannot be specified without an S3 bucket name in execute command log configuration./); test.done(); }, diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts b/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts index 5d5b8726c1319..579b73225729b 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts @@ -362,6 +362,19 @@ nodeunitShim({ Effect: 'Allow', Resource: '*', }, + { + Action: [ + 'kms:Decrypt', + 'kms:GenerateDataKey', + ], + Effect: 'Allow', + Resource: { + 'Fn::GetAtt': [ + 'KmsKey46693ADD', + 'Arn', + ], + }, + }, { Action: 'logs:DescribeLogGroups', Effect: 'Allow', @@ -416,19 +429,6 @@ nodeunitShim({ ], }, }, - { - Action: [ - 'kms:Decrypt', - 'kms:GenerateDataKey', - ], - Effect: 'Allow', - Resource: { - 'Fn::GetAtt': [ - 'KmsKey46693ADD', - 'Arn', - ], - }, - }, ], Version: '2012-10-17', }, @@ -568,6 +568,19 @@ nodeunitShim({ Effect: 'Allow', Resource: '*', }, + { + Action: [ + 'kms:Decrypt', + 'kms:GenerateDataKey', + ], + Effect: 'Allow', + Resource: { + 'Fn::GetAtt': [ + 'KmsKey46693ADD', + 'Arn', + ], + }, + }, { Action: 'logs:DescribeLogGroups', Effect: 'Allow', @@ -637,19 +650,6 @@ nodeunitShim({ ], }, }, - { - Action: [ - 'kms:Decrypt', - 'kms:GenerateDataKey', - ], - Effect: 'Allow', - Resource: { - 'Fn::GetAtt': [ - 'KmsKey46693ADD', - 'Arn', - ], - }, - }, ], Version: '2012-10-17', }, diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.exec-command.expected.json b/packages/@aws-cdk/aws-ecs/test/ec2/integ.exec-command.expected.json index afeb22712be14..3f032ba6cc07e 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/integ.exec-command.expected.json +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.exec-command.expected.json @@ -997,35 +997,6 @@ "Ec2ClusterDefaultAutoScalingGroupLifecycleHookDrainHookRole71045ED7" ] }, - "TaskDefExecutionRoleB4775C97": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "ecs-tasks.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - }, - "ManagedPolicyArns": [{ - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" - ] - ] - }] - } - }, "TaskDefTaskRole1EDB4A67": { "Type": "AWS::IAM::Role", "Properties": { @@ -1058,6 +1029,19 @@ "Effect": "Allow", "Resource": "*" }, + { + "Action": [ + "kms:Decrypt", + "kms:GenerateDataKey" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "KmsKey46693ADD", + "Arn" + ] + } + }, { "Action": "logs:DescribeLogGroups", "Effect": "Allow", @@ -1126,19 +1110,6 @@ ] ] } - }, - { - "Action": [ - "kms:Decrypt", - "kms:GenerateDataKey" - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "KmsKey46693ADD", - "Arn" - ] - } } ], "Version": "2012-10-17" @@ -1170,12 +1141,6 @@ "TaskDefTaskRole1EDB4A67", "Arn" ] - }, - "ExecutionRoleArn": { - "Fn::GetAtt": [ - "TaskDefExecutionRoleB4775C97", - "Arn" - ] } } }, diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts b/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts index 004161cd27236..495908fd47f36 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts @@ -2528,6 +2528,19 @@ nodeunitShim({ Effect: 'Allow', Resource: '*', }, + { + Action: [ + 'kms:Decrypt', + 'kms:GenerateDataKey', + ], + Effect: 'Allow', + Resource: { + 'Fn::GetAtt': [ + 'KmsKey46693ADD', + 'Arn', + ], + }, + }, { Action: 'logs:DescribeLogGroups', Effect: 'Allow', @@ -2582,19 +2595,6 @@ nodeunitShim({ ], }, }, - { - Action: [ - 'kms:Decrypt', - 'kms:GenerateDataKey', - ], - Effect: 'Allow', - Resource: { - 'Fn::GetAtt': [ - 'KmsKey46693ADD', - 'Arn', - ], - }, - }, ], Version: '2012-10-17', }, @@ -2734,6 +2734,19 @@ nodeunitShim({ Effect: 'Allow', Resource: '*', }, + { + Action: [ + 'kms:Decrypt', + 'kms:GenerateDataKey', + ], + Effect: 'Allow', + Resource: { + 'Fn::GetAtt': [ + 'KmsKey46693ADD', + 'Arn', + ], + }, + }, { Action: 'logs:DescribeLogGroups', Effect: 'Allow', @@ -2803,19 +2816,6 @@ nodeunitShim({ ], }, }, - { - Action: [ - 'kms:Decrypt', - 'kms:GenerateDataKey', - ], - Effect: 'Allow', - Resource: { - 'Fn::GetAtt': [ - 'KmsKey46693ADD', - 'Arn', - ], - }, - }, ], Version: '2012-10-17', }, diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.expected.json b/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.expected.json index ce72a3d2ce0f8..083a16ee75820 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.expected.json +++ b/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.expected.json @@ -517,35 +517,6 @@ } } }, - "TaskDefExecutionRoleB4775C97": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Statement": [ - { - "Action": "sts:AssumeRole", - "Effect": "Allow", - "Principal": { - "Service": "ecs-tasks.amazonaws.com" - } - } - ], - "Version": "2012-10-17" - }, - "ManagedPolicyArns": [{ - "Fn::Join": [ - "", - [ - "arn:", - { - "Ref": "AWS::Partition" - }, - ":iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" - ] - ] - }] - } - }, "TaskDefTaskRole1EDB4A67": { "Type": "AWS::IAM::Role", "Properties": { @@ -578,6 +549,19 @@ "Effect": "Allow", "Resource": "*" }, + { + "Action": [ + "kms:Decrypt", + "kms:GenerateDataKey" + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "KmsKey46693ADD", + "Arn" + ] + } + }, { "Action": "logs:DescribeLogGroups", "Effect": "Allow", @@ -646,19 +630,6 @@ ] ] } - }, - { - "Action": [ - "kms:Decrypt", - "kms:GenerateDataKey" - ], - "Effect": "Allow", - "Resource": { - "Fn::GetAtt": [ - "KmsKey46693ADD", - "Arn" - ] - } } ], "Version": "2012-10-17" @@ -691,12 +662,6 @@ "TaskDefTaskRole1EDB4A67", "Arn" ] - }, - "ExecutionRoleArn": { - "Fn::GetAtt": [ - "TaskDefExecutionRoleB4775C97", - "Arn" - ] } } }, From 5b1e1517e5d612d3e58f7fefb409efdf170753e0 Mon Sep 17 00:00:00 2001 From: UnnatiParekh05 Date: Fri, 21 May 2021 16:09:18 -0700 Subject: [PATCH 15/19] Updated error condition in cluster --- packages/@aws-cdk/aws-ecs/lib/cluster.ts | 10 +++------- packages/@aws-cdk/aws-ecs/test/cluster.test.ts | 4 ++-- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/packages/@aws-cdk/aws-ecs/lib/cluster.ts b/packages/@aws-cdk/aws-ecs/lib/cluster.ts index 16b4ef6bad2a1..5d1236b193d55 100644 --- a/packages/@aws-cdk/aws-ecs/lib/cluster.ts +++ b/packages/@aws-cdk/aws-ecs/lib/cluster.ts @@ -177,13 +177,9 @@ export class Cluster extends Resource implements ICluster { } if (props.executeCommandConfiguration) { - if (props.executeCommandConfiguration.logging) { - if (props.executeCommandConfiguration.logging === ExecuteCommandLogging.OVERRIDE && !props.executeCommandConfiguration.logConfiguration) { - throw new Error('Execute command log configuration needs to be provided when setting the logging to OVERRIDE.'); - } - if (props.executeCommandConfiguration.logging !== ExecuteCommandLogging.OVERRIDE && props.executeCommandConfiguration.logConfiguration) { - throw new Error('Execute command log configuration must only be specified when log configuration is OVERRIDE.'); - } + if ((props.executeCommandConfiguration.logging === ExecuteCommandLogging.OVERRIDE) !== + (props.executeCommandConfiguration.logConfiguration !== undefined)) { + throw new Error('Execute command log configuration must only be specified when logging is OVERRIDE.'); } this._executeCommandConfiguration = props.executeCommandConfiguration; } diff --git a/packages/@aws-cdk/aws-ecs/test/cluster.test.ts b/packages/@aws-cdk/aws-ecs/test/cluster.test.ts index 79acdc4e1e005..484f08a114bff 100644 --- a/packages/@aws-cdk/aws-ecs/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/cluster.test.ts @@ -2021,7 +2021,7 @@ nodeunitShim({ logging: ecs.ExecuteCommandLogging.OVERRIDE, }, }); - }, /Execute command log configuration needs to be provided when setting the logging to OVERRIDE./); + }, /Execute command log configuration must only be specified when logging is OVERRIDE./); test.done(); }, @@ -2043,7 +2043,7 @@ nodeunitShim({ logging: ecs.ExecuteCommandLogging.DEFAULT, }, }); - }, /Execute command log configuration must only be specified when log configuration is OVERRIDE./); + }, /Execute command log configuration must only be specified when logging is OVERRIDE./); test.done(); }, From e9bbdcf33daa4a497aa1276db6da88dd013181ca Mon Sep 17 00:00:00 2001 From: UnnatiParekh05 Date: Wed, 2 Jun 2021 13:20:04 -0700 Subject: [PATCH 16/19] Updated the Cluster configuration to pass log group and bucket instead of their names --- packages/@aws-cdk/aws-ecs/README.md | 19 ++++++++++++------- .../@aws-cdk/aws-ecs/lib/base/base-service.ts | 8 ++++---- packages/@aws-cdk/aws-ecs/lib/cluster.ts | 15 ++++++++------- .../@aws-cdk/aws-ecs/test/cluster.test.ts | 6 +++--- .../aws-ecs/test/ec2/ec2-service.test.ts | 12 ++++++------ .../aws-ecs/test/ec2/integ.exec-command.ts | 4 ++-- .../test/fargate/fargate-service.test.ts | 12 ++++++------ .../test/fargate/integ.exec-command.ts | 4 ++-- 8 files changed, 43 insertions(+), 37 deletions(-) diff --git a/packages/@aws-cdk/aws-ecs/README.md b/packages/@aws-cdk/aws-ecs/README.md index 127f10680ed5c..3692bf2b38f2a 100644 --- a/packages/@aws-cdk/aws-ecs/README.md +++ b/packages/@aws-cdk/aws-ecs/README.md @@ -873,24 +873,29 @@ the `executeCommandConfiguration` property for your cluster accordingly. The def logs to the CloudWatch Logs using the `awslogs` log driver that is configured in your task definition. Please note, when using your own `logConfiguration` the log group or S3 Bucket specified must already be created. -To encrypt data using your own KMS Customer Key (CMK), you must create a CMK and provide the key ARN in the `kmsKeyId` field -of the `executeCommandConfiguration`. +To encrypt data using your own KMS Customer Key (CMK), you must create a CMK and provide the key in the `kmsKeyId` field +of the `executeCommandConfiguration`. To use this key for encrypting CloudWatch log data or S3 bucket, make sure to associate the key +to these resources while creation. ```ts -const logGroup = new logs.LogGroup(stack, 'LogGroup'); - const kmsKey = new kms.Key(stack, 'KmsKey'); -const execBucket = new s3.Bucket(stack, 'EcsExecBucket'); +const logGroup = new logs.LogGroup(stack, 'LogGroup', { + encryptionKey: kmsKey, +}); + +const execBucket = new s3.Bucket(stack, 'EcsExecBucket', { + encryptionKey: kmsKey, +}); const cluster = new ecs.Cluster(stack, 'Cluster', { vpc, executeCommandConfiguration: { kmsKeyId: kmsKey, logConfiguration: { - cloudWatchLogGroupName: logGroup.logGroupName, + cloudWatchLogGroup: logGroup, cloudWatchEncryptionEnabled: true, - s3BucketName: execBucket.bucketName, + s3Bucket: execBucket, s3EncryptionEnabled: true, s3KeyPrefix: 'exec-command-output', }, diff --git a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts index 229c4919cdc9a..73adc16782627 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts @@ -455,7 +455,7 @@ export abstract class BaseService extends Resource resources: ['*'], })); - const logGroupArn = logConfiguration?.cloudWatchLogGroupName ? `arn:aws:logs:${this.stack.region}:${this.stack.account}:log-group:${logConfiguration.cloudWatchLogGroupName}:*` : '*'; + const logGroupArn = logConfiguration?.cloudWatchLogGroup ? `arn:aws:logs:${this.stack.region}:${this.stack.account}:log-group:${logConfiguration.cloudWatchLogGroup.logGroupName}:*` : '*'; this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ actions: [ 'logs:CreateLogStream', @@ -465,7 +465,7 @@ export abstract class BaseService extends Resource resources: [logGroupArn], })); - if (logConfiguration?.s3BucketName) { + if (logConfiguration?.s3Bucket?.bucketName) { this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ actions: [ 's3:GetBucketLocation', @@ -476,14 +476,14 @@ export abstract class BaseService extends Resource actions: [ 's3:PutObject', ], - resources: [`arn:aws:s3:::${logConfiguration.s3BucketName}/*`], + resources: [`arn:aws:s3:::${logConfiguration.s3Bucket.bucketName}/*`], })); if (logConfiguration.s3EncryptionEnabled) { this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ actions: [ 's3:GetEncryptionConfiguration', ], - resources: [`arn:aws:s3:::${logConfiguration.s3BucketName}`], + resources: [`arn:aws:s3:::${logConfiguration.s3Bucket.bucketName}`], })); } } diff --git a/packages/@aws-cdk/aws-ecs/lib/cluster.ts b/packages/@aws-cdk/aws-ecs/lib/cluster.ts index 5d1236b193d55..67a861ac6f8fa 100644 --- a/packages/@aws-cdk/aws-ecs/lib/cluster.ts +++ b/packages/@aws-cdk/aws-ecs/lib/cluster.ts @@ -3,6 +3,8 @@ import * as cloudwatch from '@aws-cdk/aws-cloudwatch'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as kms from '@aws-cdk/aws-kms'; +import * as logs from '@aws-cdk/aws-logs'; +import * as s3 from '@aws-cdk/aws-s3'; import * as cloudmap from '@aws-cdk/aws-servicediscovery'; import * as ssm from '@aws-cdk/aws-ssm'; import { Duration, Lazy, IResource, Resource, Stack, Aspects, IAspect, IConstruct } from '@aws-cdk/core'; @@ -247,16 +249,16 @@ export class Cluster extends Resource implements ICluster { private renderExecuteCommandLogConfiguration(): CfnCluster.ExecuteCommandLogConfigurationProperty { const logConfiguration = this._executeCommandConfiguration?.logConfiguration; - if (logConfiguration?.s3EncryptionEnabled && !logConfiguration?.s3BucketName) { + if (logConfiguration?.s3EncryptionEnabled && !logConfiguration?.s3Bucket) { throw new Error('S3EncryptionEnabled cannot be specified without an S3 bucket name in execute command log configuration.'); } - if (logConfiguration?.cloudWatchEncryptionEnabled && !logConfiguration?.cloudWatchLogGroupName) { + if (logConfiguration?.cloudWatchEncryptionEnabled && !logConfiguration?.cloudWatchLogGroup) { throw new Error('CloudWatchEncryptionEnabled cannot be specified without a CloudWatch log group in execute command log configuration.'); } return { cloudWatchEncryptionEnabled: logConfiguration?.cloudWatchEncryptionEnabled, - cloudWatchLogGroupName: logConfiguration?.cloudWatchLogGroupName, - s3BucketName: logConfiguration?.s3BucketName, + cloudWatchLogGroupName: logConfiguration?.cloudWatchLogGroup?.logGroupName, + s3BucketName: logConfiguration?.s3Bucket?.bucketName, s3EncryptionEnabled: logConfiguration?.s3EncryptionEnabled, s3KeyPrefix: logConfiguration?.s3KeyPrefix, }; @@ -1206,17 +1208,16 @@ export interface ExecuteCommandLogConfiguration { /** * The name of the CloudWatch log group to send logs to. The CloudWatch log group must already be created. - * * @default - none */ - readonly cloudWatchLogGroupName?: string, + readonly cloudWatchLogGroup?: logs.ILogGroup, /** * The name of the S3 bucket to send logs to. The S3 bucket must already be created. * * @default - none */ - readonly s3BucketName?: string, + readonly s3Bucket?: s3.IBucket, /** * Whether or not to enable encryption on the CloudWatch logs. diff --git a/packages/@aws-cdk/aws-ecs/test/cluster.test.ts b/packages/@aws-cdk/aws-ecs/test/cluster.test.ts index 484f08a114bff..83f7c36f695c3 100644 --- a/packages/@aws-cdk/aws-ecs/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/cluster.test.ts @@ -1970,9 +1970,9 @@ nodeunitShim({ executeCommandConfiguration: { kmsKey: kmsKey, logConfiguration: { - cloudWatchLogGroupName: logGroup.logGroupName, + cloudWatchLogGroup: logGroup, cloudWatchEncryptionEnabled: true, - s3BucketName: execBucket.bucketName, + s3Bucket: execBucket, s3EncryptionEnabled: true, s3KeyPrefix: 'exec-output', }, @@ -2038,7 +2038,7 @@ nodeunitShim({ new ecs.Cluster(stack, 'EcsCluster', { executeCommandConfiguration: { logConfiguration: { - cloudWatchLogGroupName: logGroup.logGroupName, + cloudWatchLogGroup: logGroup, }, logging: ecs.ExecuteCommandLogging.DEFAULT, }, diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts b/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts index 579b73225729b..f53f66d8755ad 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/ec2-service.test.ts @@ -207,8 +207,8 @@ nodeunitShim({ vpc, executeCommandConfiguration: { logConfiguration: { - cloudWatchLogGroupName: logGroup.logGroupName, - s3BucketName: execBucket.bucketName, + cloudWatchLogGroup: logGroup, + s3Bucket: execBucket, s3KeyPrefix: 'exec-output', }, logging: ecs.ExecuteCommandLogging.OVERRIDE, @@ -327,8 +327,8 @@ nodeunitShim({ executeCommandConfiguration: { kmsKey, logConfiguration: { - cloudWatchLogGroupName: logGroup.logGroupName, - s3BucketName: execBucket.bucketName, + cloudWatchLogGroup: logGroup, + s3Bucket: execBucket, }, logging: ecs.ExecuteCommandLogging.OVERRIDE, }, @@ -530,9 +530,9 @@ nodeunitShim({ executeCommandConfiguration: { kmsKey, logConfiguration: { - cloudWatchLogGroupName: logGroup.logGroupName, + cloudWatchLogGroup: logGroup, cloudWatchEncryptionEnabled: true, - s3BucketName: execBucket.bucketName, + s3Bucket: execBucket, s3EncryptionEnabled: true, s3KeyPrefix: 'exec-output', }, diff --git a/packages/@aws-cdk/aws-ecs/test/ec2/integ.exec-command.ts b/packages/@aws-cdk/aws-ecs/test/ec2/integ.exec-command.ts index 805be661dc4c7..ce48860fc4e6b 100644 --- a/packages/@aws-cdk/aws-ecs/test/ec2/integ.exec-command.ts +++ b/packages/@aws-cdk/aws-ecs/test/ec2/integ.exec-command.ts @@ -25,9 +25,9 @@ const cluster = new ecs.Cluster(stack, 'Ec2Cluster', { executeCommandConfiguration: { kmsKey, logConfiguration: { - cloudWatchLogGroupName: logGroup.logGroupName, + cloudWatchLogGroup: logGroup, cloudWatchEncryptionEnabled: true, - s3BucketName: execBucket.bucketName, + s3Bucket: execBucket, s3EncryptionEnabled: true, s3KeyPrefix: 'exec-output', }, diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts b/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts index 495908fd47f36..33a5f2baf28d4 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/fargate/fargate-service.test.ts @@ -2373,8 +2373,8 @@ nodeunitShim({ vpc, executeCommandConfiguration: { logConfiguration: { - cloudWatchLogGroupName: logGroup.logGroupName, - s3BucketName: execBucket.bucketName, + cloudWatchLogGroup: logGroup, + s3Bucket: execBucket, s3KeyPrefix: 'exec-output', }, logging: ecs.ExecuteCommandLogging.OVERRIDE, @@ -2493,8 +2493,8 @@ nodeunitShim({ executeCommandConfiguration: { kmsKey, logConfiguration: { - cloudWatchLogGroupName: logGroup.logGroupName, - s3BucketName: execBucket.bucketName, + cloudWatchLogGroup: logGroup, + s3Bucket: execBucket, }, logging: ecs.ExecuteCommandLogging.OVERRIDE, }, @@ -2696,9 +2696,9 @@ nodeunitShim({ executeCommandConfiguration: { kmsKey, logConfiguration: { - cloudWatchLogGroupName: logGroup.logGroupName, + cloudWatchLogGroup: logGroup, cloudWatchEncryptionEnabled: true, - s3BucketName: execBucket.bucketName, + s3Bucket: execBucket, s3EncryptionEnabled: true, s3KeyPrefix: 'exec-output', }, diff --git a/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.ts b/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.ts index c40a04e05be79..5bc89fb2432b7 100644 --- a/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.ts +++ b/packages/@aws-cdk/aws-ecs/test/fargate/integ.exec-command.ts @@ -25,9 +25,9 @@ const cluster = new ecs.Cluster(stack, 'FargateCluster', { executeCommandConfiguration: { kmsKey, logConfiguration: { - cloudWatchLogGroupName: logGroup.logGroupName, + cloudWatchLogGroup: logGroup, cloudWatchEncryptionEnabled: true, - s3BucketName: execBucket.bucketName, + s3Bucket: execBucket, s3EncryptionEnabled: true, s3KeyPrefix: 'exec-output', }, From c098f25fd167c239ff7db0a2517057deb36208a4 Mon Sep 17 00:00:00 2001 From: Unnati Parekh <80710604+upparekh@users.noreply.github.com> Date: Wed, 2 Jun 2021 16:06:06 -0700 Subject: [PATCH 17/19] Apply doc suggestions from code review Co-authored-by: Hsing-Hui Hsu --- packages/@aws-cdk/aws-ecs/README.md | 11 ++++++----- packages/@aws-cdk/aws-ecs/lib/cluster.ts | 12 ++++++------ 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/@aws-cdk/aws-ecs/README.md b/packages/@aws-cdk/aws-ecs/README.md index 3692bf2b38f2a..e487558d7fc76 100644 --- a/packages/@aws-cdk/aws-ecs/README.md +++ b/packages/@aws-cdk/aws-ecs/README.md @@ -854,10 +854,10 @@ taskDefinition.addContainer('cont', { ## ECS Exec command Please note, ECS Exec leverages AWS Systems Manager (SSM). So as a prerequisite for the exec command -to work you need to install SSM plugin for the AWS CLI. For more information, see +to work, you need to have the SSM plugin for the AWS CLI installed locally. For more information, see [Install Session Manager plugin for AWS CLI] (https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html). -To enable ECS Exec feature for your containers, set the boolean flag `enableExecuteCommand` to `true` in +To enable the ECS Exec feature for your containers, set the boolean flag `enableExecuteCommand` to `true` in your `Ec2Service` or `FargateService`. ```ts @@ -868,14 +868,15 @@ const service = new ecs.Ec2Service(stack, 'Service', { }); ``` -Further, enable logging of the execute session results to a CloudWatch log group or S3 bucket by setting -the `executeCommandConfiguration` property for your cluster accordingly. The default configuration will send the +### Enabling logging +You can enable sending logs of your execute session commands to a CloudWatch log group or S3 bucket by configuring +the `executeCommandConfiguration` property for your cluster. The default configuration will send the logs to the CloudWatch Logs using the `awslogs` log driver that is configured in your task definition. Please note, when using your own `logConfiguration` the log group or S3 Bucket specified must already be created. To encrypt data using your own KMS Customer Key (CMK), you must create a CMK and provide the key in the `kmsKeyId` field of the `executeCommandConfiguration`. To use this key for encrypting CloudWatch log data or S3 bucket, make sure to associate the key -to these resources while creation. +to these resources on creation. ```ts const kmsKey = new kms.Key(stack, 'KmsKey'); diff --git a/packages/@aws-cdk/aws-ecs/lib/cluster.ts b/packages/@aws-cdk/aws-ecs/lib/cluster.ts index 67a861ac6f8fa..363d3410d4892 100644 --- a/packages/@aws-cdk/aws-ecs/lib/cluster.ts +++ b/packages/@aws-cdk/aws-ecs/lib/cluster.ts @@ -516,7 +516,7 @@ export class Cluster extends Resource implements ICluster { } /** - * Getter for execute command configuration associated with the cluster + * Getter for execute command configuration associated with the cluster. */ public get executeCommandConfiguration(): ExecuteCommandConfiguration | undefined { return this._executeCommandConfiguration; @@ -911,7 +911,7 @@ export interface ClusterAttributes { /** * The execute command configuration for the cluster * - * @default - no configuration will be provided. + * @default - none. */ readonly executeCommandConfiguration?: ExecuteCommandConfiguration; } @@ -1161,20 +1161,20 @@ export interface ExecuteCommandConfiguration { /** * The log configuration for the results of the execute command actions. The logs can be sent to CloudWatch Logs or an Amazon S3 bucket. * - * @default - No log configuration + * @default - none */ readonly logConfiguration?: ExecuteCommandLogConfiguration, /** - * The log setting to use for redirecting logs for the execute command results. + * The log settings to use for logging the execute command session. * - * @default - No log setting + * @default - none */ readonly logging?: ExecuteCommandLogging, } /** - * The log setting to use for redirecting logs for the execute command results. For more information, see + * The log settings to use to for logging the execute command session. For more information, see * [Logging] https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ecs-cluster-executecommandconfiguration.html#cfn-ecs-cluster-executecommandconfiguration-logging */ export enum ExecuteCommandLogging { From fb61b520c01a8db5e28abe1b4ea3946d4642be65 Mon Sep 17 00:00:00 2001 From: UnnatiParekh05 Date: Wed, 2 Jun 2021 16:53:27 -0700 Subject: [PATCH 18/19] Code changes after review --- packages/@aws-cdk/aws-ecs/README.md | 7 ++++-- .../@aws-cdk/aws-ecs/lib/base/base-service.ts | 2 +- packages/@aws-cdk/aws-ecs/lib/cluster.ts | 25 +++++++------------ .../@aws-cdk/aws-ecs/test/cluster.test.ts | 4 +-- 4 files changed, 17 insertions(+), 21 deletions(-) diff --git a/packages/@aws-cdk/aws-ecs/README.md b/packages/@aws-cdk/aws-ecs/README.md index e487558d7fc76..6aa894f458b94 100644 --- a/packages/@aws-cdk/aws-ecs/README.md +++ b/packages/@aws-cdk/aws-ecs/README.md @@ -869,22 +869,25 @@ const service = new ecs.Ec2Service(stack, 'Service', { ``` ### Enabling logging + You can enable sending logs of your execute session commands to a CloudWatch log group or S3 bucket by configuring the `executeCommandConfiguration` property for your cluster. The default configuration will send the logs to the CloudWatch Logs using the `awslogs` log driver that is configured in your task definition. Please note, when using your own `logConfiguration` the log group or S3 Bucket specified must already be created. -To encrypt data using your own KMS Customer Key (CMK), you must create a CMK and provide the key in the `kmsKeyId` field +To encrypt data using your own KMS Customer Key (CMK), you must create a CMK and provide the key in the `kmsKey` field of the `executeCommandConfiguration`. To use this key for encrypting CloudWatch log data or S3 bucket, make sure to associate the key to these resources on creation. ```ts const kmsKey = new kms.Key(stack, 'KmsKey'); +// Pass the KMS key in the `encryptionKey` field to associate the key to the log group const logGroup = new logs.LogGroup(stack, 'LogGroup', { encryptionKey: kmsKey, }); +// Pass the KMS key in the `encryptionKey` field to associate the key to the S3 bucket const execBucket = new s3.Bucket(stack, 'EcsExecBucket', { encryptionKey: kmsKey, }); @@ -892,7 +895,7 @@ const execBucket = new s3.Bucket(stack, 'EcsExecBucket', { const cluster = new ecs.Cluster(stack, 'Cluster', { vpc, executeCommandConfiguration: { - kmsKeyId: kmsKey, + kmsKey, logConfiguration: { cloudWatchLogGroup: logGroup, cloudWatchEncryptionEnabled: true, diff --git a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts index 73adc16782627..6b63fe626467e 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts @@ -427,7 +427,7 @@ export abstract class BaseService extends Resource if (props.enableExecuteCommand) { this.enableExecuteCommand(); - const logging = this.cluster.executeCommandConfiguration?.logging || ExecuteCommandLogging.DEFAULT; + const logging = this.cluster.executeCommandConfiguration?.logging ?? ExecuteCommandLogging.DEFAULT; if (logging !== ExecuteCommandLogging.NONE) { if (this.cluster.executeCommandConfiguration?.kmsKey) { diff --git a/packages/@aws-cdk/aws-ecs/lib/cluster.ts b/packages/@aws-cdk/aws-ecs/lib/cluster.ts index 363d3410d4892..5c6e910e4507e 100644 --- a/packages/@aws-cdk/aws-ecs/lib/cluster.ts +++ b/packages/@aws-cdk/aws-ecs/lib/cluster.ts @@ -190,7 +190,7 @@ export class Cluster extends Resource implements ICluster { clusterName: this.physicalName, clusterSettings, capacityProviders: Lazy.list({ produce: () => this._fargateCapacityProviders }, { omitEmpty: true }), - configuration: this.renderConfiguration(), + configuration: this._executeCommandConfiguration && this.renderExecuteCommandConfiguration(), }); this.clusterArn = this.getResourceArnAttribute(cluster.attrArn, { @@ -230,30 +230,23 @@ export class Cluster extends Resource implements ICluster { } } - private renderConfiguration() : CfnCluster.ClusterConfigurationProperty | undefined { - if (this._executeCommandConfiguration) { - return { - executeCommandConfiguration: this.renderExecuteCommandConfiguration(), - }; - } - return undefined; - } - - private renderExecuteCommandConfiguration() : CfnCluster.ExecuteCommandConfigurationProperty { + private renderExecuteCommandConfiguration() : CfnCluster.ClusterConfigurationProperty { return { - kmsKeyId: this._executeCommandConfiguration?.kmsKey?.keyArn, - logConfiguration: this._executeCommandConfiguration?.logConfiguration ? this.renderExecuteCommandLogConfiguration() : undefined, - logging: this._executeCommandConfiguration?.logging, + executeCommandConfiguration: { + kmsKeyId: this._executeCommandConfiguration?.kmsKey?.keyArn, + logConfiguration: this._executeCommandConfiguration?.logConfiguration && this.renderExecuteCommandLogConfiguration(), + logging: this._executeCommandConfiguration?.logging, + }, }; } private renderExecuteCommandLogConfiguration(): CfnCluster.ExecuteCommandLogConfigurationProperty { const logConfiguration = this._executeCommandConfiguration?.logConfiguration; if (logConfiguration?.s3EncryptionEnabled && !logConfiguration?.s3Bucket) { - throw new Error('S3EncryptionEnabled cannot be specified without an S3 bucket name in execute command log configuration.'); + throw new Error('You must specify an S3 bucket name in the execute command log configuration to enable S3 encryption.'); } if (logConfiguration?.cloudWatchEncryptionEnabled && !logConfiguration?.cloudWatchLogGroup) { - throw new Error('CloudWatchEncryptionEnabled cannot be specified without a CloudWatch log group in execute command log configuration.'); + throw new Error('You must specify a CloudWatch log group in the execute command log configuration to enable CloudWatch encryption.'); } return { cloudWatchEncryptionEnabled: logConfiguration?.cloudWatchEncryptionEnabled, diff --git a/packages/@aws-cdk/aws-ecs/test/cluster.test.ts b/packages/@aws-cdk/aws-ecs/test/cluster.test.ts index 83f7c36f695c3..a580cb5dca197 100644 --- a/packages/@aws-cdk/aws-ecs/test/cluster.test.ts +++ b/packages/@aws-cdk/aws-ecs/test/cluster.test.ts @@ -2063,7 +2063,7 @@ nodeunitShim({ logging: ecs.ExecuteCommandLogging.OVERRIDE, }, }); - }, /CloudWatchEncryptionEnabled cannot be specified without a CloudWatch log group in execute command log configuration./); + }, /You must specify a CloudWatch log group in the execute command log configuration to enable CloudWatch encryption./); test.done(); }, @@ -2083,7 +2083,7 @@ nodeunitShim({ logging: ecs.ExecuteCommandLogging.OVERRIDE, }, }); - }, /S3EncryptionEnabled cannot be specified without an S3 bucket name in execute command log configuration./); + }, /You must specify an S3 bucket name in the execute command log configuration to enable S3 encryption./); test.done(); }, From 50f369240c5ce2e2bb5a8eb92b8299b527dde979 Mon Sep 17 00:00:00 2001 From: UnnatiParekh05 Date: Thu, 3 Jun 2021 12:39:34 -0700 Subject: [PATCH 19/19] Refactored conditions in base service --- packages/@aws-cdk/aws-ecs/lib/base/base-service.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts index 6b63fe626467e..d4023a4df6f9e 100644 --- a/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts +++ b/packages/@aws-cdk/aws-ecs/lib/base/base-service.ts @@ -429,10 +429,10 @@ export abstract class BaseService extends Resource const logging = this.cluster.executeCommandConfiguration?.logging ?? ExecuteCommandLogging.DEFAULT; + if (this.cluster.executeCommandConfiguration?.kmsKey) { + this.enableExecuteCommandEncryption(logging); + } if (logging !== ExecuteCommandLogging.NONE) { - if (this.cluster.executeCommandConfiguration?.kmsKey) { - this.enableExecuteCommandEncryption(); - } this.executeCommandLogConfiguration(); } } @@ -489,7 +489,7 @@ export abstract class BaseService extends Resource } } - private enableExecuteCommandEncryption() { + private enableExecuteCommandEncryption(logging: ExecuteCommandLogging) { this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({ actions: [ 'kms:Decrypt', @@ -506,8 +506,8 @@ export abstract class BaseService extends Resource principals: [new iam.ArnPrincipal(`arn:aws:iam::${this.stack.account}:root`)], })); - if (this.cluster.executeCommandConfiguration?.logConfiguration?.cloudWatchEncryptionEnabled) { - this.cluster.executeCommandConfiguration.kmsKey?.addToResourcePolicy(new iam.PolicyStatement({ + if (logging === ExecuteCommandLogging.DEFAULT || this.cluster.executeCommandConfiguration?.logConfiguration?.cloudWatchEncryptionEnabled) { + this.cluster.executeCommandConfiguration?.kmsKey?.addToResourcePolicy(new iam.PolicyStatement({ actions: [ 'kms:Encrypt*', 'kms:Decrypt*',