Introduction
Are you using the EC2 Instance Connect Endpoint (EIC Endpoint)? It's a groundbreaking service that allows you to eliminate bastion hosts for free.
https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/connect-with-ec2-instance-connect-endpoint.html
With CDK, it has become easy to set this up, so let me introduce you to it.
Spoiler
You can establish administrative communications not only to EC2 but also to RDS (Aurora). It's incredibly convenient, so I highly recommend giving it a try.
Preparation
While there is no official L2 construct for the EIC Endpoint, there is an L2 available in the community-driven Construct library called open-constructs. Therefore, we will set up a CDK project, install this, and use it.
npx cdk init --language=typescript
npm install @open-constructs/aws-cdk
Usecase
We will place an EC2 instance in a private subnet and try to manage it via SSH through the EIC Endpoint.
Implementation example with CDK
export class TempCdkProjectStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// NAT Gateways and Internet Gateways are not necessary. Please remove them as appropriate.
const vpc = new ec2.Vpc(this, 'VPC')
// EC2 instance
const insntance = new ec2.Instance(this, 'Instance', {
vpc,
instanceType: ec2.InstanceType.of(ec2.InstanceClass.T2, ec2.InstanceSize.MICRO),
machineImage: new ec2.AmazonLinuxImage({
generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2023,
})
});
// L2 Construct for EIC Endpoint
const eicEndpoint = new ocf.aws_ec2.InstanceConnectEndpoint(this, 'InstanceConnectEndpoint', {
vpc,
});
// Opening a hole in the Security Group from EIC Endpoint to EC2 Instance
eicEndpoint.connections.allowTo(insntance, ec2.Port.tcp(22));
// Output of Instance ID
new cdk.CfnOutput(this, 'InstanceId', {
value: insntance.instanceId,
});
}
}
Connection method
After deploying the above CDK code, execute the following command. Please modify the instance ID to the one output during the cdk deploy
process.
$ aws ec2-instance-connect ssh --instance-id i-12345example --connection-type eice
The authenticity of host '10.0.0.1 (<no hostip for proxy command>)' can't be established.
ED25519 key fingerprint is SHA256:abcdefg.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.0.0.1' (ED25519) to the list of known hosts.
, #_
~\_ ####_ Amazon Linux 2023
~~ \_#####\
~~ \###|
~~ \#/ ___ https://aws.amazon.com/linux/amazon-linux-2023
~~ V~' '->
~~~ /
~~._. _/
_/ _/
_/m/'
[ec2-user@ip-10-0-0-1 ~]$
It connected smoothly! It's fantastic.
Administrative communication to RDS (Aurora)
Next, we will place Aurora (MySQL) in a private subnet and attempt to manage it via the EIC Endpoint.
Although the default port for MySQL is 3306, which is not supported by the EIC Endpoint, we plan to circumvent this by changing the port that the DB listens on to 3389, a daring workaround.
Implementation example with CDK
export class TempCdkProjectStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const vpc = new ec2.Vpc(this, 'VPC')
// Aurora cluster
const auroraCluster = new rds.DatabaseCluster(this, 'Aurora', {
engine: rds.DatabaseClusterEngine.auroraMysql({
version: rds.AuroraMysqlEngineVersion.VER_3_06_0,
}),
// 【Important】 Change port to 3389 to use EIC Endpoint
port: 3389,
// Please use rds.Credentials.fromGeneratedSecret('hogeuser')
credentials: rds.Credentials.fromPassword('admin', cdk.SecretValue.unsafePlainText('testPass')),
vpc,
writer: rds.ClusterInstance.serverlessV2('writer'),
});
const eicEndpoint = new ocf.aws_ec2.InstanceConnectEndpoint(this, 'InstanceConnectEndpoint', {
vpc,
});
// Opening a hole in the Security Group from EIC Endpoint to EC2 Instance
eicEndpoint.connections.allowTo(auroraCluster, ec2.Port.tcp(3389));
// DB Endpoint (Use to get private IP)
new cdk.CfnOutput(this, 'AuroraEndpoint', {
value: auroraCluster.clusterEndpoint.hostname,
});
new cdk.CfnOutput(this, 'EicEndpointId', {
value: eicEndpoint.instanceConnectEndpointId,
});
}
}
Connection method
First, obtain the private IP address of the database.
$ nslookup hogehoge.cluster-cvekcubvryhp.ap-northeast-1.rds.amazonaws.com
Server: 8.8.8.8
Address: 8.8.8.8#53
Non-authoritative answer:
hogehoge.cluster-cvekcubvryhp.ap-northeast-1.rds.amazonaws.com canonical name = hogehoge.cluster-cvekcubvryhp.ap-northeast-1.rds.amazonaws.com.
Name: hogehoge.cluster-cvekcubvryhp.ap-northeast-1.rds.amazonaws.com
Address: 10.0.198.63
Next, set up an SSH tunnel. Please specify the parameters as follows:
Parameter | value |
---|---|
--private-ip-address | Private IP of the DB |
--instance-connect-endpoint-id | EIC Endpoint ID |
--local-port | Local listening port (Anything is OK) |
--remote-port | DB listening port (3389) |
$ aws ec2-instance-connect open-tunnel --instance-connect-endpoint-id eice-hogehoge --private-ip-address 10.0.198.63 --local-port 3306 --remote-port 3389
Listening for connections on port 3306.
The tunnel has been successfully established! With the above command still running, try connecting to localhost:3306 using any suitable DB client tool.
This time, we'll connect using Sequel Ace. The credentials are as specified in the CDK: username: admin, password: testPass.
Great to hear it worked! It’s fantastic, isn’t it?
This is something you can't do with Session Manager, so I think it's a major point of differentiation.
Precautions for use
Permissions when accessing via EIC Endpoint
Users need specific permissions for access via the EIC Endpoint. Please refer to the official documentation for precise information.
By leveraging this, you can grant access permissions to specific users only. Thanks to IAM, authorization management becomes a breeze!
Is database access included in the use cases for EIC Endpoint?
I'm not sure. It's probably a gray area.
Initially, when the EIC Endpoint service was released, all ports were open, but soon after, all ports except 22 and 3389 were closed. Therefore, I believe AWS wants us to use it only for SSH and RDP.
Currently, the EIC Endpoint only determines whether to allow or deny communication based on port numbers (Layer 4). I don't think they go as far as inspecting the payload at Layer 7 to make these decisions, but is it technically possible to do so...?? I'd appreciate it if someone with more expertise could clarify...
So, while it works, please use it at your own risk.
Can you also access other VPC resources?
It should work as long as you have the private IP and port number within the VPC determined.
At last
Actually, I created a PR for this L2 construct!
There are only two L2 constructs in open-constructs so far, so there are endless opportunities for contribution waiting. I encourage everyone to give it a try!
Top comments (3)
Wooow thank you for this great article on this super niche application! Made my day!
For users of dBeaver, check out this guide
I made this nifty
Taskfile
command to automate opening up the tunnel!If the
nslookup
isn't working for you, as it wasn't for me today for some reason, you need to use the Console to get the IP_RDS value for the RDS instance private IP: serverfault.com/a/1110424You can also
ping
the endpoint to find the private IP.Here's the new script, where I need to get IP_RDS manually.
Thank you for your great example!! I often use dBeaver and I want to use this script😁