Docker Tunneling in Go

Posted on

A trick i learned recently is that Docker client in Golang can accept custom dialer which can be so useful to connect to a remote Docker engine through SSH tunneling, all you need is a working SSH connection to a remote server and Docker engine on this server that listens on a local UNIX socket.

docker-tunnel

First let’s look at the NewClient function for Docker client:

func NewClient(host string, version string, client *http.Client, httpHeaders map[string]string) (*Client, error)

As you can see NewClient accepts host, version, http transport client which we will use to add the custom dailer, and finally httpHeaders that can be added to each request. First define the custom dialer that will use SSH to connect to the remote host and establish a tunnel connection to the Docker socket on this host:

type dialer struct {
	host   string
}

func (d *dialer) Dial(network, addr string) (net.Conn, error) {
	sshAddr := d.host + ":22"
	// Build SSH client configuration
	cfg, err := makeSSHConfig()
	if err != nil {
		logrus.Fatalf("Error configuring SSH: %v", err)
	}
	// Establish connection with SSH server
	conn, err := ssh.Dial("tcp", sshAddr, cfg)
	if err != nil {
		logrus.Fatalf("Error establishing SSH connection: %v", err)
	}
	remote, err := conn.Dial("unix", "/var/run/docker.sock")
	if err != nil {
		logrus.Fatalf("Error connecting to Docker socket: %v", err)
	}
	return remote, err
}

The makeSSHConfig will return ssh.ClientConfig that contain the authentication method, user, etc. To start using this custom dialer to initialize a Docker client, try using the following function:

func StartDockerTunnel() (*client.Client, error) {
	dialer := &dialer{
		host:   "example-host.com",
	}
	httpClient := &http.Client{
		Transport: &http.Transport{
			Dial: dialer.Dial,
		},
	}

	newClient, err := client.NewClient("unix:///var/run/docker.sock", DockerAPIVersion, httpClient, nil)
	if err != nil {
		return nil, fmt.Errorf("Can't connect to Docker: %v", err)
	}
	return newClient, nil
}

StartDockerTunnel function initialezes a new Docker client and sets http client to our custom client that we just defined with the custom Dialer which in turn will connect directly to example-host.com through ssh and establish a connection with the /var/run/docker.sock at the remote server.