Securely connect to your RDS instance from a local machine at almost no cost

Bohdan Petryshyn
6 min readFeb 1, 2023

So, following security best practices, you set up your RDS, Elasticache, or EC2 instance in a private subnet. The next question is how to connect to the resource from your local machine for debugging or from a CI/CD pipeline for migration and deployment.

Created with express.adobe.com

In the first part of this article, I’ll describe the standard approach to connecting to resources hidden from the Internet in private subnets. I won’t go into much detail on setting everything up manually, as in the second part, I’ll introduce a CLI tool that automates the routine for you and keeps the solution cost as low as possible.

Bastion hosts

One of the most commonly used approaches for connecting to a database or any other resource in a private AWS subnet is setting up a bastion EC2 instance in the same network as the target resource but in a public subnet. This way, you make the bastion host reachable from the Internet and the target resource only reachable from the bastion host. The bastion becomes the only Internet entry point for your hidden resources. It also controls access to your resources handling incoming connection authentication and authorization.

Created with excalidraw.com

SSH tunneling and AWS Session Manager

Okay, so how do you connect to your database through the bastion host? There’re a couple of ways to do that: SSH tunneling and AWS Session Manager port forwarding. Both of these approaches mean making your hidden target available on your localhost by forwarding traffic from a port on your local machine to the target through the bastion host. Using SSH tunneling implies managing SSH keys and exposing ports on your bastion host. Why would you bother with SSH if you’re using AWS? In AWS, authentication and authorization can be delegated to IAM.

AWS Session Manager (which is a part of AWS Systems Manager) allows establishing secure port forwarding sessions without even exposing your bastion host to the Internet. All the communication is done via the Session Manager’s “backdoor” channel. Systems Manager is a part of AWS CLI, so the only thing required to use it is a properly configured AWS toolset on your machine. Another advantage of using Session Manager is access auditability, which is useful in teams and organizations. It is achieved by keeping a history of all the connections made through the bastion host.

Drawbacks of manual bastion host management

There’re also some drawbacks to this approach. First of all, the solution’s cost. You have to pay for the bastion host running 24/7 or manage its state manually, stopping it when it’s not needed. The latter approach works only until you see a bill from AWS and realize that you forgot to shut the instance down one day. Another drawback is software updates you’ll have to do regularly and, most likely, manually to keep your bastion host secure.

Automate bastion management with Basti

Introducing Basti, an open-source CLI tool that automatically sets up a bastion host, keeps it stopped when it is not used, handles software updates to maintain the bastion instance secure, and exposes an easy-to-use CLI interface that allows you to connect to your target in a single command from your local machine or a CI/CD pipeline.

For this example, we’ll use a PostgreSQL RDS instance as a connection target. However, you can also connect to RDS MySQL, Aurora, Elasticache, or any private EC2 instance following the same set of instructions.

Prerequisites

  1. Make sure AWS credentials are properly set up on your machine. Basti doesn’t use AWS CLI but supports all its configuration methods, including credentials configured with aws configure command. In general, you should expect Basti to work if AWS CLI works in your terminal.
  2. Install Basti with NPM. Other installation methods are coming soon.
npm install -g basti

Initialize your bastion

Now, use basti init command to set up the bastion EC2 instance and configure your connection target (RDS instance in this case) to accept connections from the bastion host. You only need to run this command once. The command is interactive, so you will be prompted for all the required input along the way.

First, select the RDS instance from the list of possible connection targets detected in your account.

Now, select a public subnet for the bastion instance to be created in.

Basti will figure out everything else for you and finish setting up the infrastructure.

Connect to your target

Now, you can start a port forwarding session with thebasti connect command. You will select a target to connect to and a local port on which the target will become available.

Finally, you can connect to your database as if it was running locally.

Connect to a custom target

With Basti, you can also connect to a custom service running on an EC2 instance. This method can be used to connect to a target for which Basti doesn’t provide first-class support yet.

To initialize a custom target, select “Custom” as a target in basti init command. You will be prompted for your target’s VPC and a public subnet to create a bastion instance in. When the bastion instance is set up, it can be used to access any target in the selected VPC. You just have to ensure the target is reachable from the bastion instance by adding an inbound rule to the target’s security group.

To connect to the custom target, select “Custom” as a target in basti connect command. You will be prompted for the target’s VPC, address and port. As usual, you will choose the local port to expose your target on.

Basti in a CI/CD pipeline

With Basti, you can also connect to your target from an automation pipeline that runs outside your VPC (in GitHub Actions, CircleCI, or GitLab, for example).

All Basti commands support automatic (non-interactive) mode in which you can pass all the inputs as command line arguments.

# Initialization
basti init --rds-instance <your-instance-id> --bastion-subnet <your-subnet-id>

# Connection
basti connect --rds-instance <your-instance-id> --local-port <your-port>

# Cleanup
basti cleanup -y

Congratulations!

Your connection is now secure and cost-efficient 🔒 🎉

Don’t forget to 👏 this article if you found it helpful and ⭐️ Basti if it made your developer’s life a little bit easier!

I’m considering creating a follow-up article about the engineering behind Basti and its implementation details. Let me know in the comments if you find this topic interesting!

--

--

Bohdan Petryshyn

A software engineer at Redocly by day and an open-source enthusiast by night.