Skip to content

Commit

Permalink
fix: stronger validation for the schema of the Docker socket path (#1286
Browse files Browse the repository at this point in the history
)

* fix: stronger validation for the schema of the docker socket path

* chore: use constant for default Docker socket path

* chore: extract remote host to a test variable
  • Loading branch information
mdelapenya authored Jun 14, 2023
1 parent 9fb7a93 commit b76e5fb
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 25 deletions.
2 changes: 2 additions & 0 deletions docs/features/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,5 @@ Path to Docker's socket. Used by Ryuk, Docker Compose, and a few other container
5. If the socket contains the unix schema, the schema is removed (e.g. `unix:///var/run/docker.sock` -> `/var/run/docker.sock`)

6. Else, the default location of the docker socket is used: `/var/run/docker.sock`

In any case, if the docker socket schema is `tcp://`, the default docker socket path will be returned.
31 changes: 19 additions & 12 deletions internal/testcontainersdocker/docker_host.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ func ExtractDockerHost(ctx context.Context) string {
// 4. Else, Get the current Docker Host from the existing strategies: see ExtractDockerHost.
// 5. If the socket contains the unix schema, the schema is removed (e.g. unix:///var/run/docker.sock -> /var/run/docker.sock)
// 6. Else, the default location of the docker socket is used (/var/run/docker.sock)
//
// In any case, if the docker socket schema is "tcp://", the default docker socket path will be returned.
func ExtractDockerSocket(ctx context.Context) string {
dockerSocketPathOnce.Do(func() {
dockerSocketPathCache = extractDockerSocket(ctx)
Expand Down Expand Up @@ -133,19 +135,28 @@ func extractDockerSocket(ctx context.Context) string {
// and receiving an instance of the Docker API client interface.
// This internal method is handy for testing purposes, passing a mock type simulating the desired behaviour.
func extractDockerSocketFromClient(ctx context.Context, cli client.APIClient) string {
tcHost, err := testcontainersHostFromProperties(ctx)
if err == nil {
// check that the socket is not a tcp or unix socket
checkDockerSocketFn := func(socket string) string {
// this use case will cover the case when the docker host is a tcp socket
if strings.HasPrefix(tcHost, "tcp://") {
return "/var/run/docker.sock"
if strings.HasPrefix(socket, TCPSchema) {
return DockerSocketPath
}

return tcHost
if strings.HasPrefix(socket, DockerSocketSchema) {
return strings.Replace(socket, DockerSocketSchema, "", 1)
}

return socket
}

tcHost, err := testcontainersHostFromProperties(ctx)
if err == nil {
return checkDockerSocketFn(tcHost)
}

testcontainersDockerSocket, err := dockerSocketOverridePath(ctx)
if err == nil {
return testcontainersDockerSocket
return checkDockerSocketFn(testcontainersDockerSocket)
}

info, err := cli.Info(ctx)
Expand All @@ -155,16 +166,12 @@ func extractDockerSocketFromClient(ctx context.Context, cli client.APIClient) st

// Because Docker Desktop runs in a VM, we need to use the default docker path for rootless docker
if info.OperatingSystem == "Docker Desktop" {
return "/var/run/docker.sock"
return DockerSocketPath
}

dockerHost := extractDockerHost(ctx)

if strings.HasPrefix(dockerHost, DockerSocketSchema) {
return strings.Replace(dockerHost, DockerSocketSchema, "", 1)
}

return dockerHost
return checkDockerSocketFn(dockerHost)
}

// dockerHostFromEnv returns the docker host from the DOCKER_HOST environment variable, if it's not empty
Expand Down
56 changes: 43 additions & 13 deletions internal/testcontainersdocker/docker_host_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import (
"github.com/testcontainers/testcontainers-go/internal/config"
)

// testRemoteHost is a testcontainers host defined in the properties file for testing purposes
var testRemoteHost = TCPSchema + "127.0.0.1:12345"

var (
originalDockerSocketPath string
originalDockerSocketPathWithSchema string
Expand Down Expand Up @@ -55,14 +58,13 @@ func TestExtractDockerHost(t *testing.T) {

t.Run("Testcontainers Host is resolved first", func(t *testing.T) {
t.Setenv("DOCKER_HOST", "/path/to/docker.sock")
tmpHost := "tcp://127.0.0.1:12345"
content := "tc.host=" + tmpHost
content := "tc.host=" + testRemoteHost

setupTestcontainersProperties(t, content)

host := extractDockerHost(context.Background())

assert.Equal(t, tmpHost, host)
assert.Equal(t, testRemoteHost, host)
})

t.Run("Docker Host as environment variable", func(t *testing.T) {
Expand Down Expand Up @@ -123,14 +125,13 @@ func TestExtractDockerHost(t *testing.T) {
t.Cleanup(resetSocketOverrideFn)

t.Run("Testcontainers host is defined in properties", func(t *testing.T) {
tmpSocket := "tcp://127.0.0.1:12345"
content := "tc.host=" + tmpSocket
content := "tc.host=" + testRemoteHost

setupTestcontainersProperties(t, content)

socket, err := testcontainersHostFromProperties(context.Background())
require.Nil(t, err)
assert.Equal(t, tmpSocket, socket)
assert.Equal(t, testRemoteHost, socket)
})

t.Run("Testcontainers host is not defined in properties", func(t *testing.T) {
Expand Down Expand Up @@ -269,26 +270,24 @@ func TestExtractDockerSocketFromClient(t *testing.T) {
setupDockerHostNotFound(t)

t.Run("Docker socket from Testcontainers host defined in properties", func(t *testing.T) {
tmpSocket := "tcp://127.0.0.1:12345"
content := "tc.host=" + tmpSocket
content := "tc.host=" + testRemoteHost

setupTestcontainersProperties(t, content)

socket := extractDockerSocketFromClient(context.Background(), mockCli{OS: "foo"})
assert.Equal(t, "/var/run/docker.sock", socket)
assert.Equal(t, DockerSocketPath, socket)
})

t.Run("Docker socket from Testcontainers host takes precedence over TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE", func(t *testing.T) {
tmpSocket := "tcp://127.0.0.1:12345"
content := "tc.host=" + tmpSocket
content := "tc.host=" + testRemoteHost

setupTestcontainersProperties(t, content)

t.Cleanup(resetSocketOverrideFn)
t.Setenv("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE", "/path/to/docker.sock")

socket := extractDockerSocketFromClient(context.Background(), mockCli{OS: "foo"})
assert.Equal(t, "/var/run/docker.sock", socket)
assert.Equal(t, DockerSocketPath, socket)
})

t.Run("Docker Socket as Testcontainers environment variable", func(t *testing.T) {
Expand All @@ -302,6 +301,20 @@ func TestExtractDockerSocketFromClient(t *testing.T) {
assert.Equal(t, "/path/to/docker.sock", host)
})

t.Run("Docker Socket as Testcontainers environment variable, removes prefixes", func(t *testing.T) {
setupTestcontainersProperties(t, "")

t.Cleanup(resetSocketOverrideFn)

t.Setenv("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE", "unix:///path/to/docker.sock")
host := extractDockerSocketFromClient(context.Background(), mockCli{OS: "foo"})
assert.Equal(t, "/path/to/docker.sock", host)

t.Setenv("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE", testRemoteHost)
host = extractDockerSocketFromClient(context.Background(), mockCli{OS: "foo"})
assert.Equal(t, DockerSocketPath, host)
})

t.Run("Unix Docker Socket is passed as DOCKER_HOST variable (Docker Desktop)", func(t *testing.T) {
setupTestcontainersProperties(t, "")

Expand All @@ -313,7 +326,7 @@ func TestExtractDockerSocketFromClient(t *testing.T) {

socket := extractDockerSocketFromClient(ctx, mockCli{OS: "Docker Desktop"})

assert.Equal(t, "/var/run/docker.sock", socket)
assert.Equal(t, DockerSocketPath, socket)
})

t.Run("Unix Docker Socket is passed as DOCKER_HOST variable (Not Docker Desktop)", func(t *testing.T) {
Expand All @@ -329,6 +342,23 @@ func TestExtractDockerSocketFromClient(t *testing.T) {

assert.Equal(t, "/this/is/a/sample.sock", socket)
})

t.Run("Unix Docker Socket is passed as DOCKER_HOST variable (Not Docker Desktop), removes prefixes", func(t *testing.T) {
setupTestcontainersProperties(t, "")

t.Cleanup(resetSocketOverrideFn)

ctx := context.Background()
os.Unsetenv("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE")

t.Setenv("DOCKER_HOST", "unix:///this/is/a/sample.sock")
socket := extractDockerSocketFromClient(ctx, mockCli{OS: "Ubuntu"})
assert.Equal(t, "/this/is/a/sample.sock", socket)

t.Setenv("DOCKER_HOST", testRemoteHost)
socket = extractDockerSocketFromClient(ctx, mockCli{OS: "Ubuntu"})
assert.Equal(t, DockerSocketPath, socket)
})
}

func TestInAContainer(t *testing.T) {
Expand Down
3 changes: 3 additions & 0 deletions internal/testcontainersdocker/docker_socket.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@ var DockerSocketPath = "/var/run/docker.sock"

// DockerSocketPathWithSchema is the path to the docker socket under unix systems with the unix schema.
var DockerSocketPathWithSchema = DockerSocketSchema + DockerSocketPath

// TCPSchema is the tcp schema.
var TCPSchema = "tcp://"

0 comments on commit b76e5fb

Please sign in to comment.