Skip to content

Commit ef7e20d

Browse files
authored
feat(aws-autoscaling): add flag and aspect to require imdsv2 (#16052)
Partially fixes: #5137 Related PR: #16051 **Note:** I have some concerns about duplicated code between this and the above linked PR. Please see that PR for more details. ### Changes Adds an aspect that can enable/disable IMDSv1 on AutoScalingGroups ### Testing Added unit tests ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 83cf9b8 commit ef7e20d

File tree

7 files changed

+179
-0
lines changed

7 files changed

+179
-0
lines changed

packages/@aws-cdk/aws-autoscaling/README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,32 @@ new autoscaling.AutoScalingGroup(stack, 'ASG', {
378378
});
379379
```
380380

381+
## Configuring Instance Metadata Service (IMDS)
382+
383+
### Toggling IMDSv1
384+
385+
You can configure [EC2 Instance Metadata Service](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html) options to either
386+
allow both IMDSv1 and IMDSv2 or enforce IMDSv2 when interacting with the IMDS.
387+
388+
To do this for a single `AutoScalingGroup`, you can use set the `requireImdsv2` property.
389+
The example below demonstrates IMDSv2 being required on a single `AutoScalingGroup`:
390+
391+
```ts
392+
new autoscaling.AutoScalingGroup(stack, 'ASG', {
393+
requireImdsv2: true,
394+
// ...
395+
});
396+
```
397+
398+
You can also use `AutoScalingGroupRequireImdsv2Aspect` to apply the operation to multiple AutoScalingGroups.
399+
The example below demonstrates the `AutoScalingGroupRequireImdsv2Aspect` being used to require IMDSv2 for all AutoScalingGroups in a stack:
400+
401+
```ts
402+
const aspect = new autoscaling.AutoScalingGroupRequireImdsv2Aspect();
403+
404+
Aspects.of(stack).add(aspect);
405+
```
406+
381407
## Future work
382408

383409
* [ ] CloudWatch Events (impossible to add currently as the AutoScalingGroup ARN is
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './require-imdsv2-aspect';
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import * as cdk from '@aws-cdk/core';
2+
import { AutoScalingGroup } from '../auto-scaling-group';
3+
import { CfnLaunchConfiguration } from '../autoscaling.generated';
4+
5+
/**
6+
* Aspect that makes IMDSv2 required on instances deployed by AutoScalingGroups.
7+
*/
8+
export class AutoScalingGroupRequireImdsv2Aspect implements cdk.IAspect {
9+
constructor() {
10+
}
11+
12+
public visit(node: cdk.IConstruct): void {
13+
if (!(node instanceof AutoScalingGroup)) {
14+
return;
15+
}
16+
17+
const launchConfig = node.node.tryFindChild('LaunchConfig') as CfnLaunchConfiguration;
18+
if (cdk.isResolvableObject(launchConfig.metadataOptions)) {
19+
this.warn(node, 'CfnLaunchConfiguration.MetadataOptions field is a CDK token.');
20+
return;
21+
}
22+
23+
launchConfig.metadataOptions = {
24+
...launchConfig.metadataOptions,
25+
httpTokens: 'required',
26+
};
27+
}
28+
29+
/**
30+
* Adds a warning annotation to a node.
31+
*
32+
* @param node The scope to add the warning to.
33+
* @param message The warning message.
34+
*/
35+
protected warn(node: cdk.IConstruct, message: string) {
36+
cdk.Annotations.of(node).addWarning(`${AutoScalingGroupRequireImdsv2Aspect.name} failed on node ${node.node.id}: ${message}`);
37+
}
38+
}

packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@ import * as sns from '@aws-cdk/aws-sns';
77

88
import {
99
Annotations,
10+
Aspects,
1011
Aws,
1112
CfnAutoScalingRollingUpdate, CfnCreationPolicy, CfnUpdatePolicy,
1213
Duration, Fn, IResource, Lazy, PhysicalName, Resource, Stack, Tags,
1314
Token,
1415
Tokenization, withResolved,
1516
} from '@aws-cdk/core';
1617
import { Construct } from 'constructs';
18+
import { AutoScalingGroupRequireImdsv2Aspect } from './aspects';
1719
import { CfnAutoScalingGroup, CfnAutoScalingGroupProps, CfnLaunchConfiguration } from './autoscaling.generated';
1820
import { BasicLifecycleHookProps, LifecycleHook } from './lifecycle-hook';
1921
import { BasicScheduledActionProps, ScheduledAction } from './scheduled-action';
@@ -384,6 +386,13 @@ export interface AutoScalingGroupProps extends CommonAutoScalingGroupProps {
384386
* @default - default options
385387
*/
386388
readonly initOptions?: ApplyCloudFormationInitOptions;
389+
390+
/**
391+
* Whether IMDSv2 should be required on launched instances.
392+
*
393+
* @default - false
394+
*/
395+
readonly requireImdsv2?: boolean;
387396
}
388397

389398
/**
@@ -1065,6 +1074,10 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements
10651074
}
10661075

10671076
this.spotPrice = props.spotPrice;
1077+
1078+
if (props.requireImdsv2) {
1079+
Aspects.of(this).add(new AutoScalingGroupRequireImdsv2Aspect());
1080+
}
10681081
}
10691082

10701083
/**

packages/@aws-cdk/aws-autoscaling/lib/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export * from './aspects';
12
export * from './auto-scaling-group';
23
export * from './schedule';
34
export * from './lifecycle-hook';
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import {
2+
expect as expectCDK,
3+
haveResourceLike,
4+
} from '@aws-cdk/assert-internal';
5+
import '@aws-cdk/assert-internal/jest';
6+
import * as ec2 from '@aws-cdk/aws-ec2';
7+
import * as cdk from '@aws-cdk/core';
8+
import {
9+
AutoScalingGroup,
10+
AutoScalingGroupRequireImdsv2Aspect,
11+
CfnLaunchConfiguration,
12+
} from '../../lib';
13+
14+
describe('AutoScalingGroupRequireImdsv2Aspect', () => {
15+
let app: cdk.App;
16+
let stack: cdk.Stack;
17+
let vpc: ec2.Vpc;
18+
19+
beforeEach(() => {
20+
app = new cdk.App();
21+
stack = new cdk.Stack(app, 'Stack');
22+
vpc = new ec2.Vpc(stack, 'Vpc');
23+
});
24+
25+
test('warns when metadataOptions is a token', () => {
26+
// GIVEN
27+
const asg = new AutoScalingGroup(stack, 'AutoScalingGroup', {
28+
vpc,
29+
instanceType: new ec2.InstanceType('t2.micro'),
30+
machineImage: ec2.MachineImage.latestAmazonLinux(),
31+
});
32+
const launchConfig = asg.node.tryFindChild('LaunchConfig') as CfnLaunchConfiguration;
33+
launchConfig.metadataOptions = fakeToken();
34+
const aspect = new AutoScalingGroupRequireImdsv2Aspect();
35+
36+
// WHEN
37+
cdk.Aspects.of(stack).add(aspect);
38+
39+
// THEN
40+
expectCDK(stack).notTo(haveResourceLike('AWS::AutoScaling::LaunchConfiguration', {
41+
MetadataOptions: {
42+
HttpTokens: 'required',
43+
},
44+
}));
45+
expect(asg.node.metadataEntry).toContainEqual({
46+
data: expect.stringContaining('CfnLaunchConfiguration.MetadataOptions field is a CDK token.'),
47+
type: 'aws:cdk:warning',
48+
trace: undefined,
49+
});
50+
});
51+
52+
test('requires IMDSv2', () => {
53+
// GIVEN
54+
new AutoScalingGroup(stack, 'AutoScalingGroup', {
55+
vpc,
56+
instanceType: new ec2.InstanceType('t2.micro'),
57+
machineImage: ec2.MachineImage.latestAmazonLinux(),
58+
});
59+
const aspect = new AutoScalingGroupRequireImdsv2Aspect();
60+
61+
// WHEN
62+
cdk.Aspects.of(stack).add(aspect);
63+
64+
// THEN
65+
expectCDK(stack).to(haveResourceLike('AWS::AutoScaling::LaunchConfiguration', {
66+
MetadataOptions: {
67+
HttpTokens: 'required',
68+
},
69+
}));
70+
});
71+
});
72+
73+
function fakeToken(): cdk.IResolvable {
74+
return {
75+
creationStack: [],
76+
resolve: (_c) => {},
77+
toString: () => '',
78+
};
79+
}

packages/@aws-cdk/aws-autoscaling/test/auto-scaling-group.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1364,6 +1364,27 @@ describe('auto scaling group', () => {
13641364

13651365

13661366
});
1367+
1368+
test('requires imdsv2', () => {
1369+
// GIVEN
1370+
const stack = new cdk.Stack();
1371+
const vpc = mockVpc(stack);
1372+
1373+
// WHEN
1374+
new autoscaling.AutoScalingGroup(stack, 'MyASG', {
1375+
vpc,
1376+
instanceType: new ec2.InstanceType('t2.micro'),
1377+
machineImage: ec2.MachineImage.latestAmazonLinux(),
1378+
requireImdsv2: true,
1379+
});
1380+
1381+
// THEN
1382+
expect(stack).toHaveResourceLike('AWS::AutoScaling::LaunchConfiguration', {
1383+
MetadataOptions: {
1384+
HttpTokens: 'required',
1385+
},
1386+
});
1387+
});
13671388
});
13681389

13691390
function mockVpc(stack: cdk.Stack) {

0 commit comments

Comments
 (0)