Skip to content
Open
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
46 changes: 39 additions & 7 deletions cli/config/configfile/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment on lines +45 to +49

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is now normalising something ... different? And only to lookup the defaults, but not to normalize the other cases? (i.e., getAuthConfigKey("REGISTRY.exAmPLe.COM") returns REGISTRY.exAmPLe.COM as-is?

}
return domainName
}

// ConfigFile ~/.docker/config.json file info
Expand Down Expand Up @@ -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)
Expand All @@ -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
}

Expand Down Expand Up @@ -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,
}
Comment on lines -373 to 382

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here; I don't think we have to account for someone setting a wrong env-var; if it doesn't resolve, they should fix their env-var to use the correct casing.

}
return authConfigs, nil
Expand All @@ -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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is all de-centralizing the logic again; I don't think that's good. The earlier implementation you had kept it central, and for the logout ("delete") case, it picked some of the normalisation

}
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
Expand All @@ -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
}
Expand Down
74 changes: 74 additions & 0 deletions cli/config/configfile/file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
Loading