Skip to content

Commit

Permalink
feat(aws-ec2): add aspect to enable/disable imdsv1
Browse files Browse the repository at this point in the history
  • Loading branch information
jericht committed Aug 13, 2021
1 parent 1b29ca8 commit d95bbae
Show file tree
Hide file tree
Showing 6 changed files with 418 additions and 0 deletions.
18 changes: 18 additions & 0 deletions packages/@aws-cdk/aws-ec2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -980,6 +980,24 @@ instance.userData.addCommands(
);
```

### 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, you can use the either the `InstanceImdsAspect` for EC2 instances
or the `LaunchTemplateImdsAspect` for EC2 launch templates.

The following example demonstrates how to use the `InstanceImdsAspect` to disable IMDSv1 (thus enforcing IMDSv2) for all EC2 instances in a stack:

```ts
const aspect = new ec2.InstanceImdsAspect({
enableImdsV1: false,
});

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

## VPC Flow Logs

VPC Flow Logs is a feature that enables you to capture information about the IP traffic going to and from network interfaces in your VPC. Flow log data can be published to Amazon CloudWatch Logs and Amazon S3. After you've created a flow log, you can retrieve and view its data in the chosen destination. (<https://docs.aws.amazon.com/vpc/latest/userguide/flow-logs.html>).
Expand Down
166 changes: 166 additions & 0 deletions packages/@aws-cdk/aws-ec2/lib/aspects/imds-aspect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import * as cdk from '@aws-cdk/core';
import { CfnLaunchTemplate } from '../ec2.generated';
import { Instance } from '../instance';
import { LaunchTemplate } from '../launch-template';

/**
* Properties for `ImdsAspect`.
*/
interface ImdsAspectProps {
/**
* Whether IMDSv1 should be enabled or not.
*/
readonly enableImdsV1: boolean;

/**
* Whether warning annotations from this Aspect should be suppressed or not.
* @default false
*/
readonly suppressWarnings?: boolean;
}

/**
* Base class for IMDS configuration Aspect.
*/
abstract class ImdsAspect implements cdk.IAspect {
protected readonly enableImdsV1: boolean;
protected readonly suppressWarnings: boolean;

constructor(props: ImdsAspectProps) {
this.enableImdsV1 = props.enableImdsV1;
this.suppressWarnings = props.suppressWarnings ?? false;
}

abstract visit(node: cdk.IConstruct): void;

/**
* Adds a warning annotation to a node, unless `suppressWarnings` is true.
* @param node The scope to add the warning to.
* @param message The warning message.
*/
protected warn(node: cdk.IConstruct, message: string) {
if (this.suppressWarnings !== true) {
cdk.Annotations.of(node).addWarning(`${ImdsAspect.name} failed on node ${node.node.id}: ${message}`);
}
}
}

/**
* Properties for `InstanceImdsAspect`.
*/
export interface InstanceImdsAspectProps extends ImdsAspectProps {
/**
* Whether warnings that would be raised when an Instance is associated with an existing Launch Template
* should be suppressed or not.
*
* You can set this to `true` if `LaunchTemplateImdsAspect` is being used alongside this Aspect to
* suppress false-positive warnings because any Launch Templates associated with Instances will still be covered.
* @default false
*/
readonly suppressLaunchTemplateWarning?: boolean;
}

