Skip to content

Commit 11a2de4

Browse files
authored
Merge pull request #284 from carolynvs/docker-tls
Support connecting to TLS secured docker host
2 parents 36694c2 + 2e51536 commit 11a2de4

File tree

3 files changed

+156
-5
lines changed

3 files changed

+156
-5
lines changed

driver/docker/client.go

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package docker
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
"strconv"
8+
9+
"github.com/docker/cli/cli/command"
10+
cliconfig "github.com/docker/cli/cli/config"
11+
cliflags "github.com/docker/cli/cli/flags"
12+
"github.com/docker/go-connections/tlsconfig"
13+
)
14+
15+
const (
16+
// DockerTLSVerifyEnvVar is the Docker environment variable that indicates that
17+
// Docker socket is protected with TLS.
18+
DockerTLSVerifyEnvVar = "DOCKER_TLS_VERIFY"
19+
20+
// DockerCertPathEnvVar is the Docker environment variable that specifies a
21+
// custom path to the TLS certificates for the Docker socket.
22+
DockerCertPathEnvVar = "DOCKER_CERT_PATH"
23+
)
24+
25+
// GetDockerClient creates a Docker CLI client that uses the user's Docker configuration
26+
// such as environment variables and the Docker home directory to initialize the client.
27+
func GetDockerClient() (*command.DockerCli, error) {
28+
cli, err := command.NewDockerCli()
29+
if err != nil {
30+
return nil, fmt.Errorf("could not create new docker client: %w", err)
31+
}
32+
opts := buildDockerClientOptions()
33+
if err = cli.Initialize(opts); err != nil {
34+
return nil, fmt.Errorf("error initializing docker client: %w", err)
35+
}
36+
return cli, nil
37+
}
38+
39+
// manually handle DOCKER_TLS_VERIFY and DOCKER_CERT_PATH because the docker cli
40+
// library only binds these values when initializing its cli flags. There isn't
41+
// other parts of the library that we can take advantage of to get these values
42+
// for "free".
43+
//
44+
// DOCKER_HOST however is retrieved dynamically later so that doesn't
45+
// require additional configuration.
46+
func buildDockerClientOptions() *cliflags.ClientOptions {
47+
cliOpts := cliflags.NewClientOptions()
48+
cliOpts.ConfigDir = cliconfig.Dir()
49+
50+
// Check if TLS is enabled Docker configures TLS settings if DOCKER_TLS_VERIFY is
51+
// set to anything, so it could be false and that still means we should use TLS
52+
// (but don't check the certs).
53+
tlsVerify, tlsConfigured := os.LookupEnv(DockerTLSVerifyEnvVar)
54+
if tlsConfigured && tlsVerify != "" {
55+
cliOpts.Common.TLS = true
56+
57+
// Check if we should verify certs or allow self-signed certs (insecure)
58+
verify, _ := strconv.ParseBool(tlsVerify)
59+
cliOpts.Common.TLSVerify = verify
60+
61+
// Check if the TLS certs have been overridden
62+
var certPath string
63+
if certPathOverride, ok := os.LookupEnv(DockerCertPathEnvVar); ok && certPathOverride != "" {
64+
certPath = certPathOverride
65+
} else {
66+
certPath = cliOpts.ConfigDir
67+
}
68+
69+
cliOpts.Common.TLSOptions = &tlsconfig.Options{
70+
CAFile: filepath.Join(certPath, cliflags.DefaultCaFile),
71+
CertFile: filepath.Join(certPath, cliflags.DefaultCertFile),
72+
KeyFile: filepath.Join(certPath, cliflags.DefaultKeyFile),
73+
}
74+
}
75+
76+
return cliOpts
77+
}

