Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: authentication tests on WSL #2706

Merged
merged 2 commits into from
Aug 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 78 additions & 74 deletions docker_auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package testcontainers
import (
"context"
_ "embed"
"encoding/base64"
"fmt"
"net"
"os"
"path/filepath"
"testing"
Expand Down Expand Up @@ -75,12 +77,7 @@ func TestGetDockerConfig(t *testing.T) {
})

t.Run("DOCKER_AUTH_CONFIG env var takes precedence", func(t *testing.T) {
t.Setenv("DOCKER_AUTH_CONFIG", `{
"auths": {
"`+exampleAuth+`": {}
},
"credsStore": "desktop"
}`)
setAuthConfig(t, exampleAuth, "", "")
t.Setenv("DOCKER_CONFIG", testDockerConfigDirPath)

cfg, err := getDockerConfig()
Expand All @@ -100,36 +97,23 @@ func TestGetDockerConfig(t *testing.T) {
})

t.Run("retrieve auth with DOCKER_AUTH_CONFIG env var", func(t *testing.T) {
base64 := "Z29waGVyOnNlY3JldA==" // gopher:secret

t.Setenv("DOCKER_AUTH_CONFIG", `{
"auths": {
"`+exampleAuth+`": { "username": "gopher", "password": "secret", "auth": "`+base64+`" }
},
"credsStore": "desktop"
}`)
username, password := "gopher", "secret"
creds := setAuthConfig(t, exampleAuth, username, password)

registry, cfg, err := DockerImageAuth(context.Background(), exampleAuth+"/my/image:latest")
require.NoError(t, err)
require.NotEmpty(t, cfg)

assert.Equal(t, exampleAuth, registry)
assert.Equal(t, "gopher", cfg.Username)
assert.Equal(t, "secret", cfg.Password)
assert.Equal(t, base64, cfg.Auth)
assert.Equal(t, username, cfg.Username)
assert.Equal(t, password, cfg.Password)
assert.Equal(t, creds, cfg.Auth)
})

t.Run("match registry authentication by host", func(t *testing.T) {
base64 := "Z29waGVyOnNlY3JldA==" // gopher:secret
imageReg := "example-auth.com"
imagePath := "/my/image:latest"

t.Setenv("DOCKER_AUTH_CONFIG", `{
"auths": {
"`+exampleAuth+`": { "username": "gopher", "password": "secret", "auth": "`+base64+`" }
},
"credsStore": "desktop"
}`)
base64 := setAuthConfig(t, exampleAuth, "gopher", "secret")

registry, cfg, err := DockerImageAuth(context.Background(), imageReg+imagePath)
require.NoError(t, err)
Expand All @@ -142,17 +126,11 @@ func TestGetDockerConfig(t *testing.T) {
})

t.Run("fail to match registry authentication due to invalid host", func(t *testing.T) {
base64 := "Z29waGVyOnNlY3JldA==" // gopher:secret
imageReg := "example-auth.com"
imagePath := "/my/image:latest"
invalidRegistryURL := "://invalid-host"

t.Setenv("DOCKER_AUTH_CONFIG", `{
"auths": {
"`+invalidRegistryURL+`": { "username": "gopher", "password": "secret", "auth": "`+base64+`" }
},
"credsStore": "desktop"
}`)
setAuthConfig(t, invalidRegistryURL, "gopher", "secret")

registry, cfg, err := DockerImageAuth(context.Background(), imageReg+imagePath)
require.ErrorIs(t, err, dockercfg.ErrCredentialsNotFound)
Expand All @@ -170,16 +148,10 @@ func TestGetDockerConfig(t *testing.T) {
return ""
}

base64 := "Z29waGVyOnNlY3JldA==" // gopher:secret
imageReg := ""
imagePath := "image:latest"

t.Setenv("DOCKER_AUTH_CONFIG", `{
"auths": {
"example-auth.com": { "username": "gopher", "password": "secret", "auth": "`+base64+`" }
},
"credsStore": "desktop"
}`)
setAuthConfig(t, "example-auth.com", "gopher", "secret")

registry, cfg, err := DockerImageAuth(context.Background(), imageReg+imagePath)
require.ErrorIs(t, err, dockercfg.ErrCredentialsNotFound)
Expand Down Expand Up @@ -219,22 +191,16 @@ func removeImageFromLocalCache(t *testing.T, img string) {
Force: true,
PruneChildren: true,
})
if err != nil {
if err != nil && !client.IsErrNotFound(err) {
t.Logf("could not remove image %s: %v\n", img, err)
}
}

func TestBuildContainerFromDockerfileWithDockerAuthConfig(t *testing.T) {
mappedPort := prepareLocalRegistryWithAuth(t)
registryHost := prepareLocalRegistryWithAuth(t)

// using the same credentials as in the Docker Registry
base64 := "dGVzdHVzZXI6dGVzdHBhc3N3b3Jk" // testuser:testpassword
t.Setenv("DOCKER_AUTH_CONFIG", `{
"auths": {
"localhost:`+mappedPort+`": { "username": "testuser", "password": "testpassword", "auth": "`+base64+`" }
},
"credsStore": "desktop"
}`)
setAuthConfig(t, registryHost, "testuser", "testpassword")

ctx := context.Background()

Expand All @@ -243,30 +209,26 @@ func TestBuildContainerFromDockerfileWithDockerAuthConfig(t *testing.T) {
Context: "./testdata",
Dockerfile: "auth.Dockerfile",
BuildArgs: map[string]*string{
"REGISTRY_PORT": &mappedPort,
"REGISTRY_HOST": &registryHost,
},
Repo: "localhost",
PrintBuildLog: true,
},
AlwaysPullImage: true, // make sure the authentication takes place
ExposedPorts: []string{"6379/tcp"},
WaitingFor: wait.ForLog("Ready to accept connections"),
}

redisC, err := prepareRedisImage(ctx, req)
require.NoError(t, err)
terminateContainerOnEnd(t, ctx, redisC)
require.NoError(t, err)
}

func TestBuildContainerFromDockerfileShouldFailWithWrongDockerAuthConfig(t *testing.T) {
mappedPort := prepareLocalRegistryWithAuth(t)
registryHost := prepareLocalRegistryWithAuth(t)

// using different credentials than in the Docker Registry
base64 := "Zm9vOmJhcg==" // foo:bar
t.Setenv("DOCKER_AUTH_CONFIG", `{
"auths": {
"localhost:`+mappedPort+`": { "username": "foo", "password": "bar", "auth": "`+base64+`" }
},
"credsStore": "desktop"
}`)
setAuthConfig(t, registryHost, "foo", "bar")

ctx := context.Background()

Expand All @@ -275,7 +237,7 @@ func TestBuildContainerFromDockerfileShouldFailWithWrongDockerAuthConfig(t *test
Context: "./testdata",
Dockerfile: "auth.Dockerfile",
BuildArgs: map[string]*string{
"REGISTRY_PORT": &mappedPort,
"REGISTRY_HOST": &registryHost,
},
},
AlwaysPullImage: true, // make sure the authentication takes place
Expand All @@ -284,25 +246,19 @@ func TestBuildContainerFromDockerfileShouldFailWithWrongDockerAuthConfig(t *test
}

redisC, err := prepareRedisImage(ctx, req)
require.Error(t, err)
terminateContainerOnEnd(t, ctx, redisC)
require.Error(t, err)
}

func TestCreateContainerFromPrivateRegistry(t *testing.T) {
mappedPort := prepareLocalRegistryWithAuth(t)
registryHost := prepareLocalRegistryWithAuth(t)

// using the same credentials as in the Docker Registry
base64 := "dGVzdHVzZXI6dGVzdHBhc3N3b3Jk" // testuser:testpassword
t.Setenv("DOCKER_AUTH_CONFIG", `{
"auths": {
"localhost:`+mappedPort+`": { "username": "testuser", "password": "testpassword", "auth": "`+base64+`" }
},
"credsStore": "desktop"
}`)
setAuthConfig(t, registryHost, "testuser", "testpassword")

ctx := context.Background()
req := ContainerRequest{
Image: "localhost:" + mappedPort + "/redis:5.0-alpine",
Image: registryHost + "/redis:5.0-alpine",
AlwaysPullImage: true, // make sure the authentication takes place
ExposedPorts: []string{"6379/tcp"},
WaitingFor: wait.ForLog("Ready to accept connections"),
Expand All @@ -312,8 +268,8 @@ func TestCreateContainerFromPrivateRegistry(t *testing.T) {
ContainerRequest: req,
Started: true,
})
require.NoError(t, err)
terminateContainerOnEnd(t, ctx, redisContainer)
require.NoError(t, err)
}

func prepareLocalRegistryWithAuth(t *testing.T) string {
Expand Down Expand Up @@ -356,10 +312,12 @@ func prepareLocalRegistryWithAuth(t *testing.T) string {
mappedPort, err := registryC.MappedPort(ctx, "5000/tcp")
require.NoError(t, err)

ip := localAddress(t)
mp := mappedPort.Port()
addr := ip + ":" + mp

t.Cleanup(func() {
removeImageFromLocalCache(t, "localhost:"+mp+"/redis:5.0-alpine")
removeImageFromLocalCache(t, addr+"/redis:5.0-alpine")
})
t.Cleanup(func() {
require.NoError(t, registryC.Terminate(context.Background()))
Expand All @@ -368,7 +326,7 @@ func prepareLocalRegistryWithAuth(t *testing.T) string {
_, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)

return mp
return addr
}

func prepareRedisImage(ctx context.Context, req ContainerRequest) (Container, error) {
Expand All @@ -378,9 +336,55 @@ func prepareRedisImage(ctx context.Context, req ContainerRequest) (Container, er
Started: true,
}

redisC, err := GenericContainer(ctx, genContainerReq)
return GenericContainer(ctx, genContainerReq)
}

// setAuthConfig sets the DOCKER_AUTH_CONFIG environment variable with
// authentication for with the given host, username and password.
// It returns the base64 encoded credentials.
func setAuthConfig(t *testing.T, host, username, password string) string {
t.Helper()

var creds string
if username != "" || password != "" {
creds = base64.StdEncoding.EncodeToString([]byte(username + ":" + password))
}

auth := fmt.Sprintf(`{
"auths": {
%q: {
"username": %q,
"password": %q,
"auth": %q
}
},
"credsStore": "desktop"
}`,
host,
username,
password,
creds,
)
t.Setenv("DOCKER_AUTH_CONFIG", auth)

return creds
}

// localAddress returns the local address of the machine
// which can be used to connect to the local registry.
// This avoids the issues with localhost on WSL.
func localAddress(t *testing.T) string {
if os.Getenv("WSL_DISTRO_NAME") == "" {
return "localhost"
}

conn, err := net.Dial("udp", "golang.org:80")
require.NoError(t, err)
defer conn.Close()

localAddr := conn.LocalAddr().(*net.UDPAddr)

return redisC, err
return localAddr.IP.String()
}

//go:embed testdata/.docker/config.json
Expand Down
4 changes: 2 additions & 2 deletions testdata/auth.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
ARG REGISTRY_PORT=5001
ARG REGISTRY_HOST=localhost:5001

FROM localhost:${REGISTRY_PORT}/redis:5.0-alpine
FROM ${REGISTRY_HOST}/redis:5.0-alpine
1 change: 0 additions & 1 deletion testhelpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ func terminateContainerOnEnd(tb testing.TB, ctx context.Context, ctr testcontain
return
}
tb.Cleanup(func() {
tb.Log("terminating container")
require.NoError(tb, ctr.Terminate(ctx))
})
}