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(aws-autoscaling): add flag and aspect to require imdsv2 #16052

Merged
merged 6 commits into from
Oct 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
26 changes: 26 additions & 0 deletions packages/@aws-cdk/aws-autoscaling/README.md
Original file line number Diff line number Diff line change
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);
```

rix0rrr marked this conversation as resolved.
Show resolved Hide resolved
## 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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './require-imdsv2-aspect';
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './aspects';
export * from './auto-scaling-group';
export * from './schedule';
export * from './lifecycle-hook';
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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