-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(aws-autoscaling): add aspect to enable/disable imdsv1
- Loading branch information
Showing
6 changed files
with
214 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
83 changes: 83 additions & 0 deletions
83
packages/@aws-cdk/aws-autoscaling/lib/aspects/imds-aspect.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
import * as cdk from '@aws-cdk/core'; | ||
import { AutoScalingGroup } from '../auto-scaling-group'; | ||
import { CfnLaunchConfiguration } from '../autoscaling.generated'; | ||
|
||
/** | ||
* 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 `AutoScalingGroupImdsAspect`. | ||
*/ | ||
export interface AutoScalingGroupImdsAspectProps extends ImdsAspectProps {} | ||
|
||
/** | ||
* Aspect that applies IMDS configuration to AutoScalingGroups. | ||
*/ | ||
export class AutoScalingGroupImdsAspect extends ImdsAspect { | ||
constructor(props: AutoScalingGroupImdsAspectProps) { | ||
super(props); | ||
} | ||
|
||
visit(node: cdk.IConstruct): void { | ||
/* istanbul ignore next */ | ||
if (node === undefined || !(node instanceof AutoScalingGroup)) { | ||
return; | ||
} | ||
|
||
const launchConfig = node.node.tryFindChild('LaunchConfig') as CfnLaunchConfiguration; | ||
if (launchConfig.metadataOptions !== undefined && implementsIResolvable(launchConfig.metadataOptions)) { | ||
this.warn(node, 'CfnLaunchConfiguration.MetadataOptions field is a CDK token.'); | ||
return; | ||
} | ||
|
||
launchConfig.metadataOptions = { | ||
...launchConfig.metadataOptions, | ||
httpTokens: this.enableImdsV1 ? 'optional' : 'required', | ||
}; | ||
} | ||
} | ||
|
||
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'; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './imds-aspect'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
110 changes: 110 additions & 0 deletions
110
packages/@aws-cdk/aws-autoscaling/test/aspects/imds-aspect.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
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 * as sinon from 'sinon'; | ||
import { | ||
AutoScalingGroup, | ||
AutoScalingGroupImdsAspect, | ||
CfnLaunchConfiguration, | ||
} from '../../lib'; | ||
|
||
describe('ImdsAspect', () => { | ||
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('suppresses warnings', () => { | ||
// GIVEN | ||
const aspect = new AutoScalingGroupImdsAspect({ | ||
enableImdsV1: true, | ||
suppressWarnings: true, | ||
}); | ||
const errmsg = 'ERROR'; | ||
const stub = sinon.stub(aspect, 'visit').callsFake((node) => { | ||
// @ts-ignore | ||
aspect.warn(node, errmsg); | ||
}); | ||
const construct = new cdk.Construct(stack, 'Construct'); | ||
|
||
// WHEN | ||
aspect.visit(construct); | ||
|
||
// THEN | ||
expect(stub.calledOnce).toBeTruthy(); | ||
expect(construct.node.metadataEntry).not.toContainEqual({ | ||
data: expect.stringContaining(errmsg), | ||
type: 'aws:cdk:warning', | ||
trace: undefined, | ||
}); | ||
}); | ||
|
||
describe('AutoScalingGroupImdsAspect', () => { | ||
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 AutoScalingGroupImdsAspect({ enableImdsV1: false }); | ||
|
||
// WHEN | ||
aspect.visit(asg); | ||
|
||
// THEN | ||
expect(asg.node.metadataEntry).toContainEqual({ | ||
data: expect.stringContaining('CfnLaunchConfiguration.MetadataOptions field is a CDK token.'), | ||
type: 'aws:cdk:warning', | ||
trace: undefined, | ||
}); | ||
expectCDK(stack).notTo(haveResourceLike('AWS::AutoScaling::LaunchConfiguration', { | ||
MetadataOptions: { | ||
HttpTokens: 'required', | ||
}, | ||
})); | ||
}); | ||
|
||
test.each([ | ||
[true], | ||
[false], | ||
])('toggles IMDSv1 (enabled=%s)', (enableImdsV1: boolean) => { | ||
// GIVEN | ||
const asg = new AutoScalingGroup(stack, 'AutoScalingGroup', { | ||
vpc, | ||
instanceType: new ec2.InstanceType('t2.micro'), | ||
machineImage: ec2.MachineImage.latestAmazonLinux(), | ||
}); | ||
const aspect = new AutoScalingGroupImdsAspect({ enableImdsV1 }); | ||
|
||
// WHEN | ||
aspect.visit(asg); | ||
|
||
// THEN | ||
expectCDK(stack).to(haveResourceLike('AWS::AutoScaling::LaunchConfiguration', { | ||
MetadataOptions: { | ||
HttpTokens: enableImdsV1 ? 'optional' : 'required', | ||
}, | ||
})); | ||
}); | ||
}); | ||
}); | ||
|
||
function fakeToken(): cdk.IResolvable { | ||
return { | ||
creationStack: [], | ||
resolve: (_c) => {}, | ||
toString: () => '', | ||
}; | ||
} |