-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathEC2BastionHostConstruct.ts
More file actions
169 lines (155 loc) · 5.25 KB
/
EC2BastionHostConstruct.ts
File metadata and controls
169 lines (155 loc) · 5.25 KB
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
import { Construct } from "constructs";
import * as cdk from "aws-cdk-lib";
import * as ec2 from "aws-cdk-lib/aws-ec2";
import * as iam from "aws-cdk-lib/aws-iam";
/**
* Properties for configuring the EC2BastionHostConstruct.
*/
export interface EC2BastionHostConstructProps extends cdk.StackProps {
/**
* The application name to use as a prefix in resource names.
*/
appName: string;
/**
* The environment tag to apply to EC2 resources.
*/
tagEnv: string;
/**
* The VPC where the bastion host will be deployed.
*/
vpc: ec2.IVpc;
}
/**
* Creates an EC2 bastion host for secure administrative access to resources within a VPC.
*
* This construct deploys an Amazon Linux 2023 ARM64 instance in a private subnet with
* security groups configured for SSM Session Manager access. No public IP is assigned
* to enhance security.
*
* The bastion host follows security best practices:
* - Uses SSM for connection instead of SSH key pairs
* - No public IP address
* - IMDSv2 required (organization requirement)
* - Restrictive security group rules
* - Minimal IAM permissions
*
* @example
* const bastionHost = new EC2BastionHostConstruct(this, 'BastionHost', {
* appName: 'my-app',
* tagEnv: 'production',
* vpc: myVpc,
* });
*/
export class EC2BastionHostConstruct extends Construct {
/**
* The created EC2 instance resource.
*/
public readonly ec2Instance: ec2.CfnInstance;
/**
* Constructs a new instance of the EC2BastionHostConstruct.
*
* @param {Construct} scope - The parent construct, typically a CDK stack.
* @param {string} id - The unique identifier for this construct.
* @param {EC2BastionHostConstructProps} props - Properties for configuring the EC2 bastion host.
*/
constructor(
scope: Construct,
id: string,
props: EC2BastionHostConstructProps
) {
super(scope, id);
/* Create EC2 Security Group with restricted permissions */
const ec2SecurityGroup = new ec2.SecurityGroup(
this,
`${props.appName}-ec2-security-group`,
{
vpc: props.vpc,
securityGroupName: `${props.appName}-ec2-security-group`,
}
);
/* [CRITICAL] - Allow HTTPS traffic for SSM connectivity */
ec2SecurityGroup.addEgressRule(
ec2.Peer.anyIpv4(),
ec2.Port.tcp(443),
"Allow HTTPS traffic for SSM"
);
/* Allow all outbound connections for EC2 instance (can be restricted further) */
ec2SecurityGroup.addEgressRule(ec2.Peer.anyIpv4(), ec2.Port.allTraffic());
/* Define Amazon Linux 2023 ARM64 AMI for cost and performance optimization */
const ami = new ec2.AmazonLinuxImage({
generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2023,
cpuType: ec2.AmazonLinuxCpuType.ARM_64,
edition: ec2.AmazonLinuxEdition.STANDARD,
}).getImage(this).imageId;
/* Create EC2 Launch Template with organization-required security settings */
const ec2InstanceLaunchTemplate = new ec2.CfnLaunchTemplate(
this,
`${props.appName}-ec2-launch-template`,
{
launchTemplateData: {
imageId: ami,
instanceType: "t4g.micro",
metadataOptions: {
httpEndpoint: "enabled",
httpPutResponseHopLimit: 1 /* Organization requirement 1: maximum of 1 hop */,
httpTokens:
"required" /* Organization requirement 2: require IMDSv2 */,
instanceMetadataTags: "enabled",
},
networkInterfaces: [
{
associatePublicIpAddress:
false /* Enhanced security: no public IP */,
deviceIndex: 0,
subnetId: props.vpc.privateSubnets[0].subnetId,
groups: [ec2SecurityGroup.securityGroupId],
},
],
monitoring: {
enabled: true,
},
tagSpecifications: [
{
resourceType: "instance",
tags: [
{ key: "Name", value: `${props.appName}-bastion-host` },
{ key: "Environment", value: props.tagEnv },
{ key: "Role", value: "BastionHost" },
{ key: "Project", value: props.appName },
],
},
],
},
}
);
/* Create IAM role with minimal permissions for SSM access */
const ec2IamRole = new iam.Role(this, `${props.appName}-ec2-iam-role`, {
assumedBy: new iam.ServicePrincipal("ec2.amazonaws.com"),
roleName: `${props.appName}-ec2-iam-role`,
});
/* Attach SSM managed policy for Session Manager connectivity */
ec2IamRole.addManagedPolicy(
iam.ManagedPolicy.fromAwsManagedPolicyName("AmazonSSMManagedInstanceCore")
);
/* Create instance profile to associate IAM role with EC2 */
const instanceProfile = new iam.CfnInstanceProfile(
this,
`${props.appName}-instance-profile`,
{
roles: [ec2IamRole.roleName],
}
);
/* Instantiate the EC2 instance using the launch template */
this.ec2Instance = new ec2.CfnInstance(
this,
`${props.appName}-ec2-instance`,
{
iamInstanceProfile: instanceProfile.ref,
launchTemplate: {
launchTemplateId: ec2InstanceLaunchTemplate.ref,
version: ec2InstanceLaunchTemplate.attrLatestVersionNumber,
},
}
);
}
}