Python module for forwarding commands over WebSockets. Fully asyncio compatible.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
John-Mark Gurney c6f81e4f5f make ctrl-c work properly.. 3 years ago
wsfwd make ctrl-c work properly.. 3 years ago
.gitignore add first cut of wsfwd, only implements auth.. rest to follow.. 4 years ago
Makefile add first cut of wsfwd, only implements auth.. rest to follow.. 4 years ago
README.md add some documentation, wrap a few long lines.. 3 years ago
requirements.txt add first cut of wsfwd, only implements auth.. rest to follow.. 4 years ago
setup.py add support for server and client to do forwarding to TCP streams... 3 years ago

README.md

WSFWD

WSFWD is a protocol for authentication (optional) and forwarding command data over a WebSocket. This originally is for simple stdin and stdout forwarding, for running a program like sshd -i to bypass port blocking, or allow more custom routing and execution.

It is designed so that in the future, it could support forwarding stderr separately, but also out of band messages, such as window change information, so that a full tty could be forwarded over the connection. As transporting a protocol like ssh does all of this for you, it is unlikely to be expanded to support it.

Usage

Included is a sample program that can be used to forward stream connections to one of the specified hosts and port.

First install the dependencies. Note that often it’s best to install it into a virtual environmentvenv, so, create one:

python -m venv dirname

Where dirname will be a directory that will be created w/ the virtual environment. Start the virtual environment (if you’re using bash/zsh, other shells are documented in the link above):

source ./dirname/bin/activate

Install wsfwd:

pip install git+https://www.funkthat.com/gitea/jmg/wsfwd

Run the server (for example to forward to the local machine’s sshd):

wsfwd serve 127.0.0.1:22

Multiple ip:port maybe specified on the command to allow forwarding to multiple hosts.

This will start up a webserver. It uses hypercorn, so, all the hypercorn command line arguments may be specified via the --hypercorn argument. For example, to bind only to 127.0.0.1, you can run:

wsfwd serve --hypercorn '--bind 127.0.0.1' 127.0.0.1:22

Once that has been setup, the client can be used to connect to the specified server:

wsfwd connect http://127.0.0.1/connect 127.0.0.1:22

This is most useful to be specified to ProxyCommand in your .ssh/config file:

Host localhost
	ProxyCommand /path/to/dirname/bin/wsfwd connect http://127.0.0.1:8000/connect 127.0.0.1:22

There are many ways to use it. Another option could also be to launch it via inetd to transparently pass normal ssh sessions to remote hosts.

Protocol

All WebSocket messages much be treated as binary. This is for simplicity and speed when doing large binary transfers. The format of the messages are:

The chan byte value determines the meaning of payload.

If chan byte is zero (aka 00), then the payload is a UTF-8 encoded JSON message that contains a command, for example, an authentication request or command to execute.

Other values for chan byte are dynamically allocated based upon the commands sent.

For commands, the JSON object MUST have a key of cmd, and the value is the command to execute. Each cmd that is sent MUST be acknowledged with an object that has a key of resp, and a value that is the same as the command. If the command fails, the response object MUST have a key of error, and the value MUST be a string containing an error message.

If the response to a command could be confused, it is expected that parts of command request be included in the response. In other cases, such as auth, it is expected that the auth message NOT be included, and that the response to a successful auth message is simply: { ‘resp’: ‘auth’ }

Typical command flow

The following is a typical command flow. Failure messages are included in-line for additional context.

Auth message (optional):

{ 'cmd': 'auth', 'auth': { 'bearer': <token> } }

Error message in case of invalid auth:

{ 'resp': 'auth', 'error': 'Invalid auth' }

Command to execute a program:

{ 'cmd': 'exec', 'args': [ ... ], 'stdin': <chan>, 'stdout': <chan> }

Error if unable to exec the requested program:

{ 'resp': 'exec', 'error': 'Unable to exec', ... }, 'args': [...], [...] }

Success:

{ 'resp': 'exec', 'args': [...], [...] }

Here, messages on the stdin/stdout channels are exchanged.

Close stdin or stdout or other channel:

{ 'cmd': 'chanclose', 'chan': <chan> }

Success:

{ 'resp': 'chanclose', 'chan': <chan> }

Once stdin is closed, the server side will wait for stdout to be closed by the program, send the proper chanclose command for it, and then wait for the program to exit, and return the exit code.

{ 'cmd': 'exit', 'code': <code> }

Currently not implemented, but if stdin is closed by the program, the server may send a chanclose preemptively. The server will discard any additional data, and it is an error to send additional data on the channel after the client responds w/ a resp: chanclose acknowledgment.

Additional Notes

These are the target client and server implementations that WSFWD is designed to work with. It should easily be able to be made to work with other implementations.

FastAPI uses starlette: https://www.starlette.io/websockets/

Client: https://github.com/aaugustin/websockets