Skip to content

Commit

Permalink
feat(ec2): add Instance and Bastion Host (#3697)
Browse files Browse the repository at this point in the history
Add Instance resource and create Bastion Host construct based on it.

Fixes #3174 and #1713.
  • Loading branch information
hoegertn authored and rix0rrr committed Aug 23, 2019
1 parent d950d8e commit ef09aba
Show file tree
Hide file tree
Showing 11 changed files with 7,337 additions and 463 deletions.
22 changes: 22 additions & 0 deletions packages/@aws-cdk/aws-ec2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -420,3 +420,25 @@ A VPC endpoint enables you to privately connect your VPC to supported AWS servic
Endpoints are virtual devices. They are horizontally scaled, redundant, and highly available VPC components that allow communication between instances in your VPC and services without imposing availability risks or bandwidth constraints on your network traffic.

[example of setting up VPC endpoints](test/integ.vpc-endpoint.lit.ts)

### Bastion Hosts
A bastion host functions as an instance used to access servers and resources in a VPC without open up the complete VPC on a network level.
You can use bastion hosts using a standard SSH connection targetting port 22 on the host. As an alternative, you can connect the SSH connection
feature of AWS Systems Manager Session Manager, which does not need an opened security group. (https://aws.amazon.com/about-aws/whats-new/2019/07/session-manager-launches-tunneling-support-for-ssh-and-scp/)

A default bastion host for use via SSM can be configured like:
```ts
const host = new ec2.BastionHostLinux(this, 'BastionHost', { vpc });
```

If you want to connect from the internet using SSH, you need to place the host into a public subnet. You can then configure allowed source hosts.
```ts
const host = new ec2.BastionHostLinux(this, 'BastionHost', {
vpc,
subnetSelection: { subnetType: SubnetType.PUBLIC },
});
host.allowSshAccessFrom(Peer.ipv4('1.2.3.4/32'));
```

As there are no SSH public keys deployed on this machine, you need to use [EC2 Instance Connect](https://aws.amazon.com/de/blogs/compute/new-using-amazon-ec2-instance-connect-for-ssh-access-to-your-ec2-instances/)
with the command `aws ec2-instance-connect send-ssh-public-key` to provide your SSH public key.
164 changes: 164 additions & 0 deletions packages/@aws-cdk/aws-ec2/lib/bastion-host.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import { IRole, PolicyStatement } from "@aws-cdk/aws-iam";
import { CfnOutput, Construct, Stack } from "@aws-cdk/core";
import { AmazonLinuxGeneration, AmazonLinuxImage, InstanceClass, InstanceSize, InstanceType } from ".";
import { Connections } from "./connections";
import { IInstance, Instance } from "./instance";
import { IPeer } from "./peer";
import { Port } from "./port";
import { ISecurityGroup } from "./security-group";
import { IVpc, SubnetSelection, SubnetType } from "./vpc";

/**
* Properties of the bastion host
*
* @experimental
*/
export interface BastionHostLinuxProps {

/**
* In which AZ to place the instance within the VPC
*
* @default - Random zone.
*/
readonly availabilityZone?: string;

/**
* VPC to launch the instance in.
*/
readonly vpc: IVpc;

/**
* The name of the instance
*
* @default 'BastionHost'
*/
readonly instanceName?: string;

/**
* Select the subnets to run the bastion host in.
* Set this to PUBLIC if you need to connect to this instance via the internet and cannot use SSM.
* You have to allow port 22 manually by using the connections field
*
* @default - private subnets of the supplied VPC
*/
readonly subnetSelection?: SubnetSelection;

/**
* Security Group to assign to this instance
*
* @default - create new security group with no inbound and all outbound traffic allowed
*/
readonly securityGroup?: ISecurityGroup;

/**
* Type of instance to launch
* @default 't3.nano'
*/
readonly instanceType?: InstanceType;

}

/**
* This creates a linux bastion host you can use to connect to other instances or services in your VPC.
* The recommended way to connect to the bastion host is by using AWS Systems Manager Session Manager.
*
* The operating system is Amazon Linux 2 with the latest SSM agent installed
*
* You can also configure this bastion host to allow connections via SSH
*
* @experimental
*/
export class BastionHostLinux extends Construct implements IInstance {
public readonly stack: Stack;

/**
* Allows specify security group connections for the instance.
*/
public readonly connections: Connections;

/**
* The IAM role assumed by the instance.
*/
public readonly role: IRole;

/**
* The underlying instance resource
*/
public readonly instance: Instance;

/**
* @attribute
*/
public readonly instanceId: string;

/**
* @attribute
*/
public readonly instanceAvailabilityZone: string;

/**
* @attribute
*/
public readonly instancePrivateDnsName: string;
/**
* @attribute
*/
public readonly instancePrivateIp: string;
/**
* @attribute
*/
public readonly instancePublicDnsName: string;
/**
* @attribute
*/
public readonly instancePublicIp: string;

constructor(scope: Construct, id: string, props: BastionHostLinuxProps) {
super(scope, id);
this.stack = Stack.of(scope);
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: new AmazonLinuxImage({ generation: AmazonLinuxGeneration.AMAZON_LINUX_2 }),
vpcSubnets: props.subnetSelection || { subnetType: SubnetType.PRIVATE },
});
this.instance.addToRolePolicy(new PolicyStatement({
actions: [
'ssmmessages:*',
'ssm:UpdateInstanceInformation',
'ec2messages:*'
],
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.instanceId = this.instance.instanceId;
this.instancePrivateIp = this.instance.instancePrivateIp;
this.instanceAvailabilityZone = this.instance.instanceAvailabilityZone;
this.instancePrivateDnsName = this.instance.instancePrivateDnsName;
this.instancePublicIp = this.instance.instancePublicIp;
this.instancePublicDnsName = this.instance.instancePublicDnsName;

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

/**
* Allow SSH access from the given peer or peers
*
* Necessary if you want to connect to the instance using ssh. If not
* called, you should use SSM Session Manager to connect to the instance.
*/
public allowSshAccessFrom(...peer: IPeer[]): void {
peer.forEach(p => {
this.connections.allowFrom(p, Port.tcp(22), 'SSH access');
});
}
}
2 changes: 2 additions & 0 deletions packages/@aws-cdk/aws-ec2/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export * from './bastion-host';
export * from './connections';
export * from './instance-types';
export * from './instance';
export * from './machine-image';
export * from './port';
export * from './security-group';
Expand Down
Loading

0 comments on commit ef09aba

Please sign in to comment.