-
Notifications
You must be signed in to change notification settings - Fork 4k
/
Copy pathbastion-host.ts
241 lines (213 loc) · 7.11 KB
/
bastion-host.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
import { Construct } from 'constructs';
import { InstanceArchitecture, InstanceClass, InstanceSize, InstanceType } from '.';
import { CloudFormationInit } from './cfn-init';
import { Connections } from './connections';
import { ApplyCloudFormationInitOptions, IInstance, Instance } from './instance';
import { AmazonLinuxCpuType, IMachineImage, MachineImage } from './machine-image';
import { IPeer } from './peer';
import { Port } from './port';
import { ISecurityGroup } from './security-group';
import { BlockDevice } from './volume';
import { IVpc, SubnetSelection } from './vpc';
import { IPrincipal, IRole, PolicyStatement } from '../../aws-iam';
import { CfnOutput, Resource, Stack } from '../../core';
/**
* Properties of the bastion host
*
*
*/
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;
/**
* The machine image to use, assumed to have SSM Agent preinstalled.
*
* @default - An Amazon Linux 2 image which is kept up-to-date automatically (the instance
* may be replaced on every deployment) and already has SSM Agent installed.
*/
readonly machineImage?: IMachineImage;
/**
* Specifies how block devices are exposed to the instance. You can specify virtual devices and EBS volumes.
*
* Each instance that is launched has an associated root device volume,
* either an Amazon EBS volume or an instance store volume.
* You can use block device mappings to specify additional EBS volumes or
* instance store volumes to attach to an instance when it is launched.
*
* @see https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/block-device-mapping-concepts.html
*
* @default - Uses the block device mapping of the AMI
*/
readonly blockDevices?: BlockDevice[];
/**
* Apply the given CloudFormation Init configuration to the instance at startup
*
* @default - no CloudFormation init
*/
readonly init?: CloudFormationInit;
/**
* Use the given options for applying CloudFormation Init
*
* Describes the configsets to use and the timeout to wait
*
* @default - default options
*/
readonly initOptions?: ApplyCloudFormationInitOptions;
/**
* Whether IMDSv2 should be required on this instance
*
* @default - false
*/
readonly requireImdsv2?: boolean;
}
/**
* 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
*
*
* @resource AWS::EC2::Instance
*/
export class BastionHostLinux extends Resource 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 principal to grant permissions to
*/
public readonly grantPrincipal: IPrincipal;
/**
* 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);
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,
machineImage: props.machineImage ?? MachineImage.latestAmazonLinux2({
cpuType: this.toAmazonLinuxCpuType(instanceType.architecture),
}),
vpcSubnets: props.subnetSelection ?? {},
blockDevices: props.blockDevices ?? undefined,
init: props.init,
initOptions: props.initOptions,
requireImdsv2: props.requireImdsv2 ?? false,
});
this.instance.addToRolePolicy(new PolicyStatement({
actions: [
'ssmmessages:*',
'ssm:UpdateInstanceInformation',
'ec2messages:*',
],
resources: ['*'],
}));
this.connections = this.instance.connections;
this.role = this.instance.role;
this.grantPrincipal = 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,
});
}
/**
* Returns the AmazonLinuxCpuType corresponding to the given instance architecture
* @param architecture the instance architecture value to convert
*/
private toAmazonLinuxCpuType(architecture: InstanceArchitecture): AmazonLinuxCpuType {
if (architecture === InstanceArchitecture.ARM_64) {
return AmazonLinuxCpuType.ARM_64;
} else if (architecture === InstanceArchitecture.X86_64) {
return AmazonLinuxCpuType.X86_64;
}
throw new Error(`Unsupported instance architecture '${architecture}'`);
}
/**
* 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');
});
}
}