Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions misc/serverless-ssh-proxy/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
target
45 changes: 45 additions & 0 deletions misc/serverless-ssh-proxy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Serverless SSH Proxy

Instance management wastes people quite a lot of [money](https://x.com/rspruijt/status/1878162991792685252). This project provides a proof of concept of a serverless proxy server that compatible with OpenSSH clients that automatically manages Modal container lifecycles with your connections. A container is automatically booted up upon connection establishment, and a filesystem snapshot is retrieved upon closure and used as a base image for future connections. Besides serving the proxy itself, you never pay for container management.

## Environment Variables

To deploy this script, a few environment variables need to be set.
- `MODAL_TOKEN_ID`: This should be equal to your `token_id` in `~/.modal.toml` assuming you have a (Modal account)[https://modal.com/docs/guide].
- `MODAL_TOKEN_SECRET`: This should be equal to `token_secret` in `~/.modal.toml`.
- `SSH_PUBLIC_KEY`: You can generate a SSH keypair with `ssh-keygen -t ed25519 -C "[email protected]"`. Then run `export SSH_PUBLIC_KEY="$(<~/.ssh/id_ed25519.pub)"`.

Optionally you can also set `MODAL_ENVIRONMENT_NAME` to your desired environment. This will default to `main` if not set.


## Deploy and run

```sh
modal run deploy_script.py
```

As SSH operates above TCP instead of TLS, and instead implements its own security layer, we set an `unencrypted=true` flag when we create a tunnel to our container in our deploy script. Y

## SSH into a container

A command to start an SSH connection will be outputted in your app logs. Assuming you have your SSH private key in `~/.ssh/id_ed25519`, it'll look something like:

```sh
ssh -p 35109 -i ~/.ssh/id_ed25519 [email protected]
```

Note that in this implementation of the proxy server the `user` field is a wildcard that can be set to any string and is not used by the proxy server.


## Demo
Apologies for low resolution! You can view a higher resolution version [here!](./demo.mp4)
![Demo preview](./demo.gif)

## Contributing

Pull requests and issues are welcome!


## License

MIT
Binary file added misc/serverless-ssh-proxy/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added misc/serverless-ssh-proxy/demo.mp4
Binary file not shown.
52 changes: 52 additions & 0 deletions misc/serverless-ssh-proxy/deploy_script.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import os

import modal

app = modal.App("ssh-proxy-app")


ssh_proxy_dir = os.path.dirname(os.path.abspath(__file__)) + "/ssh-proxy"

ssh_proxy_image = (
modal.Image.debian_slim()
.apt_install("curl", "pkg-config", "libssl-dev", "protobuf-compiler")
.run_commands("curl https://sh.rustup.rs -sSf | sh -s -- -y")
.add_local_dir(ssh_proxy_dir, remote_path="/ssh-proxy")
)


modal_secrets = {
"TOKEN_ID": os.environ.get("MODAL_TOKEN_ID"),
"TOKEN_SECRET": os.environ.get("MODAL_TOKEN_SECRET"),
"SSH_PUBLIC_KEY": os.environ.get("SSH_PUBLIC_KEY"),
"RUSTFLAGS": " -Awarnings",
"MODAL_ENVIRONMENT_NAME": os.environ.get("MODAL_ENVIRONMENT_NAME", "main"),
}


@app.function(
secrets=[modal.Secret.from_dict(modal_secrets)],
image=ssh_proxy_image,
timeout=30 * 60,
)
def run_ssh_proxy():
import subprocess

subprocess.run(["ls", "/ssh-proxy"], check=True)
with modal.forward(22, unencrypted=True) as tunnel:
(hostname, port) = tunnel.tcp_socket

print(f"""Tunnel created\n. Use the following SSH command to connect:
ssh -p {port} -i [private_key_path] user@{hostname}
Note that in this implementation user can be any string.""")
result = subprocess.run(
["/root/.cargo/bin/cargo", "run", "--release", "--bin", "ssh_proxy"],
check=True,
cwd="/ssh-proxy",
)
return result.returncode


@app.local_entrypoint()
def main():
run_ssh_proxy.remote()
Loading