I've been using AWS CloudShell from the Console for a while. It's convenient: a pre-authenticated shell in your browser, right there in the AWS Console. But I always wondered: why can't I use it from my terminal? Why is there no aws cloudshell command?
Turns out, you can make it happen. The API exists, it's just not public. And once you have CLI access to CloudShell, you can do interesting things with it, like using a VPC-attached CloudShell as a bastion to reach your private RDS instances.
Checkout the companion repository as you read through this blog post.
CloudShell: an undocumented API
AWS CloudShell has no official SDK or CLI support. But the Console has to talk to something, right? By looking at what the browser does when you open CloudShell, you can reverse-engineer the API.
Thankfully, Jérémie Guyon already did that work and published a boto3-compatible service model. His work made this whole thing possible.
The API is straightforward: create environments, start/stop them, create sessions, upload/download files. The session mechanism uses SSM's WebSocket protocol under the hood, which means session-manager-plugin (the same binary that powers aws ssm start-session) can connect to CloudShell sessions.
Teaching the AWS CLI a new trick
The AWS CLI has a little-known feature: aws configure add-model. Give it a JSON service model, and suddenly the CLI knows about a new service. AWS uses this internally for private previews.
(The boto3 model from Jérémie's repo just needs a "version": "2.0" field added at the top level to become CLI-compatible.)
Run:
aws configure add-model \
--service-model file://cloudshell-cli-model.json \
--service-name cloudshell
That's it. Now I have aws cloudshell with tab completion and everything:
$ aws cloudshell help
AVAILABLE COMMANDS
create-environment
create-session
delete-environment
describe-environments
get-environment-status
start-environment
stop-environment
...
Connecting to CloudShell from the terminal
The workflow is simple:
# Create or find an environment
aws cloudshell create-environment --region eu-west-1
# Wait for it to be RUNNING
aws cloudshell get-environment-status --environment-id <ID> --region eu-west-1
# Create a session and connect
session-manager-plugin "$(aws cloudshell create-session \
--environment-id <ID> \
--session-type TMUX \
--tab-id "$(uuidgen | tr '[:upper:]' '[:lower:]')" \
--q-cli-disabled \
--region eu-west-1 \
--query '{SessionId:SessionId,TokenValue:TokenValue,StreamUrl:StreamUrl}' \
--output json)" eu-west-1 StartSession
And you're in. A full shell on a CloudShell instance, from your terminal. No browser needed.
The credentials problem
There's a catch. When you use CloudShell from the Console, AWS injects your credentials automatically via a PutCredentials API call. This uses your console session token (the cookie-based auth from your browser login) to feed temporary credentials into the container's metadata endpoint.
When you connect programmatically, that doesn't happen. The container's credential endpoint returns a 500 error. You need to inject credentials yourself:
# Run locally, then paste the output into your CloudShell session
aws configure export-credentials --profile my-profile --format env
Not ideal, but it works.
The bastion use case
Here's where it gets interesting. You can create a VPC-attached CloudShell environment:
aws cloudshell create-environment \
--environment-name db-access \
--vpc-config '{
"VpcId": "vpc-abc123",
"SubnetIds": ["subnet-private-1"],
"SecurityGroupIds": ["sg-allowed-by-rds"]
}' \
--region eu-west-1
Put it in the same security group that your RDS allows, and suddenly you can connect to your database directly from the shell:
mysql -h my-instance.xxx.eu-west-1.rds.amazonaws.com -u admin -p
No EC2 bastion instance to maintain. No SSH keys to manage. No hourly cost when you're not using it (CloudShell is free). The environment suspends after 20 minutes of inactivity and you can keep it alive with aws cloudshell send-heart-beat.
What doesn't work (and I tried..)
I spent a fair amount of time trying to make CloudShell work as a proper port-forwarding bastion, so you could use local tools like DBeaver against a remote RDS through it. Here's what I found:
SSM-based port forwarding doesn't work.
ECS, for instance, registers containers as SSM targets. Its SSM identifier is undocumented but once you know it, it works well, as I have described in a previous blog post. This way you can run aws ssm start-session --document-name AWS-StartPortForwardingSessionToRemoteHost.
SageMaker notebooks have kinda the same behaviour.
CloudShell instances/containers seem not to be registered as SSM managed instances. Or if they are, it's hidden and as of today, no one at AWS leaked their ID format :) I tried every combination of environment ID, session ID, and prefix format I could think of. None of them work.
Local port forwarding through the PTY doesn't work either. The session is a terminal, not a raw TCP stream. You can't pipe binary MySQL protocol data through it. I even tried setting up an ncat relay inside CloudShell and tunneling through the session. The relay works fine internally, but there's no way to expose it as a local TCP port on your machine.
UDP hole punching is theoretically possible but requires the CloudShell to have internet access (NAT Gateway on its subnet), and even then you're fighting NAT symmetry issues on both ends. I got STUN working from CloudShell, but the full hole punch is fragile and impractical for production use.
So what is it good for?
Honestly, quite a lot:
Quick database access without maintaining a bastion EC2 instance. Connect, run your queries, disconnect. Free.
Automation. You can script command execution on CloudShell via Python +
session-manager-plugin. Useful for running things inside a VPC without deploying a Lambda or Fargate task.Debugging network connectivity. Spin up a CloudShell in a specific subnet/SG combination and test what can reach what.
File transfer (from public environments). The
get-file-upload-urlsandget-file-download-urlsAPIs give you presigned S3 URLs.
The main limitation is that you're stuck running commands inside the shell. You can't use it as a transparent tunnel for local tools. For that, you still need an EC2 instance with SSM agent, or an ECS task with execute-command enabled.
Try it yourself
I published the model and a sample script here: github.com/psantus/cloudshell-cli
Installation is one command. The whole thing is a single JSON file that teaches your AWS CLI a new service. Just remember: this is an undocumented API. AWS can change or break it at any time. Don't build anything mission-critical on top of it.
But for quick VPC access from your terminal? It's pretty great.
Top comments (0)