Skip to content

Commit

Permalink
fix(ec2): fix misconfiguration of arm-backed bastion host instances (#…
Browse files Browse the repository at this point in the history
…12279)

Adds support for ARM-based instance types used with BastionHostLinux: identifies ARM-based instance types and uses the architecutre-appropriate Amazon Linux 2 AMI and SSM Agent package.

Fixes #12279
  • Loading branch information
Jacob-Doetsch committed Jan 3, 2021
1 parent 269c241 commit f42eae9
Show file tree
Hide file tree
Showing 6 changed files with 807 additions and 9 deletions.
25 changes: 18 additions & 7 deletions packages/@aws-cdk/aws-ec2/lib/bastion-host.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { IPrincipal, IRole, PolicyStatement } from '@aws-cdk/aws-iam';
import { CfnOutput, Resource, Stack } from '@aws-cdk/core';
import { Construct } from 'constructs';
import { AmazonLinuxGeneration, InstanceClass, InstanceSize, InstanceType } from '.';
import { AmazonLinuxGeneration, InstanceArchitecture, InstanceClass, InstanceSize, InstanceType } from '.';
import { Connections } from './connections';
import { IInstance, Instance } from './instance';
import { IMachineImage, MachineImage } from './machine-image';
import { AmazonLinuxCpuType, IMachineImage, MachineImage } from './machine-image';
import { IPeer } from './peer';
import { Port } from './port';
import { ISecurityGroup } from './security-group';
Expand Down Expand Up @@ -146,14 +146,17 @@ export class BastionHostLinux extends Resource implements IInstance {
constructor(scope: Construct, id: string, props: BastionHostLinuxProps) {
super(scope, id);
this.stack = Stack.of(scope);

const instanceType = props.instanceType ?? InstanceType.of(InstanceClass.T3, InstanceSize.NANO);
this.instance = new Instance(this, 'Resource', {
vpc: props.vpc,
availabilityZone: props.availabilityZone,
securityGroup: props.securityGroup,
instanceName: props.instanceName ?? 'BastionHost',
instanceType: props.instanceType ?? InstanceType.of(InstanceClass.T3, InstanceSize.NANO),
machineImage: props.machineImage ?? MachineImage.latestAmazonLinux({ generation: AmazonLinuxGeneration.AMAZON_LINUX_2 }),
instanceType,
machineImage: props.machineImage ?? MachineImage.latestAmazonLinux({
generation: AmazonLinuxGeneration.AMAZON_LINUX_2,
cpuType: instanceType.architecture as string as AmazonLinuxCpuType,
}),
vpcSubnets: props.subnetSelection ?? {},
blockDevices: props.blockDevices ?? undefined,
});
Expand All @@ -165,8 +168,6 @@ export class BastionHostLinux extends Resource implements IInstance {
],
resources: ['*'],
}));
this.instance.addUserData('yum install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm');

this.connections = this.instance.connections;
this.role = this.instance.role;
this.grantPrincipal = this.instance.role;
Expand All @@ -176,13 +177,23 @@ export class BastionHostLinux extends Resource implements IInstance {
this.instancePrivateDnsName = this.instance.instancePrivateDnsName;
this.instancePublicIp = this.instance.instancePublicIp;
this.instancePublicDnsName = this.instance.instancePublicDnsName;
this.applyUserData();

new CfnOutput(this, 'BastionHostId', {
description: 'Instance ID of the bastion host. Use this to connect via SSM Session Manager',
value: this.instanceId,
});
}

/**
* Install the architecture-appropriate SSM Agent package
*/
private applyUserData(): void {
const instanceType = new InstanceType(this.instance.instance.instanceType!);
const ssmAgentArch = instanceType.architecture === InstanceArchitecture.ARM_64 ? 'linux_arm64' : 'linux_amd64';
this.instance.addUserData(`yum install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/${ssmAgentArch}/amazon-ssm-agent.rpm`);
}

/**
* Allow SSH access from the given peer or peers
*
Expand Down
35 changes: 35 additions & 0 deletions packages/@aws-cdk/aws-ec2/lib/instance-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,21 @@ export enum InstanceClass {
INF1 = 'inf1'
}

/**
* Identifies an instance's CPU architecture
*/
export enum InstanceArchitecture {
/**
* ARM64 architecture
*/
ARM_64 = 'arm64',

/**
* x86-64 architecture
*/
X86_64 = 'x86_64',
}

/**
* What size of instance to use
*/
Expand Down Expand Up @@ -556,7 +571,13 @@ export class InstanceType {
return new InstanceType(`${instanceClass}.${instanceSize}`);
}