/**
* Aspect that applies IMDS configuration on EC2 Instance constructs.
*
* This aspect configures IMDS on an EC2 instance by creating a Launch Template with the
* IMDS configuration and associating that Launch Template with the instance. If an Instance
* is already associated with a Launch Template, a warning will (optionally) be added to the
* construct node and it will be skipped.
*
* To cover Instances already associated with Launch Templates, use `LaunchTemplateImdsAspect`.
*/
export class InstanceImdsAspect extends ImdsAspect {
private readonly suppressLaunchTemplateWarning: boolean;

constructor(props: InstanceImdsAspectProps) {
super(props);
this.suppressLaunchTemplateWarning = props.suppressLaunchTemplateWarning ?? false;
}

visit(node: cdk.IConstruct): void {
/* istanbul ignore next */
if (!(node instanceof Instance)) {
return;
}
if (node.instance.launchTemplate !== undefined) {
this.warn(node, 'Cannot toggle IMDSv1 because this Instance is associated with an existing Launch Template.');
return;
}

const name = `${node.node.id}LaunchTemplate`;
const launchTemplate = new CfnLaunchTemplate(node, 'LaunchTemplate', {
launchTemplateData: {
metadataOptions: {
httpTokens: this.enableImdsV1 ? 'optional' : 'required',
},
},
launchTemplateName: name,
});
node.instance.launchTemplate = {
launchTemplateName: name,
version: launchTemplate.getAtt('LatestVersionNumber').toString(),
};
}

protected warn(node: cdk.IConstruct, message: string) {
if (this.suppressLaunchTemplateWarning !== true) {
super.warn(node, message);
}
}
}

/**
* Properties for `LaunchTemplateImdsAspect`.
*/
export interface LaunchTemplateImdsAspectProps extends ImdsAspectProps {}

/**
* Aspect that applies IMDS configuration on EC2 Launch Template constructs.
* @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-launchtemplate-launchtemplatedata-metadataoptions.html
*/
export class LaunchTemplateImdsAspect extends ImdsAspect {
constructor(props: LaunchTemplateImdsAspectProps) {
super(props);
}

visit(node: cdk.IConstruct): void {
/* istanbul ignore next */
if (!(node instanceof LaunchTemplate)) {
return;
}

const launchTemplate = node.node.tryFindChild('Resource') as CfnLaunchTemplate;
if (launchTemplate === undefined || !(launchTemplate instanceof CfnLaunchTemplate)) {
this.warn(node, 'CfnLaunchTemplate cannot be found because the LaunchTemplate construct implementation has changed.');
return;
}

const data = launchTemplate.launchTemplateData;
if (data !== undefined && implementsIResolvable(data)) {
this.warn(node, 'LaunchTemplateData is a CDK token.');
return;
}

const metadataOptions = (data as CfnLaunchTemplate.LaunchTemplateDataProperty).metadataOptions;
if (metadataOptions !== undefined && implementsIResolvable(metadataOptions)) {
this.warn(node, 'LaunchTemplateData.MetadataOptions is a CDK token.');
return;
}

const newData: CfnLaunchTemplate.LaunchTemplateDataProperty = {
...data,
metadataOptions: {
...metadataOptions,
httpTokens: this.enableImdsV1 ? 'optional' : 'required',
},
};
launchTemplate.launchTemplateData = newData;
}
}

function implementsIResolvable(obj: any): boolean {
return 'resolve' in obj && typeof(obj.resolve) === 'function' &&
'creationStack' in obj && Array.isArray(obj.creationStack) &&
'toString' in obj && typeof(obj.toString) === 'function';
}
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-ec2/lib/aspects/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './imds-aspect';
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-ec2/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './aspects';
export * from './bastion-host';
export * from './connections';
export * from './cfn-init';
Expand Down
2 changes: 2 additions & 0 deletions packages/@aws-cdk/aws-ec2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,14 @@
"devDependencies": {
"@types/aws-lambda": "^8.10.79",
"@types/jest": "^26.0.24",
"@types/sinon": "^9.0.11",
"@aws-cdk/cx-api": "0.0.0",
"cdk-build-tools": "0.0.0",
"cdk-integ-tools": "0.0.0",
"cfn2ts": "0.0.0",
"nodeunit-shim": "0.0.0",
"pkglint": "0.0.0",
"sinon": "^9.2.4",
"@aws-cdk/cloud-assembly-schema": "0.0.0",
"@aws-cdk/assert-internal": "0.0.0"
},
Expand Down
Loading

0 comments on commit d95bbae

Please sign in to comment.