Skip to content

Commit eb43744

Browse files
committed
Use DOCKER_AUTH_CONFIG env as credential store
This patch gives the CLI the ability to pick up the `DOCKER_AUTH_CONFIG` environment variable and use it as a credential store. Credentials stored in `DOCKER_AUTH_CONFIG` would take precedence over any credentials in `~/.docker/config.json` or stored in the credential helper. Destructive actions, such as deleting a credential would result in a noop if found in the environment credential. Credentials found in the file or native store would get removed. Signed-off-by: Alano Terblanche <[email protected]>
1 parent fc99fe2 commit eb43744

File tree

3 files changed

+165
-2
lines changed

3 files changed

+165
-2
lines changed

cli/config/configfile/file.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -256,10 +256,20 @@ func decodeAuth(authStr string) (string, string, error) {
256256
// GetCredentialsStore returns a new credentials store from the settings in the
257257
// configuration file
258258
func (configFile *ConfigFile) GetCredentialsStore(registryHostname string) credentials.Store {
259+
store := credentials.NewFileStore(configFile)
260+
259261
if helper := getConfiguredCredentialStore(configFile, registryHostname); helper != "" {
260-
return newNativeStore(configFile, helper)
262+
store = newNativeStore(configFile, helper)
263+
}
264+
265+
// if DOCKER_AUTH_CONFIG is set, we need to use the env store instead
266+
// it falls back to native or file store if a value is not found
267+
// in the environment
268+
if os.Getenv("DOCKER_AUTH_CONFIG") != "" {
269+
return credentials.NewEnvStore(store)
261270
}
262-
return credentials.NewFileStore(configFile)
271+
272+
return store
263273
}
264274

265275
// var for unit testing.

cli/config/credentials/env_store.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package credentials
2+
3+
import (
4+
"encoding/json"
5+
"maps"
6+
"os"
7+
8+
"github.com/docker/cli/cli/config/types"
9+
)
10+
11+
type envStore struct {
12+
envCredentials map[string]types.AuthConfig
13+
fileStore Store
14+
}
15+
16+
func (e *envStore) Erase(serverAddress string) error {
17+
// We cannot erase any credentials in the environment
18+
// let's fallback to the file store
19+
if err := e.fileStore.Erase(serverAddress); err != nil {
20+
return err
21+
}
22+
23+
return nil
24+
}
25+
26+
func (e *envStore) Get(serverAddress string) (types.AuthConfig, error) {
27+
authConfig, ok := e.envCredentials[serverAddress]
28+
if !ok {
29+
return e.fileStore.Get(serverAddress)
30+
}
31+
return authConfig, nil
32+
}
33+
34+
func (e *envStore) GetAll() (map[string]types.AuthConfig, error) {
35+
credentials := make(map[string]types.AuthConfig)
36+
fileCredentials, err := e.fileStore.GetAll()
37+
if err == nil {
38+
credentials = fileCredentials
39+
}
40+
41+
// override the file credentials with the env credentials
42+
maps.Copy(credentials, e.envCredentials)
43+
return credentials, nil
44+
}
45+
46+
func (e *envStore) Store(authConfig types.AuthConfig) error {
47+
// We cannot store any credentials in the environment
48+
// let's fallback to the file store
49+
return e.fileStore.Store(authConfig)
50+
}
51+
52+
// NewEnvStore creates a new credentials store
53+
// from the environment variable DOCKER_AUTH_CONFIG.
54+
// It will parse the value set in the environment variable
55+
// as a JSON object and use it as the credentials store.
56+
//
57+
// As a fallback, it will use the parent store.
58+
// Any parsing errors will be ignored and the parent store
59+
// will be used instead.
60+
func NewEnvStore(parentStore Store) Store {
61+
v, ok := os.LookupEnv("DOCKER_AUTH_CONFIG")
62+
if !ok {
63+
return parentStore
64+
}
65+
66+
var credentials map[string]map[string]types.AuthConfig
67+
if err := json.Unmarshal([]byte(v), &credentials); err != nil {
68+
return parentStore
69+
}
70+
auth, ok := credentials["auth"]
71+
if !ok {
72+
return parentStore
73+
}
74+
75+
return &envStore{
76+
envCredentials: auth,
77+
fileStore: parentStore,
78+
}
79+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package credentials
2+
3+
import (
4+
"encoding/json"
5+
"testing"
6+
7+
"github.com/docker/cli/cli/config/types"
8+
"gotest.tools/v3/assert"
9+
)
10+
11+
func TestEnvStore(t *testing.T) {
12+
envConfig := map[string]types.AuthConfig{
13+
"https://example.com": {
14+
Email: "something-something",
15+
ServerAddress: "https://example.com",
16+
Auth: "super_secret_token",
17+
},
18+
}
19+
d, err := json.Marshal(map[string]map[string]types.AuthConfig{
20+
"auth": envConfig,
21+
})
22+
assert.NilError(t, err)
23+
24+
t.Setenv("DOCKER_AUTH_CONFIG", string(d))
25+
26+
fileConfig := map[string]types.AuthConfig{
27+
"https://only-in-file.com": {
28+
Email: "something-something",
29+
ServerAddress: "https://only-in-file.com",
30+
Auth: "super_secret_token",
31+
},
32+
}
33+
34+
var saveCount int
35+
fileStore := NewFileStore(&fakeStore{
36+
configs: fileConfig,
37+
saveFn: func(*fakeStore) error {
38+
saveCount++
39+
return nil
40+
},
41+
})
42+
43+
envStore := NewEnvStore(fileStore)
44+
45+
t.Run("case=get credentials from env", func(t *testing.T) {
46+
c, err := envStore.Get("https://example.com")
47+
assert.NilError(t, err)
48+
assert.Equal(t, c, envConfig["https://example.com"])
49+
})
50+
51+
t.Run("case=get credentials from file", func(t *testing.T) {
52+
c, err := envStore.Get("https://only-in-file.com")
53+
assert.NilError(t, err)
54+
assert.Equal(t, c, fileConfig["https://only-in-file.com"])
55+
})
56+
57+
t.Run("case=storing credentials should not update env", func(t *testing.T) {
58+
err := envStore.Store(types.AuthConfig{
59+
Email: "not-in-env",
60+
ServerAddress: "https://not-in-env",
61+
Auth: "not-in-env",
62+
})
63+
assert.NilError(t, err)
64+
assert.Equal(t, saveCount, 1)
65+
})
66+
67+
t.Run("case=delete credentials should not update env", func(t *testing.T) {
68+
err := envStore.Erase("https://example.com")
69+
assert.NilError(t, err)
70+
c, err := envStore.Get("https://example.com")
71+
assert.NilError(t, err)
72+
assert.Equal(t, c, envConfig["https://example.com"])
73+
})
74+
}

0 commit comments

Comments
 (0)