Rust library and CLI for the AWS CloudShell API — reverse-engineered from the AWS Console.
AWS CloudShell has no public SDK. This project provides a type-safe, high-performance Rust client using manual SigV4 signing. It's a modern alternative to the Python cloudshell-boto3 project.
⚠️ Disclaimer — This uses an undocumented API. AWS can change or break it at any time without notice. Do not build mission-critical systems on top of it.
✅ All 12 CloudShell Operations
DescribeEnvironments— List environmentsCreateEnvironment— Create public or VPC environmentsStartEnvironment/StopEnvironment— Manage environment lifecycleGetEnvironmentStatus— Poll environment statusCreateSession— Create SSM WebSocket sessionsSendHeartBeat— Keep environments aliveDeleteSession/DeleteEnvironment— CleanupGetFileUploadUrls/GetFileDownloadUrls— File transferPutCredentials— Forward credentials (console sessions only)
✨ Type-Safe Rust Library
- Compile-time type safety with
Result<T, E>error handling - Response wrapper types with helper methods
- No runtime type errors
- Reusable across projects
🚀 High Performance
- ~5-10 MB memory (vs 60-100 MB Python)
- ~10ms startup (vs 500ms Python)
- Static binary (no dependencies needed)
- Zero-copy async/await
📦 Dual Interface
- Library:
CloudShellClientfor embedding in Rust apps - CLI:
cloudshell-rsbinary with 10+ subcommands - Production-ready examples (full lifecycle, SSM integration)
🧪 Well-Tested
- 63 comprehensive integration tests
- Input validation (VPC, UUID v4)
- Edge cases, unicode, complex JSON nesting
- Tests run in < 100ms
- Rust 1.93.0+ (install)
- AWS credentials configured (profile, env vars, or IAM role)
- For SSM integration:
session-manager-plugin(optional)
git clone https://github.com/bartleboeuf/cloudshell-rs.git
cd cloudshell-rs
cargo build --releaseThe binary is at target/release/cloudshell-rs (~3-5 MB).
Add to your Cargo.toml:
[dependencies]
cloudshell-rs = { path = "../cloudshell-rs" }
tokio = { version = "1.40", features = ["full"] }Help & Documentation:
# Show available commands
./target/release/cloudshell-rs --help
# Show detailed help for a command
./target/release/cloudshell-rs create --help
./target/release/cloudshell-rs connect --help
./target/release/cloudshell-rs heartbeat --help
# Each command includes usage examples, prerequisites, and security notesEnvironment Management:
# List environments
./target/release/cloudshell-rs describe
# Create public environment
./target/release/cloudshell-rs create
./target/release/cloudshell-rs create --name my-env
# Create VPC environment
./target/release/cloudshell-rs create --name my-vpc-env \
--vpc-id vpc-0123456789abcdef0 \
--subnet-ids subnet-1,subnet-2 \
--security-group-ids sg-1,sg-2
# Check status
./target/release/cloudshell-rs status abc123-def4-5678-ghij
# Start environment
./target/release/cloudshell-rs start abc123-def4-5678-ghij
# Stop environment
./target/release/cloudshell-rs stop abc123-def4-5678-ghij
# Delete environment
./target/release/cloudshell-rs delete abc123-def4-5678-ghijSessions & Connection:
# Create session (returns stream URL and token)
./target/release/cloudshell-rs session abc123-def4-5678-ghij --tab-id $(uuidgen)
# Connect to environment via SSM Session Manager (interactive shell)
./target/release/cloudshell-rs connect
# Connect to specific environment
./target/release/cloudshell-rs connect abc123-def4-5678-ghij
# Auto-inject AWS credentials (silent, secure)
./target/release/cloudshell-rs connect --inject-credentials
# Increase environment wait timeout
./target/release/cloudshell-rs connect --timeout-secs 300Maintenance & File Transfer:
# Send heartbeat (keeps environment alive)
./target/release/cloudshell-rs heartbeat abc123-def4-5678-ghij
# Get file upload presigned URLs
./target/release/cloudshell-rs upload abc123-def4-5678-ghij
# Get file download presigned URLs
./target/release/cloudshell-rs download abc123-def4-5678-ghijWith AWS profile and region:
./target/release/cloudshell-rs --profile myprofile --region us-east-1 describe
./target/release/cloudshell-rs --profile myprofile --region eu-west-1 status abc-123
./target/release/cloudshell-rs --profile myprofile --region us-east-1 connectuse cloudshell_rs::{CloudShellClient, VpcConfig};
use uuid::Uuid;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create client
let client = CloudShellClient::new(
"us-east-1".to_string(),
Some("myprofile".to_string()),
).await?;
// List environments
let response = client.describe_environments().await?;
println!("{}", response.pretty_print()?);
// Create public environment
let create_resp = client.create_environment(Some("my-env"), None).await?;
let env_id = create_resp.environment_id().ok_or("No env_id")?;
println!("Created: {}", env_id);
// Alternatively, create VPC environment with validation
// let vpc = VpcConfig::new(
// "vpc-0123456789abcdef0".to_string(),
// vec!["subnet-1".to_string()],
// vec!["sg-1".to_string()],
// );
// let create_resp = client.create_environment(Some("my-vpc-env"), Some(vpc)).await?;
// Wait for RUNNING state
loop {
let status = client.get_environment_status(&env_id).await?.status();
if status == Some("RUNNING".to_string()) { break; }
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
}
// Create session (TabId automatically validated as UUID v4)
let tab_id = Uuid::new_v4().to_string();
let session = client.create_session(
&env_id,
"TMUX",
&tab_id,
Some(true),
).await?;
println!("Stream URL: {}", session.stream_url().ok_or("No URL")?);
// Get file URLs
let upload = client.get_file_upload_urls(&env_id).await?;
println!("Upload URL: {}", upload.file_upload_presigned_url().unwrap_or_default());
// Send heartbeat
client.send_heart_beat(&env_id).await?;
// Cleanup
client.delete_session(&env_id, session.session_id().as_ref().ok_or("No session_id")?).await?;
client.delete_environment(&env_id).await?;
Ok(())
}Two production-ready examples are included:
Complete environment workflow with file operations:
cargo run --example full_lifecycle -- --profile myprofile --region us-east-1What it does:
- Creates/finds environment
- Waits for RUNNING state
- Creates session
- Gets upload/download presigned URLs
- Sends heartbeat
- Cleans up session
- Keeps environment for reuse
See: examples/full_lifecycle.rs (192 lines)
Interactive shell access with AWS Systems Manager:
# Interactive shell
cargo run --example connect_via_ssm -- --profile myprofile --region us-east-1
# Auto-inject AWS credentials (silent, secure)
cargo run --example connect_via_ssm -- --profile myprofile --inject-credentials trueWhat it does:
- Verifies
session-manager-pluginis installed - Creates environment + session
- Spawns
session-manager-pluginsubprocess - Provides interactive shell or securely injects AWS credentials
Modes:
- Interactive (default): Full shell access via
session-manager-plugin. Typeexitto disconnect - Secure credential injection (
--inject-credentials): Silently injects AWS credentials from current profile into the environment (hidden from screen and bash history). Shell is ready immediately for your commands
Prerequisites: session-manager-plugin installed (macOS | Linux)
See: examples/connect_via_ssm.rs (180 lines)
| Metric | Python | Rust | Improvement |
|---|---|---|---|
| Binary size | N/A | 3-5 MB | — |
| Memory usage | 60-100 MB | 5-10 MB | 10-20x smaller |
| Startup time | ~500ms | ~10ms | 50x faster |
| Per-API call | 5-10ms | 1-5ms | 2-10x faster |
| Type safety | Runtime errors | Compile-time checks | ✅ |
| Static binary | ❌ | ✅ | ✅ |
# Debug build
cargo build
# Release build (optimized)
cargo build --release
# Run tests
cargo test
# Run tests with output
cargo test -- --nocapture
# Lint check
cargo clippy
# Format code
cargo fmtsrc/
├── lib.rs # CloudShellClient library (607 lines)
│ # - 12 API operations
│ # - VpcConfig with validation
│ # - 7 response wrapper types
└── main.rs # CLI with 11 subcommands (688 lines)
# - Comprehensive help text
# - VPC environment support
# - Input validation
examples/
├── full_lifecycle.rs # Complete workflow demo
└── connect_via_ssm.rs # SSM integration demo
tests/
└── integration_tests.rs # 63 comprehensive tests
# - Response types (24 tests)
# - Input validation (10 tests)
# - Edge cases (29 tests)
| Crate | Purpose |
|---|---|
aws-config |
AWS credential resolution |
aws-sigv4 |
SigV4 request signing |
reqwest |
Async HTTP client |
tokio |
Async runtime |
clap |
CLI argument parsing |
serde_json |
JSON serialization |
uuid |
UUID v4 generation |
All methods are async and return Result<T, Box<dyn std::error::Error>>:
// Environment management
pub async fn describe_environments(&self) -> Result<DescribeEnvironmentsResponse, ...>
pub async fn create_environment(&self, name: Option<&str>, vpc: Option<VpcConfig>)
-> Result<CreateEnvironmentResponse, ...>
pub async fn get_environment_status(&self, env_id: &str)
-> Result<EnvironmentStatusResponse, ...>
pub async fn start_environment(&self, env_id: &str) -> Result<(), ...>
pub async fn stop_environment(&self, env_id: &str) -> Result<StopEnvironmentResponse, ...>
pub async fn delete_environment(&self, env_id: &str) -> Result<(), ...>
// Session management
pub async fn create_session(&self, env_id: &str, session_type: &str, tab_id: &str,
q_cli_disabled: Option<bool>) -> Result<CreateSessionResponse, ...>
pub async fn delete_session(&self, env_id: &str, session_id: &str) -> Result<(), ...>
// Heartbeat
pub async fn send_heart_beat(&self, env_id: &str) -> Result<(), ...>
// File transfer
pub async fn get_file_upload_urls(&self, env_id: &str)
-> Result<FileTransferResponse, ...>
pub async fn get_file_download_urls(&self, env_id: &str)
-> Result<FileTransferResponse, ...>
// Credentials (console sessions only)
pub async fn put_credentials(&self, env_id: &str) -> Result<(), ...>For creating VPC environments with input validation:
// Create with validation (panics if invalid)
let vpc = VpcConfig::new(
"vpc-0123456789abcdef0".to_string(),
vec!["subnet-1".to_string(), "subnet-2".to_string()],
vec!["sg-1".to_string()],
);
// Or validate explicitly (returns Result)
let vpc = VpcConfig {
vpc_id: "vpc-0123456789abcdef0".to_string(),
subnet_ids: vec!["subnet-1".to_string()],
security_group_ids: vec!["sg-1".to_string(), "sg-2".to_string()],
};
match vpc.validate() {
Ok(_) => {
// VPC config is valid
let response = client.create_environment(Some("my-vpc-env"), Some(vpc)).await?;
}
Err(e) => eprintln!("Invalid VPC config: {}", e),
}Validation rules:
- Min 1 subnet required
- Max 5 security groups allowed
VpcConfig::new()panics on invalid config (fail-fast)VpcConfig::validate()returns Result for error handling
Each response type has helper methods:
// DescribeEnvironmentsResponse
response.environments() -> Vec<serde_json::Value>
response.pretty_print() -> Result<String, ...>
// CreateEnvironmentResponse
response.environment_id() -> Option<String>
response.status() -> Option<String>
// EnvironmentStatusResponse
response.status() -> Option<String>
response.environment_id() -> Option<String>
response.status_reason() -> Option<String>
// CreateSessionResponse
response.session_id() -> Option<String>
response.token_value() -> Option<String>
response.stream_url() -> Option<String>
// FileTransferResponse
response.file_upload_presigned_url() -> Option<String>
response.file_upload_presigned_fields() -> Option<serde_json::Value>
response.file_download_presigned_url() -> Option<String>
response.file_download_presigned_key() -> Option<String>
response.file_download_presigned_key_hash() -> Option<String>Ensure your AWS principal has these CloudShell permissions:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"cloudshell:DescribeEnvironments",
"cloudshell:GetEnvironmentStatus",
"cloudshell:CreateEnvironment",
"cloudshell:DeleteEnvironment",
"cloudshell:StartEnvironment",
"cloudshell:StopEnvironment",
"cloudshell:CreateSession",
"cloudshell:DeleteSession",
"cloudshell:SendHeartBeat",
"cloudshell:GetFileUploadUrls",
"cloudshell:GetFileDownloadUrls"
],
"Resource": "*"
}
]
}For VPC environments, also add EC2 permissions: ec2:CreateNetworkInterface, ec2:CreateTags, etc.
The library includes built-in validation for API parameters:
// Enforced automatically
- Min 1 subnet required
- Max 5 security groups allowed// Session creation validates UUID v4 format
let response = client.create_session(
&env_id,
"TMUX",
&Uuid::new_v4().to_string(), // Must be valid UUID v4
Some(true)
).await?;
// Invalid UUID returns error before API call
let invalid = client.create_session(
&env_id,
"TMUX",
"not-a-uuid", // ❌ Error: Invalid TabId
None
).await; // Err: "Invalid TabId: 'not-a-uuid' is not a valid UUID"CloudShell uses the rest-json protocol — operations are routed by URI path (e.g., /describeEnvironments), not HTTP headers.
Request flow:
- Build JSON request body
- Sign with SigV4 (AWS credentials + service + region)
- POST to
https://cloudshell.<region>.amazonaws.com/<operation> - Parse JSON response
- Return typed response wrapper
Why manual signing?
Standard AWS SDKs may not support the CloudShell service definition, so this project uses the aws-sigv4 crate for direct request signing. This gives full control over the request format.
- Max 2 VPC environments per IAM principal
- Max 5 security groups per VPC environment
- ~1 GB storage per environment per region
- Environments auto-sleep after inactivity (use heartbeats to prevent)
- Environments can be reclaimed by AWS without notice
When using the connect command with --inject-credentials, AWS credentials are automatically extracted from your current AWS profile and injected as environment variables into the CloudShell session. This enables AWS CLI commands to work without additional setup.
Automatic credential injection:
./target/release/cloudshell-rs connect --inject-credentialsThis will:
- Extract credentials from your current AWS profile (or
--profileif specified) - Silently inject them as
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_SESSION_TOKEN,AWS_DEFAULT_REGION - Make credentials read-only to prevent accidental exposure
- Hide all injections from terminal output and bash history
Security features:
- ✅ Terminal echo disabled during injection (credentials never appear on screen)
- ✅ Leading space prefix prevents bash history recording
- ✅ Bash history disabled during setup (
HISTCONTROL=ignorespace,set +o history) - ✅ Credentials marked as
readonly(preventsecho $AWS_*) - ✅ Screen cleared after injection to remove visual traces
The credentials are passed securely through the SSM Session Manager session with maximum protection against exposure.
- Python Reference: cloudshell-boto3 — upstream project with Python examples
- AWS CloudShell Docs: https://docs.aws.amazon.com/cloudshell/latest/userguide/
- AWS SigV4 Signing: https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
- Smithy Language: https://smithy.io/ — service model format
- CloudShell Deep Dive: https://awsteele.com/blog/2024/01/11/deep-dive-into-aws-cloudshell.html
MIT License. See LICENSE for details.
Found a bug? Have a feature request? Open an issue on GitHub.
Created and maintained by Bart Leboeuf.
This project reverse-engineers an undocumented AWS API. AWS can change or deprecate it at any time. Use at your own risk.
For mission-critical workloads, stick with officially documented AWS services.