diff --git a/cli/config/configfile/file.go b/cli/config/configfile/file.go index 26e148f05987..dec1922478db 100644 --- a/cli/config/configfile/file.go +++ b/cli/config/configfile/file.go @@ -42,10 +42,12 @@ const authConfigKey = "https://index.docker.io/v1/" // // [registry.GetAuthConfigKey]: https://pkg.go.dev/github.com/docker/docker@v28.5.1+incompatible/registry#GetAuthConfigKey func getAuthConfigKey(domainName string) string { - if domainName == "docker.io" || domainName == "index.docker.io" { + switch strings.TrimSpace(domainName) { + case "docker.io", "index.docker.io", "https://index.docker.io/v1", authConfigKey: return authConfigKey + default: + return domainName } - return domainName } // ConfigFile ~/.docker/config.json file info @@ -129,6 +131,7 @@ func (c *ConfigFile) LoadFromReader(configData io.Reader) error { return err } var err error + normalizedAuthConfigs := make(map[string]types.AuthConfig, len(c.AuthConfigs)) for addr, ac := range c.AuthConfigs { if ac.Auth != "" { ac.Username, ac.Password, err = decodeAuth(ac.Auth) @@ -137,9 +140,10 @@ func (c *ConfigFile) LoadFromReader(configData io.Reader) error { } } ac.Auth = "" - ac.ServerAddress = addr - c.AuthConfigs[addr] = ac + ac.ServerAddress = getAuthConfigKey(addr) + normalizedAuthConfigs[getAuthConfigKey(addr)] = ac } + c.AuthConfigs = normalizedAuthConfigs return nil } @@ -370,10 +374,11 @@ func parseEnvConfig(v string) (map[string]types.AuthConfig, error) { if err != nil { return nil, err } - authConfigs[addr] = types.AuthConfig{ + normalizedAddr := getAuthConfigKey(addr) + authConfigs[normalizedAddr] = types.AuthConfig{ Username: username, Password: password, - ServerAddress: addr, + ServerAddress: normalizedAddr, } } return authConfigs, nil @@ -387,7 +392,27 @@ var newNativeStore = func(configFile *ConfigFile, helperSuffix string) credentia // GetAuthConfig for a repository from the credential store func (c *ConfigFile) GetAuthConfig(registryHostname string) (types.AuthConfig, error) { acKey := getAuthConfigKey(registryHostname) - return c.GetCredentialsStore(acKey).Get(acKey) + store := c.GetCredentialsStore(acKey) + if authConfig, err := store.Get(acKey); err == nil && authConfig != (types.AuthConfig{}) { + return authConfig, nil + } + + if registryHostname != "" && registryHostname != acKey { + if authConfig, err := store.Get(registryHostname); err == nil && authConfig != (types.AuthConfig{}) { + return authConfig, nil + } + } + if acKey == authConfigKey { + for _, candidate := range []string{"docker.io", "index.docker.io", "https://index.docker.io/v1"} { + if candidate == acKey || candidate == "" { + continue + } + if authConfig, err := store.Get(candidate); err == nil && authConfig != (types.AuthConfig{}) { + return authConfig, nil + } + } + } + return store.Get(acKey) } // getConfiguredCredentialStore returns the credential helper configured for the @@ -398,6 +423,13 @@ func getConfiguredCredentialStore(c *ConfigFile, registryHostname string) string if helper, exists := c.CredentialHelpers[registryHostname]; exists { return helper } + if registryHostname == authConfigKey { + for _, key := range []string{"docker.io", "index.docker.io", "https://index.docker.io/v1"} { + if helper, exists := c.CredentialHelpers[key]; exists { + return helper + } + } + } } return c.CredentialsStore } diff --git a/cli/config/configfile/file_test.go b/cli/config/configfile/file_test.go index 92df02c74352..c2f3ca389e0d 100644 --- a/cli/config/configfile/file_test.go +++ b/cli/config/configfile/file_test.go @@ -481,6 +481,80 @@ func TestLoadFromReaderWithUsernamePassword(t *testing.T) { } } +func TestDockerHubAuthConfigAliases(t *testing.T) { + t.Run("config file aliases normalize to the canonical Docker Hub key", func(t *testing.T) { + configFile := New("test-load-dockerhub") + defer os.Remove("test-load-dockerhub") + + cf := ConfigFile{ + AuthConfigs: map[string]types.AuthConfig{ + "docker.io": { + Username: "user", + Password: "pass", + }, + }, + } + + b, err := json.Marshal(cf) + assert.NilError(t, err) + + err = configFile.LoadFromReader(bytes.NewReader(b)) + assert.NilError(t, err) + + got, err := configFile.GetAuthConfig("index.docker.io") + assert.NilError(t, err) + assert.Check(t, is.Equal(got.Username, "user")) + assert.Check(t, is.Equal(got.Password, "pass")) + _, ok := configFile.AuthConfigs[authConfigKey] + assert.Check(t, ok) + }) + + t.Run("manually populated auth maps accept Docker Hub aliases", func(t *testing.T) { + configFile := &ConfigFile{ + AuthConfigs: map[string]types.AuthConfig{ + "docker.io": { + Username: "user", + Password: "pass", + }, + }, + } + + got, err := configFile.GetAuthConfig("index.docker.io") + assert.NilError(t, err) + assert.Check(t, is.Equal(got.Username, "user")) + assert.Check(t, is.Equal(got.Password, "pass")) + }) + + t.Run("DOCKER_AUTH_CONFIG aliases normalize to the canonical Docker Hub key", func(t *testing.T) { + config := &ConfigFile{} + t.Setenv("DOCKER_AUTH_CONFIG", `{"auths":{"docker.io":{"auth":"dXNlcjpwYXNz"}}}`) + + authConfigs, err := config.GetAllCredentials() + assert.NilError(t, err) + expected := map[string]types.AuthConfig{ + authConfigKey: { + Username: "user", + Password: "pass", + ServerAddress: authConfigKey, + }, + } + assert.Check(t, is.DeepEqual(authConfigs, expected)) + + got, err := config.GetAuthConfig("docker.io") + assert.NilError(t, err) + assert.Check(t, is.DeepEqual(got, expected[authConfigKey])) + }) + + t.Run("credential helper aliases resolve to the canonical Docker Hub key", func(t *testing.T) { + config := &ConfigFile{ + CredentialHelpers: map[string]string{ + "docker.io": "docker-credential-dummy", + }, + } + assert.Check(t, is.Equal(getConfiguredCredentialStore(config, authConfigKey), "docker-credential-dummy")) + }) +} + const envTestUserPassConfig = `{ "auths": { "env.example.test": {