Skip to content

Commit 07c4b41

Browse files
author
Jake Sanders
committed
Add registry-specific credential helper support
Signed-off-by: Jake Sanders <[email protected]>
1 parent 0a5cb18 commit 07c4b41

File tree

8 files changed

+139
-24
lines changed

8 files changed

+139
-24
lines changed

cli/command/cli.go

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"runtime"
1111

1212
"github.com/docker/docker/api"
13+
"github.com/docker/docker/api/types"
1314
"github.com/docker/docker/api/types/versions"
1415
cliflags "github.com/docker/docker/cli/flags"
1516
"github.com/docker/docker/cliconfig"
@@ -86,15 +87,55 @@ func (cli *DockerCli) ConfigFile() *configfile.ConfigFile {
8687
return cli.configFile
8788
}
8889

90+
// GetAllCredentials returns all of the credentials stored in all of the
91+
// configured credential stores.
92+
func (cli *DockerCli) GetAllCredentials() (map[string]types.AuthConfig, error) {
93+
auths := make(map[string]types.AuthConfig)
94+
for registry := range cli.configFile.CredentialHelpers {
95+
helper := cli.CredentialsStore(registry)
96+
newAuths, err := helper.GetAll()
97+
if err != nil {
98+
return nil, err
99+
}
100+
addAll(auths, newAuths)
101+
}
102+
defaultStore := cli.CredentialsStore("")
103+
newAuths, err := defaultStore.GetAll()
104+
if err != nil {
105+
return nil, err
106+
}
107+
addAll(auths, newAuths)
108+
return auths, nil
109+
}
110+
111+
func addAll(to, from map[string]types.AuthConfig) {
112+
for reg, ac := range from {
113+
to[reg] = ac
114+
}
115+
}
116+
89117
// CredentialsStore returns a new credentials store based
90-
// on the settings provided in the configuration file.
91-
func (cli *DockerCli) CredentialsStore() credentials.Store {
92-
if cli.configFile.CredentialsStore != "" {
93-
return credentials.NewNativeStore(cli.configFile)
118+
// on the settings provided in the configuration file. Empty string returns
119+
// the default credential store.
120+
func (cli *DockerCli) CredentialsStore(serverAddress string) credentials.Store {
121+
if helper := getConfiguredCredentialStore(cli.configFile, serverAddress); helper != "" {
122+
return credentials.NewNativeStore(cli.configFile, helper)
94123
}
95124
return credentials.NewFileStore(cli.configFile)
96125
}
97126

127+
// getConfiguredCredentialStore returns the credential helper configured for the
128+
// given registry, the default credsStore, or the empty string if neither are
129+
// configured.
130+
func getConfiguredCredentialStore(c *configfile.ConfigFile, serverAddress string) string {
131+
if c.CredentialHelpers != nil && serverAddress != "" {
132+
if helper, exists := c.CredentialHelpers[serverAddress]; exists {
133+
return helper
134+
}
135+
}
136+
return c.CredentialsStore
137+
}
138+
98139
// Initialize the dockerCli runs initialization that must happen after command
99140
// line flags are parsed.
100141
func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {

cli/command/image/build.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
280280
}
281281
}
282282

283-
authConfig, _ := dockerCli.CredentialsStore().GetAll()
283+
authConfigs, _ := dockerCli.GetAllCredentials()
284284
buildOptions := types.ImageBuildOptions{
285285
Memory: memory,
286286
MemorySwap: memorySwap,
@@ -301,7 +301,7 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
301301
ShmSize: shmSize,
302302
Ulimits: options.ulimits.GetList(),
303303
BuildArgs: runconfigopts.ConvertKVStringsToMap(options.buildArgs.GetAll()),
304-
AuthConfigs: authConfig,
304+
AuthConfigs: authConfigs,
305305
Labels: runconfigopts.ConvertKVStringsToMap(options.labels.GetAll()),
306306
CacheFrom: options.cacheFrom,
307307
SecurityOpt: options.securityOpt,

cli/command/registry.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ func ResolveAuthConfig(ctx context.Context, cli *DockerCli, index *registrytypes
6767
configKey = ElectAuthServer(ctx, cli)
6868
}
6969

70-
a, _ := cli.CredentialsStore().Get(configKey)
70+
a, _ := cli.CredentialsStore(configKey).Get(configKey)
7171
return a
7272
}
7373

@@ -82,7 +82,7 @@ func ConfigureAuth(cli *DockerCli, flUser, flPassword, serverAddress string, isD
8282
serverAddress = registry.ConvertToHostname(serverAddress)
8383
}
8484

85-
authconfig, err := cli.CredentialsStore().Get(serverAddress)
85+
authconfig, err := cli.CredentialsStore(serverAddress).Get(serverAddress)
8686
if err != nil {
8787
return authconfig, err
8888
}

cli/command/registry/login.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ func runLogin(dockerCli *command.DockerCli, opts loginOptions) error {
6969
authConfig.Password = ""
7070
authConfig.IdentityToken = response.IdentityToken
7171
}
72-
if err := dockerCli.CredentialsStore().Store(authConfig); err != nil {
72+
if err := dockerCli.CredentialsStore(serverAddress).Store(authConfig); err != nil {
7373
return fmt.Errorf("Error saving credentials: %v", err)
7474
}
7575

cli/command/registry/logout.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ func runLogout(dockerCli *command.DockerCli, serverAddress string) error {
6868

6969
fmt.Fprintf(dockerCli.Out(), "Removing login credentials for %s\n", hostnameAddress)
7070
for _, r := range regsToLogout {
71-
if err := dockerCli.CredentialsStore().Erase(r); err != nil {
71+
if err := dockerCli.CredentialsStore(r).Erase(r); err != nil {
7272
fmt.Fprintf(dockerCli.Err(), "WARNING: could not erase credentials: %v\n", err)
7373
}
7474
}

cliconfig/config_test.go

Lines changed: 83 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ func TestEmptyFile(t *testing.T) {
8686
}
8787
}
8888

89-
func TestEmptyJson(t *testing.T) {
89+
func TestEmptyJSON(t *testing.T) {
9090
tmpHome, err := ioutil.TempDir("", "config-test")
9191
if err != nil {
9292
t.Fatal(err)
@@ -193,7 +193,7 @@ func TestOldValidAuth(t *testing.T) {
193193
}
194194
}
195195

196-
func TestOldJsonInvalid(t *testing.T) {
196+
func TestOldJSONInvalid(t *testing.T) {
197197
tmpHome, err := ioutil.TempDir("", "config-test")
198198
if err != nil {
199199
t.Fatal(err)
@@ -219,7 +219,7 @@ func TestOldJsonInvalid(t *testing.T) {
219219
}
220220
}
221221

222-
func TestOldJson(t *testing.T) {
222+
func TestOldJSON(t *testing.T) {
223223
tmpHome, err := ioutil.TempDir("", "config-test")
224224
if err != nil {
225225
t.Fatal(err)
@@ -265,7 +265,7 @@ func TestOldJson(t *testing.T) {
265265
}
266266
}
267267

268-
func TestNewJson(t *testing.T) {
268+
func TestNewJSON(t *testing.T) {
269269
tmpHome, err := ioutil.TempDir("", "config-test")
270270
if err != nil {
271271
t.Fatal(err)
@@ -304,7 +304,7 @@ func TestNewJson(t *testing.T) {
304304
}
305305
}
306306

307-
func TestNewJsonNoEmail(t *testing.T) {
307+
func TestNewJSONNoEmail(t *testing.T) {
308308
tmpHome, err := ioutil.TempDir("", "config-test")
309309
if err != nil {
310310
t.Fatal(err)
@@ -343,7 +343,7 @@ func TestNewJsonNoEmail(t *testing.T) {
343343
}
344344
}
345345

346-
func TestJsonWithPsFormat(t *testing.T) {
346+
func TestJSONWithPsFormat(t *testing.T) {
347347
tmpHome, err := ioutil.TempDir("", "config-test")
348348
if err != nil {
349349
t.Fatal(err)
@@ -376,6 +376,78 @@ func TestJsonWithPsFormat(t *testing.T) {
376376
}
377377
}
378378

379+
func TestJSONWithCredentialStore(t *testing.T) {
380+
tmpHome, err := ioutil.TempDir("", "config-test")
381+
if err != nil {
382+
t.Fatal(err)
383+
}
384+
defer os.RemoveAll(tmpHome)
385+
386+
fn := filepath.Join(tmpHome, ConfigFileName)
387+
js := `{
388+
"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "[email protected]" } },
389+
"credsStore": "crazy-secure-storage"
390+
}`
391+
if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
392+
t.Fatal(err)
393+
}
394+
395+
config, err := Load(tmpHome)
396+
if err != nil {
397+
t.Fatalf("Failed loading on empty json file: %q", err)
398+
}
399+
400+
if config.CredentialsStore != "crazy-secure-storage" {
401+
t.Fatalf("Unknown credential store: %s\n", config.CredentialsStore)
402+
}
403+
404+
// Now save it and make sure it shows up in new form
405+
configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
406+
if !strings.Contains(configStr, `"credsStore":`) ||
407+
!strings.Contains(configStr, "crazy-secure-storage") {
408+
t.Fatalf("Should have save in new form: %s", configStr)
409+
}
410+
}
411+
412+
func TestJSONWithCredentialHelpers(t *testing.T) {
413+
tmpHome, err := ioutil.TempDir("", "config-test")
414+
if err != nil {
415+
t.Fatal(err)
416+
}
417+
defer os.RemoveAll(tmpHome)
418+
419+
fn := filepath.Join(tmpHome, ConfigFileName)
420+
js := `{
421+
"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "[email protected]" } },
422+
"credHelpers": { "images.io": "images-io", "containers.com": "crazy-secure-storage" }
423+
}`
424+
if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
425+
t.Fatal(err)
426+
}
427+
428+
config, err := Load(tmpHome)
429+
if err != nil {
430+
t.Fatalf("Failed loading on empty json file: %q", err)
431+
}
432+
433+
if config.CredentialHelpers == nil {
434+
t.Fatal("config.CredentialHelpers was nil")
435+
} else if config.CredentialHelpers["images.io"] != "images-io" ||
436+
config.CredentialHelpers["containers.com"] != "crazy-secure-storage" {
437+
t.Fatalf("Credential helpers not deserialized properly: %v\n", config.CredentialHelpers)
438+
}
439+
440+
// Now save it and make sure it shows up in new form
441+
configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
442+
if !strings.Contains(configStr, `"credHelpers":`) ||
443+
!strings.Contains(configStr, "images.io") ||
444+
!strings.Contains(configStr, "images-io") ||
445+
!strings.Contains(configStr, "containers.com") ||
446+
!strings.Contains(configStr, "crazy-secure-storage") {
447+
t.Fatalf("Should have save in new form: %s", configStr)
448+
}
449+
}
450+
379451
// Save it and make sure it shows up in new form
380452
func saveConfigAndValidateNewFormat(t *testing.T, config *configfile.ConfigFile, homeFolder string) string {
381453
if err := config.Save(); err != nil {
@@ -420,7 +492,7 @@ func TestConfigFile(t *testing.T) {
420492
}
421493
}
422494

423-
func TestJsonReaderNoFile(t *testing.T) {
495+
func TestJSONReaderNoFile(t *testing.T) {
424496
js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "[email protected]" } } }`
425497

426498
config, err := LoadFromReader(strings.NewReader(js))
@@ -435,7 +507,7 @@ func TestJsonReaderNoFile(t *testing.T) {
435507

436508
}
437509

438-
func TestOldJsonReaderNoFile(t *testing.T) {
510+
func TestOldJSONReaderNoFile(t *testing.T) {
439511
js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"[email protected]"}}`
440512

441513
config, err := LegacyLoadFromReader(strings.NewReader(js))
@@ -449,7 +521,7 @@ func TestOldJsonReaderNoFile(t *testing.T) {
449521
}
450522
}
451523

452-
func TestJsonWithPsFormatNoFile(t *testing.T) {
524+
func TestJSONWithPsFormatNoFile(t *testing.T) {
453525
js := `{
454526
"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "[email protected]" } },
455527
"psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
@@ -465,7 +537,7 @@ func TestJsonWithPsFormatNoFile(t *testing.T) {
465537

466538
}
467539

468-
func TestJsonSaveWithNoFile(t *testing.T) {
540+
func TestJSONSaveWithNoFile(t *testing.T) {
469541
js := `{
470542
"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv" } },
471543
"psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
@@ -507,7 +579,7 @@ func TestJsonSaveWithNoFile(t *testing.T) {
507579
}
508580
}
509581

510-
func TestLegacyJsonSaveWithNoFile(t *testing.T) {
582+
func TestLegacyJSONSaveWithNoFile(t *testing.T) {
511583

512584
js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"[email protected]"}}`
513585
config, err := LegacyLoadFromReader(strings.NewReader(js))

cliconfig/configfile/file.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ type ConfigFile struct {
3131
StatsFormat string `json:"statsFormat,omitempty"`
3232
DetachKeys string `json:"detachKeys,omitempty"`
3333
CredentialsStore string `json:"credsStore,omitempty"`
34+
CredentialHelpers map[string]string `json:"credHelpers,omitempty"`
3435
Filename string `json:"-"` // Note: for internal use only
3536
ServiceInspectFormat string `json:"serviceInspectFormat,omitempty"`
3637
}
@@ -96,7 +97,8 @@ func (configFile *ConfigFile) LoadFromReader(configData io.Reader) error {
9697
// in this file or not.
9798
func (configFile *ConfigFile) ContainsAuth() bool {
9899
return configFile.CredentialsStore != "" ||
99-
(configFile.AuthConfigs != nil && len(configFile.AuthConfigs) > 0)
100+
len(configFile.CredentialHelpers) > 0 ||
101+
len(configFile.AuthConfigs) > 0
100102
}
101103

102104
// SaveToWriter encodes and writes out all the authorization information to

cliconfig/credentials/native_store.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ type nativeStore struct {
2222

2323
// NewNativeStore creates a new native store that
2424
// uses a remote helper program to manage credentials.
25-
func NewNativeStore(file *configfile.ConfigFile) Store {
26-
name := remoteCredentialsPrefix + file.CredentialsStore
25+
func NewNativeStore(file *configfile.ConfigFile, helperSuffix string) Store {
26+
name := remoteCredentialsPrefix + helperSuffix
2727
return &nativeStore{
2828
programFunc: client.NewShellProgramFunc(name),
2929
fileStore: NewFileStore(file),

0 commit comments

Comments
 (0)