Giter Club home page Giter Club logo

sshx's Introduction

sshx

A secure web-based, collaborative terminal.

Features:

  • Run a single command to share your terminal with anyone.
  • Resize, move windows, and freely zoom and pan on an infinite canvas.
  • See other people's cursors moving in real time.
  • Connect to the nearest server in a globally distributed mesh.
  • End-to-end encryption with Argon2 and AES.
  • Automatic reconnection and real-time latency estimates.
  • Predictive echo for faster local editing (à la Mosh).

Visit sshx.io to learn more.

Installation

Just run this command to get the sshx binary for your platform.

curl -sSf https://sshx.io/get | sh

Supports Linux and MacOS on x86_64 and ARM64 architectures, as well as embedded ARMv6 and ARMv7-A systems. The precompiled Linux binaries are statically linked.

If you just want to try it out without installing, use:

curl -sSf https://sshx.io/get | sh -s run

Inspect the script for additional options.

CI/CD

You can run sshx in continuous integration workflows to help debug tricky issues, like in GitHub Actions.

name: CI
on: push

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      # ... other steps ...

      - run: curl -sSf https://sshx.io/get | sh -s run
      #      ^
      #      └ This will open a remote terminal session and print the URL. It
      #        should take under a second.

We don't have a prepackaged action because it's just a single command. It works anywhere: GitLab CI, CircleCI, Buildkite, CI on your Raspberry Pi, etc.

Be careful adding this to a public GitHub repository, as any user can view the logs of a CI job while it is running.

Development

Here's how to work on the project, if you want to contribute.

Building from source

To build the latest version of the client from source, clone this repository and run, with Rust installed:

cargo install --path crates/sshx

This will compile the sshx binary and place it in your ~/.cargo/bin folder.

Workflow

First, start service containers for development.

docker compose up -d

Install Rust 1.70+, Node v18, NPM v9, and mprocs. Then, run

npm install
mprocs

This will compile and start the server, an instance of the client, and the web frontend in parallel on your machine.

Deployment

I host the application servers on Fly.io and with Redis Cloud.

Self-hosted deployments are not supported at the moment. If you want to deploy sshx, you'll need to properly implement HTTP/TCP reverse proxies, gRPC forwarding, TLS termination, private mesh networking, and graceful shutdown.

Please do not run the development commands in a public setting, as this is insecure.

sshx's People

Contributors

ekzhang avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

sshx's Issues

Support proper zooming

Currently, terminal text does not scale with browser zoom level.

When editing a terminal, Ctrl+scroll wheel changes the browser zoom level.

Re mprocs TODO

Regarding this line:

stop: SIGKILL # TODO: Why is this necessary?

