From f42eae920ee82dc805965460f698c697c860ea8f Mon Sep 17 00:00:00 2001 From: Jacob Doetsch Date: Sat, 26 Dec 2020 22:35:31 -1000 Subject: [PATCH] fix(ec2): fix misconfiguration of arm-backed bastion host instances (#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 --- packages/@aws-cdk/aws-ec2/lib/bastion-host.ts | 25 +- .../@aws-cdk/aws-ec2/lib/instance-types.ts | 35 + .../aws-ec2/test/bastion-host.test.ts | 41 +- .../@aws-cdk/aws-ec2/test/instance.test.ts | 30 +- ...teg.bastion-host-arm-support.expected.json | 659 ++++++++++++++++++ .../test/integ.bastion-host-arm-support.ts | 26 + 6 files changed, 807 insertions(+), 9 deletions(-) create mode 100644 packages/@aws-cdk/aws-ec2/test/integ.bastion-host-arm-support.expected.json create mode 100644 packages/@aws-cdk/aws-ec2/test/integ.bastion-host-arm-support.ts diff --git a/packages/@aws-cdk/aws-ec2/lib/bastion-host.ts b/packages/@aws-cdk/aws-ec2/lib/bastion-host.ts index dbc6c45fe415a..d50e9f63ca9ce 100644 --- a/packages/@aws-cdk/aws-ec2/lib/bastion-host.ts +++ b/packages/@aws-cdk/aws-ec2/lib/bastion-host.ts @@ -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'; @@ -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, }); @@ -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; @@ -176,6 +177,7 @@ 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', @@ -183,6 +185,15 @@ export class BastionHostLinux extends Resource implements IInstance { }); } + /** + * 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 * diff --git a/packages/@aws-cdk/aws-ec2/lib/instance-types.ts b/packages/@aws-cdk/aws-ec2/lib/instance-types.ts index 12d7a483f8cf9..a49ecf440a2e6 100644 --- a/packages/@aws-cdk/aws-ec2/lib/instance-types.ts +++ b/packages/@aws-cdk/aws-ec2/lib/instance-types.ts @@ -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 */ @@ -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(); } /** @@ -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; + } } diff --git a/packages/@aws-cdk/aws-ec2/test/bastion-host.test.ts b/packages/@aws-cdk/aws-ec2/test/bastion-host.test.ts index e950a397707b8..909c7c25e3a98 100644 --- a/packages/@aws-cdk/aws-ec2/test/bastion-host.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/bastion-host.test.ts @@ -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) { @@ -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(); }, }); diff --git a/packages/@aws-cdk/aws-ec2/test/instance.test.ts b/packages/@aws-cdk/aws-ec2/test/instance.test.ts index 6d002e38af568..621afe55935b6 100644 --- a/packages/@aws-cdk/aws-ec2/test/instance.test.ts +++ b/packages/@aws-cdk/aws-ec2/test/instance.test.ts @@ -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'; @@ -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) { diff --git a/packages/@aws-cdk/aws-ec2/test/integ.bastion-host-arm-support.expected.json b/packages/@aws-cdk/aws-ec2/test/integ.bastion-host-arm-support.expected.json new file mode 100644 index 0000000000000..53ccea731ea20 --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/test/integ.bastion-host-arm-support.expected.json @@ -0,0 +1,659 @@ +{ + "Resources": { + "VPCB9E5F0B4": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": "10.0.0.0/16", + "EnableDnsHostnames": true, + "EnableDnsSupport": true, + "InstanceTenancy": "default", + "Tags": [ + { + "Key": "Name", + "Value": "TestStack/VPC" + } + ] + } + }, + "VPCPublicSubnet1SubnetB4246D30": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.0.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "TestStack/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1RouteTableFEE4B781": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "TestStack/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1RouteTableAssociation0B0896DC": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + } + } + }, + "VPCPublicSubnet1DefaultRoute91CEF279": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCPublicSubnet1EIP6AD938E8": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "TestStack/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet1NATGatewayE0556630": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet1EIP6AD938E8", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VPCPublicSubnet1SubnetB4246D30" + }, + "Tags": [ + { + "Key": "Name", + "Value": "TestStack/VPC/PublicSubnet1" + } + ] + } + }, + "VPCPublicSubnet2Subnet74179F39": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.32.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "TestStack/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet2RouteTable6F1A15F1": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "TestStack/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet2RouteTableAssociation5A808732": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + } + } + }, + "VPCPublicSubnet2DefaultRouteB7481BBA": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCPublicSubnet2EIP4947BC00": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "TestStack/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet2NATGateway3C070193": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet2EIP4947BC00", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VPCPublicSubnet2Subnet74179F39" + }, + "Tags": [ + { + "Key": "Name", + "Value": "TestStack/VPC/PublicSubnet2" + } + ] + } + }, + "VPCPublicSubnet3Subnet631C5E25": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.64.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": true, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Public" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Public" + }, + { + "Key": "Name", + "Value": "TestStack/VPC/PublicSubnet3" + } + ] + } + }, + "VPCPublicSubnet3RouteTable98AE0E14": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "TestStack/VPC/PublicSubnet3" + } + ] + } + }, + "VPCPublicSubnet3RouteTableAssociation427FE0C6": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet3RouteTable98AE0E14" + }, + "SubnetId": { + "Ref": "VPCPublicSubnet3Subnet631C5E25" + } + } + }, + "VPCPublicSubnet3DefaultRouteA0D29D46": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet3RouteTable98AE0E14" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + }, + "DependsOn": [ + "VPCVPCGW99B986DC" + ] + }, + "VPCPublicSubnet3EIPAD4BC883": { + "Type": "AWS::EC2::EIP", + "Properties": { + "Domain": "vpc", + "Tags": [ + { + "Key": "Name", + "Value": "TestStack/VPC/PublicSubnet3" + } + ] + } + }, + "VPCPublicSubnet3NATGatewayD3048F5C": { + "Type": "AWS::EC2::NatGateway", + "Properties": { + "AllocationId": { + "Fn::GetAtt": [ + "VPCPublicSubnet3EIPAD4BC883", + "AllocationId" + ] + }, + "SubnetId": { + "Ref": "VPCPublicSubnet3Subnet631C5E25" + }, + "Tags": [ + { + "Key": "Name", + "Value": "TestStack/VPC/PublicSubnet3" + } + ] + } + }, + "VPCPrivateSubnet1Subnet8BCA10E0": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.96.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1a", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "TestStack/VPC/PrivateSubnet1" + } + ] + } + }, + "VPCPrivateSubnet1RouteTableBE8A6027": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "TestStack/VPC/PrivateSubnet1" + } + ] + } + }, + "VPCPrivateSubnet1RouteTableAssociation347902D1": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + } + } + }, + "VPCPrivateSubnet1DefaultRouteAE1D6490": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet1RouteTableBE8A6027" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet1NATGatewayE0556630" + } + } + }, + "VPCPrivateSubnet2SubnetCFCDAA7A": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.128.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1b", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "TestStack/VPC/PrivateSubnet2" + } + ] + } + }, + "VPCPrivateSubnet2RouteTable0A19E10E": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "TestStack/VPC/PrivateSubnet2" + } + ] + } + }, + "VPCPrivateSubnet2RouteTableAssociation0C73D413": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet2SubnetCFCDAA7A" + } + } + }, + "VPCPrivateSubnet2DefaultRouteF4F5CFD2": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet2RouteTable0A19E10E" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet2NATGateway3C070193" + } + } + }, + "VPCPrivateSubnet3Subnet3EDCD457": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": "10.0.160.0/19", + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "AvailabilityZone": "test-region-1c", + "MapPublicIpOnLaunch": false, + "Tags": [ + { + "Key": "aws-cdk:subnet-name", + "Value": "Private" + }, + { + "Key": "aws-cdk:subnet-type", + "Value": "Private" + }, + { + "Key": "Name", + "Value": "TestStack/VPC/PrivateSubnet3" + } + ] + } + }, + "VPCPrivateSubnet3RouteTable192186F8": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "Tags": [ + { + "Key": "Name", + "Value": "TestStack/VPC/PrivateSubnet3" + } + ] + } + }, + "VPCPrivateSubnet3RouteTableAssociationC28D144E": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet3RouteTable192186F8" + }, + "SubnetId": { + "Ref": "VPCPrivateSubnet3Subnet3EDCD457" + } + } + }, + "VPCPrivateSubnet3DefaultRoute27F311AE": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPrivateSubnet3RouteTable192186F8" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "NatGatewayId": { + "Ref": "VPCPublicSubnet3NATGatewayD3048F5C" + } + } + }, + "VPCIGWB7E252D3": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Name", + "Value": "TestStack/VPC" + } + ] + } + }, + "VPCVPCGW99B986DC": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "VpcId": { + "Ref": "VPCB9E5F0B4" + }, + "InternetGatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, + "BastionHostInstanceSecurityGroupE75D4274": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "TestStack/BastionHost/Resource/InstanceSecurityGroup", + "SecurityGroupEgress": [ + { + "CidrIp": "0.0.0.0/0", + "Description": "Allow all outbound traffic by default", + "IpProtocol": "-1" + } + ], + "Tags": [ + { + "Key": "Name", + "Value": "BastionHost" + } + ], + "VpcId": { + "Ref": "VPCB9E5F0B4" + } + } + }, + "BastionHostInstanceRoleDD3FA5F1": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": { + "Fn::Join": [ + "", + [ + "ec2.", + { + "Ref": "AWS::URLSuffix" + } + ] + ] + } + } + } + ], + "Version": "2012-10-17" + }, + "Tags": [ + { + "Key": "Name", + "Value": "BastionHost" + } + ] + } + }, + "BastionHostInstanceRoleDefaultPolicy17347525": { + "Type": "AWS::IAM::Policy", + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "ssmmessages:*", + "ssm:UpdateInstanceInformation", + "ec2messages:*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "BastionHostInstanceRoleDefaultPolicy17347525", + "Roles": [ + { + "Ref": "BastionHostInstanceRoleDD3FA5F1" + } + ] + } + }, + "BastionHostInstanceProfile770FCA07": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": { + "Roles": [ + { + "Ref": "BastionHostInstanceRoleDD3FA5F1" + } + ] + } + }, + "BastionHost30F9ED05": { + "Type": "AWS::EC2::Instance", + "Properties": { + "AvailabilityZone": "test-region-1a", + "IamInstanceProfile": { + "Ref": "BastionHostInstanceProfile770FCA07" + }, + "ImageId": { + "Ref": "SsmParameterValueawsserviceamiamazonlinuxlatestamzn2amihvmarm64gp2C96584B6F00A464EAD1953AFF4B05118Parameter" + }, + "InstanceType": "t4g.nano", + "SecurityGroupIds": [ + { + "Fn::GetAtt": [ + "BastionHostInstanceSecurityGroupE75D4274", + "GroupId" + ] + } + ], + "SubnetId": { + "Ref": "VPCPrivateSubnet1Subnet8BCA10E0" + }, + "Tags": [ + { + "Key": "Name", + "Value": "BastionHost" + } + ], + "UserData": { + "Fn::Base64": "#!/bin/bash\nyum install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_arm64/amazon-ssm-agent.rpm" + } + }, + "DependsOn": [ + "BastionHostInstanceRoleDefaultPolicy17347525", + "BastionHostInstanceRoleDD3FA5F1" + ] + } + }, + "Outputs": { + "BastionHostBastionHostIdC743CBD6": { + "Description": "Instance ID of the bastion host. Use this to connect via SSM Session Manager", + "Value": { + "Ref": "BastionHost30F9ED05" + } + } + }, + "Parameters": { + "SsmParameterValueawsserviceamiamazonlinuxlatestamzn2amihvmarm64gp2C96584B6F00A464EAD1953AFF4B05118Parameter": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-arm64-gp2" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-ec2/test/integ.bastion-host-arm-support.ts b/packages/@aws-cdk/aws-ec2/test/integ.bastion-host-arm-support.ts new file mode 100644 index 0000000000000..06d6d12557ba9 --- /dev/null +++ b/packages/@aws-cdk/aws-ec2/test/integ.bastion-host-arm-support.ts @@ -0,0 +1,26 @@ +/* + * Stack verification steps: + * * aws ssm start-session --target + * * lscpu # Architecture should be aarch64 + */ +import * as cdk from '@aws-cdk/core'; +import * as ec2 from '../lib'; + +const app = new cdk.App(); + +class TestStack extends cdk.Stack { + constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + const vpc = new ec2.Vpc(this, 'VPC'); + + new ec2.BastionHostLinux(this, 'BastionHost', { + vpc, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.T4G, ec2.InstanceSize.NANO), + }); + } +} + +new TestStack(app, 'TestStack'); + +app.synth();