Skip to content

Commit

Permalink
feat(aws-autoscaling): add flag and aspect to require imdsv2 (#16052)
Browse files Browse the repository at this point in the history
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*
  • Loading branch information
jericht committed Oct 19, 2021
1 parent 83cf9b8 commit ef7e20d
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 0 deletions.
26 changes: 26 additions & 0 deletions packages/@aws-cdk/aws-autoscaling/README.md
Expand Up @@ -378,6 +378,32 @@ new autoscaling.AutoScalingGroup(stack, 'ASG', {
});
```

## Configuring Instance Metadata Service (IMDS)

### Toggling IMDSv1

You can configure [EC2 Instance Metadata Service](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html) options to either
allow both IMDSv1 and IMDSv2 or enforce IMDSv2 when interacting with the IMDS.

To do this for a single `AutoScalingGroup`, you can use set the `requireImdsv2` property.
The example below demonstrates IMDSv2 being required on a single `AutoScalingGroup`:

```ts
new autoscaling.AutoScalingGroup(stack, 'ASG', {
requireImdsv2: true,
// ...
});
```

You can also use `AutoScalingGroupRequireImdsv2Aspect` to apply the operation to multiple AutoScalingGroups.
The example below demonstrates the `AutoScalingGroupRequireImdsv2Aspect` being used to require IMDSv2 for all AutoScalingGroups in a stack:

```ts
const aspect = new autoscaling.AutoScalingGroupRequireImdsv2Aspect();

Aspects.of(stack).add(aspect);
```

## Future work

* [ ] CloudWatch Events (impossible to add currently as the AutoScalingGroup ARN is
Expand Down
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-autoscaling/lib/aspects/index.ts
@@ -0,0 +1 @@
export * from './require-imdsv2-aspect';
@@ -0,0 +1,38 @@
import * as cdk from '@aws-cdk/core';
import { AutoScalingGroup } from '../auto-scaling-group';
import { CfnLaunchConfiguration } from '../autoscaling.generated';

/**
* Aspect that makes IMDSv2 required on instances deployed by AutoScalingGroups.
*/
export class AutoScalingGroupRequireImdsv2Aspect implements cdk.IAspect {
constructor() {
}

public visit(node: cdk.IConstruct): void {
if (!(node instanceof AutoScalingGroup)) {
return;
}

const launchConfig = node.node.tryFindChild('LaunchConfig') as CfnLaunchConfiguration;
if (cdk.isResolvableObject(launchConfig.metadataOptions)) {
this.warn(node, 'CfnLaunchConfiguration.MetadataOptions field is a CDK token.');
return;
}

launchConfig.metadataOptions = {
...launchConfig.metadataOptions,
httpTokens: 'required',
};
}

/**
* Adds a warning annotation to a node.
*
* @param node The scope to add the warning to.
* @param message The warning message.
*/
protected warn(node: cdk.IConstruct, message: string) {
cdk.Annotations.of(node).addWarning(`${AutoScalingGroupRequireImdsv2Aspect.name} failed on node ${node.node.id}: ${message}`);
}
}
13 changes: 13 additions & 0 deletions packages/@aws-cdk/aws-autoscaling/lib/auto-scaling-group.ts
Expand Up @@ -7,13 +7,15 @@ import * as sns from '@aws-cdk/aws-sns';

import {
Annotations,
Aspects,
Aws,
CfnAutoScalingRollingUpdate, CfnCreationPolicy, CfnUpdatePolicy,
Duration, Fn, IResource, Lazy, PhysicalName, Resource, Stack, Tags,
Token,
Tokenization, withResolved,
} from '@aws-cdk/core';
import { Construct } from 'constructs';
import { AutoScalingGroupRequireImdsv2Aspect } from './aspects';
import { CfnAutoScalingGroup, CfnAutoScalingGroupProps, CfnLaunchConfiguration } from './autoscaling.generated';
import { BasicLifecycleHookProps, LifecycleHook } from './lifecycle-hook';
import { BasicScheduledActionProps, ScheduledAction } from './scheduled-action';
Expand Down Expand Up @@ -384,6 +386,13 @@ export interface AutoScalingGroupProps extends CommonAutoScalingGroupProps {
* @default - default options
*/
readonly initOptions?: ApplyCloudFormationInitOptions;

/**
* Whether IMDSv2 should be required on launched instances.
*
* @default - false
*/
readonly requireImdsv2?: boolean;
}

/**
Expand Down Expand Up @@ -1065,6 +1074,10 @@ export class AutoScalingGroup extends AutoScalingGroupBase implements
}

this.spotPrice = props.spotPrice;

if (props.requireImdsv2) {
Aspects.of(this).add(new AutoScalingGroupRequireImdsv2Aspect());
}
}

/**
Expand Down
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-autoscaling/lib/index.ts
@@ -1,3 +1,4 @@
export * from './aspects';
export * from './auto-scaling-group';
export * from './schedule';
export * from './lifecycle-hook';
Expand Down
@@ -0,0 +1,79 @@
import {
expect as expectCDK,
haveResourceLike,
} from '@aws-cdk/assert-internal';
import '@aws-cdk/assert-internal/jest';
import * as ec2 from '@aws-cdk/aws-ec2';
import * as cdk from '@aws-cdk/core';
import {
AutoScalingGroup,
AutoScalingGroupRequireImdsv2Aspect,
CfnLaunchConfiguration,
} from '../../lib';

describe('AutoScalingGroupRequireImdsv2Aspect', () => {
let app: cdk.App;
let stack: cdk.Stack;
let vpc: ec2.Vpc;

beforeEach(() => {
app = new cdk.App();
stack = new cdk.Stack(app, 'Stack');
vpc = new ec2.Vpc(stack, 'Vpc');
});

test('warns when metadataOptions is a token', () => {
// GIVEN
const asg = new AutoScalingGroup(stack, 'AutoScalingGroup', {
vpc,
instanceType: new ec2.InstanceType('t2.micro'),
machineImage: ec2.MachineImage.latestAmazonLinux(),
});
const launchConfig = asg.node.tryFindChild('LaunchConfig') as CfnLaunchConfiguration;
launchConfig.metadataOptions = fakeToken();
const aspect = new AutoScalingGroupRequireImdsv2Aspect();

// WHEN
cdk.Aspects.of(stack).add(aspect);

// THEN
expectCDK(stack).notTo(haveResourceLike('AWS::AutoScaling::LaunchConfiguration', {
MetadataOptions: {
HttpTokens: 'required',
},
}));
expect(asg.node.metadataEntry).toContainEqual({
data: expect.stringContaining('CfnLaunchConfiguration.MetadataOptions field is a CDK token.'),
type: 'aws:cdk:warning',
trace: undefined,
});
});

test('requires IMDSv2', () => {
// GIVEN
new AutoScalingGroup(stack, 'AutoScalingGroup', {
vpc,
instanceType: new ec2.InstanceType('t2.micro'),
machineImage: ec2.MachineImage.latestAmazonLinux(),
});
const aspect = new AutoScalingGroupRequireImdsv2Aspect();

// WHEN
cdk.Aspects.of(stack).add(aspect);

// THEN
expectCDK(stack).to(haveResourceLike('AWS::AutoScaling::LaunchConfiguration', {
MetadataOptions: {
HttpTokens: 'required',
},
}));
});
});

function fakeToken(): cdk.IResolvable {
return {
creationStack: [],
resolve: (_c) => {},
toString: () => '',
};
}
21 changes: 21 additions & 0 deletions packages/@aws-cdk/aws-autoscaling/test/auto-scaling-group.test.ts
Expand Up @@ -1364,6 +1364,27 @@ describe('auto scaling group', () => {


});

test('requires imdsv2', () => {
// GIVEN
const stack = new cdk.Stack();
const vpc = mockVpc(stack);

// WHEN
new autoscaling.AutoScalingGroup(stack, 'MyASG', {
vpc,
instanceType: new ec2.InstanceType('t2.micro'),
machineImage: ec2.MachineImage.latestAmazonLinux(),
requireImdsv2: true,
});

// THEN
expect(stack).toHaveResourceLike('AWS::AutoScaling::LaunchConfiguration', {
MetadataOptions: {
HttpTokens: 'required',
},
});
});
});

function mockVpc(stack: cdk.Stack) {
Expand Down

0 comments on commit ef7e20d

Please sign in to comment.