From 23ee450ecaff49070736d9908bcb50440fe5c3db Mon Sep 17 00:00:00 2001 From: Lukas Fruntke Date: Tue, 14 Jun 2022 01:58:15 +0200 Subject: [PATCH] feat(ecs-patterns): add ecs exec support (#18663) Fixes #15769, #15197 Supersedes #15497 by implementing the change for all patterns. This PR implements support for ECS Exec in all ecs-patterns services. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-ecs-patterns/README.md | 29 +++ .../application-load-balanced-service-base.ts | 6 + ...ion-multiple-target-groups-service-base.ts | 7 + .../network-load-balanced-service-base.ts | 7 + ...ork-multiple-target-groups-service-base.ts | 7 + .../lib/base/queue-processing-service-base.ts | 7 + .../application-load-balanced-ecs-service.ts | 1 + ...tion-multiple-target-groups-ecs-service.ts | 1 + .../ecs/network-load-balanced-ecs-service.ts | 1 + ...work-multiple-target-groups-ecs-service.ts | 1 + .../lib/ecs/queue-processing-ecs-service.ts | 1 + ...plication-load-balanced-fargate-service.ts | 1 + ...-multiple-target-groups-fargate-service.ts | 1 + .../network-load-balanced-fargate-service.ts | 1 + ...-multiple-target-groups-fargate-service.ts | 1 + .../queue-processing-fargate-service.ts | 1 + ...e-application-load-balanced-ecs-service.ts | 1 + .../aws-ecs-patterns/test/ec2/l3s-v2.test.ts | 226 +++++++++++++++++- .../aws-ecs-integ.template.json | 31 +++ .../ec2/queue-processing-ecs-service.test.ts | 83 +++++++ .../aws-ecs-integ-alb-fg-https.template.json | 31 +++ .../integ.alb-fargate-service-https.ts | 1 + .../load-balanced-fargate-service-v2.test.ts | 111 +++++++++ .../queue-processing-fargate-service.test.ts | 75 +++++- packages/@aws-cdk/cx-api/package.json | 1 + 25 files changed, 630 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/aws-ecs-patterns/README.md b/packages/@aws-cdk/aws-ecs-patterns/README.md index d94f732282c40..2306c0d9a32e2 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/README.md +++ b/packages/@aws-cdk/aws-ecs-patterns/README.md @@ -733,3 +733,32 @@ const loadBalancedFargateService = new ecsPatterns.ApplicationLoadBalancedFargat loadBalancerName: 'application-lb-name', }); ``` + +### ECS Exec + +You can use ECS Exec to run commands in or get a shell to a container running on an Amazon EC2 instance or on +AWS Fargate. Enable ECS Exec, by setting `enableExecuteCommand` to `true`. + +ECS Exec is supported by all Services i.e. `ApplicationLoadBalanced(Fargate|Ec2)Service`, `ApplicationMultipleTargetGroups(Fargate|Ec2)Service`, `NetworkLoadBalanced(Fargate|Ec2)Service`, `NetworkMultipleTargetGroups(Fargate|Ec2)Service`, `QueueProcessing(Fargate|Ec2)Service`. It is not supported for `ScheduledTask`s. + +Read more about ECS Exec in the [ECS Developer Guide](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-exec.html). + +Example: + +```ts +declare const cluster: ecs.Cluster; +const loadBalancedFargateService = new ecsPatterns.ApplicationLoadBalancedFargateService(this, 'Service', { + cluster, + memoryLimitMiB: 1024, + desiredCount: 1, + cpu: 512, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"), + }, + enableExecuteCommand: true +}); +``` + +Please note, ECS Exec leverages AWS Systems Manager (SSM). So as a prerequisite for the exec command +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). diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts index 947730fa64f1d..73b5ff45b6c3b 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-load-balanced-service-base.ts @@ -254,6 +254,12 @@ export interface ApplicationLoadBalancedServiceBaseProps { */ readonly loadBalancerName?: string; + /** + * Whether ECS Exec should be enabled + * + * @default - false + */ + readonly enableExecuteCommand?: boolean; } export interface ApplicationLoadBalancedTaskImageOptions { diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-multiple-target-groups-service-base.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-multiple-target-groups-service-base.ts index 139fa8b0cb5e6..c5e1dbf0d7370 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-multiple-target-groups-service-base.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/base/application-multiple-target-groups-service-base.ts @@ -107,6 +107,13 @@ export interface ApplicationMultipleTargetGroupsServiceBaseProps { * @default - default portMapping registered as target group and attached to the first defined listener */ readonly targetGroups?: ApplicationTargetProps[]; + + /** + * Whether ECS Exec should be enabled + * + * @default - false + */ + readonly enableExecuteCommand?: boolean; } /** diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-load-balanced-service-base.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-load-balanced-service-base.ts index 5883fe0b7fc4b..0fb0008af842d 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-load-balanced-service-base.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-load-balanced-service-base.ts @@ -182,6 +182,13 @@ export interface NetworkLoadBalancedServiceBaseProps { * @default - disabled */ readonly circuitBreaker?: DeploymentCircuitBreaker; + + /** + * Whether ECS Exec should be enabled + * + * @default - false + */ + readonly enableExecuteCommand?: boolean; } export interface NetworkLoadBalancedTaskImageOptions { diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-multiple-target-groups-service-base.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-multiple-target-groups-service-base.ts index b67c8179d8139..6f3189c8937c7 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-multiple-target-groups-service-base.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/base/network-multiple-target-groups-service-base.ts @@ -98,6 +98,13 @@ export interface NetworkMultipleTargetGroupsServiceBaseProps { * @default - default portMapping registered as target group and attached to the first defined listener */ readonly targetGroups?: NetworkTargetProps[]; + + /** + * Whether ECS Exec should be enabled + * + * @default - false + */ + readonly enableExecuteCommand?: boolean; } /** diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/base/queue-processing-service-base.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/base/queue-processing-service-base.ts index 9c26567978f5d..9b73999ee866f 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/base/queue-processing-service-base.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/base/queue-processing-service-base.ts @@ -211,6 +211,13 @@ export interface QueueProcessingServiceBaseProps { * */ readonly capacityProviderStrategies?: CapacityProviderStrategy[]; + + /** + * Whether ECS Exec should be enabled + * + * @default - false + */ + readonly enableExecuteCommand?: boolean; } /** diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/application-load-balanced-ecs-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/application-load-balanced-ecs-service.ts index 9bb2bcf607caa..2cc55912eda57 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/application-load-balanced-ecs-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/application-load-balanced-ecs-service.ts @@ -152,6 +152,7 @@ export class ApplicationLoadBalancedEc2Service extends ApplicationLoadBalancedSe cloudMapOptions: props.cloudMapOptions, deploymentController: props.deploymentController, circuitBreaker: props.circuitBreaker, + enableExecuteCommand: props.enableExecuteCommand, placementConstraints: props.placementConstraints, placementStrategies: props.placementStrategies, }); diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/application-multiple-target-groups-ecs-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/application-multiple-target-groups-ecs-service.ts index 6c00f2074ac85..9fd2441621bd3 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/application-multiple-target-groups-ecs-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/application-multiple-target-groups-ecs-service.ts @@ -167,6 +167,7 @@ export class ApplicationMultipleTargetGroupsEc2Service extends ApplicationMultip propagateTags: props.propagateTags, enableECSManagedTags: props.enableECSManagedTags, cloudMapOptions: props.cloudMapOptions, + enableExecuteCommand: props.enableExecuteCommand, placementConstraints: props.placementConstraints, placementStrategies: props.placementStrategies, }); diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/network-load-balanced-ecs-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/network-load-balanced-ecs-service.ts index 6372fd6d7806e..bc947b5bcdea2 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/network-load-balanced-ecs-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/network-load-balanced-ecs-service.ts @@ -150,6 +150,7 @@ export class NetworkLoadBalancedEc2Service extends NetworkLoadBalancedServiceBas cloudMapOptions: props.cloudMapOptions, deploymentController: props.deploymentController, circuitBreaker: props.circuitBreaker, + enableExecuteCommand: props.enableExecuteCommand, placementConstraints: props.placementConstraints, placementStrategies: props.placementStrategies, }); diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/network-multiple-target-groups-ecs-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/network-multiple-target-groups-ecs-service.ts index 1ecaba043c990..fc9e92d6f5ef3 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/network-multiple-target-groups-ecs-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/network-multiple-target-groups-ecs-service.ts @@ -167,6 +167,7 @@ export class NetworkMultipleTargetGroupsEc2Service extends NetworkMultipleTarget propagateTags: props.propagateTags, enableECSManagedTags: props.enableECSManagedTags, cloudMapOptions: props.cloudMapOptions, + enableExecuteCommand: props.enableExecuteCommand, placementConstraints: props.placementConstraints, placementStrategies: props.placementStrategies, }); diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/queue-processing-ecs-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/queue-processing-ecs-service.ts index 93db9d384a39f..ead2b27c05386 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/queue-processing-ecs-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/ecs/queue-processing-ecs-service.ts @@ -141,6 +141,7 @@ export class QueueProcessingEc2Service extends QueueProcessingServiceBase { deploymentController: props.deploymentController, circuitBreaker: props.circuitBreaker, capacityProviderStrategies: props.capacityProviderStrategies, + enableExecuteCommand: props.enableExecuteCommand, placementConstraints: props.placementConstraints, placementStrategies: props.placementStrategies, }); diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/application-load-balanced-fargate-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/application-load-balanced-fargate-service.ts index 80b6ceac06ae2..9648781f1d93e 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/application-load-balanced-fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/application-load-balanced-fargate-service.ts @@ -175,6 +175,7 @@ export class ApplicationLoadBalancedFargateService extends ApplicationLoadBalanc circuitBreaker: props.circuitBreaker, securityGroups: props.securityGroups, vpcSubnets: props.taskSubnets, + enableExecuteCommand: props.enableExecuteCommand, }); this.addServiceAsTarget(this.service); } diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/application-multiple-target-groups-fargate-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/application-multiple-target-groups-fargate-service.ts index c092c4d1ee111..9299cfc2e3023 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/application-multiple-target-groups-fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/application-multiple-target-groups-fargate-service.ts @@ -184,6 +184,7 @@ export class ApplicationMultipleTargetGroupsFargateService extends ApplicationMu enableECSManagedTags: props.enableECSManagedTags, cloudMapOptions: props.cloudMapOptions, platformVersion: props.platformVersion, + enableExecuteCommand: props.enableExecuteCommand, }); } } diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/network-load-balanced-fargate-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/network-load-balanced-fargate-service.ts index 1f6c924205979..e24af6774b42b 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/network-load-balanced-fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/network-load-balanced-fargate-service.ts @@ -161,6 +161,7 @@ export class NetworkLoadBalancedFargateService extends NetworkLoadBalancedServic deploymentController: props.deploymentController, circuitBreaker: props.circuitBreaker, vpcSubnets: props.taskSubnets, + enableExecuteCommand: props.enableExecuteCommand, }); this.addServiceAsTarget(this.service); } diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/network-multiple-target-groups-fargate-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/network-multiple-target-groups-fargate-service.ts index 37dde3a851757..4e610dac65154 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/network-multiple-target-groups-fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/network-multiple-target-groups-fargate-service.ts @@ -184,6 +184,7 @@ export class NetworkMultipleTargetGroupsFargateService extends NetworkMultipleTa enableECSManagedTags: props.enableECSManagedTags, cloudMapOptions: props.cloudMapOptions, platformVersion: props.platformVersion, + enableExecuteCommand: props.enableExecuteCommand, }); } } diff --git a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/queue-processing-fargate-service.ts b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/queue-processing-fargate-service.ts index 660c286c4fd82..89b666fd39405 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/queue-processing-fargate-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/lib/fargate/queue-processing-fargate-service.ts @@ -159,6 +159,7 @@ export class QueueProcessingFargateService extends QueueProcessingServiceBase { assignPublicIp: props.assignPublicIp, circuitBreaker: props.circuitBreaker, capacityProviderStrategies: props.capacityProviderStrategies, + enableExecuteCommand: props.enableExecuteCommand, }); this.configureAutoscalingForService(this.service); diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/integ.multiple-application-load-balanced-ecs-service.ts b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/integ.multiple-application-load-balanced-ecs-service.ts index 775977a7dff0f..b36de5cf9da43 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/integ.multiple-application-load-balanced-ecs-service.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/integ.multiple-application-load-balanced-ecs-service.ts @@ -17,6 +17,7 @@ new ApplicationMultipleTargetGroupsEc2Service(stack, 'myService', { taskImageOptions: { image: ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), }, + enableExecuteCommand: true, targetGroups: [ { containerPort: 80, diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/l3s-v2.test.ts b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/l3s-v2.test.ts index 03975b2cebec3..e06150a238f6f 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/l3s-v2.test.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/l3s-v2.test.ts @@ -1,8 +1,8 @@ import { Match, Template } from '@aws-cdk/assertions'; import { AutoScalingGroup } from '@aws-cdk/aws-autoscaling'; import { Certificate } from '@aws-cdk/aws-certificatemanager'; -import { MachineImage, Vpc } from '@aws-cdk/aws-ec2'; import * as ec2 from '@aws-cdk/aws-ec2'; +import { MachineImage, Vpc } from '@aws-cdk/aws-ec2'; import { AsgCapacityProvider, AwsLogDriver, @@ -131,6 +131,7 @@ describe('When Application Load Balancer', () => { cpu: 256, desiredCount: 3, enableECSManagedTags: true, + enableExecuteCommand: true, healthCheckGracePeriod: Duration.millis(2000), loadBalancers: [ { @@ -173,6 +174,7 @@ describe('When Application Load Balancer', () => { DesiredCount: 3, LaunchType: 'EC2', EnableECSManagedTags: true, + EnableExecuteCommand: true, HealthCheckGracePeriodSeconds: 2, LoadBalancers: [ { @@ -963,6 +965,100 @@ describe('When Network Load Balancer', () => { }); }); + + test('Assert EnableExecuteCommand is missing if not set', () => { + // GIVEN + const stack = new Stack(); + const vpc = new Vpc(stack, 'VPC'); + const cluster = new Cluster(stack, 'Cluster', { vpc }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); + const zone = new PublicHostedZone(stack, 'HostedZone', { zoneName: 'example.com' }); + + // WHEN + new NetworkMultipleTargetGroupsEc2Service(stack, 'Service', { + cluster, + memoryLimitMiB: 256, + taskImageOptions: { + image: ContainerImage.fromRegistry('test'), + containerName: 'myContainer', + containerPorts: [80, 90], + enableLogging: false, + environment: { + TEST_ENVIRONMENT_VARIABLE1: 'test environment variable 1 value', + TEST_ENVIRONMENT_VARIABLE2: 'test environment variable 2 value', + }, + logDriver: new AwsLogDriver({ + streamPrefix: 'TestStream', + }), + family: 'Ec2TaskDef', + executionRole: new Role(stack, 'ExecutionRole', { + path: '/', + assumedBy: new CompositePrincipal( + new ServicePrincipal('ecs.amazonaws.com'), + new ServicePrincipal('ecs-tasks.amazonaws.com'), + ), + }), + taskRole: new Role(stack, 'TaskRole', { + assumedBy: new ServicePrincipal('ecs-tasks.amazonaws.com'), + }), + dockerLabels: { label1: 'labelValue1', label2: 'labelValue2' }, + }, + cpu: 256, + desiredCount: 3, + enableECSManagedTags: true, + enableExecuteCommand: false, + healthCheckGracePeriod: Duration.millis(2000), + loadBalancers: [ + { + name: 'lb1', + domainName: 'api.example.com', + domainZone: zone, + publicLoadBalancer: false, + listeners: [ + { + name: 'listener1', + }, + ], + }, + { + name: 'lb2', + listeners: [ + { + name: 'listener2', + port: 81, + }, + ], + }, + ], + propagateTags: PropagatedTagSource.SERVICE, + memoryReservationMiB: 256, + serviceName: 'myService', + targetGroups: [ + { + containerPort: 80, + listener: 'listener1', + }, + { + containerPort: 90, + listener: 'listener2', + }, + ], + placementStrategies: [PlacementStrategy.spreadAcrossInstances(), PlacementStrategy.packedByCpu(), PlacementStrategy.randomly()], + placementConstraints: [PlacementConstraint.memberOf('attribute:ecs.instance-type =~ m5a.*')], + }); + + // THEN + expect(() => Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + EnableExecuteCommand: true, + })).toThrow('Expected true but received false at /Properties/EnableExecuteCommand (using objectLike matcher)'); + }); + test('test ECS NLB construct with all settings', () => { // GIVEN const stack = new Stack(); @@ -1009,6 +1105,7 @@ describe('When Network Load Balancer', () => { cpu: 256, desiredCount: 3, enableECSManagedTags: true, + enableExecuteCommand: true, healthCheckGracePeriod: Duration.millis(2000), loadBalancers: [ { @@ -1053,6 +1150,7 @@ describe('When Network Load Balancer', () => { Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { DesiredCount: 3, EnableECSManagedTags: true, + EnableExecuteCommand: true, HealthCheckGracePeriodSeconds: 2, LaunchType: 'EC2', LoadBalancers: [ @@ -1147,6 +1245,132 @@ describe('When Network Load Balancer', () => { }); }); + test('EnableExecuteCommand flag generated IAM Permissions', () => { + const stack = new Stack(); + const vpc = new Vpc(stack, 'VPC'); + const cluster = new Cluster(stack, 'Cluster', { vpc }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); + const zone = new PublicHostedZone(stack, 'HostedZone', { zoneName: 'example.com' }); + + // WHEN + new NetworkMultipleTargetGroupsEc2Service(stack, 'Service', { + cluster, + memoryLimitMiB: 256, + taskImageOptions: { + image: ContainerImage.fromRegistry('test'), + containerName: 'myContainer', + containerPorts: [80, 90], + enableLogging: false, + environment: { + TEST_ENVIRONMENT_VARIABLE1: 'test environment variable 1 value', + TEST_ENVIRONMENT_VARIABLE2: 'test environment variable 2 value', + }, + logDriver: new AwsLogDriver({ + streamPrefix: 'TestStream', + }), + family: 'Ec2TaskDef', + executionRole: new Role(stack, 'ExecutionRole', { + path: '/', + assumedBy: new CompositePrincipal( + new ServicePrincipal('ecs.amazonaws.com'), + new ServicePrincipal('ecs-tasks.amazonaws.com'), + ), + }), + taskRole: new Role(stack, 'TaskRole', { + assumedBy: new ServicePrincipal('ecs-tasks.amazonaws.com'), + }), + dockerLabels: { label1: 'labelValue1', label2: 'labelValue2' }, + }, + cpu: 256, + desiredCount: 3, + enableECSManagedTags: true, + enableExecuteCommand: true, + healthCheckGracePeriod: Duration.millis(2000), + loadBalancers: [ + { + name: 'lb1', + domainName: 'api.example.com', + domainZone: zone, + publicLoadBalancer: false, + listeners: [ + { + name: 'listener1', + }, + ], + }, + { + name: 'lb2', + listeners: [ + { + name: 'listener2', + port: 81, + }, + ], + }, + ], + propagateTags: PropagatedTagSource.SERVICE, + memoryReservationMiB: 256, + serviceName: 'myService', + targetGroups: [ + { + containerPort: 80, + listener: 'listener1', + }, + { + containerPort: 90, + listener: 'listener2', + }, + ], + placementStrategies: [PlacementStrategy.spreadAcrossInstances(), PlacementStrategy.packedByCpu(), PlacementStrategy.randomly()], + placementConstraints: [PlacementConstraint.memberOf('attribute:ecs.instance-type =~ m5a.*')], + }); + + // ECS Exec + Template.fromStack(stack).hasResourceProperties('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: '*', + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'TaskRoleDefaultPolicy07FC53DE', + Roles: [ + { + Ref: 'TaskRole30FC0FBB', + }, + ], + }); + }); + test('able to pass pre-defined task definition', () => { // GIVEN const stack = new Stack(); diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/multiple-application-load-balanced-ecs-service.integ.snapshot/aws-ecs-integ.template.json b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/multiple-application-load-balanced-ecs-service.integ.snapshot/aws-ecs-integ.template.json index 1000d06836915..1c7c44c5325cf 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/multiple-application-load-balanced-ecs-service.integ.snapshot/aws-ecs-integ.template.json +++ b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/multiple-application-load-balanced-ecs-service.integ.snapshot/aws-ecs-integ.template.json @@ -1039,6 +1039,36 @@ } } }, + "myServiceTaskDefTaskRoleDefaultPolicyD48473C0": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogStream", + "logs:DescribeLogGroups", + "logs:DescribeLogStreams", + "logs:PutLogEvents", + "ssmmessages:CreateControlChannel", + "ssmmessages:CreateDataChannel", + "ssmmessages:OpenControlChannel", + "ssmmessages:OpenDataChannel" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "myServiceTaskDefTaskRoleDefaultPolicyD48473C0", + "Roles": [ + { + "Ref": "myServiceTaskDefTaskRole1C1DE6CC" + } + ] + } + }, "myServiceTaskDef7FB8322A": { "Type": "AWS::ECS::TaskDefinition", "Properties": { @@ -1155,6 +1185,7 @@ "MinimumHealthyPercent": 50 }, "EnableECSManagedTags": false, + "EnableExecuteCommand": true, "HealthCheckGracePeriodSeconds": 60, "LaunchType": "EC2", "LoadBalancers": [ diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/queue-processing-ecs-service.test.ts b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/queue-processing-ecs-service.test.ts index 7ae4aa4109cdd..3589cd9fbb807 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/ec2/queue-processing-ecs-service.test.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/ec2/queue-processing-ecs-service.test.ts @@ -196,6 +196,89 @@ test('test ECS queue worker service construct - with optional props for queues', }); }); +test('test ECS queue worker service construct - with ECS Exec', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + cluster.addAsgCapacityProvider(new AsgCapacityProvider(stack, 'DefaultAutoScalingGroupProvider', { + autoScalingGroup: new AutoScalingGroup(stack, 'DefaultAutoScalingGroup', { + vpc, + instanceType: new ec2.InstanceType('t2.micro'), + machineImage: MachineImage.latestAmazonLinux(), + }), + })); + + // WHEN + new ecsPatterns.QueueProcessingFargateService(stack, 'Service', { + cluster, + memoryLimitMiB: 512, + image: ecs.ContainerImage.fromRegistry('test'), + enableExecuteCommand: true, + }); + + + // THEN + // ECS Exec + Template.fromStack(stack).hasResourceProperties('AWS::ECS::Service', { + EnableExecuteCommand: true, + }); + + Template.fromStack(stack).hasResourceProperties('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: '*', + }, + { + Action: [ + 'sqs:ReceiveMessage', + 'sqs:ChangeMessageVisibility', + 'sqs:GetQueueUrl', + 'sqs:DeleteMessage', + 'sqs:GetQueueAttributes', + ], + Effect: 'Allow', + Resource: { + 'Fn::GetAtt': [ + 'ServiceEcsProcessingQueueC266885C', + 'Arn', + ], + }, + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'ServiceQueueProcessingTaskDefTaskRoleDefaultPolicy11D50174', + Roles: [ + { + Ref: 'ServiceQueueProcessingTaskDefTaskRoleBDE5D3C6', + }, + ], + }); +}); + testDeprecated('test ECS queue worker service construct - with optional props', () => { // GIVEN const stack = new cdk.Stack(); diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/alb-fargate-service-https.integ.snapshot/aws-ecs-integ-alb-fg-https.template.json b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/alb-fargate-service-https.integ.snapshot/aws-ecs-integ-alb-fg-https.template.json index 3be79eeaf8ae1..b43512a5773e7 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/alb-fargate-service-https.integ.snapshot/aws-ecs-integ-alb-fg-https.template.json +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/alb-fargate-service-https.integ.snapshot/aws-ecs-integ-alb-fg-https.template.json @@ -587,6 +587,36 @@ } } }, + "myServiceTaskDefTaskRoleDefaultPolicyD48473C0": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "logs:CreateLogStream", + "logs:DescribeLogGroups", + "logs:DescribeLogStreams", + "logs:PutLogEvents", + "ssmmessages:CreateControlChannel", + "ssmmessages:CreateDataChannel", + "ssmmessages:OpenControlChannel", + "ssmmessages:OpenDataChannel" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "myServiceTaskDefTaskRoleDefaultPolicyD48473C0", + "Roles": [ + { + "Ref": "myServiceTaskDefTaskRole1C1DE6CC" + } + ] + } + }, "myServiceTaskDef7FB8322A": { "Type": "AWS::ECS::TaskDefinition", "Properties": { @@ -698,6 +728,7 @@ "MinimumHealthyPercent": 50 }, "EnableECSManagedTags": true, + "EnableExecuteCommand": true, "HealthCheckGracePeriodSeconds": 60, "LaunchType": "FARGATE", "LoadBalancers": [ diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.alb-fargate-service-https.ts b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.alb-fargate-service-https.ts index fe6940d272cc9..44e97e104c8f4 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.alb-fargate-service-https.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/integ.alb-fargate-service-https.ts @@ -20,6 +20,7 @@ new ApplicationLoadBalancedFargateService(stack, 'myService', { }, protocol: ApplicationProtocol.HTTPS, enableECSManagedTags: true, + enableExecuteCommand: true, domainName: 'test.example.com', domainZone: route53.HostedZone.fromHostedZoneAttributes(stack, 'HostedZone', { hostedZoneId: 'fakeId', diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/load-balanced-fargate-service-v2.test.ts b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/load-balanced-fargate-service-v2.test.ts index dc11d17291b8f..a081df88b9529 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/load-balanced-fargate-service-v2.test.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/load-balanced-fargate-service-v2.test.ts @@ -6,6 +6,37 @@ import { CompositePrincipal, Role, ServicePrincipal } from '@aws-cdk/aws-iam'; import { Duration, Stack } from '@aws-cdk/core'; import { ApplicationLoadBalancedFargateService, ApplicationMultipleTargetGroupsFargateService, NetworkLoadBalancedFargateService, NetworkMultipleTargetGroupsFargateService } from '../../lib'; + +const enableExecuteCommandPermissions = { + 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: '*', + }, + ], + Version: '2012-10-17', +}; + describe('When Application Load Balancer', () => { test('test Fargate loadbalanced construct with default settings', () => { // GIVEN @@ -117,6 +148,7 @@ describe('When Application Load Balancer', () => { memoryLimitMiB: 512, desiredCount: 3, enableECSManagedTags: true, + enableExecuteCommand: true, healthCheckGracePeriod: Duration.millis(2000), platformVersion: ecs.FargatePlatformVersion.VERSION1_4, propagateTags: ecs.PropagatedTagSource.SERVICE, @@ -182,6 +214,17 @@ describe('When Application Load Balancer', () => { ServiceName: 'myService', }); + // ECS Exec + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: enableExecuteCommandPermissions, + PolicyName: 'TaskRoleDefaultPolicy07FC53DE', + Roles: [ + { + Ref: 'TaskRole30FC0FBB', + }, + ], + }); + Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { ContainerDefinitions: [ { @@ -484,6 +527,7 @@ describe('When Network Load Balancer', () => { memoryLimitMiB: 512, desiredCount: 3, enableECSManagedTags: true, + enableExecuteCommand: true, healthCheckGracePeriod: Duration.millis(2000), propagateTags: ecs.PropagatedTagSource.SERVICE, serviceName: 'myService', @@ -544,6 +588,7 @@ describe('When Network Load Balancer', () => { ServiceName: 'myService', }); + Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', { ContainerDefinitions: [ { @@ -610,6 +655,72 @@ describe('When Network Load Balancer', () => { }); }); + test('EnableExecuteCommand generates correct IAM Permissions', () => { + // GIVEN + const stack = new Stack(); + const vpc = new Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + // WHEN + new NetworkMultipleTargetGroupsFargateService(stack, 'Service', { + cluster, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('test'), + containerName: 'hello', + containerPorts: [80, 90], + enableLogging: false, + environment: { + TEST_ENVIRONMENT_VARIABLE1: 'test environment variable 1 value', + TEST_ENVIRONMENT_VARIABLE2: 'test environment variable 2 value', + }, + logDriver: new ecs.AwsLogDriver({ + streamPrefix: 'TestStream', + }), + family: 'Ec2TaskDef', + executionRole: new Role(stack, 'ExecutionRole', { + path: '/', + assumedBy: new CompositePrincipal( + new ServicePrincipal('ecs.amazonaws.com'), + new ServicePrincipal('ecs-tasks.amazonaws.com'), + ), + }), + taskRole: new Role(stack, 'TaskRole', { + assumedBy: new ServicePrincipal('ecs-tasks.amazonaws.com'), + }), + dockerLabels: { label1: 'labelValue1', label2: 'labelValue2' }, + }, + cpu: 256, + assignPublicIp: true, + memoryLimitMiB: 512, + desiredCount: 3, + enableECSManagedTags: true, + enableExecuteCommand: true, + healthCheckGracePeriod: Duration.millis(2000), + propagateTags: ecs.PropagatedTagSource.SERVICE, + serviceName: 'myService', + targetGroups: [ + { + containerPort: 80, + }, + { + containerPort: 90, + }, + ], + }); + + + // ECS Exec + Template.fromStack(stack).hasResourceProperties('AWS::IAM::Policy', { + PolicyDocument: enableExecuteCommandPermissions, + PolicyName: 'TaskRoleDefaultPolicy07FC53DE', + Roles: [ + { + Ref: 'TaskRole30FC0FBB', + }, + ], + }); + }); + test('errors if no essential container in pre-defined task definition', () => { // GIVEN const stack = new Stack(); diff --git a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/queue-processing-fargate-service.test.ts b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/queue-processing-fargate-service.test.ts index 2a4dbcfe057ed..0164356061d61 100644 --- a/packages/@aws-cdk/aws-ecs-patterns/test/fargate/queue-processing-fargate-service.test.ts +++ b/packages/@aws-cdk/aws-ecs-patterns/test/fargate/queue-processing-fargate-service.test.ts @@ -1,9 +1,9 @@ import { Match, Template } from '@aws-cdk/assertions'; import { AutoScalingGroup } from '@aws-cdk/aws-autoscaling'; -import { MachineImage } from '@aws-cdk/aws-ec2'; import * as ec2 from '@aws-cdk/aws-ec2'; -import { AsgCapacityProvider } from '@aws-cdk/aws-ecs'; +import { MachineImage } from '@aws-cdk/aws-ec2'; import * as ecs from '@aws-cdk/aws-ecs'; +import { AsgCapacityProvider } from '@aws-cdk/aws-ecs'; import * as sqs from '@aws-cdk/aws-sqs'; import { testDeprecated, testFutureBehavior } from '@aws-cdk/cdk-build-tools'; import * as cdk from '@aws-cdk/core'; @@ -232,6 +232,77 @@ test('test fargate queue worker service construct - with optional props for queu }); }); +test('test Fargate queue worker service construct - with ECS Exec', () => { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + const cluster = new ecs.Cluster(stack, 'Cluster', { vpc }); + + // WHEN + new ecsPatterns.QueueProcessingFargateService(stack, 'Service', { + cluster, + memoryLimitMiB: 512, + image: ecs.ContainerImage.fromRegistry('test'), + enableExecuteCommand: true, + }); + + // THEN + // ECS Exec + Template.fromStack(stack).hasResourceProperties('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: '*', + }, + { + Action: [ + 'sqs:ReceiveMessage', + 'sqs:ChangeMessageVisibility', + 'sqs:GetQueueUrl', + 'sqs:DeleteMessage', + 'sqs:GetQueueAttributes', + ], + Effect: 'Allow', + Resource: { + 'Fn::GetAtt': [ + 'ServiceEcsProcessingQueueC266885C', + 'Arn', + ], + }, + }, + ], + Version: '2012-10-17', + }, + PolicyName: 'ServiceQueueProcessingTaskDefTaskRoleDefaultPolicy11D50174', + Roles: [ + { + Ref: 'ServiceQueueProcessingTaskDefTaskRoleBDE5D3C6', + }, + ], + }); +}); + test('test Fargate queue worker service construct - without desiredCount specified', () => { // GIVEN const stack = new cdk.Stack(); diff --git a/packages/@aws-cdk/cx-api/package.json b/packages/@aws-cdk/cx-api/package.json index 68d90bf8630a2..7cd3f8f9b0fdd 100644 --- a/packages/@aws-cdk/cx-api/package.json +++ b/packages/@aws-cdk/cx-api/package.json @@ -67,6 +67,7 @@ "license": "Apache-2.0", "devDependencies": { "@aws-cdk/cdk-build-tools": "0.0.0", + "@aws-cdk/cloud-assembly-schema": "0.0.0", "@aws-cdk/pkglint": "0.0.0", "@types/jest": "^27.5.2", "@types/mock-fs": "^4.13.1",