Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat(ecs): Adding support for ECS Exec #14670

Merged
merged 29 commits into from
Jun 3, 2021
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
64cc86a
feat(aws-ecs): add enableExecuteCommand to Service
SoManyHs Mar 18, 2021
129f0a0
Merge remote-tracking branch 'hhh/enable-exec' into upparekh/support-…
upparekh Apr 30, 2021
74f51c1
Allowing exec without logging or encryption
upparekh May 10, 2021
ad96731
In-progress: Cluster configuration for exec command
upparekh May 13, 2021
314b39e
Merge branch 'master' into upparekh/support-for-enable-exec-command
upparekh May 13, 2021
cf04750
Merge branch 'master' of https://github.com/upparekh/aws-cdk into upp…
upparekh May 14, 2021
4d0373c
Resolve conflicts after updating branch
upparekh May 14, 2021
1d32595
Resolved linter errors
upparekh May 14, 2021
56a1eac
Logging for CloudWatch and S3 and Encryption for S3
upparekh May 17, 2021
a0b14ef
Updated README
upparekh May 17, 2021
d037795
Resolved merge conflict
upparekh May 17, 2021
e2bf45a
Ecryption for CloudWatch Logs enabled
upparekh May 18, 2021
acad3bb
Merge branch 'master' into upparekh/support-for-enable-exec-command
upparekh May 19, 2021
6c02f67
Modified permissions and added errors
upparekh May 19, 2021
ad3261d
Added unit tests
upparekh May 19, 2021
eb0fb69
Added integ tests for exec command
upparekh May 19, 2021
900f3b8
Merge branch 'upparekh/support-for-enable-exec-command' of https://gi…
upparekh May 19, 2021
967f6b5
Enabling only session encryption
upparekh May 20, 2021
4dc4db5
Documentation suggestions from review
upparekh May 20, 2021
9e824d7
Updates after review
upparekh May 21, 2021
5b1e151
Updated error condition in cluster
upparekh May 21, 2021
103bba9
Merge branch 'master' into upparekh/support-for-enable-exec-command
upparekh May 21, 2021
fe2ab80
Merge branch 'master' into upparekh/support-for-enable-exec-command
upparekh Jun 2, 2021
e9bbdcf
Updated the Cluster configuration to pass log group and bucket instea…
upparekh Jun 2, 2021
c098f25
Apply doc suggestions from code review
upparekh Jun 2, 2021
fb61b52
Code changes after review
upparekh Jun 2, 2021
5f9b060
Merge branch 'master' into upparekh/support-for-enable-exec-command
SoManyHs Jun 3, 2021
50f3692
Refactored conditions in base service
upparekh Jun 3, 2021
880a2a1
Merge branch 'master' into upparekh/support-for-enable-exec-command
SoManyHs Jun 3, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions packages/@aws-cdk/aws-ecs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -850,3 +850,60 @@ 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 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 the 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,
});
```

### 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 `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,
});

const cluster = new ecs.Cluster(stack, 'Cluster', {
vpc,
executeCommandConfiguration: {
kmsKey,
logConfiguration: {
cloudWatchLogGroup: logGroup,
cloudWatchEncryptionEnabled: true,
s3Bucket: execBucket,
s3EncryptionEnabled: true,
s3KeyPrefix: 'exec-command-output',
},
logging: ecs.ExecuteCommandLogging.OVERRIDE,
},
});
```
112 changes: 111 additions & 1 deletion packages/@aws-cdk/aws-ecs/lib/base/base-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, ExecuteCommandLogging } from '../cluster';
import { ContainerDefinition, Protocol } from '../container-definition';
import { CfnService } from '../ecs.generated';
import { ScalableTaskCount } from './scalable-task-count';
Expand Down Expand Up @@ -189,6 +189,13 @@ export interface BaseServiceOptions {
*
*/
readonly capacityProviderStrategies?: CapacityProviderStrategy[];

upparekh marked this conversation as resolved.
Show resolved Hide resolved
/**
* Whether to enable the ability to execute into a container
*
* @default - undefined
*/
readonly enableExecuteCommand?: boolean;
}

/**
Expand Down Expand Up @@ -391,6 +398,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 */
Expand All @@ -416,6 +424,18 @@ export abstract class BaseService extends Resource
this.enableCloudMap(props.cloudMapOptions);
}

if (props.enableExecuteCommand) {
upparekh marked this conversation as resolved.
Show resolved Hide resolved
this.enableExecuteCommand();

const logging = this.cluster.executeCommandConfiguration?.logging ?? ExecuteCommandLogging.DEFAULT;

if (this.cluster.executeCommandConfiguration?.kmsKey) {
this.enableExecuteCommandEncryption(logging);
}
if (logging !== ExecuteCommandLogging.NONE) {
this.executeCommandLogConfiguration();
}
}
this.node.defaultChild = this.resource;
}

Expand All @@ -426,6 +446,84 @@ export abstract class BaseService extends Resource
return this.cloudmapService;
}

private executeCommandLogConfiguration() {
const logConfiguration = this.cluster.executeCommandConfiguration?.logConfiguration;
this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({
actions: [
'logs:DescribeLogGroups',
],
resources: ['*'],
}));

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',
'logs:DescribeLogStreams',
'logs:PutLogEvents',
],
resources: [logGroupArn],
}));

if (logConfiguration?.s3Bucket?.bucketName) {
this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({
actions: [
's3:GetBucketLocation',
],
resources: ['*'],
}));
this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({
actions: [
's3:PutObject',
],
resources: [`arn:aws:s3:::${logConfiguration.s3Bucket.bucketName}/*`],
}));
if (logConfiguration.s3EncryptionEnabled) {
this.taskDefinition.addToTaskRolePolicy(new iam.PolicyStatement({
actions: [
's3:GetEncryptionConfiguration',
],
resources: [`arn:aws:s3:::${logConfiguration.s3Bucket.bucketName}`],
}));
}
}
}

private enableExecuteCommandEncryption(logging: ExecuteCommandLogging) {
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 (logging === ExecuteCommandLogging.DEFAULT || 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.
*
Expand Down Expand Up @@ -790,6 +888,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: ['*'],
}));
}
}

/**
Expand Down
Loading