Skip to content

Commit

Permalink
fix: docker config error handling
Browse files Browse the repository at this point in the history
Handle file not exist error in getDockerAuthConfigs, treating it as if
no authentication was provided.

Use config directly for cache instead of loading the file a second time
which may be the wrong file if loaded from the environment.

Correctly handle json decode errors in getDockerConfig instead of
falling back to the default config, which would result in unexpected
behaviour.

Fixes #2767
  • Loading branch information
stevenh committed Sep 7, 2024
1 parent 553afd3 commit 8f66bf1
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 29 deletions.
49 changes: 20 additions & 29 deletions docker_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"net/url"
"os"
"sync"
Expand Down Expand Up @@ -137,24 +136,12 @@ func (c *credentialsCache) Get(hostname, configKey string) (string, string, erro
return user, password, nil
}

// configFileKey returns a key to use for caching credentials based on
// configKey returns a key to use for caching credentials based on
// the contents of the currently active config.
func configFileKey() (string, error) {
configPath, err := dockercfg.ConfigPath()
if err != nil {
return "", err
}

f, err := os.Open(configPath)
if err != nil {
return "", fmt.Errorf("open config file: %w", err)
}

defer f.Close()

func configKey(cfg *dockercfg.Config) (string, error) {
h := md5.New()
if _, err := io.Copy(h, f); err != nil {
return "", fmt.Errorf("copying config file: %w", err)
if err := json.NewEncoder(h).Encode(cfg); err != nil {
return "", fmt.Errorf("encode config: %w", err)
}

return hex.EncodeToString(h.Sum(nil)), nil
Expand All @@ -165,10 +152,14 @@ func configFileKey() (string, error) {
func getDockerAuthConfigs() (map[string]registry.AuthConfig, error) {
cfg, err := getDockerConfig()
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return map[string]registry.AuthConfig{}, nil
}

return nil, err
}

configKey, err := configFileKey()
key, err := configKey(cfg)
if err != nil {
return nil, err
}
Expand All @@ -195,7 +186,7 @@ func getDockerAuthConfigs() (map[string]registry.AuthConfig, error) {
switch {
case ac.Username == "" && ac.Password == "":
// Look up credentials from the credential store.
u, p, err := creds.Get(k, configKey)
u, p, err := creds.Get(k, key)
if err != nil {
results <- authConfigResult{err: err}
return
Expand All @@ -218,7 +209,7 @@ func getDockerAuthConfigs() (map[string]registry.AuthConfig, error) {
go func(k string) {
defer wg.Done()

u, p, err := creds.Get(k, configKey)
u, p, err := creds.Get(k, key)
if err != nil {
results <- authConfigResult{err: err}
return
Expand Down Expand Up @@ -260,20 +251,20 @@ func getDockerAuthConfigs() (map[string]registry.AuthConfig, error) {
// 1. the DOCKER_AUTH_CONFIG environment variable, unmarshalling it into a dockercfg.Config
// 2. the DOCKER_CONFIG environment variable, as the path to the config file
// 3. else it will load the default config file, which is ~/.docker/config.json
func getDockerConfig() (dockercfg.Config, error) {
dockerAuthConfig := os.Getenv("DOCKER_AUTH_CONFIG")
if dockerAuthConfig != "" {
cfg := dockercfg.Config{}
err := json.Unmarshal([]byte(dockerAuthConfig), &cfg)
if err == nil {
return cfg, nil
func getDockerConfig() (*dockercfg.Config, error) {
if env := os.Getenv("DOCKER_AUTH_CONFIG"); env != "" {
var cfg dockercfg.Config
if err := json.Unmarshal([]byte(env), &cfg); err != nil {
return nil, fmt.Errorf("unmarshal DOCKER_AUTH_CONFIG: %w", err)
}

return &cfg, nil
}

cfg, err := dockercfg.LoadDefaultConfig()
if err != nil {
return cfg, err
return nil, fmt.Errorf("load default config: %w", err)
}

return cfg, nil
return &cfg, nil
}
84 changes: 84 additions & 0 deletions docker_auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -414,4 +414,88 @@ func Test_getDockerAuthConfigs(t *testing.T) {
}
require.Equal(t, expected, got)
})

t.Run("file-not-found", func(t *testing.T) {
t.Setenv("DOCKER_CONFIG", "")
tmp := t.TempDir()
t.Setenv("HOME", tmp)
t.Setenv("USERPROFILE", tmp) // Windows

cfg, err := getDockerAuthConfigs()
require.NoError(t, err)
require.Empty(t, cfg)
})
}

func Test_getDockerConfig(t *testing.T) {
t.Run("file/valid", func(t *testing.T) {
testDockerConfigHome(t, "testdata")

got, err := getDockerConfig()
require.NoError(t, err)
require.NotNil(t, got)
})

t.Run("file/not-found", func(t *testing.T) {
testDockerConfigHome(t, "testdata/not-exist")

got, err := getDockerConfig()
require.ErrorIs(t, err, os.ErrNotExist)
require.Nil(t, got)
})

t.Run("file/bad", func(t *testing.T) {
testDockerConfigHome(t, "testdata/bad-config")

got, err := getDockerConfig()
require.ErrorContains(t, err, "json: cannot unmarshal array")
require.Nil(t, got)
})

t.Run("env/literal", func(t *testing.T) {
testDockerConfigHome(t, "testdata/bad-config")
t.Setenv("DOCKER_AUTH_CONFIG", dockerConfig)

got, err := getDockerConfig()
require.NoError(t, err)
require.NotNil(t, got)
})

t.Run("env/literal-bad", func(t *testing.T) {
testDockerConfigHome(t, "testdata/bad-config")
t.Setenv("DOCKER_AUTH_CONFIG", `{"auths": []}`)

got, err := getDockerConfig()
require.ErrorContains(t, err, "json: cannot unmarshal array")
require.Nil(t, got)
})

t.Run("env/dir", func(t *testing.T) {
testDockerConfigHome(t, "testdata/bad-config")
t.Setenv("DOCKER_CONFIG", "testdata/.docker")

got, err := getDockerConfig()
require.NoError(t, err)
require.NotNil(t, got)
})

t.Run("env/dir-bad", func(t *testing.T) {
testDockerConfigHome(t, "testdata")
t.Setenv("DOCKER_CONFIG", "testdata/bad-config/.docker")

got, err := getDockerConfig()
require.ErrorContains(t, err, "json: cannot unmarshal array")
require.Nil(t, got)
})
}

// testDockerConfigHome sets the user's home directory to the given path
// and unsets the DOCKER_CONFIG and DOCKER_AUTH_CONFIG environment variables.
func testDockerConfigHome(t *testing.T, dir string) {
t.Helper()

t.Setenv("DOCKER_AUTH_CONFIG", "")
t.Setenv("DOCKER_CONFIG", "")
t.Setenv("HOME", dir)
t.Setenv("USERPROFILE", dir) // Windows
}
3 changes: 3 additions & 0 deletions testdata/bad-config/.docker/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"auths": []
}

0 comments on commit 8f66bf1

Please sign in to comment.