Skip to content

Commit 2a80e4b

Browse files
authored
fix(ec2): launch template names in imdsv2 not unique across stacks (under feature flag) (#17766)
Fixes #17656 ### Notes Changes the name for the `LaunchTemplate` created in the aspect that enforces IMDSv2 on EC2 instances to a unique name. Introduces a new feature flag (`@aws-cdk/aws-ec2:uniqueImdsv2TemplateName`) to change the launch template name. ### Testing Added a unit test ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 91f3539 commit 2a80e4b

File tree

3 files changed

+74
-3
lines changed

3 files changed

+74
-3
lines changed

Diff for: packages/@aws-cdk/aws-ec2/lib/aspects/require-imdsv2-aspect.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import * as cdk from '@aws-cdk/core';
2+
import { FeatureFlags } from '@aws-cdk/core';
3+
import * as cxapi from '@aws-cdk/cx-api';
24
import { CfnLaunchTemplate } from '../ec2.generated';
35
import { Instance } from '../instance';
46
import { LaunchTemplate } from '../launch-template';
@@ -83,17 +85,20 @@ export class InstanceRequireImdsv2Aspect extends RequireImdsv2Aspect {
8385
return;
8486
}
8587

86-
const name = `${node.node.id}LaunchTemplate`;
8788
const launchTemplate = new CfnLaunchTemplate(node, 'LaunchTemplate', {
8889
launchTemplateData: {
8990
metadataOptions: {
9091
httpTokens: 'required',
9192
},
9293
},
93-
launchTemplateName: name,
9494
});
95+
if (FeatureFlags.of(node).isEnabled(cxapi.EC2_UNIQUE_IMDSV2_LAUNCH_TEMPLATE_NAME)) {
96+
launchTemplate.launchTemplateName = cdk.Names.uniqueId(launchTemplate);
97+
} else {
98+
launchTemplate.launchTemplateName = `${node.node.id}LaunchTemplate`;
99+
}
95100
node.instance.launchTemplate = {
96-
launchTemplateName: name,
101+
launchTemplateName: launchTemplate.launchTemplateName,
97102
version: launchTemplate.getAtt('LatestVersionNumber').toString(),
98103
};
99104
}

Diff for: packages/@aws-cdk/aws-ec2/test/aspects/require-imdsv2-aspect.test.ts

+53
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import {
44
haveResourceLike,
55
} from '@aws-cdk/assert-internal';
66
import '@aws-cdk/assert-internal/jest';
7+
import { testFutureBehavior, testLegacyBehavior } from '@aws-cdk/cdk-build-tools';
78
import * as cdk from '@aws-cdk/core';
9+
import * as cxapi from '@aws-cdk/cx-api';
810
import {
911
CfnLaunchTemplate,
1012
Instance,
@@ -135,6 +137,57 @@ describe('RequireImdsv2Aspect', () => {
135137
trace: undefined,
136138
});
137139
});
140+
141+
testFutureBehavior('launch template name is unique with feature flag', { [cxapi.EC2_UNIQUE_IMDSV2_LAUNCH_TEMPLATE_NAME]: true }, cdk.App, (app2) => {
142+
// GIVEN
143+
const otherStack = new cdk.Stack(app2, 'OtherStack');
144+
const otherVpc = new Vpc(otherStack, 'OtherVpc');
145+
const otherInstance = new Instance(otherStack, 'OtherInstance', {
146+
vpc: otherVpc,
147+
instanceType: new InstanceType('t2.micro'),
148+
machineImage: MachineImage.latestAmazonLinux(),
149+
});
150+
const imdsv2Stack = new cdk.Stack(app2, 'RequireImdsv2Stack');
151+
const imdsv2Vpc = new Vpc(imdsv2Stack, 'Vpc');
152+
const instance = new Instance(imdsv2Stack, 'Instance', {
153+
vpc: imdsv2Vpc,
154+
instanceType: new InstanceType('t2.micro'),
155+
machineImage: MachineImage.latestAmazonLinux(),
156+
});
157+
const aspect = new InstanceRequireImdsv2Aspect();
158+
159+
// WHEN
160+
cdk.Aspects.of(imdsv2Stack).add(aspect);
161+
cdk.Aspects.of(otherStack).add(aspect);
162+
app2.synth();
163+
164+
// THEN
165+
const launchTemplate = instance.node.tryFindChild('LaunchTemplate') as LaunchTemplate;
166+
const otherLaunchTemplate = otherInstance.node.tryFindChild('LaunchTemplate') as LaunchTemplate;
167+
expect(launchTemplate).toBeDefined();
168+
expect(otherLaunchTemplate).toBeDefined();
169+
expect(launchTemplate.launchTemplateName !== otherLaunchTemplate.launchTemplateName);
170+
});
171+
172+
testLegacyBehavior('launch template name uses legacy id without feature flag', cdk.App, (app2) => {
173+
// GIVEN
174+
const imdsv2Stack = new cdk.Stack(app2, 'RequireImdsv2Stack');
175+
const imdsv2Vpc = new Vpc(imdsv2Stack, 'Vpc');
176+
const instance = new Instance(imdsv2Stack, 'Instance', {
177+
vpc: imdsv2Vpc,
178+
instanceType: new InstanceType('t2.micro'),
179+
machineImage: MachineImage.latestAmazonLinux(),
180+
});
181+
const aspect = new InstanceRequireImdsv2Aspect();
182+
183+
// WHEN
184+
cdk.Aspects.of(imdsv2Stack).add(aspect);
185+
app2.synth();
186+
187+
// THEN
188+
const launchTemplate = instance.node.tryFindChild('LaunchTemplate') as LaunchTemplate;
189+
expect(launchTemplate.launchTemplateName).toEqual(`${instance.node.id}LaunchTemplate`);
190+
});
138191
});
139192

