Skip to content
tg123Public

Latest commit

Β 

History

897 Commits

Folders and files

Repository files navigation

sshpiper πŸ–‡

E2E Go Report Card Docker Image

sshpiper is the reverse proxy for sshd. all protocols, including ssh, scp, port forwarding, running on top of ssh are supported.

Note: this is v1 version, checkout legacy v0 here

Overview and Terminology

  • downstream: the client side, typically an ssh client.
  • upstream: the server side, typically an ssh server.
  • plugin: handles the routing from downstream to upstream. The plugin is also responsible for mapping authentication methods to the upstream server. For example, the downstream may use password authentication, but the upstream server may receive public key authentication mapped by sshpiper.
  • additional challenge: some plugins will not only perform routing but also add additional challenges to SSH authentication for the upstream server. For example, the downstream may be asked for two-factor authentication provided by the plugin.
+---------+                      +------------------+          +-----------------+
|         |                      |                  |          |                 |
|   Bob   +----ssh -l bob----+   |   sshpiper    +------------->   Bob' machine  |
|         |                  |   |               |  |          |                 |
+---------+                  |   |               |  |          +-----------------+
                             +---> pipe-by-name--+  |                             
+---------+                  |   |               |  |          +-----------------+
|         |                  |   |               |  |          |                 |
|  Alice  +----ssh -l alice--+   |               +------------->  Alice' machine |
|         |                      |                  |          |                 |
+---------+                      +------------------+          +-----------------+


 downstream                         sshpiper                        upstream                     

Quick start

Build

git clone https://github.com/tg123/sshpiper
cd sshpiper

mkdir out
go build -tags full -o out ./...
(cd cmd/sshpiperd && go build -o ../../out/ .)

Native Windows and macOS E2E tests

On Windows, run from the repository root in PowerShell with Windows Go and Docker Compose installed in the default WSL2 distribution (with its Docker daemon running):

.\e2e\windows\run.ps1

On macOS, use native Go and a running Docker daemon with Compose (for example, Docker Desktop or Colima), then run from the repository root:

bash e2e/macos/run.sh

Both runners use the shared e2e/native suite. It reads .goreleaser.yaml to build every plugin shipped for the current OS and fails if any release plugin lacks a native E2E scenario. The daemon and plugins run from paths containing spaces. Windows and macOS currently ship the same eight plugins:

Plugin Native E2E behavior
fixed Password rejection, binary stdin/stdout, stderr, exit status, reconnects
workingdir Directory-based username remapping and rejection of missing users
yaml Regex username remapping and rejection of unmatched users
username-router Target/port and upstream user parsed from the downstream username; malformed username rejection
lua Script-based routing, username remapping, and rejection
failtoban Chaining with fixed, banning after failed authentication, and loopback allowlisting
metrics Chaining with fixed, HTTP connection gauge lifecycle, authentication and pipe-creation error counters
revtunnel Public-key registration, password-authenticated reverse forwarding, native file session store, and revocation

On Windows, the suite also checks plugin termination when the daemon is forcibly killed, using a helper that deliberately survives stdio closure. File-based routing tests use --no-check-perm only on Windows, whose mode bits cannot express Unix owner-only permissions; macOS keeps permission checks enabled. Docker and Kubernetes plugins are Linux-only in the release config; simplemath is not shipped. On macOS, a pipe-watching supervisor terminates each daemon's process group, including its plugins, even if the parent test process times out or is interrupted.

The upstream is the real OpenSSH host-password service from e2e/docker-compose.yml, with e2e/docker-compose.native.yml publishing an ephemeral loopback port. Each runner starts an isolated Compose project (through WSL2 on Windows), runs native go test, and collects logs and removes the containers and volumes even on failure. Only the upstream SSH server runs in Docker; the Go runner, daemon, and plugins stay native to the host OS.

The E2E workflow provisions WSL2 and Docker on windows-latest and Colima on macos-15-intel, alongside the existing Linux Docker Compose suite. The macOS job uses Intel because hosted Apple Silicon runners do not support the nested virtualization Colima needs; native macOS arm64 is not exercised in CI. To run Go tests directly against an already-started Compose host-password service, set SSHPIPERD_E2E_UPSTREAM to its published host:port, then run go test -v -count=1 -tags e2e -timeout 10m ./e2e/native.

Run simple demo

start dummy sshd server

docker run -d -e USER_NAME=user -e USER_PASSWORD=pass -e PASSWORD_ACCESS=true -p 127.0.0.1:5522:2222 lscr.io/linuxserver/openssh-server

start sshpiperd with fixed plugin targeting the dummy sshd server

./out/sshpiperd -i /tmp/sshpiperkey --server-key-generate-mode notexist --log-level=trace ./out/fixed --target 127.0.0.1:5522

test ssh connection (password: pass)

ssh 127.0.0.1 -l user -p 2222

βž• math before login?

Here illustrates the example of additional challenge before the fixed plugin.

./out/sshpiperd -i /tmp/sshpiperkey --server-key-generate-mode notexist --log-level=trace ./out/simplemath -- ./out/fixed --target 127.0.0.1:5522

More examples

For Docker Compose demos (including username routing and Lua publickey git routing), see examples/.

Admin UI (sshpiperd-webadmin)

