Plugin Anatomy and Skel Helpers
Relevant source files
The following files were used as context for generating this wiki page:
The sshpiper plugin system is designed to decouple the core proxy daemon, sshpiperd, from the logic that handles routing and authentication. In v1, plugins are standalone executables or gRPC servers that communicate with the daemon via standard input/output (wrapped for gRPC) or remote network connections. The "Skel" (Skeleton) helpers provide a standardized framework for building these plugins, reducing boilerplate and providing common abstractions for mapping downstream clients to upstream servers.
Plugins are responsible for two primary tasks: determining the appropriate upstream target for a given downstream connection and mapping authentication methods between the two. The Skel infrastructure specifically streamlines the creation of plugins that follow a "Pipe" pattern, where connections are matched against predefined criteria to identify a destination and its required credentials.
Plugin Architecture and gRPC Integration
Every sshpiper plugin is fundamentally a gRPC server implementing the SshPiperPlugin service. While sshpiperd can connect to remote gRPC endpoints, most local plugins use stdin/stdout wrapped as a network connection for communication.
The Plugin Service Definition
The gRPC service defines the lifecycle of an SSH connection through various callback RPCs. These include initial connection notification, authentication challenges, and notification of the pipe's status.
The diagram shows the standard RPC flow between the daemon and a plugin during connection establishment.
Sources: libplugin/plugin.pb.go:1022-1039, cmd/sshpiperd/internal/plugin/grpc.go:61-125
Core RPC Methods
The SshPiperPlugin service includes the following critical methods:
- ListCallbacks: Allows the plugin to advertise which authentication and notice methods it supports.
- NewConnection: Signals the start of a new client connection.
- NextAuthMethods: Returns a list of supported authentication methods (e.g.,
PASSWORD,PUBLICKEY). - PasswordAuth / PublicKeyAuth: Handles specific authentication attempts and returns an
Upstreamobject. - KeyboardInteractiveAuth: A bidirectional stream for complex, prompt-based authentication.
Sources: libplugin/plugin.pb.go:1022-1039, libplugin/plugin.proto:52-68
Skel Helper Framework
The skel package provides the SkelPlugin type, which abstracts the gRPC callback implementation. Instead of manually handling every RPC, developers implement a listPipe function that returns a slice of SkelPipe interfaces.
Skel Components
The Skel framework relies on three primary interfaces:
- SkelPipe: Represents a routing rule, containing "From" matchers.
- SkelPipeFrom: Matches a
downstreamconnection (e.g., by username, password verification, or public key). - SkelPipeTo: Defines the
upstreamdestination (host, user) and the authentication to use (password override or private key).
The class hierarchy for the Skel framework, illustrating how routing rules are evaluated.
Sources: libplugin/skel/skel.go:21-65
Authentication Mapping in Skel
Skel handles the complexity of verifying downstream credentials and providing upstream credentials. For example, if a SkelPipeFromPublicKey is matched, Skel will verify the client's key against AuthorizedKeys or TrustedUserCAKeys. If valid, it uses the corresponding SkelPipeTo to provide a private key for the upstream connection.
Sources: libplugin/skel/skel.go:167-229
Plugin Initialization Templates
To further simplify plugin development, libplugin provides the PluginTemplate. This allows developers to use the urfave/cli library to handle flags and configuration before starting the gRPC service.
Using CreateAndRunPluginTemplate
A typical plugin main function uses the template to define its metadata, flags, and a CreateConfig callback. The CreateConfig callback is where the SkelPlugin is typically instantiated.
| Field | Description |
|---|---|
Name | The name of the plugin for the CLI. |
Usage | A brief description of the plugin's purpose. |
Flags | CLI flags (using urfave/cli/v2). |
CreateConfig | A function that returns *libplugin.SshPiperPluginConfig. |
| Sources: libplugin/template.go:12-18 |
Example Implementation (Working Directory Plugin)
The workingdir plugin demonstrates the use of both the template and Skel helpers to route users based on directory structures.
func main() {
libplugin.CreateAndRunPluginTemplate(&libplugin.PluginTemplate{
Name: "workingdir",
Flags: []cli.Flag{ /* ... root path flag ... */ },
CreateConfig: func(c *cli.Context) (*libplugin.SshPiperPluginConfig, error) {
fac := workdingdirFactory{ root: c.String("root") }
skel := skel.NewSkelPlugin(fac.listPipe)
return skel.CreateConfig(), nil
},
})
}
Sources: plugin/workingdir/main.go:17-151, plugin/kubernetes/main.go:12-36
Connection Metadata and Tracking
Plugins receive a ConnMeta object (or ConnMetadata interface) for every operation. This object is crucial for routing decisions.
ConnMeta Structure
| Field | Type | Description |
|---|---|---|
UserName | string | The username provided by the downstream client. |
FromAddr | string | The remote IP/Port of the downstream client. |
UniqId | string | A UUID generated by sshpiperd for tracking the session. |
Metadata | map[string]string | Key-value pairs for sharing state between auth steps or plugins. |
| Sources: libplugin/plugin.pb.go:76-82, libplugin/plugin.proto:7-12 |
The UniqueID is used by Skel's internal cache to store the matched SkelPipeTo between the authentication phase and the host key verification phase.
Sources: libplugin/skel/skel.go:111-125
Conclusion
The Plugin Anatomy and Skel Helpers provide a robust, interface-driven approach to extending sshpiper. By utilizing the PluginTemplate for CLI management and the SkelPlugin for routing logic, developers can create complex SSH proxies with minimal code, focusing solely on the logic of mapping connections rather than the underlying gRPC or SSH protocol details.