Pretty sure it's because Node by default doesn't have any SIGTERM handler (mprocs defaults to SIGTERM: https://github.com/pvolok/mprocs/blob/c7da714949ca9dff6a83ac020d3b527929d9f979/src/proc/mod.rs#L191), so you need to hook in your own to perform shutdown logic. Setting the stop command to SIGKILL makes it actually stop the process immediately instead of hanging because SIGTERM wasn't handled.

Though you are using vite dev and it does have SIGTERM handling vitejs/vite@4338d7d so 🤷‍♂️ it should work.

Just wanted to add context in case it's helpful; just came reading this from HN, looks awesome!

Server refusing connection

I'm encountering issues while running the development environment with 'mprocs.' The 'sshx' client app is throwing the following error:

`ERROR sshx: transport error

Caused by:
0: error trying to connect: tcp connect error: Connection refused (os error 111)
1: tcp connect error: Connection refused (os error 111)
2: Connection refused (os error 111)`

Node version: 18.16.0
NPM version: 9.5.1
Rust version: 1.71.1

The server and web components are running without any problems. Any ideas on how to debug this?

Feature request | Embed Browser view

I want to add browsers in the view.
Use case is pair programming.
If i'm coding a website with somebody, I want them to be able to see the results.

Not sure If this can be done with just an Iframe or something.

The commands are encrypted but not authenticated, allowing the server to tamper with them

Right now sshx uses AES-CTR, which encrypts data but does not authenticate it. This means that while the server cannot see what the user types, the server can alter the commands transferred or inject its own commands, and there is no cryptographic protection against that. Injecting commands and observing whether they were executed or not can also be sometimes used to decrypt the data transmitted.

To protect against the server injecting its own commands you will need authenticated encryption: https://en.wikipedia.org/wiki/Authenticated_encryption

https://github.com/RustCrypto/AEADs lists Rust implementations of various authenticated encryption constructs. You can switch to AES-GCM-SIV if you want to stick with AES, although I would recommend switching to ChaCha20+Poly1305 since it is simpler and is far more resilient to timing attacks.

allow users to specify the destination bin-dir

Thanks for developing this great tool.

I 'd like however more control on where an executable ends up.

allow users to specify the destination bin-dir
eg via a target-option "--target-bin $HOME/bin

Creating a terminal in Alpine Linux exits immediately

I'm getting an UNSUPPORTED_OS, like on the screenshot:

image

I'm not sure where this error is coming from, and how to fix it. Happy to provide more details, but not sure what those would be at point 🤔

I'm trying to connect to an Alpine-based container, running in GitHub Actions.

github actions how to stop

For github action usage:

After opening a terminal with
run: curl -sSf https://sshx.io/get | sh && sshx

Is it possible to gracefully shut it down and let the action proceed?

The container can be shut down with e.g. linux shutdown command, but then the workflow is not passed.

Great project, thanks a lot!

Permission Denied Error

image Error
2024-01-01T07:55:37.418583Z ERROR sshx: status: PermissionDenied, message: "grpc-status header missing, mapped from HTTP status code 403", details: [], metadata: MetadataMap { headers: {"date": "Mon, 01 Jan 2024 07:55:38 GMT", "cache-control": "private, max-age=0, no-store, no-cache, must-revalidate, post-check=0, pre-check=0", "expires": "Thu, 01 Jan 1970 00:00:01 GMT", "report-to": "{\"endpoints\":[{\"url\":\"https:\/\/a.nel.cloudflare.com\/report\/v3?s=I5MYTyX%2FD%2BkCv26TnLAykfds6I5Ljxr4QqJtlwguZKl9W4JuVqCMV5fCKoFWYMNI3irudiRfJsQMWTzYIwU56f7qNeiLrcIfiXXJs92571t9UgBBRfPIeCik\"}],\"group\":\"cf-nel\",\"max_age\":604800}", "nel": "{\"success_fraction\":0,\"report_to\":\"cf-nel\",\"max_age\":604800}", "vary": "Accept-Encoding", "server": "cloudflare", "cf-ray": "83e9337c3958f425-BOM", "alt-svc": "h3=\":443\"; ma=86400"} }

Allow specification of trusted certificates / CAs

First and foremost: Thanks for sharing sshx and huge respect for what you've built here!

I'm running my own sshx server and it gets a certificate by an internal (private) CA. This leads to the sshx client not accepting the cert, because it seems it brings a list of CAs with it using a rust package.

So, it would be nice to be able to specify a file or folder with certificates of CAs (aka trust store) that should be trusted by the sshx client additionally or instead of the ones included, similar to the --cacert option in curl or many others.

Thanks!

Env (or dotfile in HOME) for default server config

Haven't actually played around with this yet, but I was looking at the code (since it's a small codebase):

#[clap(long, default_value = "https://sshx.io")]
server: String,

It doesn't seem like there's a way to set a different default for --server.