sshpiperd can optionally expose a small admin gRPC API that lets external tools list live SSH sessions, view their terminal output in real time, and kill them. This API is off by default and is enabled by passing a non-zero --admin-grpc-port. By default the API requires TLS, so a server cert/key must be supplied:

./out/sshpiperd --admin-grpc-port 8222 \
  --admin-grpc-tls-cert server.crt \
  --admin-grpc-tls-key server.key \
  ... <plugin> ...

For mutual TLS (recommended for production), also pass --admin-grpc-tls-cacert ca.crt so clients must present a certificate signed by ca.crt. To opt out of TLS entirely on a trusted network, pass --admin-grpc-insecure.

A separate binary, sshpiperd-webadmin, aggregates one or more sshpiperd admin endpoints and serves a browser dashboard plus a JSON HTTP API:

./out/sshpiperd-webadmin \
  --sshpiperd 127.0.0.1:8222 \
  --sshpiperd piper-2.internal:8222 \
  --tls-cacert ca.crt --tls-cert client.crt --tls-key client.key \
  -l 127.0.0.1 -p 8080

Then open http://127.0.0.1:8080. Endpoints can also be supplied via the SSHPIPERD_WEBADMIN_ENDPOINTS env var (comma-separated). Pass --insecure on the webadmin side when sshpiperd is started with --admin-grpc-insecure. The same client library (libadmin/) is structured so a future CLI tool (sshpiperd-admin) can reuse the discovery + aggregator code.

Plugins

icons

  • πŸ”€: routing plugin
  • πŸ”’: additional challenge plugin
  • πŸ“ˆ: metrics plugin

Plugin list

  • workingdir πŸ”€: /home-like directory to managed upstreams routing by sshpiperd.
  • yaml πŸ”€: config routing with a single yaml file.
  • lua πŸ”€πŸ”’: scriptable plugin for custom routing and auth challenges.
  • docker πŸ”€: pipe into docker containers.
  • kubernetes πŸ”€: manage pipes via Kubernetes CRD.
  • azdevicecode πŸ”’: ask user to enter azure device code before login
  • fixed πŸ”€: fixed targeting the dummy sshd server
  • username-router πŸ”€: route based on username, the username format is target+username, where target is the target host and username is the username to use for that target.
  • simplemath πŸ”’: ask for very simple math question before login, demo purpose
  • githubapp πŸ”€: login ssh with your github account
  • restful by @11notes πŸ”€πŸ”’: The rest plugin for sshpiperd is a simple plugin that allows you to use a restful backend for authentication and challenge.
  • failtoban πŸ”’: ban ip after failed login attempts
  • openpubkeyπŸ”€πŸ”’: integrate with openpubkey
  • metrics πŸ“ˆ: serve prometheus metrics on open connections and auth errors

Screen recording

asciicast

recording the screen in asciicast format https://docs.asciinema.org/manual/asciicast/v2/

To use it, start sshpiperd with --screen-recording-format asciicast and --screen-recording-dir /path/to/recordingdir

Example:

```
ssh user_name@
... do some commands
exit

asciinema play /path/to/recordingdir/<conn_guid>/shell-channel-0.cast

```

typescript

recording the screen in typescript format (not the lang). The format is compatible with scriptreplay(1)

To use it, start sshpiperd with --screen-recording-format typescript and --screen-recording-dir /path/to/recordingdir

Example:

```
ssh user_name@127.0.0.1 -p 2222
... do some commands
exit


$ cd /path/to/recordingdir/<conn_guid>
$ ls *.timing *.typescript
1472847798.timing 1472847798.typescript

$ scriptreplay -t 1472847798.timing 1472847798.typescript # will replay the ssh session
```

Public key authentication when using sshpiper (Private key remapping)

During SSH publickey auth, RFC 4252 Section 7, ssh client sign session_id and some other data using private key into a signature sig. This is for server to verify that the connection is from the client not the man in the middle.

However, sshpiper actually holds two ssh connection, and it is doing what the man in the middle does. the two ssh connections' session_id will never be the same, because they are hash of the shared secret. RFC 4253 Section 7.2.

To support publickey auth, sshpiper routing plugin must provide a new private key for the upstream to sign the session_id. This new private key is called mapping key.

How this work

+------------+        +------------------------+                       
|            |        |                        |                       
|   client   |        |   sshpiper             |                       
|   PK_X     +-------->      |                 |                       
|            |        |      v                 |                       
|            |        |   Check Permission     |                       
+------------+        |      |                 |                       
                      |      |                 |                       
                      |      |                 |     +----------------+
                      |      v                 |     |                |
                      |   sign again           |     |   server       |
                      |   using PK_Y  +-------------->   check PK_Y   |
                      |                        |     |                |
                      |                        |     |                |
                      +------------------------+     +----------------+

Ports to other platforms

Migrating from v0

What's the major change in v1

  • low level sshpiper api is fully redesigned to support more routing protocols.
  • plugins system totally redesigned to be more flexible and extensible.
    • plugins are now separated from main process and no longer a single big binary, this allow user to write their own plugins without touching sshpiperd code.
  • grpc is first class now, the plugins are built on top of it

For plugins already in v1, you need change params to new params. However, not all plugins are migrated to v1 yet, they are being migrated gradually. you can still use the old plugins in v0 branch

Contributing

see CONTRIBUTING.md

License

MIT

Releases

Sponsor this project

Packages

Used by

Contributors

Languages