driver/docker/client_test.go

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package docker
2+
3+
import (
4+
"os"
5+
"testing"
6+
7+
"github.com/docker/go-connections/tlsconfig"
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func Test_buildDockerClientOptions(t *testing.T) {
12+
// Tell Docker where its config is located, so that we have repeatable paths in the tests
13+
os.Setenv("DOCKER_CONFIG", "/home/me/.docker")
14+
defer os.Unsetenv("DOCKER_CONFIG")
15+
16+
defaultTLSOptions := &tlsconfig.Options{
17+
CAFile: "/home/me/.docker/ca.pem",
18+
CertFile: "/home/me/.docker/cert.pem",
19+
KeyFile: "/home/me/.docker/key.pem",
20+
}
21+
22+
customTLSOptions := &tlsconfig.Options{
23+
CAFile: "/mycerts/ca.pem",
24+
CertFile: "/mycerts/cert.pem",
25+
KeyFile: "/mycerts/key.pem",
26+
}
27+
28+
t.Run("tls disabled", func(t *testing.T) {
29+
os.Unsetenv(DockerTLSVerifyEnvVar)
30+
opts := buildDockerClientOptions()
31+
assert.False(t, opts.Common.TLS, "expected TLS to be disabled")
32+
assert.False(t, opts.Common.TLSVerify, "expected TLSVerify to be disabled")
33+
assert.Nil(t, opts.Common.TLSOptions, "expected TLSOptions to be unset")
34+
})
35+
36+
t.Run("tls enabled without certs", func(t *testing.T) {
37+
os.Setenv(DockerTLSVerifyEnvVar, "true")
38+
os.Unsetenv(DockerCertPathEnvVar)
39+
defer func() {
40+
os.Unsetenv(DockerTLSVerifyEnvVar)
41+
}()
42+
43+
opts := buildDockerClientOptions()
44+
assert.True(t, opts.Common.TLS, "expected TLS to be enabled")
45+
assert.True(t, opts.Common.TLSVerify, "expected the certs to be verified")
46+
assert.Equal(t, defaultTLSOptions, opts.Common.TLSOptions, "expected TLSOptions to be initialized to the default TLS settings")
47+
})
48+
49+
t.Run("tls enabled with custom certs", func(t *testing.T) {
50+
os.Setenv(DockerTLSVerifyEnvVar, "true")
51+
os.Setenv(DockerCertPathEnvVar, "/mycerts")
52+
defer func() {
53+
os.Unsetenv(DockerTLSVerifyEnvVar)
54+
os.Unsetenv(DockerCertPathEnvVar)
55+
}()
56+
57+
opts := buildDockerClientOptions()
58+
assert.True(t, opts.Common.TLS, "expected TLS to be enabled")
59+
assert.True(t, opts.Common.TLSVerify, "expected the certs to be verified")
60+
assert.Equal(t, customTLSOptions, opts.Common.TLSOptions, "expected TLSOptions to use the custom DOCKER_CERT_PATH set")
61+
})
62+
63+
t.Run("tls enabled with self-signed certs", func(t *testing.T) {
64+
os.Setenv(DockerTLSVerifyEnvVar, "false")
65+
os.Setenv(DockerCertPathEnvVar, "/mycerts")
66+
defer func() {
67+
os.Unsetenv(DockerTLSVerifyEnvVar)
68+
os.Unsetenv(DockerCertPathEnvVar)
69+
}()
70+
71+
opts := buildDockerClientOptions()
72+
assert.True(t, opts.Common.TLS, "expected TLS to be enabled")
73+
assert.False(t, opts.Common.TLSVerify, "expected TLSVerify to be false")
74+
assert.Equal(t, customTLSOptions, opts.Common.TLSOptions, "expected TLSOptions to use the custom DOCKER_CERT_PATH set")
75+
})
76+
}

driver/docker/docker.go

+3-5
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212
"strings"
1313

1414
"github.com/docker/cli/cli/command"
15-
cliflags "github.com/docker/cli/cli/flags"
1615
"github.com/docker/distribution/reference"
1716
"github.com/docker/docker/api/types"
1817
"github.com/docker/docker/api/types/container"
@@ -167,16 +166,15 @@ func (d *Driver) initializeDockerCli() (command.Cli, error) {
167166
return d.dockerCli, nil
168167
}
169168

170-
cli, err := command.NewDockerCli()
169+
cli, err := GetDockerClient()
171170
if err != nil {
172171
return nil, err
173172
}
173+
174174
if d.config["DOCKER_DRIVER_QUIET"] == "1" {
175175
cli.Apply(command.WithCombinedStreams(ioutil.Discard))
176176
}
177-
if err := cli.Initialize(cliflags.NewClientOptions()); err != nil {
178-
return nil, err
179-
}
177+
180178
d.dockerCli = cli
181179
return cli, nil
182180
}

0 commit comments

Comments
 (0)