Skip to content

developmentseed/trunnel

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

8 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Trunnel

Automated SSM tunneling and self-healing bastion host for private RDS.

       __________________________________________________________________
      | %%% H H H H H H H H H H H H H H H H H H H H H H H H H H H H H%%%|
      | %%% H H H H H H H H H H H H H H H H H H H H H H H H H H H H H%%%|
      | %%% [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ]  %%%|
      | %%%__________________________________________________________%%%|
      | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%|
      | %%%%%%     _________________        _________________     %%%%%%|
      | %%%%%     / ############### \      / ############### \     %%%%%|
      | %%%%     / ################# \    / ################# \     %%%%|
      | %%%     | ####### [ ] ####### |  | ####### [ ] ####### |     %%%|
      | %%%     | #####  LOCAL  ##### |  | #####  PRIVATE ##### |    %%%|
      | %%%     | ####  MACHINE  #### |  | ####    RDS    #### |     %%%|
      | %%%     | #####  :5432  ##### |  | #####  ACCESS  ##### |    %%%|
      | %%%     | ################### |  | ################### |     %%%|
     _| %%%_____| ################### |__| ################### |_____%%%|_
    | |=========| ################### |==| ################### |========| |
    |_|_________|_____________________|__|_____________________|________|_|
      [ SSM ]   ################################################  [ VPC ]

Trunnel (a play on the East Side Trolley Tunnel, or a wooden peg used to form a strong connection between pieces of wood) helps automate securely connecting to your private AWS infrastructure through AWS Systems Manager (SSM). It replaces manual SSH management with automated SSM discovery and a self-healing CDK bastion. Trunnel can discover the bastion and RDS instance you need to connect to, fetch database credentials stored in Secrets Manager, and bore the encrypted tunnel β€” all from a single CLI.

This tool was designed in response to help reduce minor frustrations like,

  • What is the EC2 instance identifier for my bastion host?
  • What is the RDS host URL and port I need to use?
  • What is the syntax for the aws ssm command I need to use?
  • How can I keep my Bastion host up to date with new AMIs?

Trunnel is composed of two packages: one for infrastructure deployment (trunnel-infra) and another for end users (trunnel-cli).

Trunnel Infrastructure

To deploy the EC2 bastion host using AWS Cloud Development Kit (CDK), first add the package to your deployment dependencies.

uv add --group deploy "trunnel-infra @ git+https://github.com/developmentseed/trunnel#subdirectory=packages/trunnel-infra"

Next, integrate the AutoRotatingBastion construct into your CDK stack. It handles the Auto Scaling Group (ASG) setup, SSM permissions, and automated AMI rotation logic.

from trunnel_infra.bastion import AutoRotatingBastion

# Inside your Stack...
AutoRotatingBastion(
    self,
    "Bastion",
    vpc=vpc,
    # Databases you want to connect to can be specified below.
    # You could also leave this blank if you'd prefer to have
    # RDS owners manage their own Security Group ingress rules
    db_targets=[my_rds_instance],
    # Tracks an SSM parameter. Updating the parameter triggers
    # a zero-downtime rolling Instance Refresh.
    ami="/company/images/latest-linux-ami"
)

# Export the Security Group ID so other stacks can reference it
bastion.export_security_group("BastionSG-Production")

Trunnel CLI

The Trunnel CLI makes it easy to find the bastion host and connect to your RDS database via an encrypted SSM tunnel, and to fetch connection credentials stored in AWS Secrets Manager. Resources are located by tag key=value pairs.

Installation

Before beginning, you must first have the following installed:

  • AWS CLI v2
  • SSM Session Manager Plugin

Install into your project's development dependencies (for example using uv):

uv add --dev "trunnel-cli @ git+https://github.com/developmentseed/trunnel#subdirectory=packages/trunnel-cli"

trunnel connect

Opens an encrypted SSM port-forward tunnel to a private RDS instance.

$ trunnel connect --rds-key Service --rds-value payments-api --reconnect

πŸ” Searching AWS...

Select a Bastion:
 1) i-0abcd1234efgh5678 - Production-Bastion
 2) i-09876fedcba54321 - Staging-Bastion

Enter number: 1

🚎 Trunnel Active: localhost:5432 -> payments-api-db.cluster.aws.com
πŸ”— payments-api-db via Production-Bastion (i-0abcd1234efgh5678)

Starting session with SessionId: developer-0123456789abcdef
Port 5432 opened for session developer-0123456789abcdef.
Waiting for connections...
$ trunnel connect --help

Usage: trunnel connect [OPTIONS]

  Securely bore a tunnel to RDS via Trunnel.

