Username Router Plugin
Relevant source files
The following files were used as context for generating this wiki page:
The Username Router Plugin is a routing component for sshpiperd that determines the upstream destination based on the format of the downstream username. It allows users to specify their target host and port directly within the SSH connection string, eliminating the need for pre-configured static mapping files for every possible destination.
Within the sshpiper ecosystem, this plugin acts as a downstream to upstream mapper. It decodes a specific username format—typically target+username—to extract the target address and the actual username to be used for authentication on the remote server.
Sources: plugin/username-router/main.go:27, README.md:144
Architecture and Data Flow
The Username Router operates as a gRPC-based plugin that communicates with the sshpiperd daemon. It implements the PasswordCallback to intercept the initial connection metadata, specifically the username provided by the client.
Connection Lifecycle
When a client connects to sshpiperd, the daemon identifies the Username Router as the active plugin. The daemon sends a gRPC request containing the ConnMeta (Connection Metadata), which includes the raw username provided by the client. The plugin then parses this string to derive the routing information.
Parsing Logic
The core functionality resides in the parseTargetUser function. It expects the raw input string to contain exactly one + character separating the target destination from the intended username.
- Input Format:
[target:port]+user - Delimiter: The
+character is used as the split point. - Target: The portion before the
+(e.g.,example.com:22). - User: The portion after the
+(e.g.,admin).
Sources: plugin/username-router/main.go:15-25
Components and Implementation
The plugin is built using the libplugin template, which handles the boilerplate for gRPC service registration and I/O communication between sshpiperd and the plugin executable.
Key Data Structures
The plugin interacts with several key messages defined in the libplugin protocol buffers:
| Structure | Description | Source |
|---|---|---|
ConnMeta | Contains user_name, from_addr, and uniq_id of the downstream connection. | libplugin/plugin.proto:9-14 |
Upstream | The response object containing host, port, user_name, and auth details for the target. | libplugin/plugin.proto:16-32 |
PluginTemplate | A wrapper used to define the plugin name, usage, and configuration creation logic. | libplugin/template.go:11-17 |
Core Functions
main()
Initializes the plugin using libplugin.CreateAndRunPluginTemplate. It sets the plugin name to username-router and defines the CreateConfig logic.
Sources: plugin/username-router/main.go:27-55
PasswordCallback
This callback is triggered when the downstream client provides a password. The Username Router uses this opportunity to:
- Extract the raw username via
conn.User(). - Invoke
parseTargetUserto split the target and user. - Utilize
libplugin.SplitHostPortForSSHto separate the target host from the port. - Construct an
Upstreamobject withIgnoreHostKey: trueand the original password forwarded to the target.
Sources: plugin/username-router/main.go:32-52
Configuration and Usage
The plugin is executed as a child process of sshpiperd. It does not require complex configuration files because the routing logic is dynamic and embedded in the connection string itself.
Connection String Format
The client must use the following format when connecting:
ssh [target-host]:[target-port]+[remote-user]@[sshpiper-address]
Example:
To connect to internal-server.local on port 22 as user ubuntu via an sshpiper instance at proxy.company.com:
ssh internal-server.local:22+ubuntu@proxy.company.com -p 2222
Sources: plugin/username-router/main.go:29, README.md:144
Implementation Snippet
The following snippet shows how the plugin constructs the upstream routing information:
// From plugin/username-router/main.go:42-51
address, user, err := parseTargetUser(conn.User())
if err != nil {
return nil, fmt.Errorf("invalid username format %q: %w", conn.User(), err)
}
host, port, err := libplugin.SplitHostPortForSSH(address)
if err != nil {
return nil, fmt.Errorf("invalid target address %q: %w", address, err)
}
return &libplugin.Upstream{
UserName: user,
Host: host,
Port: int32(port),
IgnoreHostKey: true,
Auth: libplugin.CreatePasswordAuth(password),
}, nil
Sources: plugin/username-router/main.go:42-51
Conclusion
The Username Router Plugin provides a flexible, zero-config routing mechanism for sshpiperd. By leveraging the username string as a carrier for routing metadata, it enables ad-hoc SSH proxying to any reachable upstream server that supports password authentication. Its reliance on the standard libplugin gRPC interface ensures high performance and compatibility with the core sshpiperd daemon.