From f6f71c4a02bf9243dbd802a6924217ba5410cb9f Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Wed, 9 Apr 2025 20:53:14 +0000 Subject: [PATCH 1/2] fix: provide backwards compatibility for Docker Auth secrets without a hostname --- dockerutil/client.go | 41 +++++++++++++---- integration/docker_test.go | 94 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+), 9 deletions(-) diff --git a/dockerutil/client.go b/dockerutil/client.go index 00dc4b9..11ca00d 100644 --- a/dockerutil/client.go +++ b/dockerutil/client.go @@ -82,16 +82,39 @@ func parseConfig(cfg dockercfg.Config, reg string) (AuthConfig, error) { } if secret != "" { - if username == "" { - return AuthConfig{ - IdentityToken: secret, - }, nil - } - return AuthConfig{ - Username: username, - Password: secret, - }, nil + return toAuthConfig(username, secret), nil } + // // This to preserve backwards compatibility with older variants of envbox + // // that didn't mandate a hostname key in the config file. We just take the + // // first valid auth config we find and use that. + // for _, auth := range cfg.AuthConfigs { + // if auth.IdentityToken != "" { + // return toAuthConfig("", auth.IdentityToken), nil + // } + + // if auth.Username != "" && auth.Password != "" { + // return toAuthConfig(auth.Username, auth.Password), nil + // } + + // username, secret, err = dockercfg.DecodeBase64Auth(auth) + // if err == nil && secret != "" { + // return toAuthConfig(username, secret), nil + // } + // // Invalid auth config, skip it. + // } + return AuthConfig{}, xerrors.Errorf("no auth config found for registry %s: %w", reg, os.ErrNotExist) } + +func toAuthConfig(username, secret string) AuthConfig { + if username == "" { + return AuthConfig{ + IdentityToken: secret, + } + } + return AuthConfig{ + Username: username, + Password: secret, + } +} diff --git a/integration/docker_test.go b/integration/docker_test.go index c7c1a6d..61c961d 100644 --- a/integration/docker_test.go +++ b/integration/docker_test.go @@ -393,6 +393,100 @@ func TestDocker(t *testing.T) { require.True(t, recorder.ContainsLog("Envbox startup complete!")) }) + // This test provides backwards compatibility for older variants of envbox that may specify a + // Docker Auth config without a hostname key. + t.Run("NoHostnameAuthConfig", func(t *testing.T) { + t.Parallel() + + var ( + dir = integrationtest.TmpDir(t) + binds = integrationtest.DefaultBinds(t, dir) + ) + + pool, err := dockertest.NewPool("") + require.NoError(t, err) + + // Create some listeners for the Docker and Coder + // services we'll be running with self signed certs. + bridgeIP := integrationtest.DockerBridgeIP(t) + coderListener, err := net.Listen("tcp", fmt.Sprintf("%s:0", bridgeIP)) + require.NoError(t, err) + defer coderListener.Close() + coderAddr := tcpAddr(t, coderListener) + + registryListener, err := net.Listen("tcp", fmt.Sprintf("%s:0", bridgeIP)) + require.NoError(t, err) + err = registryListener.Close() + require.NoError(t, err) + registryAddr := tcpAddr(t, registryListener) + + coderCert := integrationtest.GenerateTLSCertificate(t, "host.docker.internal", coderAddr.IP.String()) + dockerCert := integrationtest.GenerateTLSCertificate(t, "host.docker.internal", registryAddr.IP.String()) + + // Startup our fake Coder "control-plane". + recorder := integrationtest.FakeBuildLogRecorder(t, coderListener, coderCert) + + certDir := integrationtest.MkdirAll(t, dir, "certs") + + // Write the Coder cert disk. + coderCertPath := filepath.Join(certDir, "coder_cert.pem") + coderKeyPath := filepath.Join(certDir, "coder_key.pem") + integrationtest.WriteCertificate(t, coderCert, coderCertPath, coderKeyPath) + coderCertMount := integrationtest.BindMount(certDir, "/tmp/certs", false) + + // Write the Registry cert to disk. + regCertPath := filepath.Join(certDir, "registry_cert.crt") + regKeyPath := filepath.Join(certDir, "registry_key.pem") + integrationtest.WriteCertificate(t, dockerCert, regCertPath, regKeyPath) + + username := "coder" + password := "helloworld" + + // Start up the docker registry and push an image + // to it that we can reference. + image := integrationtest.RunLocalDockerRegistry(t, pool, integrationtest.RegistryConfig{ + HostCertPath: regCertPath, + HostKeyPath: regKeyPath, + Image: integrationtest.UbuntuImage, + TLSPort: strconv.Itoa(registryAddr.Port), + PasswordDir: dir, + Username: username, + Password: password, + }) + + type authConfigs struct { + Auths map[string]dockerutil.AuthConfig `json:"auths"` + } + + auths := authConfigs{ + Auths: map[string]dockerutil.AuthConfig{ + "": {Username: username, Password: password}, + }, + } + + authStr, err := json.Marshal(auths) + require.NoError(t, err) + + envs := []string{ + integrationtest.EnvVar(cli.EnvAgentToken, "faketoken"), + integrationtest.EnvVar(cli.EnvAgentURL, fmt.Sprintf("https://%s:%d", "host.docker.internal", coderAddr.Port)), + integrationtest.EnvVar(cli.EnvExtraCertsPath, "/tmp/certs"), + integrationtest.EnvVar(cli.EnvBoxPullImageSecretEnvVar, string(authStr)), + } + + // Run the envbox container. + _ = integrationtest.RunEnvbox(t, pool, &integrationtest.CreateDockerCVMConfig{ + Image: image.String(), + Username: "coder", + Envs: envs, + OuterMounts: append(binds, coderCertMount), + }) + + // This indicates we've made it all the way to end + // of the logs we attempt to push. + require.True(t, recorder.ContainsLog("Envbox startup complete!")) + }) + // This tests the inverse of SelfSignedCerts. We assert that // the container fails to startup since we don't have a valid // cert for the registry. It mainly tests that we aren't From de24a3b135281d537946f3e8729d2ccec5062381 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Wed, 9 Apr 2025 21:02:11 +0000 Subject: [PATCH 2/2] validated fix --- dockerutil/client.go | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/dockerutil/client.go b/dockerutil/client.go index 11ca00d..c860fbc 100644 --- a/dockerutil/client.go +++ b/dockerutil/client.go @@ -85,24 +85,24 @@ func parseConfig(cfg dockercfg.Config, reg string) (AuthConfig, error) { return toAuthConfig(username, secret), nil } - // // This to preserve backwards compatibility with older variants of envbox - // // that didn't mandate a hostname key in the config file. We just take the - // // first valid auth config we find and use that. - // for _, auth := range cfg.AuthConfigs { - // if auth.IdentityToken != "" { - // return toAuthConfig("", auth.IdentityToken), nil - // } - - // if auth.Username != "" && auth.Password != "" { - // return toAuthConfig(auth.Username, auth.Password), nil - // } - - // username, secret, err = dockercfg.DecodeBase64Auth(auth) - // if err == nil && secret != "" { - // return toAuthConfig(username, secret), nil - // } - // // Invalid auth config, skip it. - // } + // This to preserve backwards compatibility with older variants of envbox + // that didn't mandate a hostname key in the config file. We just take the + // first valid auth config we find and use that. + for _, auth := range cfg.AuthConfigs { + if auth.IdentityToken != "" { + return toAuthConfig("", auth.IdentityToken), nil + } + + if auth.Username != "" && auth.Password != "" { + return toAuthConfig(auth.Username, auth.Password), nil + } + + username, secret, err = dockercfg.DecodeBase64Auth(auth) + if err == nil && secret != "" { + return toAuthConfig(username, secret), nil + } + // Invalid auth config, skip it. + } return AuthConfig{}, xerrors.Errorf("no auth config found for registry %s: %w", reg, os.ErrNotExist) }