Creating Your First Plugin
Relevant source files
The following files were used as context for generating this wiki page:
Introduction
Plugins in sshpiper are standalone gRPC servers responsible for routing SSH connections from a downstream client to an upstream server. They handle authentication mapping, additional security challenges (like 2FA), and dynamic routing logic. While sshpiperd manages the SSH daemon lifecycle, plugins determine how users are authenticated and where their connections are sent.
Sources: CONTRIBUTING.md:50-56, README.md:21-25
A plugin typically communicates with sshpiperd via stdin/stdout using a wrapped connection, though remote gRPC connections are also supported. Developers can leverage the provided libplugin templates to create new plugins with minimal boilerplate, often requiring fewer than 50 lines of code for basic functionality.
Sources: CONTRIBUTING.md:58-61, CONTRIBUTING.md:65-66
Architecture and Data Flow
The sshpiper plugin architecture is built on gRPC. When a client connects to sshpiperd, the daemon invokes the configured plugin(s) to determine the next steps in the authentication and routing process.
High-Level Plugin Interaction
The following diagram illustrates the flow between the downstream client, sshpiperd, and the plugin.
Sources: CONTRIBUTING.md:58-61, README.md:21-25
Connection Metadata
Every callback receives a ConnMetadata object, which contains essential information about the incoming connection.
| Field | Type | Description |
|---|---|---|
UserName | string | The username provided by the downstream client. |
FromAddr | string | The remote address (IP:Port) of the downstream client. |
UniqId | string | A unique UUID generated for the connection session. |
Metadata | map[string]string | Custom key-value pairs passed between plugins in a chain. |
Sources: libplugin/plugin.proto:7-12, cmd/sshpiperd/internal/plugin/grpc.go:121-131
Plugin Development Basics
The easiest way to create a plugin is to use the libplugin.PluginTemplate and the CreateAndRunPluginTemplate helper. This handles the application lifecycle, command-line flag parsing (via urfave/cli), and gRPC server setup.
Core Plugin Template Structure
type PluginTemplate struct {
Name string
Usage string
Flags []cli.Flag
LogFormatter logrus.Formatter
CreateConfig func(c *cli.Context) (*SshPiperPluginConfig, error)
}
Sources: libplugin/template.go:11-17
Implementation Steps
- Define Flags: Use
cli.Flagto define configuration options for your plugin (e.g., target hosts, root directories). - Implement
CreateConfig: This function returns anSshPiperPluginConfigcontaining the authentication and routing logic. - Define Callbacks: Assign functions to handles like
PasswordCallback,PublicKeyCallback, orKeyboardInteractiveCallback.
Sources: libplugin/template.go:19-45, plugin/workingdir/main.go:19-58
Authentication Callbacks
Plugins influence the SSH handshake through specific callbacks. Each callback must return an Upstream object or an error to deny the connection.
Password Callback Example
A simple plugin might verify a password and return a fixed upstream:
PasswordCallback: func(conn libplugin.ConnMetadata, password []byte) (*libplugin.Upstream, error) {
return &libplugin.Upstream{
Host: "1.2.3.4",
Port: 22,
IgnoreHostKey: true,
Auth: libplugin.CreatePasswordAuth(password),
}, nil
}
Sources: CONTRIBUTING.md:68-78
Callback Sequence
The order of execution depends on the authentication methods requested by the client and permitted by the plugin.
Advanced: Keyboard Interactive & 2FA
Plugins can implement "Additional Challenges" using the KeyboardInteractiveCallback. This allows the plugin to prompt the user for extra information, such as a TOTP code or a simple math answer, before allowing access to the upstream.
Implementation Logic:
- Define the
KeyboardInteractiveCallback. - Use the
clienthelper to send prompts to the user. - Validate the input.
- Return an
UpstreamwithCreateNextPluginAuthorCreateRetryCurrentPluginAuthto pass metadata to subsequent steps.
Sources: plugin/simplemath/main.go:19-47, plugin/workingdir/main.go:78-112
Summary of Configuration Options
The Upstream object returned by callbacks defines how sshpiperd connects to the destination server.
| Option | Description |
|---|---|
Host / Port | The address of the upstream SSH server. |
UserName | The username to use on the upstream server. |
IgnoreHostKey | Boolean to skip upstream host key verification. |
Auth | The authentication method for upstream (None, Password, PrivateKey, RemoteSigner). |
Sources: libplugin/plugin.proto:14-30, cmd/sshpiperd/internal/plugin/grpc.go:187-259
Build and Execution
Plugins are typically built as standalone binaries and passed to sshpiperd as arguments.
- Build:
go build -tags full -o myplugin - Run:
sshpiperd /path/to/myplugin --plugin-flag value
Sources: CONTRIBUTING.md:82-93
Conclusion
Creating a plugin for sshpiper involves implementing a gRPC service that fulfills specific authentication and routing callbacks defined in libplugin. By utilizing the provided PluginTemplate and understanding the metadata flow, developers can create powerful routing logic and multi-factor authentication systems that integrate seamlessly with the sshpiperd daemon.