Jan Tuomi

Opening multiple DB tunnels with AWS SSM
(2024-06-26) [note]

To open a tunneled connection to an AWS managed database, such as RDS or DocumentDB, the commonly recommended way is to use a bastion host and aws ssm start-session. The bastion host is an EC2 instance that is in the same VPC as your database. The AWS CLI command allows you to connect to SSM-enabled EC2 instances from your development machine using IAM authorization, without having to manage SSH keys.

The script below connects to the specified PostgreSQL RDS and DocumentDB instances using a bastion host. Both databases will be accessible on a localhost port. Modify as necessary.

#!/bin/bash

# Configure to match your environment
BASTION_NAME="<YOUR_BASTION_INSTANCE_NAME>"
DOCDB_CLUSTER_NAME="<YOUR_DOCDB_CLUSTER_NAME>"
PG_CLUSTER_NAME="<YOUR_RDS_CLUSTER_NAME>"
DOCDB_PORT="27017"
PG_PORT="5432"

# Fail on errors, do not allow use of unset variables
set -eu

# Colors for nicer output
BB="\\033[34m"
RST="\\033[0m"

# Fetch the endpoints URIs and bastion instance ID
DOCDB_ENDPOINT=$(aws docdb describe-db-clusters --db-cluster-identifier "${DOCDB_CLUSTER_NAME}" --query "DBClusters[0].Endpoint" --output text)
PG_ENDPOINT=$(aws rds describe-db-clusters --db-cluster-identifier "${PG_CLUSTER_NAME}" --query "DBClusters[0].Endpoint" --output text)
BASTION_INSTANCE=$(aws ec2 describe-instances --filter "Name=tag:Name,Values=${BASTION_NAME}" --query "Reservations[].Instances[?State.Name == 'running'].InstanceId[]" --output text)

echo "DocumentDB endpoint: ${DOCDB_ENDPOINT}"
echo "PostgreSQL endpoint: ${PG_ENDPOINT}"
echo "Bastion host instance ID: ${BASTION_INSTANCE}"
echo ""

echo -e "${BB}Starting SSM sessions with DocumentDB and PostgreSQL port forwarding in localhost in this terminal...${RST}"

# DocumentDB port forwarding
aws ssm start-session \
  --target "${BASTION_INSTANCE}" \
  --document-name AWS-StartPortForwardingSessionToRemoteHost \
  --parameters '{"portNumber":["'${DOCDB_PORT}'"],"localPortNumber":["'${DOCDB_PORT}'"],"host":["'"${DOCDB_ENDPOINT}"'"]}' &
PID1=$!

# PostgreSQL port forwarding
aws ssm start-session \
  --target "${BASTION_INSTANCE}" \
  --document-name AWS-StartPortForwardingSessionToRemoteHost \
  --parameters '{"portNumber":["'${PG_PORT}'"],"localPortNumber":["'${PG_PORT}'"],"host":["'"${PG_ENDPOINT}"'"]}' &
PID2=$!

cleanup() {
  echo "Caught CTRL-C, stopping both tunnels..."
  kill $PID1 $PID2
  exit
}

# Catch the interrupt signal (CTRL-C)
trap cleanup INT

# Wait for both processes to exit
wait $PID1
wait $PID2