Open-source tool · AWS
ssmx — a smarter AWS SSM CLI
Shell access, one-off commands and port forwarding over AWS Systems Manager — using names instead of instance IDs, endpoints and ports. You give it the RDS identifier; it looks up the endpoint and port, picks a sensible local port, and opens the tunnel. No bastion SSH keys, no open ports, no copy-pasting endpoints from the console.
The short version
- Install:
pipx install git+https://github.com/originalpages/ssmx.git— plus the AWS CLI v2 and the Session Manager plugin — Install. - Shell by name:
ssmx connect api-1— Open a shell. - Tunnel to a database by name:
ssmx forward bastion-1 --to prod-db— Port forward. - Same IAM, same APIs. A nicer interface over the AWS CLI, not a different security model — IAM permissions.
Why: three commands become one
Tunnelling to a private RDS database with the plain AWS CLI means looking up the endpoint, looking up the bastion’s instance ID, and then pasting both into a JSON parameter blob:
aws rds describe-db-instances --db-instance-identifier prod-db \
--query 'DBInstances[0].Endpoint' # 1. find host + port
aws ec2 describe-instances --filters Name=tag:Name,Values=bastion-1 \
--query 'Reservations[].Instances[].InstanceId' # 2. find instance ID
aws ssm start-session --target i-0abc1234def5678ab \
--document-name AWS-StartPortForwardingSessionToRemoteHost \
--parameters '{"host":["prod-db.abc123.eu-west-1.rds.amazonaws.com"],
"portNumber":["5432"],"localPortNumber":["5432"]}' # 3. open tunnelssmx forward bastion-1 --to prod-db
# prod-db (rds:postgres) -> prod-db.abc123.eu-west-1.rds.amazonaws.com:5432
# forwarding localhost:5432 via bastion-1 (i-0abc1234def5678ab) - Ctrl+C to stop| Task | AWS CLI | ssmx |
|---|---|---|
| Shell on an instance | aws ssm start-session --target i-0abc... | ssmx connect web-1 |
| Run a command | aws ssm send-command ... + poll for output | ssmx run web-1 -- uptime |
| Tunnel to RDS / Aurora / Redis | three commands, above | ssmx forward web-1 --to prod-db |
| Instance by Name tag | manual describe-instances | built in |
| Read a SecureString | aws ssm get-parameter --with-decryption ... | ssmx param get /app/DB_PASS |
How it works
ssmx is a single Python file on top of boto3. It does the lookups itself, then hands the finished session to the aws CLI and the Session Manager plugin — the same path you would take by hand, with the same profile and region.
localhost through the bastion to the database.Because the connection runs through Systems Manager, the bastion needs no inbound security-group rules and nobody needs SSH keys. What the bastion does need is a network path to the database: its security group must be allowed into the database’s security group on the database port.
Install
Prerequisites
- Python 3.9+
- AWS CLI v2
- Session Manager plugin — needed for
connectandforward - Target instances must be SSM-managed: SSM Agent running, and an instance profile with
AmazonSSMManagedInstanceCore
Recommended: pipx
An isolated install that puts ssmx on your PATH.
Or pip
Or just the script
It is a single file with one dependency.
Check it works
Usage, command by command
Find instances
$ ssmx list
NAME INSTANCE ID TYPE PRIVATE IP PING
api-1 i-0abc1234def5678ab t3.medium 10.0.1.12 Online
bastion-1 i-0fed9876cba54321f t3.micro 10.0.0.5 OnlineOpen a shell
Run a one-off command
stdout and stderr come back to your terminal, and the remote exit code becomes ssmx’s exit code — so it works in scripts and && chains.
Port forward — the smart part
--to takes a name and works out what it is. It tries these in order and uses the first match:
- host:portUsed as-is — anything TCP: OpenSearch, Kafka, an internal ALB…
- RDS instanceInstance endpoint and port
- Aurora / RDS clusterWriter endpoint
- ElastiCache replication groupPrimary endpoint, or configuration endpoint in cluster mode
- ElastiCache cache clusterRedis, Valkey or Memcached node endpoint
The local port defaults to the remote port. Then, in another terminal:
$ psql -h localhost -p 5432 -U admin mydb
$ redis-cli -p 6379Already running the same database locally? Use --local, e.g. --local 15432 next to a local Postgres on 5432. Otherwise you get Address already in use.
Check what a name resolves to
Useful before connecting, or in scripts.
$ ssmx resolve prod-db
prod-db (rds:postgres) -> prod-db.abc123.eu-west-1.rds.amazonaws.com:5432
$ ssmx resolve api-1 --ec2
api-1 -> i-0abc1234def5678abParameter Store
param put defaults to SecureString. Prefer the - (stdin) form for secrets so the value does not end up in your shell history.
Profiles, regions and SSO
ssmx uses the standard AWS credential chain via boto3, and passes the same profile and region to the aws CLI when it opens a session. Global options (--profile, --region, --no-cache, --cache-ttl) go before the command.
The lookup cache
Resolving names costs one or more AWS API calls, so ssmx caches successful lookups and repeat commands start instantly.
- What is cached: Name tag → instance ID, and resource name → host/port. Never credentials, parameter values or command output.
- Where:
~/.cache/ssmx/cache.json(respectsXDG_CACHE_HOME), file mode600. - Scope: per profile + region, so prod and dev never mix.
- Lifetime: 5 minutes by default.
ssmx listalso warms the cache.
If an instance was replaced and a cached ID is stale, the session fails fast; run ssmx cache clear (or wait for the TTL) and retry.
IAM permissions
Same APIs, same permissions as the AWS CLI. Minimum actions for the caller, per command:
| Command | Actions |
|---|---|
list | ec2:DescribeInstances, ssm:DescribeInstanceInformation |
connect | ec2:DescribeInstances, ssm:StartSession, ssm:TerminateSession |
run | ec2:DescribeInstances, ssm:SendCommand, ssm:GetCommandInvocation |
forward | connect permissions + rds:DescribeDBInstances, rds:DescribeDBClusters, elasticache:DescribeReplicationGroups, elasticache:DescribeCacheClusters |
param get | ssm:GetParameter (+ kms:Decrypt for a SecureString with a customer key) |
param put | ssm:PutParameter (+ kms:Encrypt) |
param list | ssm:DescribeParameters |
param delete | ssm:DeleteParameter |
forward also needs ssm:StartSession on the document AWS-StartPortForwardingSessionToRemoteHost. A starting-point policy covering every command — tighten the Resource entries for production:
Tag conditions and path-scoped Parameter Store examples are in docs/iam.md.
Troubleshooting
| Symptom | Fix |
|---|---|
no AWS region configured | Pass --region, set AWS_REGION, or add region to your profile |
| Token expired / SSO errors | aws sso login --profile <name> |
SessionManagerPlugin is not found | Install the Session Manager plugin (see Prerequisites) |
Instance not in ssmx list | Not SSM-managed: check the SSM Agent and the instance profile |
matches several running instances | Two instances share a Name tag — use the instance ID |
| Tunnel opens but the DB times out | The DB’s security group does not allow the tunnel instance |
Address already in use | Something already listens on that port — use --local <other port> |
| Connects to an old or terminated instance | ssmx cache clear |
Source, issues and pull requests: github.com/originalpages/ssmx.