Skip to content

Commit 30a0d33

Browse files
authored
fix(ecs): EC2 metadata access is blocked when using EC2 capacity provider for autoscaling (#28437)
### Why is this needed? When adding a auto scaling group as a capacity provider using `Cluster.addAsgCapacityProvider` and when the task definition being run uses the AWS_VPC network mode, it results in the metadata service at `169.254.169.254` being blocked . This is a security best practice as detailed [here](https://docs.aws.amazon.com/AmazonECS/latest/bestpracticesguide/security-iam-roles.html). This practice is implemented [here](https://github.com/aws/aws-cdk/blame/2d9de189e583186f2b77386ae4fcfff42c864568/packages/aws-cdk-lib/aws-ecs/lib/cluster.ts#L502-L504). However by doing this, some applications such as those raised in #28270 as well as the aws-otel package will not be able to source for the AWS region and thus, cause the application to crash and exit. ### What does it implement? This PR add an override to the addContainer method when using the Ec2TaskDefinition to add in the AWS_REGION environment variable to the container if the network mode is set as AWS_VPC. The region is sourced by referencing to the stack which includes this construct at synth time.This environment variable is only required in the EC2 Capacity Provider mode and not in Fargate as this issue of not being able to source for the region on startup is only present when using the EC2 Capacity Provider with the AWS_VPC networking mode. The initial issue addresses this during the `addAsgCapacityProvider` action which targets the cluster. However, we cannot mutate the task definition at that point in time thus, this change addresses it when the task definition is actually added to a service that meets all the requirements whereby the failure to source for region will occur. Updated the relevant integration tests to reflect the new environment variable being created alongside user-defined environment variables. Closes #28270 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 426d889 commit 30a0d33

File tree

8 files changed

+196
-2
lines changed

8 files changed

+196
-2
lines changed

packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/ec2/integ.app-mesh-proxy-config.js.snapshot/aws-ecs-integ-appmesh-proxy.template.json

+16
Original file line numberDiff line numberDiff line change
@@ -919,12 +919,28 @@
919919
"Properties": {
920920
"ContainerDefinitions": [
921921
{
922+
"Environment": [
923+
{
924+
"Name": "AWS_REGION",
925+
"Value": {
926+
"Ref": "AWS::Region"
927+
}
928+
}
929+
],
922930
"Essential": true,
923931
"Image": "amazon/amazon-ecs-sample",
924932
"Memory": 256,
925933
"Name": "web"
926934
},
927935
{
936+
"Environment": [
937+
{
938+
"Name": "AWS_REGION",
939+
"Value": {
940+
"Ref": "AWS::Region"
941+
}
942+
}
943+
],
928944
"Essential": true,
929945
"Image": "envoyproxy/envoy:v1.16.2",
930946
"Memory": 256,

packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/ec2/integ.firelens-s3-config.js.snapshot/aws-ecs-integ.template.json

+8
Original file line numberDiff line numberDiff line change
@@ -964,6 +964,14 @@
964964
"Name": "log_router"
965965
},
966966
{
967+
"Environment": [
968+
{
969+
"Name": "AWS_REGION",
970+
"Value": {
971+
"Ref": "AWS::Region"
972+
}
973+
}
974+
],
967975
"Essential": true,
968976
"Image": "nginx",
969977
"LogConfiguration": {

packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/ec2/integ.lb-awsvpc-nw.js.snapshot/aws-ecs-integ.template.json

+12
Original file line numberDiff line numberDiff line change
@@ -919,6 +919,18 @@
919919
"Properties": {
920920
"ContainerDefinitions": [
921921
{
922+
"Environment": [
923+
{
924+
"Name": "SOME_VARIABLE",
925+
"Value": "value"
926+
},
927+
{
928+
"Name": "AWS_REGION",
929+
"Value": {
930+
"Ref": "AWS::Region"
931+
}
932+
}
933+
],
922934
"Essential": true,
923935
"Image": "amazon/amazon-ecs-sample",
924936
"Memory": 256,

packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/ec2/integ.lb-awsvpc-nw.ts

+3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ const taskDefinition = new ecs.Ec2TaskDefinition(stack, 'TaskDef', {
2020
const container = taskDefinition.addContainer('web', {
2121
image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
2222
memoryLimitMiB: 256,
23+
environment: {
24+
SOME_VARIABLE: 'value',
25+
},
2326
});
2427

2528
container.addPortMappings({

packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/ec2/integ.pseudo-terminal.js.snapshot/aws-ecs-integ-pseudo-terminal.template.json

+8
Original file line numberDiff line numberDiff line change
@@ -919,6 +919,14 @@
919919
"Properties": {
920920
"ContainerDefinitions": [
921921
{
922+
"Environment": [
923+
{
924+
"Name": "AWS_REGION",
925+
"Value": {
926+
"Ref": "AWS::Region"
927+
}
928+
}
929+
],
922930
"Essential": true,
923931
"Image": "amazon/amazon-ecs-sample",
924932
"Memory": 256,

packages/@aws-cdk-testing/framework-integ/test/aws-ecs/test/ec2/integ.sd-awsvpc-nw.js.snapshot/aws-ecs-integ-ecs.template.json

+8
Original file line numberDiff line numberDiff line change
@@ -928,6 +928,14 @@
928928
"Properties": {
929929
"ContainerDefinitions": [
930930
{
931+
"Environment": [
932+
{
933+
"Name": "AWS_REGION",
934+
"Value": {
935+
"Ref": "AWS::Region"
936+
}
937+
}
938+
],
931939
"Essential": true,
932940
"Image": "amazon/amazon-ecs-sample",
933941
"Memory": 256,

packages/aws-cdk-lib/aws-ecs/lib/ec2/ec2-task-definition.ts

+21-2
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
import { Construct } from 'constructs';
2+
import { Stack } from '../../../core';
23
import { ImportedTaskDefinition } from '../base/_imported-task-definition';
34
import {
45
CommonTaskDefinitionAttributes,
56
CommonTaskDefinitionProps,
67
Compatibility,
8+
InferenceAccelerator,
79
IpcMode,
810
ITaskDefinition,
911
NetworkMode,
1012
PidMode,
1113
TaskDefinition,
12-
InferenceAccelerator,
1314
} from '../base/task-definition';
15+
import { ContainerDefinition, ContainerDefinitionOptions } from '../container-definition';
1416
import { PlacementConstraint } from '../placement';
1517

1618
/**
@@ -83,7 +85,6 @@ export interface Ec2TaskDefinitionAttributes extends CommonTaskDefinitionAttribu
8385
* @resource AWS::ECS::TaskDefinition
8486
*/
8587
export class Ec2TaskDefinition extends TaskDefinition implements IEc2TaskDefinition {
86-
8788
/**
8889
* Imports a task definition from the specified task definition ARN.
8990
*/
@@ -146,4 +147,22 @@ export class Ec2TaskDefinition extends TaskDefinition implements IEc2TaskDefinit
146147
// Validate the placement constraints
147148
Ec2TaskDefinition.validatePlacementConstraints(props.placementConstraints ?? []);
148149
}
150+
151+
/**
152+
* Tasks running in AWSVPC networking mode requires an additional environment variable for the region to be sourced.
153+
* This override adds in the additional environment variable as required
154+
*/
155+
override addContainer(id: string, props: ContainerDefinitionOptions): ContainerDefinition {
156+
if (this.networkMode === NetworkMode.AWS_VPC) {
157+
return super.addContainer(id, {
158+
...props,
159+
environment: {
160+
...props.environment,
161+
AWS_REGION: Stack.of(this).region,
162+
},
163+
});
164+
}
165+
// If network mode is not AWSVPC, then just add the container as normal
166+
return super.addContainer(id, props);
167+
}
149168
}

packages/aws-cdk-lib/aws-ecs/test/ec2/ec2-task-definition.test.ts

+120
Original file line numberDiff line numberDiff line change
@@ -1110,6 +1110,126 @@ describe('ec2 task definition', () => {
11101110
}],
11111111
});
11121112
});
1113+
1114+
test('correctly sets env variables when using EC2 capacity provider with AWSVPC mode - with no other user-defined env variables', () => {
1115+
// GIVEN AWS-VPC network mode
1116+
const stack = new cdk.Stack();
1117+
const taskDefiniton = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', {
1118+
networkMode: ecs.NetworkMode.AWS_VPC,
1119+
});
1120+
taskDefiniton.addContainer('some-container', {
1121+
image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
1122+
memoryLimitMiB: 512,
1123+
});
1124+
1125+
// THEN it should include the AWS_REGION env variable - when no user defined env variables are provided
1126+
Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', {
1127+
NetworkMode: ecs.NetworkMode.AWS_VPC,
1128+
ContainerDefinitions: [{
1129+
Name: 'some-container',
1130+
Image: 'amazon/amazon-ecs-sample',
1131+
Memory: 512,
1132+
Environment: [{
1133+
Name: 'AWS_REGION',
1134+
Value: {
1135+
Ref: 'AWS::Region',
1136+
},
1137+
}],
1138+
}],
1139+
});
1140+
});
1141+
1142+
test('correctly sets env variables when using EC2 capacity provider with AWSVPC mode - with other user-defined env variables', () => {
1143+
// GIVEN AWS-VPC network mode
1144+
const stack = new cdk.Stack();
1145+
const taskDefiniton = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', {
1146+
networkMode: ecs.NetworkMode.AWS_VPC,
1147+
});
1148+
taskDefiniton.addContainer('some-container', {
1149+
image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
1150+
memoryLimitMiB: 512,
1151+
environment: {
1152+
SOME_VARIABLE: 'some-value',
1153+
},
1154+
});
1155+
1156+
// THEN it should include the AWS_REGION env variable
1157+
Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', {
1158+
NetworkMode: ecs.NetworkMode.AWS_VPC,
1159+
ContainerDefinitions: [{
1160+
Name: 'some-container',
1161+
Image: 'amazon/amazon-ecs-sample',
1162+
Memory: 512,
1163+
Environment: [{
1164+
Name: 'SOME_VARIABLE',
1165+
Value: 'some-value',
1166+
}, {
1167+
Name: 'AWS_REGION',
1168+
Value: {
1169+
Ref: 'AWS::Region',
1170+
},
1171+
}],
1172+
}],
1173+
});
1174+
});
1175+
1176+
test('correctly sets env variables when using EC2 capacity provider with HOST mode', () => {
1177+
// GIVEN HOST network mode
1178+
const stack = new cdk.Stack();
1179+
const taskDefiniton = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', {
1180+
networkMode: ecs.NetworkMode.HOST,
1181+
});
1182+
taskDefiniton.addContainer('some-container', {
1183+
image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
1184+
memoryLimitMiB: 512,
1185+
environment: {
1186+
SOME_VARIABLE: 'some-value',
1187+
},
1188+
});
1189+
1190+
// THEN it should not include the AWS_REGION env variable
1191+
Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', {
1192+
NetworkMode: ecs.NetworkMode.HOST,
1193+
ContainerDefinitions: [{
1194+
Name: 'some-container',
1195+
Image: 'amazon/amazon-ecs-sample',
1196+
Memory: 512,
1197+
Environment: [{
1198+
Name: 'SOME_VARIABLE',
1199+
Value: 'some-value',
1200+
}],
1201+
}],
1202+
});
1203+
});
1204+
1205+
test('correctly sets env variables when using EC2 capacity provider with BRIDGE mode', () => {
1206+
// GIVEN HOST network mode
1207+
const stack = new cdk.Stack();
1208+
const taskDefiniton = new ecs.Ec2TaskDefinition(stack, 'Ec2TaskDef', {
1209+
networkMode: ecs.NetworkMode.BRIDGE,
1210+
});
1211+
taskDefiniton.addContainer('some-container', {
1212+
image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
1213+
memoryLimitMiB: 512,
1214+
environment: {
1215+
SOME_VARIABLE: 'some-value',
1216+
},
1217+
});
1218+
1219+
// THEN it should not include the AWS_REGION env variable
1220+
Template.fromStack(stack).hasResourceProperties('AWS::ECS::TaskDefinition', {
1221+
NetworkMode: ecs.NetworkMode.BRIDGE,
1222+
ContainerDefinitions: [{
1223+
Name: 'some-container',
1224+
Image: 'amazon/amazon-ecs-sample',
1225+
Memory: 512,
1226+
Environment: [{
1227+
Name: 'SOME_VARIABLE',
1228+
Value: 'some-value',
1229+
}],
1230+
}],
1231+
});
1232+
});
11131233
});
11141234

11151235
describe('setting inferenceAccelerators', () => {

0 commit comments

Comments
 (0)