RDS Jumphost with Session Manager
Demo CDK project showing how to securely access an RDS database in a private subnet using AWS Session Manager port forwarding through an EC2 jumphost.
No SSH keys. No public IPs. No NAT gateway.
Architecture
┌─────────────────────────────────────────────────────────────────┐
│ VPC │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Private Isolated Subnet │ │
│ │ │ │
│ │ ┌─────────────┐ ┌──────────────────┐ │ │
│ │ │ EC2 Jumphost│──────────────│ RDS PostgreSQL │ │ │
│ │ │ (SSM Agent) │ Port 5432 │ │ │ │
│ │ └─────────────┘ └──────────────────┘ │ │
│ │ │ │ │
│ │ │ SSM via VPC Endpoints │ │
│ │ ▼ │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ VPC Endpoints (Interface) │ │ │
│ │ │ - ssm │ │ │
│ │ │ - ssmmessages │ │ │
│ │ │ - ec2messages │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
│ Session Manager Port Forward
▼
┌──────────┐
│ Local │ localhost:5432 → RDS:5432
│ Machine │
└──────────┘
Prerequisites
AWS CLI v2
aws --version # Should be 2.xSession Manager Plugin
# macOS brew install --cask session-manager-plugin # Or download from: # https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.htmlNode.js and pnpm
node --version # 18.x or later pnpm --versionAWS Credentials configured with permissions for:
- CloudFormation
- EC2
- RDS
- SSM
- Secrets Manager
- VPC
Deploy
# Install dependencies
pnpm install
# Deploy the stack
pnpm cdk deploy
# Note the outputs - you'll need the instance ID and RDS endpoint
Connect to the Database
Option 1: Use the tunnel script (recommended)
# Start the tunnel (forwards localhost:5432 → RDS:5432)
./scripts/tunnel.sh
# In another terminal, connect with psql
psql -h localhost -p 5432 -U postgres -d demo
Option 2: Manual port forwarding
one-liner:
# Get password from Secrets Manager and connect via psql
DB_SECRET=$(aws secretsmanager get-secret-value \
--secret-id $(aws cloudformation describe-stacks \
--stack-name RdsJumphostSessionManagerStack \
--query "Stacks[0].Outputs[?OutputKey=='DatabaseSecretArn'].OutputValue" \
--output text) \
--query SecretString --output text) && \
PGPASSWORD=$(echo $DB_SECRET | jq -r .password) psql \
-h localhost \
-p 5432 \
-U $(echo $DB_SECRET | jq -r .username) \
-d postgres
```
```bash
# Get the instance ID and RDS endpoint from stack outputs
aws cloudformation describe-stacks \
--stack-name RdsJumphostSessionManagerStack \
--query 'Stacks[0].Outputs'
# Start port forwarding
aws ssm start-session \
--target <INSTANCE_ID> \
--document-name AWS-StartPortForwardingSessionToRemoteHost \
--parameters '{"host":["<RDS_ENDPOINT>"],"portNumber":["5432"],"localPortNumber":["5432"]}'
Option 3: Connect directly from the jumphost
# Start a shell session on the jumphost
aws ssm start-session --target <INSTANCE_ID>
# Install PostgreSQL client (first time only)
sudo dnf install -y postgresql15
# Connect to RDS
psql -h <RDS_ENDPOINT> -U postgres -d demo
Get Database Password
The password is stored in AWS Secrets Manager:
# Get the secret ARN
SECRET_ARN=$(aws cloudformation describe-stacks \
--stack-name RdsJumphostSessionManagerStack \
--query "Stacks[0].Outputs[?OutputKey=='DatabaseSecretArn'].OutputValue" \
--output text)
# Retrieve the password
aws secretsmanager get-secret-value \
--secret-id "$SECRET_ARN" \
--query SecretString \
--output text | jq -r .password
Use with GUI Tools
Start the tunnel, then connect your favorite database tool to:
| Setting | Value |
|---|---|
| Host | localhost |
| Port | 5432 |
| Database | demo |
| Username | postgres |
| Password | (from Secrets Manager) |
Works with: DBeaver, DataGrip, pgAdmin, TablePlus, etc.
Cleanup
pnpm cdk destroy
How It Works
VPC Endpoints allow the EC2 instance to communicate with AWS Systems Manager without internet access (no NAT gateway needed)
Session Manager creates an encrypted tunnel through the SSM service, not through the network
Port Forwarding (
AWS-StartPortForwardingSessionToRemoteHost) tunnels traffic from your local machine through the EC2 instance to the RDS endpointSecurity Groups only allow traffic from the jumphost to RDS on port 5432
Cost Considerations
This demo uses cost-optimized resources:
| Resource | Type | ~Monthly Cost |
|---|---|---|
| EC2 Jumphost | t4g.micro | ~$6 |
| RDS PostgreSQL | db.t4g.micro | ~$12 |
| VPC Endpoints (3x) | Interface | ~$22 |
| Total | ~$40/month |
For production, consider:
- Using NAT Gateway instead of VPC Endpoints (if you need other outbound traffic)
- Right-sizing instances based on actual usage
- Enabling RDS backups and multi-AZ
Useful Commands
# Synthesize CloudFormation template
pnpm cdk synth
# Compare deployed stack with current state
pnpm cdk diff
# Deploy with approval prompt
pnpm cdk deploy
# Destroy all resources
pnpm cdk destroy