Architecture Overview
Relevant source files
The following files were used as context for generating this wiki page:
sshpiper is a reverse proxy for SSH connections, supporting protocols that run on top of SSH such as SCP and port forwarding. It acts as a specialized "man-in-the-middle" that manages two separate SSH connections: one from a downstream client and one to an upstream server. The core logic of routing these connections and handling authentication mapping is offloaded to a modular plugin system.
The project is structured around three primary components: sshpiperd (the main daemon), various plugins (handling routing and auth logic), and sshpiperd-webadmin (an optional administration interface). By decoupling the routing logic into plugins, sshpiper allows for dynamic upstream targeting based on usernames, source IPs, or custom scripts without modifying the core proxy engine.
Sources: README.md:1-25, CONTRIBUTING.md:73-82
Core Components
The architecture consists of three distinct layers: the proxy engine, the plugin interface, and the administration layer.
sshpiperd (Proxy Engine)
sshpiperd is the daemon that wraps the crypto/ssh library to manage SSH connections. It receives incoming connections from downstream clients and pipes them to upstream servers. It handles the low-level SSH handshake, session management, and optional features like screen recording and PROXY protocol support.
Sources: CONTRIBUTING.md:78-82, cmd/sshpiperd/main.go:130-220
Plugins (Routing & Logic)
Plugins are responsible for determining the upstream destination for a given downstream connection. They communicate with sshpiperd via gRPC, typically over stdin/stdout using the ioconn wrapper, though remote gRPC connections are also supported. Plugins can implement various authentication methods, including password, public key, and keyboard-interactive (2FA).
Sources: CONTRIBUTING.md:84-90, libplugin/plugin.proto:52-66
Admin Interface
sshpiperd-webadmin is an optional component that aggregates data from one or more sshpiperd instances. It provides a web UI and JSON HTTP API for monitoring live sessions, viewing terminal output in real-time, and terminating active connections.
Sources: README.md:70-90, cmd/sshpiperd-webadmin/main.go:10-25
Data and Connection Flow
The following diagram illustrates the lifecycle of a connection as it passes through sshpiperd and interacts with a plugin to reach the upstream server.
This diagram shows the sequence from initial client connection to the establishment of the upstream pipe.
Sources: README.md:27-40, CONTRIBUTING.md:78-82, libplugin/plugin.proto:55-60
Plugin Interfacing
sshpiperd utilizes a gRPC-based plugin architecture defined in libplugin/plugin.proto. This allows plugins to be written in any language and run as separate processes.
Connection Metadata
When a connection is established, sshpiperd passes a ConnMeta object to the plugin. This metadata includes the username, source address, and a unique identifier for the session.
| Field | Type | Description |
|---|---|---|
user_name | string | The username provided by the downstream client. |
from_addr | string | The remote address (IP:Port) of the downstream client. |
uniq_id | string | A UUID assigned to the connection by sshpiperd. |
metadata | map | Additional key-value pairs for plugin state tracking. |
Sources: libplugin/plugin.proto:7-12
Upstream Configuration
Plugins return an Upstream message to sshpiperd to specify where and how to connect to the target server.
| Option | Type | Description |
|---|---|---|
uri | string | The connection URI for the upstream server. |
user_name | string | The username to use for the upstream connection. |
ignore_host_key | bool | If true, skips upstream host key verification. |
auth | oneof | Authentication choice: none, password, private_key, next_plugin, etc. |
Sources: libplugin/plugin.proto:14-30
Public Key Remapping
Since sshpiperd acts as a proxy, the session_id for the downstream connection and the upstream connection will never match (as they are derived from different shared secrets). To support public key authentication, sshpiper performs a "sign-again" operation using a mapping key provided by the plugin.
This sequence shows how sshpiperd bridges two distinct public key authentications by re-signing for the upstream.
Sources: README.md:144-170
Plugin Chaining
sshpiperd supports chaining multiple plugins together using a -- separator in the command line. This allows for multi-stage authentication or complex routing logic (e.g., a failtoban plugin followed by a yaml routing plugin).
The ChainPlugins structure manages the sequence of callbacks:
NewConnectionis called on all plugins in the chain.- Authentication callbacks (Password, PublicKey, etc.) are executed by the current plugin in the chain.
- A plugin can return
UpstreamNextPluginAuthto pass control to the next plugin. - If a plugin returns a final
Upstreamdestination, the chain terminates and the connection is established.
Sources: cmd/sshpiperd/internal/plugin/chain.go:15-50, cmd/sshpiperd/main.go:340-370
Admin API Architecture
The administration layer uses a separate gRPC service defined in libadmin. It provides endpoints for monitoring and managing the proxy cluster.
gRPC Endpoints
| Method | Request | Response | Description |
|---|---|---|---|
ServerInfo | ServerInfoRequest | ServerInfoResponse | Returns daemon version, ID, and start time. |
ListSessions | ListSessionsRequest | ListSessionsResponse | Lists all active downstream-to-upstream pipes. |
KillSession | KillSessionRequest | KillSessionResponse | Terminates a specific pipe by ID. |
StreamSession | StreamSessionRequest | SessionFrame (stream) | Provides live terminal output (asciicast format). |
Sources: libadmin/admin.pb.go:500-520
Security and TLS
The admin API is disabled by default. When enabled via --admin-grpc-port, it requires TLS certificates unless --admin-grpc-insecure is specified. It supports Mutual TLS (mTLS) by providing a CA certificate via --admin-grpc-tls-cacert.
Sources: cmd/sshpiperd/main.go:40-65, cmd/sshpiperd/main.go:400-430
Conclusion
The architecture of sshpiper is designed for extensibility and modularity. By abstracting the SSH proxy logic into sshpiperd and the routing logic into gRPC-based plugins, the system can adapt to diverse environment requirements—from simple static routing to complex Kubernetes-integrated or scriptable Lua-based environments—while maintaining a consistent core for connection handling and session management.