Skip to content

Commit

Permalink
Merge pull request #284 from carolynvs/docker-tls
Browse files Browse the repository at this point in the history
Support connecting to TLS secured docker host
  • Loading branch information
carolynvs committed Jun 8, 2022
2 parents 36694c2 + 2e51536 commit 11a2de4
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 5 deletions.
77 changes: 77 additions & 0 deletions driver/docker/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package docker

import (
"fmt"
"os"
"path/filepath"
"strconv"

"github.com/docker/cli/cli/command"
cliconfig "github.com/docker/cli/cli/config"
cliflags "github.com/docker/cli/cli/flags"
"github.com/docker/go-connections/tlsconfig"
)

const (
// DockerTLSVerifyEnvVar is the Docker environment variable that indicates that
// Docker socket is protected with TLS.
DockerTLSVerifyEnvVar = "DOCKER_TLS_VERIFY"

// DockerCertPathEnvVar is the Docker environment variable that specifies a
// custom path to the TLS certificates for the Docker socket.
DockerCertPathEnvVar = "DOCKER_CERT_PATH"
)

// GetDockerClient creates a Docker CLI client that uses the user's Docker configuration
// such as environment variables and the Docker home directory to initialize the client.
func GetDockerClient() (*command.DockerCli, error) {
cli, err := command.NewDockerCli()
if err != nil {
return nil, fmt.Errorf("could not create new docker client: %w", err)
}
opts := buildDockerClientOptions()
if err = cli.Initialize(opts); err != nil {
return nil, fmt.Errorf("error initializing docker client: %w", err)
}
return cli, nil
}

// manually handle DOCKER_TLS_VERIFY and DOCKER_CERT_PATH because the docker cli
// library only binds these values when initializing its cli flags. There isn't
// other parts of the library that we can take advantage of to get these values
// for "free".
//
// DOCKER_HOST however is retrieved dynamically later so that doesn't
// require additional configuration.
func buildDockerClientOptions() *cliflags.ClientOptions {
cliOpts := cliflags.NewClientOptions()
cliOpts.ConfigDir = cliconfig.Dir()

// Check if TLS is enabled Docker configures TLS settings if DOCKER_TLS_VERIFY is
// set to anything, so it could be false and that still means we should use TLS
// (but don't check the certs).
tlsVerify, tlsConfigured := os.LookupEnv(DockerTLSVerifyEnvVar)
if tlsConfigured && tlsVerify != "" {
cliOpts.Common.TLS = true

// Check if we should verify certs or allow self-signed certs (insecure)
verify, _ := strconv.ParseBool(tlsVerify)
cliOpts.Common.TLSVerify = verify

// Check if the TLS certs have been overridden
var certPath string
if certPathOverride, ok := os.LookupEnv(DockerCertPathEnvVar); ok && certPathOverride != "" {
certPath = certPathOverride
} else {
certPath = cliOpts.ConfigDir
}

cliOpts.Common.TLSOptions = &tlsconfig.Options{
CAFile: filepath.Join(certPath, cliflags.DefaultCaFile),
CertFile: filepath.Join(certPath, cliflags.DefaultCertFile),
KeyFile: filepath.Join(certPath, cliflags.DefaultKeyFile),
}
}

return cliOpts
}
76 changes: 76 additions & 0 deletions driver/docker/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package docker

import (
"os"
"testing"

"github.com/docker/go-connections/tlsconfig"
"github.com/stretchr/testify/assert"
)

func Test_buildDockerClientOptions(t *testing.T) {
// Tell Docker where its config is located, so that we have repeatable paths in the tests
os.Setenv("DOCKER_CONFIG", "/home/me/.docker")
defer os.Unsetenv("DOCKER_CONFIG")

defaultTLSOptions := &tlsconfig.Options{
CAFile: "/home/me/.docker/ca.pem",
CertFile: "/home/me/.docker/cert.pem",
KeyFile: "/home/me/.docker/key.pem",
}

customTLSOptions := &tlsconfig.Options{
CAFile: "/mycerts/ca.pem",
CertFile: "/mycerts/cert.pem",
KeyFile: "/mycerts/key.pem",
}

t.Run("tls disabled", func(t *testing.T) {
os.Unsetenv(DockerTLSVerifyEnvVar)
opts := buildDockerClientOptions()
assert.False(t, opts.Common.TLS, "expected TLS to be disabled")
assert.False(t, opts.Common.TLSVerify, "expected TLSVerify to be disabled")
assert.Nil(t, opts.Common.TLSOptions, "expected TLSOptions to be unset")
})

t.Run("tls enabled without certs", func(t *testing.T) {
os.Setenv(DockerTLSVerifyEnvVar, "true")
os.Unsetenv(DockerCertPathEnvVar)
defer func() {
os.Unsetenv(DockerTLSVerifyEnvVar)
}()

opts := buildDockerClientOptions()
assert.True(t, opts.Common.TLS, "expected TLS to be enabled")
assert.True(t, opts.Common.TLSVerify, "expected the certs to be verified")
assert.Equal(t, defaultTLSOptions, opts.Common.TLSOptions, "expected TLSOptions to be initialized to the default TLS settings")
})

t.Run("tls enabled with custom certs", func(t *testing.T) {
os.Setenv(DockerTLSVerifyEnvVar, "true")
os.Setenv(DockerCertPathEnvVar, "/mycerts")
defer func() {
os.Unsetenv(DockerTLSVerifyEnvVar)
os.Unsetenv(DockerCertPathEnvVar)
}()

opts := buildDockerClientOptions()
assert.True(t, opts.Common.TLS, "expected TLS to be enabled")
assert.True(t, opts.Common.TLSVerify, "expected the certs to be verified")
assert.Equal(t, customTLSOptions, opts.Common.TLSOptions, "expected TLSOptions to use the custom DOCKER_CERT_PATH set")
})

t.Run("tls enabled with self-signed certs", func(t *testing.T) {
os.Setenv(DockerTLSVerifyEnvVar, "false")
os.Setenv(DockerCertPathEnvVar, "/mycerts")
defer func() {
os.Unsetenv(DockerTLSVerifyEnvVar)
os.Unsetenv(DockerCertPathEnvVar)
}()

opts := buildDockerClientOptions()
assert.True(t, opts.Common.TLS, "expected TLS to be enabled")
assert.False(t, opts.Common.TLSVerify, "expected TLSVerify to be false")
assert.Equal(t, customTLSOptions, opts.Common.TLSOptions, "expected TLSOptions to use the custom DOCKER_CERT_PATH set")
})
}
8 changes: 3 additions & 5 deletions driver/docker/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"strings"

"github.com/docker/cli/cli/command"
cliflags "github.com/docker/cli/cli/flags"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
Expand Down Expand Up @@ -167,16 +166,15 @@ func (d *Driver) initializeDockerCli() (command.Cli, error) {
return d.dockerCli, nil
}

cli, err := command.NewDockerCli()
cli, err := GetDockerClient()
if err != nil {
return nil, err
}

if d.config["DOCKER_DRIVER_QUIET"] == "1" {
cli.Apply(command.WithCombinedStreams(ioutil.Discard))
}
if err := cli.Initialize(cliflags.NewClientOptions()); err != nil {
return nil, err
}

d.dockerCli = cli
return cli, nil
}
Expand Down

0 comments on commit 11a2de4

Please sign in to comment.