I tried building an environment using AWS CDK to connect to an EC2 in a private subnet utilizing EC2 Instance Connect
Hello! I'm Kobayashi from the Manufacturing Business Technology Department.
This time, I implemented an environment to connect to EC2 instances using private IP addresses and EC2 Instance Connect (EIC) with AWS CDK.
What is EC2 Instance Connect?
EC2 Instance Connect is a service that supports SSH and RDP connections to EC2 instances using public/private IP addresses from the internet without the need for bastion hosts or VPNs.
Benefits of using EC2 Instance Connect
Using EC2 Instance Connect provides the following benefits:
- No need to assign public IP addresses to EC2 instances, eliminating direct access from the internet
- No need to create key pairs or instance profiles
- EC2 Instance Connect endpoint service is free of charge
Connecting to EC2 using private IP addresses and EC2 Instance Connect Endpoint
Connecting to EC2 using private IP addresses and EC2 Instance Connect Endpoint looks like this:
Let's try it
Now let's build the following resources using AWS CDK:
- Create a VPC
- Create a security group for EC2 instance
- Create a security group for EIC endpoint
- Configure security group communication settings
- Create an EIC endpoint
- Create an EC2 instance
Creating a VPC
First, let's create a VPC and define a subnet of PRIVATE_ISOLATED type.
/**
* Creating a VPC
*/
const vpc = new ec2.Vpc(this, 'Vpc', {
maxAzs: 2,
subnetConfiguration: [
{
cidrMask: 24,
name: 'PrivateIsolated',
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
},
],
});
Creating Security Groups
Let's create dedicated security groups for both the EC2 instance and the EIC endpoint.
Create a security group for EC2 instance
/**
* Security group for EC2 instance
* SSH connections to the EC2 instance are only allowed from the EC2 Instance Connect Endpoint security group
*/
const instanceSg = new ec2.SecurityGroup(this, 'Ec2InstanceSg', {
vpc,
allowAllOutbound: false, // Restrict outbound traffic
});
Create a security group for EIC endpoint
/**
* Security group for EC2 Instance Connect Endpoint
*/
const eicSg = new ec2.SecurityGroup(this, 'EicEndpointSg', {
vpc,
allowAllOutbound: false, // Restrict outbound traffic
});
```## Security Group Communication Settings
We will configure the EC2 instance security group to allow only SSH (port 22) from the EIC endpoint security group. This ensures that connections are only permitted through the EIC endpoint.
```ts
// Allow SSH connection from EC2 Instance Connect Endpoint to EC2 instance
eicSg.connections.allowTo(
instanceSg,
ec2.Port.tcp(22),
);
In this case, we're using the convenient Connections feature to write this simply. For more details, please see:
Create EIC Endpoint
Create an EC2 Instance Connect Endpoint in the VPC. For subnetId, specify the ID of the subnet we created earlier.
// Creating EC2 Instance Connect Endpoint
const eicEndpoint = new ec2.CfnInstanceConnectEndpoint(this, 'EicEndpoint', {
subnetId: vpc.isolatedSubnets[0].subnetId,
securityGroupIds: [eicSg.securityGroupId],
});
```## Create EC2
Create an Amazon Linux 2023 EC2 instance in the PRIVATE_ISOLATED subnet.
```ts
/**
* Create EC2 instance
*/
const ec2Instance = new ec2.Instance(this, 'Ec2Instance', {
vpc,
vpcSubnets: {
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
},
instanceType: ec2.InstanceType.of(
ec2.InstanceClass.T3,
ec2.InstanceSize.MICRO
),
machineImage: new ec2.AmazonLinuxImage({
generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2023,
}),
securityGroup: instanceSg,
disableApiTermination: false, // Allow instance deletion for testing purposes
});
Here is the complete CDK source code that includes all the resources described so far.
Complete CDK source
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
export class Ec2InstanceConnectStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
/**
* Create VPC
*/
const vpc = new ec2.Vpc(this, 'Vpc', {
maxAzs: 2,
subnetConfiguration: [
{
cidrMask: 24,
name: 'PrivateIsolated',
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
},
],
});
/**
* Security group for EC2 instance
* SSH connections to EC2 instance are only allowed from the EC2 Instance Connect Endpoint security group
*/
const instanceSg = new ec2.SecurityGroup(this, 'Ec2InstanceSg', {
vpc,
allowAllOutbound: false, // Restrict outbound traffic
});
/**
* Security group for EC2 Instance Connect Endpoint
*/
const eicSg = new ec2.SecurityGroup(this, 'EicEndpointSg', {
vpc,
allowAllOutbound: false, // Restrict outbound traffic
});
// Allow SSH connection from EC2 Instance Connect Endpoint -> EC2 instance
eicSg.connections.allowTo(
instanceSg,
ec2.Port.tcp(22),
);
// Create EC2 Instance Connect Endpoint
const eicEndpoint = new ec2.CfnInstanceConnectEndpoint(this, 'EicEndpoint', {
subnetId: vpc.isolatedSubnets[0].subnetId,
securityGroupIds: [eicSg.securityGroupId],
});
/**
* Create EC2 instance
*/
const ec2Instance = new ec2.Instance(this, 'Ec2Instance', {
vpc,
vpcSubnets: {
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
},
instanceType: ec2.InstanceType.of(
ec2.InstanceClass.T3,
ec2.InstanceSize.MICRO
),
machineImage: new ec2.AmazonLinuxImage({
generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2023,
}),
securityGroup: instanceSg,
disableApiTermination: false, // Allow instance deletion for testing purposes
});
}
}
```Create EC2
Create an Amazon Linux 2023 EC2 instance in the PRIVATE_ISOLATED subnet. To enable SSH connection via EC2 Instance Connect, attach an IAM role to the instance to communicate with SSM.
TypeScript
/**
* Create EC2 instance
*/
const ec2Instance = new ec2.Instance(this, 'Ec2Instance', {
vpc,
vpcSubnets: {
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
},
instanceType: ec2.InstanceType.of(
ec2.InstanceClass.T3,
ec2.InstanceSize.MICRO
),
machineImage: new ec2.AmazonLinuxImage({
generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2023,
}),
securityGroup: instanceSg,
disableApiTermination: false, // Allow instance deletion for verification purposes
});
Now, the EC2 instance is placed in a secure private network.
Complete Code
Below is the complete CDK source code that includes all the resources we've explained so far. By executing this code, you can deploy the entire configuration at once.
:::details Complete CDK source
TypeScript
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
export class Ec2InstanceConnectStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
/**
* Create VPC
*/
const vpc = new ec2.Vpc(this, 'Vpc', {
maxAzs: 2,
subnetConfiguration: [
{
cidrMask: 24,
name: 'PrivateIsolated',
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
},
],
});
/**
* Security group for EC2 instance
* SSH connections to EC2 instance are only allowed from EC2 Instance Connect Endpoint security group
*/
const instanceSg = new ec2.SecurityGroup(this, 'Ec2InstanceSg', {
vpc,
allowAllOutbound: false, // Restrict outbound traffic
});
/**
* Security group for EC2 Instance Connect Endpoint
*/
const eicSg = new ec2.SecurityGroup(this, 'EicEndpointSg', {
vpc,
allowAllOutbound: false, // Restrict outbound traffic
});
// Allow SSH connection from EC2 Instance Connect Endpoint -> EC2 instance
eicSg.connections.allowTo(
instanceSg,
ec2.Port.tcp(22),
);
// Create EC2 Instance Connect Endpointconst eicEndpoint = new ec2.CfnInstanceConnectEndpoint(this, 'EicEndpoint', {
subnetId: vpc.isolatedSubnets[0].subnetId,
securityGroupIds: [eicSg.securityGroupId],
});
/**
* Creating EC2 instance
*/
const ec2Instance = new ec2.Instance(this, 'Ec2Instance', {
vpc,
vpcSubnets: {
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
},
instanceType: ec2.InstanceType.of(
ec2.InstanceClass.T3,
ec2.InstanceSize.MICRO
),
machineImage: new ec2.AmazonLinuxImage({
generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2023,
}),
securityGroup: instanceSg,
disableApiTermination: false, // Allow instance deletion for testing purposes
});
}
}
That completes the resource creation! Now let's run the cdk deploy command in the terminal to deploy the resources.
Verification
Now, let's verify that we can actually connect using the AWS console and AWS CLI.
Connecting via AWS console
Navigate to the EC2 screen in the AWS Management Console and select the instance you created.
Select "Connect" in the upper right corner of the screen, go to the EC2 Instance Connect tab. Make sure "Connect using private IP" is checked, then select the "Connect" button.
Now a browser-based terminal will open, and we've confirmed that the connection works!
Connecting via AWS CLI
Next, let's try connecting using AWS CLI. We'll use the aws ec2-instance-connect ssh command.
# Set environment variable
export INSTANCE_ID="i-XXXXXXXXXXXXXXXX"
# Connect using private IP (max 20 simultaneous connections, max 1 hour session)
aws ec2-instance-connect ssh \
--instance-id $INSTANCE_ID \
Connection successful!
Conclusion
In this article, we showed you how to build an EC2 connection environment using EC2 Instance Connect Endpoint with AWS CDK. I found it quite attractive that we can easily connect to EC2 without needing key pairs or EC2 instance profiles.
I hope this article was helpful for you.
References