140193
describe('LaunchTemplateRequireImdsv2Aspect', () => {

Diff for: packages/@aws-cdk/cx-api/lib/features.ts

+13
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,17 @@ export const TARGET_PARTITIONS = '@aws-cdk/core:target-partitions';
178178
*/
179179
export const ECS_SERVICE_EXTENSIONS_ENABLE_DEFAULT_LOG_DRIVER = '@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver';
180180

181+
/**
182+
* Enable this feature flag to have Launch Templates generated by the `InstanceRequireImdsv2Aspect` use unique names.
183+
*
184+
* Previously, the generated Launch Template names were only unique within a stack because they were based only on the
185+
* `Instance` construct ID. If another stack that has an `Instance` with the same construct ID is deployed in the same
186+
* account and region, the deployments would always fail as the generated Launch Template names were the same.
187+
*
188+
* The new implementation addresses this issue by generating the Launch Template name with the `Names.uniqueId` method.
189+
*/
190+
export const EC2_UNIQUE_IMDSV2_LAUNCH_TEMPLATE_NAME = '@aws-cdk/aws-ec2:uniqueImdsv2TemplateName';
191+
181192
/**
182193
* This map includes context keys and values for feature flags that enable
183194
* capabilities "from the future", which we could not introduce as the default
@@ -206,6 +217,7 @@ export const FUTURE_FLAGS: { [key: string]: boolean } = {
206217
[LAMBDA_RECOGNIZE_VERSION_PROPS]: true,
207218
[CLOUDFRONT_DEFAULT_SECURITY_POLICY_TLS_V1_2_2021]: true,
208219
[ECS_SERVICE_EXTENSIONS_ENABLE_DEFAULT_LOG_DRIVER]: true,
220+
[EC2_UNIQUE_IMDSV2_LAUNCH_TEMPLATE_NAME]: true,
209221

210222
// We will advertise this flag when the feature is complete
211223
// [NEW_STYLE_STACK_SYNTHESIS_CONTEXT]: 'true',
@@ -245,6 +257,7 @@ const FUTURE_FLAGS_DEFAULTS: { [key: string]: boolean } = {
245257
[LAMBDA_RECOGNIZE_VERSION_PROPS]: false,
246258
[CLOUDFRONT_DEFAULT_SECURITY_POLICY_TLS_V1_2_2021]: false,
247259
[ECS_SERVICE_EXTENSIONS_ENABLE_DEFAULT_LOG_DRIVER]: false,
260+
[EC2_UNIQUE_IMDSV2_LAUNCH_TEMPLATE_NAME]: false,
248261
};
249262

250263
export function futureFlagDefault(flag: string): boolean | undefined {

0 commit comments

Comments
 (0)