Docker Integration Plugin
Relevant source files
The following files were used as context for generating this wiki page:
The Docker integration plugin for sshpiper enables dynamic routing of SSH connections directly into Docker containers. It functions by querying the Docker daemon for active containers and establishing "pipes" based on specific metadata defined in container labels. This allows sshpiperd to act as a secure gateway for container access without exposing individual container SSH ports to the public network.
The plugin supports two primary modes of operation: traditional SSH routing to an SSH daemon (sshd) running inside the container, and a specialized "Docker Exec" mode. In the latter, the plugin establishes a bridge that executes commands directly into containers that do not have an internal SSH daemon.
Sources: plugin/docker/README.md:1-5, README.md:100-110
Plugin Architecture and Logic
The plugin is built using a skeleton template (skel) and interacts with the Docker engine using the Moby API client. It maintains an internal state to track container IDs, SSH keys, and bridge addresses for advanced routing scenarios.
Core Data Structures
The primary unit of data is the pipe struct, which encapsulates all information required to route a downstream connection to a containerized upstream.
| Field | Description |
|---|---|
ClientUsername | The username requested by the downstream client. |
ContainerUsername | The actual username used to log in to the container's sshd. |
Host | The IP address or bridge address of the target container. |
DockerSshdCmd | The command to execute in bridge mode (defaults to /bin/sh). |
AuthorizedKeys | Base64 encoded keys for downstream authentication. |
PrivateKey | Base64 encoded private key used to authenticate with the container. |
Sources: plugin/docker/docker.go:19-27, plugin/docker/dockersshd_bridge.go:21-23
Connection Discovery Flow
The plugin periodically lists containers via the Docker API and filters them based on the presence of specific labels. It resolves the container's IP address by inspecting its network settings, defaulting to the first available network unless a specific network is defined via labels.
Sources: plugin/docker/docker.go:50-130
Docker Exec Mode (SSH-to-Exec Bridge)
A unique feature of the Docker plugin is the ability to connect to containers lacking an SSH daemon. When sshpiper.docker_exec_cmd is set to true, the plugin initializes a local TCP bridge.
Bridge Mechanism
- Bridge Listener: The plugin starts a local listener on
127.0.0.1. - Key Registration: It generates a transient Ed25519 private key for the specific container and registers the mapping.
- Command Execution: When a connection hits the bridge, the plugin uses
docker execlogic to spawn a shell (or a custom command) inside the target container, piping the SSH session directly to the process's standard I/O.
Sources: plugin/docker/dockersshd_bridge.go:25-50, plugin/docker/dockersshd_bridge.go:107-150
Security Sequence in Bridge Mode
The following diagram illustrates the authentication handover when utilizing the Docker Exec bridge.
Configuration via Labels
Container behavior is controlled entirely through Docker labels. The plugin reads these labels during its discovery phase to build the routing table.
Supported Labels
| Label | Required | Description |
|---|---|---|
sshpiper.username | Optional* | Downstream username. If empty, authorized_keys must be provided. |
sshpiper.container_username | Yes | Username used on the upstream container. |
sshpiper.port | No | Port of container sshd (defaults to 22). |
sshpiper.authorized_keys | Optional | Base64 encoded authorized_keys for client auth. |
sshpiper.private_key | Optional** | Base64 private key for upstream auth. |
sshpiper.docker_exec_cmd | No | Set to true to enable bridge mode without an internal sshd. |
sshpiper.network | No | Specifies which network to use if the container is on multiple. |
*Note: If username, authorized_keys, and trusted_ca_keys are all missing, the container is skipped.
**Note: In docker_exec_cmd mode, the plugin ignores the private_key label and generates one automatically.
Sources: plugin/docker/docker.go:58-85, plugin/docker/README.md:25-33
Technical Implementation Details
Initialization
The plugin initializes by creating a Moby client using environment variables (e.g., DOCKER_HOST, DOCKER_CERT_PATH). It is registered in main.go using libplugin.CreateAndRunPluginTemplate.
// plugin/docker/main.go:13-26
func main() {
libplugin.CreateAndRunPluginTemplate(&libplugin.PluginTemplate{
Name: "docker",
Usage: "sshpiperd docker plugin...",
CreateConfig: func(c *cli.Context) (*libplugin.SshPiperPluginConfig, error) {
plugin, err := newDockerPlugin()
if err != nil {
return nil, err
}
skel := skel.NewSkelPlugin(plugin.listPipe)
return skel.CreateConfig(), nil
},
})
}
Sources: plugin/docker/main.go:13-26, plugin/docker/docker.go:37-48
State Synchronization
The plugin includes a synchronization routine syncDockerSshdState that purges cached private keys and bridge mappings when containers are stopped or removed. This ensures that the internal memory map of the plugin remains consistent with the actual state of the Docker engine.
Sources: plugin/docker/dockersshd_bridge.go:167-183