/**
* The instance's CPU architecture
*/
public readonly architecture: InstanceArchitecture;

constructor(private readonly instanceTypeIdentifier: string) {
this.architecture = this.resolveArchitecture();
}

/**
Expand All @@ -565,4 +586,18 @@ export class InstanceType {
public toString(): string {
return this.instanceTypeIdentifier;
}

/**
* Returns the CPU architecture for the instance type
*/
private resolveArchitecture(): InstanceArchitecture {
const instanceClass = this.instanceTypeIdentifier.split('.')[0];

// Graviton-based (a1, t4g, c6g, c6gd, c6gn, m6g, m6gd, r6g, r6gd)
if (/^a1|[cmrt][\d]g[dn]?$/.test(instanceClass)) {
return InstanceArchitecture.ARM_64;
}

return InstanceArchitecture.X86_64;
}
}
41 changes: 40 additions & 1 deletion packages/@aws-cdk/aws-ec2/test/bastion-host.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { expect, haveResource } from '@aws-cdk/assert';
import { Stack } from '@aws-cdk/core';
import { nodeunitShim, Test } from 'nodeunit-shim';
import { BastionHostLinux, BlockDeviceVolume, SubnetType, Vpc } from '../lib';
import { BastionHostLinux, BlockDeviceVolume, InstanceClass, InstanceSize, InstanceType, SubnetType, Vpc } from '../lib';

nodeunitShim({
'default instance is created in basic'(test: Test) {
Expand Down Expand Up @@ -83,6 +83,45 @@ nodeunitShim({
],
}));

test.done();
},
'x86-64 instances use x86-64 ssm agent package'(test: Test) {
// GIVEN
const stack = new Stack();
const vpc = new Vpc(stack, 'VPC');

// WHEN
new BastionHostLinux(stack, 'Bastion', {
vpc,
});

// THEN
expect(stack).to(haveResource('AWS::EC2::Instance', {
UserData: {
'Fn::Base64': '#!/bin/bash\nyum install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm',
},
}));

test.done();
},
'arm instances use arm ssm agent package'(test: Test) {
// GIVEN
const stack = new Stack();
const vpc = new Vpc(stack, 'VPC');

// WHEN
new BastionHostLinux(stack, 'Bastion', {
vpc,
instanceType: InstanceType.of(InstanceClass.T4G, InstanceSize.NANO),
});

// THEN
expect(stack).to(haveResource('AWS::EC2::Instance', {
UserData: {
'Fn::Base64': '#!/bin/bash\nyum install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_arm64/amazon-ssm-agent.rpm',
},
}));

test.done();
},
});
30 changes: 29 additions & 1 deletion packages/@aws-cdk/aws-ec2/test/instance.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Stack } from '@aws-cdk/core';
import { nodeunitShim, Test } from 'nodeunit-shim';
import {
AmazonLinuxImage, BlockDeviceVolume, CloudFormationInit,
EbsDeviceVolumeType, InitCommand, Instance, InstanceClass, InstanceSize, InstanceType, UserData, Vpc,
EbsDeviceVolumeType, InitCommand, Instance, InstanceArchitecture, InstanceClass, InstanceSize, InstanceType, UserData, Vpc,
} from '../lib';


Expand Down Expand Up @@ -107,6 +107,34 @@ nodeunitShim({

test.done();
},
'instance architecture is correctly discerned for arm instances'(test: Test) {
// GIVEN
const sampleInstanceClasses = ['a1', 't4g', 'c6g', 'c6gd', 'c6gn', 'm6g', 'm6gd', 'r6g', 'r6gd']; // Graviton-based

for (const instanceClass of sampleInstanceClasses) {
// WHEN
const instanceType = InstanceType.of(instanceClass as InstanceClass, InstanceSize.NANO);

// THEN
expect(instanceType.architecture).toBe(InstanceArchitecture.ARM_64);
}

test.done();
},
'instance architecture is correctly discerned for x86-64 instance'(test: Test) {
// GIVEN
const sampleInstanceClasses = ['c5', 'm5ad', 'r5n', 'm6', 't3a']; // A sample of x86-64 instance classes

for (const instanceClass of sampleInstanceClasses) {
// WHEN
const instanceType = InstanceType.of(instanceClass as InstanceClass, InstanceSize.NANO);

// THEN
expect(instanceType.architecture).toBe(InstanceArchitecture.X86_64);
}

test.done();
},

blockDeviceMappings: {
'can set blockDeviceMappings'(test: Test) {
Expand Down
Loading

0 comments on commit f42eae9

Please sign in to comment.