In cases where the server is self-hosted, I envision it would be preferred to have this set in env (in my .bashrc, e.g. SSHX_SERVER=https://sshx.internal-domain.com) or via a dotfile of somekind.

This would avoid the "risk" of accidentally using sshx.io when data trust is important (corporate network etc), and making it easier to use by default.

Alternative is to make a bash alias with --server always set, I guess, but that kinda feels worse than proper config.

Icons are sometimes not centered

Also only for some operating systems and browsers?

ok tiny detail but the icons are not always centered, depending on where the terminal window is placed:

image

probably just the browser trying to round the icon position to the nearest pixel

add option to ignore ssl cert problems

Some corp networks injects an SSL cert (e.g. via Zscaler). This makes sshx bail with "unknownIssuer".

It would be great with a cmdline argument that tells it to ignore such errors.

sshx in CI/CD of public projects can have public logs

Great project!

But speaking of CI/CD, consider scenario:

  • to debug, someone puts sshx to CI/CD of their open-source project
  • the URL is printed in the CI/CD output and anyone can read it
  • anyone can connect
  • printenv shows all environment variables, including secrets (if any, of course)

I'd add at least a warning about this in the README.

Terminall freezes with nushell

I started a session on a nushell shell and it defaulted to bash, which did work. When I opened a terminal and tried to run nu on it, that terminal would just freeze.
I tried to run it with the --shell nu and --shell /home/slushee/.cargo/bin/nu arguments and any terminal I created on the website would just freeze without giving a prompt.

I wonder if this is an issue with nu not being POSIX compliant, this could be tested with fish.

Current behavior

Terminals which attempt to use the nu shell freeze and don't show any prompt

Expected behavior

The application works the same way as it does with bash and zsh, showing a functional nushell prompt for every terminal opened.

Envoriment

  • sshx version: 0.2.1, compiled with rustc 1.73.0
  • Nushell version: 0.86.0
  • Browser: Librewolf 120.0-1
  • OS: Fedora 38 workstation (kernel 6.5.10-200.fc38.x86_64)

feature request: sshx-server handles TLS via chain and key options

Since the current self-built service steps are, first the front-end build static files placed into the sshx-server working directory, and then start an https server forwarding to the sshx service listener, so I recommend adding parameters to specify the certificate for ssl listening to start, and the front-end files embedded in the program, eliminating the need to build the front-end and traffic forwarding of these steps.

Network connection UI

WifiOff/Wifi icons, perhaps auto-display the dropdown or something

long-term stretch: latency numbers and server region info

Connecting

When I set up sshx myself and run mproc, the top left corner always displays "Connecting".

run sshx in the background

perhaps sshx -b would allow you to run a sshx session without blocking the current shell and instead running it in a background thread.

New site logo

Can't really use the current one anymore because of negative associations with "X"

Binary handling on MacOS/Ventura

There's no /usr/local/bin path, by default, on Ventrua.
And no handling if the path doesn't exist:

printf "\n${ansi_reset}${ansi_info}↯ Adding sshx binary to ${ansi_underline}%s${ansi_reset}\n" "/usr/local/bin"
if [ "$(id -u)" -ne 0 ]; then
  sudo tar xf "$temp" -C /usr/local/bin
else
  tar xf "$temp" -C /usr/local/bin
fi
↯ Adding sshx binary to /usr/local/bin
tar: could not chdir to '/usr/local/bin'


↯ Done! You can now run sshx.

The target can be added (including PATH).
Optionally, a brew (or other) package may be helpful here for Mac installs.

Version number in download file

Hi! Cool utility! I wanna put this in the Homebrew repo but first it would be nice if you could put the version number in the download file. Also, using Github tags and releases would be great. You can upload the release files for each release for free with Github Actions.

deb/rpm packaging

Thanks you for this amazing solution/service; I can see a million use cases where I can get a good mileage out of it.

Installing binaries with curl | sh has a bad rep, although this is the easiest way to go and I use it for my project as well. But I would suggest looking at providing rpm/deb packages that can be uploaded to gemfury.com deb/apt repo for free.

Supply chain issue would rather demand having a non-curl-based installation option with a potential gpg signing of the repo and artifacts. This might be way ahead of time, since sshx has just been released, but I'd like to see it mature and look into alternative installation options.

Thanks again, you rock!

nginx: stream error received: unspecific protocol error detected

Hey 👋 and thanks for the great project!

After several attempts to figure out how to make it work with nginx reverse-proxy, I've created half-working solution. I am providing below steps to reproduce the issue and ultimately figure out what is the problem.

sshx-server setup

  1. Create and enter temp folder:
mkdir /tmp/reproduce-issue && cd /tmp/reproduce-issue
  1. Clone and enter the sshx repository:
git clone https://github.com/ekzhang/sshx.git && cd sshx
  1. Build frontend:
npm ci && npm run build
  1. Build backend:
cargo build --release --bin sshx-server
  1. Launch sshx (which will also serve frontend).:
mv target/release/sshx-server . && ./sshx-server --secret dev-secret --listen :: --override-origin https://localhost:8080

nginx setup

  1. Create and enter nginx folder:
mkdir /tmp/reproduce-issue/nginx && cd /tmp/reproduce-issue/nginx
  1. Create nginx.conf with the following content:
# Run nginx using:
#     nginx -p $PWD -e stderr -c nginx.conf

daemon off;  # run in foreground

events {}

pid nginx.pid;

http {
    access_log /dev/stdout;

    # Locally generated SSL certificates from mkcert
    ssl_certificate ./localhost+1.pem;
    ssl_certificate_key ./localhost+1-key.pem;
    ssl_protocols	TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;

    # Directories nginx needs configured to start up.
    client_body_temp_path .;
    proxy_temp_path .;
    fastcgi_temp_path .;
    uwsgi_temp_path .;
    scgi_temp_path .;
    
    ###################
    client_body_timeout 5;  # <------ makes sshx client spitting error log every 5 seconds
    ###################

    # Connection upgrade variable for websocket
    map $http_upgrade $connection_upgrade {  
        default upgrade;
        ''      close;
    }

    server {
        server_name localhost 127.0.0.1;
        listen 8080 ssl http2;

        location / {
            proxy_pass   http://127.0.0.1:8051;
        }

        location /api {
            # Websocket headers
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection $connection_upgrade;
            proxy_set_header Host $http_host;
            proxy_pass   http://127.0.0.1:8051;
        }
    }
}
  1. Setup local certificates with mkcert (otherwise grpc/websockets won't work) and assign correct permissions:
sudo mkcert -install && sudo mkcert localhost 127.0.0.1 && sudo chown $USER:$USER *.pem
  1. Launch nginx:
nginx -p $PWD -e stderr -c nginx.conf

sshx setup

The default sshx binaries won't work, because tonic is compiled with tls-webpki-roots feature, which means that sshx will not lookup OS ca-certificates bundle, making our self-signed certificates useless and throwing the following error:

❯ ./sshx --server https://localhost:8080
2023-11-06T12:15:26.058671Z ERROR sshx: transport error

Caused by:
    0: error trying to connect: invalid peer certificate: UnknownIssuer
    1: invalid peer certificate: UnknownIssuer

In order to fix that we need to recompile sshx binary with tls-roots feature (which will lookup OS ca-certificates):

  1. Enter sshx cloned git repository:
cd /tmp/reproduce-issue/sshx
  1. Save the following content as tonic_fix.patch:
diff --git a/Cargo.toml b/Cargo.toml
index 4b0c58e..c028b8d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -19,7 +19,7 @@ rand = "0.8.5"
 serde = { version = "1.0.188", features = ["derive", "rc"] }
 tokio = { version = "1.32.0", features = ["full"] }
 tokio-stream = { version = "0.1.14", features = ["sync"] }
-tonic = { version = "0.10.0", features = ["tls", "tls-webpki-roots"] }
+tonic = { version = "0.10.0", features = ["tls", "tls-roots"] }
 tracing = "0.1.37"
 tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }

and apply it like so:

git apply tonic_fix.patch
  1. Compile modified sshx:
cargo build --release --bin sshx
  1. Launch client:
mv target/release/sshx . && ./sshx --server https://localhost:8080

Now, here's the issue: there's no connection between server and shell and I'm unable to spawn terminal:

image

And once in a while sshx produces the following log:

2023-11-06T14:25:05.152762Z ERROR sshx::controller: disconnected, retrying in 1s... err=status: Internal, message: "h2 protocol error: http2 error: stream error received: unspecific protocol error detected", details: [], metadata: MetadataMap { headers: {} }

Here's the log with RUST_LOG=trace: trace_sshx.log

Any help would be much appreciated!

Key derivation seems needlessly convoluted

The way the encryption key is currently derived seems to be:

  1. rand gets 128 or more bits entropy from the OS and seeds a ChaCha PRNG with that
  2. No more than 83 bits of entropy out of it get turned into an alphanumeric string
  3. That string is fed to Argon2 to pad it back to 128 bits of length (still 83 bits of entropy)

The straightforward way to derive an encryption key would be just getting 128 bits from the OS entropy source with getrandom crate, and that seems superior in every way: it is more secure (128 bits of entropy rather than 83), faster (no intermediate time-consuming cryptography), and results in a smaller download size for both the native binary and web client. When the key needs to be represented in text, it can simply be base64-encoded.

Why is key derivation implemented this way, instead of the seemingly superior straightforward solution?

References

The key is derived here:

let encryption_key = rand_alphanumeric(14); // 83.3 bits of entropy

where rand_alphanumeric uses rand::ThreadRng internally:

/// Generate a cryptographically-secure, random alphanumeric value.
pub fn rand_alphanumeric(len: usize) -> String {
use rand::{distributions::Alphanumeric, thread_rng, Rng};
thread_rng()
.sample_iter(Alphanumeric)
.take(len)
.map(char::from)
.collect()
}

The ThreadRng is backed by some sort of ChaCha variant, according to cargo tree.

After the key is derived it is then fed to Argon2 with a public salt:

// Note: The KDF salt is public, as it needs to be used from the web client. It
// only exists to make rainbow table attacks less likely.
const SALT: &str =
"This is a non-random salt for sshx.io, since we want to stretch the security of 83-bit keys!";

Do not require root

This is the suggested way to install sshx: curl -sSf https://sshx.io/get | sh. It tries to use sudo, but on some systems, users have no rights to become administrators. What about using $HOME/.local/bin instead of /usr/local/bin for non-root users on Linux systems (I have no Idea what MacOS uses as path for local binaries)?

sshx/static/get

Lines 42 to 47 in 91c82d4

printf "\n${ansi_reset}${ansi_info}↯ Adding sshx binary to ${ansi_underline}%s${ansi_reset}\n" "/usr/local/bin"
if [ "$(id -u)" -ne 0 ]; then
sudo tar xf "$temp" -C /usr/local/bin
else
tar xf "$temp" -C /usr/local/bin
fi

[bug] Zombie Process and Memory Leak on Termination

I have identified a critical issue on my embedded device (aarch64 architecture). Upon running a specific process in the terminal, I observed that when I close the terminal, it leaves behind a zombie process, and the associated memory is not reclaimed, causing continuous memory growth. This is impacting system performance and may have adverse effects on other processes and device functionalities.

Device Information:

  • Architecture: aarch64
  • Operating System: linux
  • Relevant Process: adbd --> bash --> sshx --> bash

image

How to self host the sshx server?

Hi, I am currently trying to run the server locally.

What I did

docker build -t sshx .
docker run -p 8051:8051 sshx:latest

image

image

Issue is not only for WSL.
Am I missing a step?

GitHub Actions 2 (hosted)

2023-11-08T08:51:54.0362441Z Requested labels: ubuntu-latest
2023-11-08T08:51:54.0362720Z Job defined at: cnmeeia/sshx/.github/workflows/sshx.yaml@refs/heads/main
2023-11-08T08:51:54.0362831Z Waiting for a runner to pick up this job...
2023-11-08T08:51:54.5835535Z Job is waiting for a hosted runner to come online.
2023-11-08T08:51:57.4520653Z Job is about to start running on the hosted runner: GitHub Actions 2 (hosted)

Password authentication

I want to preface this with: very cool project, works flawlessly right now

I thing I noticed is that the link alone is not enough to actually enough to gain full system access as the user running sshx, that could lead to potential compromise if the url is leaked for any reason.

This is why I propose that you either implement password authentication where the sshx binary does the password validation or something similiar to ssh's authorized_keys file. So that any user can send you their public key, you give it to sshx, then the user visits the url, they get prompted for a key, which the server sends to sshx which does the validation.

That would help increase trust and security in the project

Create an xxh plugin

An xxh plugin would make sshx instantly handy on remote sessions created with this other life-changing tool.

How cool would that be ?! 🤩

Access permissions via CLI flags

Maybe a sshx --read-only flag would be nice, so that you can let someone read + scroll on your terminal. Also request to collaborate from the WebUI would be nice. Then this would show a "y/n" style prompt in the terminal where sshx is running.

New homepage design

i know it's techie, but i changed my mind, wanted something a little more relaxed and inviting. blue-indigo gradients are tired.

the actual application page can be rethemed too, up to user setting

image

image

image

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.