Options:
  --bastion-key TEXT    Tag key for Bastion.  [env var: TRUNNEL_CONNECT_BASTION_KEY; default: Role]
  --bastion-value TEXT  Tag value for Bastion.  [env var: TRUNNEL_CONNECT_BASTION_VALUE; default: Bastion]
  --rds-key TEXT        Tag key for RDS.  [env var: TRUNNEL_CONNECT_RDS_KEY; required]
  --rds-value TEXT      Tag value for RDS.  [env var: TRUNNEL_CONNECT_RDS_VALUE; required]
  --local-port INTEGER  [env var: TRUNNEL_CONNECT_LOCAL_PORT; default: 5432]
  --profile TEXT        AWS CLI profile.  [env var: TRUNNEL_CONNECT_PROFILE]
  --reconnect           Auto-retry on disconnect.  [env var: TRUNNEL_CONNECT_RECONNECT]
  --help                Show this message and exit.

trunnel secrets

Looks up an AWS Secrets Manager secret by tag and prints its value to stdout.

$ trunnel secrets --secret-key Stack --secret-value payments-api

πŸ” Searching AWS Secrets Manager...
{"username":"app","password":"s3cr3t","host":"payments-api-db.cluster.aws.com","port":5432}

If multiple secrets match the tag filter, Trunnel prompts you to choose:

Select a Secret:
 1) payments-api/db-credentials - Primary database credentials
 2) payments-api/readonly-credentials - Read-only replica credentials

Enter number: 1
$ trunnel secrets --help

Usage: trunnel secrets [OPTIONS]

  Fetch and print a Secrets Manager secret by tag.

Options:
  --secret-key TEXT    Tag key to filter secrets.  [env var: TRUNNEL_SECRETS_SECRET_KEY; required]
  --secret-value TEXT  Tag value to filter secrets.  [env var: TRUNNEL_SECRETS_SECRET_VALUE; required]
  --profile TEXT    AWS CLI profile.  [env var: TRUNNEL_SECRETS_PROFILE]
  --help            Show this message and exit.

trunnel psql

Looks up credentials from Secrets Manager, opens an SSM tunnel, and drops you straight into a psql session. Requires psql to be installed alongside the AWS CLI prerequisites.

$ trunnel psql \
    --rds-key Stack --rds-value payments-api \
    --secret-key Stack --secret-value payments-api-user-alice

πŸ” Searching AWS...

🚎 Opening tunnel: localhost:5432 -> payments-api-db.cluster.aws.com
πŸ”— payments-api-db via Production-Bastion (i-0abcd1234efgh5678)
⏳ Waiting for tunnel...
🐘 Connecting as alice...
psql (16.2)
Type "help" for help.

payments_api=#

The RDS and secret tags can differ β€” useful when a shared database has per-user secrets with different tags.

$ trunnel psql --help

Usage: trunnel psql [OPTIONS]

  Fetch credentials, open a tunnel, and launch psql.

Options:
  --bastion-key TEXT    Tag key for Bastion.  [env var: TRUNNEL_PSQL_BASTION_KEY; default: Role]
  --bastion-value TEXT  Tag value for Bastion.  [env var: TRUNNEL_PSQL_BASTION_VALUE; default: Bastion]
  --rds-key TEXT        Tag key for RDS.  [env var: TRUNNEL_PSQL_RDS_KEY; required]
  --rds-value TEXT      Tag value for RDS.  [env var: TRUNNEL_PSQL_RDS_VALUE; required]
  --secret-key TEXT     Tag key for the secret.  [env var: TRUNNEL_PSQL_SECRET_KEY; required]
  --secret-value TEXT   Tag value for the secret.  [env var: TRUNNEL_PSQL_SECRET_VALUE; required]
  --local-port INTEGER  [env var: TRUNNEL_PSQL_LOCAL_PORT; default: 5432]
  --profile TEXT        AWS CLI profile.  [env var: TRUNNEL_PSQL_PROFILE]
  --help                Show this message and exit.

Environment variables

All options can be set via environment variables. Each subcommand has its own prefix:

Subcommand Prefix Example
trunnel connect TRUNNEL_CONNECT_ TRUNNEL_CONNECT_RDS_KEY=Service
trunnel secrets TRUNNEL_SECRETS_ TRUNNEL_SECRETS_SECRET_KEY=Stack
trunnel psql TRUNNEL_PSQL_ TRUNNEL_PSQL_RDS_KEY=Stack

You might consider using direnv to set these per project. For example,

# .envrc
export TRUNNEL_CONNECT_RDS_KEY=Stack
export TRUNNEL_CONNECT_RDS_VALUE=payments-api
export TRUNNEL_PSQL_RDS_KEY=Stack
export TRUNNEL_PSQL_RDS_VALUE=payments-api
export TRUNNEL_PSQL_SECRET_KEY=Stack
export TRUNNEL_PSQL_SECRET_VALUE=payments-api-user-alice

About

Automated SSM tunnels for private AWS RDS.

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

 
 
 

Contributors