浏览代码

Add registry-specific credential helper support

Signed-off-by: Jake Sanders <jsand@google.com>
Jake Sanders 9 年之前
父节点
当前提交
07c4b4124b

+ 45 - 4
cli/command/cli.go

@@ -10,6 +10,7 @@ import (
 	"runtime"
 
 	"github.com/docker/docker/api"
+	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/versions"
 	cliflags "github.com/docker/docker/cli/flags"
 	"github.com/docker/docker/cliconfig"
@@ -86,15 +87,55 @@ func (cli *DockerCli) ConfigFile() *configfile.ConfigFile {
 	return cli.configFile
 }
 
+// GetAllCredentials returns all of the credentials stored in all of the
+// configured credential stores.
+func (cli *DockerCli) GetAllCredentials() (map[string]types.AuthConfig, error) {
+	auths := make(map[string]types.AuthConfig)
+	for registry := range cli.configFile.CredentialHelpers {
+		helper := cli.CredentialsStore(registry)
+		newAuths, err := helper.GetAll()
+		if err != nil {
+			return nil, err
+		}
+		addAll(auths, newAuths)
+	}
+	defaultStore := cli.CredentialsStore("")
+	newAuths, err := defaultStore.GetAll()
+	if err != nil {
+		return nil, err
+	}
+	addAll(auths, newAuths)
+	return auths, nil
+}
+
+func addAll(to, from map[string]types.AuthConfig) {
+	for reg, ac := range from {
+		to[reg] = ac
+	}
+}
+
 // CredentialsStore returns a new credentials store based
-// on the settings provided in the configuration file.
-func (cli *DockerCli) CredentialsStore() credentials.Store {
-	if cli.configFile.CredentialsStore != "" {
-		return credentials.NewNativeStore(cli.configFile)
+// on the settings provided in the configuration file. Empty string returns
+// the default credential store.
+func (cli *DockerCli) CredentialsStore(serverAddress string) credentials.Store {
+	if helper := getConfiguredCredentialStore(cli.configFile, serverAddress); helper != "" {
+		return credentials.NewNativeStore(cli.configFile, helper)
 	}
 	return credentials.NewFileStore(cli.configFile)
 }
 
+// getConfiguredCredentialStore returns the credential helper configured for the
+// given registry, the default credsStore, or the empty string if neither are
+// configured.
+func getConfiguredCredentialStore(c *configfile.ConfigFile, serverAddress string) string {
+	if c.CredentialHelpers != nil && serverAddress != "" {
+		if helper, exists := c.CredentialHelpers[serverAddress]; exists {
+			return helper
+		}
+	}
+	return c.CredentialsStore
+}
+
 // Initialize the dockerCli runs initialization that must happen after command
 // line flags are parsed.
 func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {

+ 2 - 2
cli/command/image/build.go

@@ -280,7 +280,7 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
 		}
 	}
 
-	authConfig, _ := dockerCli.CredentialsStore().GetAll()
+	authConfigs, _ := dockerCli.GetAllCredentials()
 	buildOptions := types.ImageBuildOptions{
 		Memory:         memory,
 		MemorySwap:     memorySwap,
@@ -301,7 +301,7 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
 		ShmSize:        shmSize,
 		Ulimits:        options.ulimits.GetList(),
 		BuildArgs:      runconfigopts.ConvertKVStringsToMap(options.buildArgs.GetAll()),
-		AuthConfigs:    authConfig,
+		AuthConfigs:    authConfigs,
 		Labels:         runconfigopts.ConvertKVStringsToMap(options.labels.GetAll()),
 		CacheFrom:      options.cacheFrom,
 		SecurityOpt:    options.securityOpt,

+ 2 - 2
cli/command/registry.go

@@ -67,7 +67,7 @@ func ResolveAuthConfig(ctx context.Context, cli *DockerCli, index *registrytypes
 		configKey = ElectAuthServer(ctx, cli)
 	}
 
-	a, _ := cli.CredentialsStore().Get(configKey)
+	a, _ := cli.CredentialsStore(configKey).Get(configKey)
 	return a
 }
 
@@ -82,7 +82,7 @@ func ConfigureAuth(cli *DockerCli, flUser, flPassword, serverAddress string, isD
 		serverAddress = registry.ConvertToHostname(serverAddress)
 	}
 
-	authconfig, err := cli.CredentialsStore().Get(serverAddress)
+	authconfig, err := cli.CredentialsStore(serverAddress).Get(serverAddress)
 	if err != nil {
 		return authconfig, err
 	}

+ 1 - 1
cli/command/registry/login.go

@@ -69,7 +69,7 @@ func runLogin(dockerCli *command.DockerCli, opts loginOptions) error {
 		authConfig.Password = ""
 		authConfig.IdentityToken = response.IdentityToken
 	}
-	if err := dockerCli.CredentialsStore().Store(authConfig); err != nil {
+	if err := dockerCli.CredentialsStore(serverAddress).Store(authConfig); err != nil {
 		return fmt.Errorf("Error saving credentials: %v", err)
 	}
 

+ 1 - 1
cli/command/registry/logout.go

@@ -68,7 +68,7 @@ func runLogout(dockerCli *command.DockerCli, serverAddress string) error {
 
 	fmt.Fprintf(dockerCli.Out(), "Removing login credentials for %s\n", hostnameAddress)
 	for _, r := range regsToLogout {
-		if err := dockerCli.CredentialsStore().Erase(r); err != nil {
+		if err := dockerCli.CredentialsStore(r).Erase(r); err != nil {
 			fmt.Fprintf(dockerCli.Err(), "WARNING: could not erase credentials: %v\n", err)
 		}
 	}

+ 83 - 11
cliconfig/config_test.go

@@ -86,7 +86,7 @@ func TestEmptyFile(t *testing.T) {
 	}
 }
 
-func TestEmptyJson(t *testing.T) {
+func TestEmptyJSON(t *testing.T) {
 	tmpHome, err := ioutil.TempDir("", "config-test")
 	if err != nil {
 		t.Fatal(err)
@@ -193,7 +193,7 @@ func TestOldValidAuth(t *testing.T) {
 	}
 }
 
-func TestOldJsonInvalid(t *testing.T) {
+func TestOldJSONInvalid(t *testing.T) {
 	tmpHome, err := ioutil.TempDir("", "config-test")
 	if err != nil {
 		t.Fatal(err)
@@ -219,7 +219,7 @@ func TestOldJsonInvalid(t *testing.T) {
 	}
 }
 
-func TestOldJson(t *testing.T) {
+func TestOldJSON(t *testing.T) {
 	tmpHome, err := ioutil.TempDir("", "config-test")
 	if err != nil {
 		t.Fatal(err)
@@ -265,7 +265,7 @@ func TestOldJson(t *testing.T) {
 	}
 }
 
-func TestNewJson(t *testing.T) {
+func TestNewJSON(t *testing.T) {
 	tmpHome, err := ioutil.TempDir("", "config-test")
 	if err != nil {
 		t.Fatal(err)
@@ -304,7 +304,7 @@ func TestNewJson(t *testing.T) {
 	}
 }
 
-func TestNewJsonNoEmail(t *testing.T) {
+func TestNewJSONNoEmail(t *testing.T) {
 	tmpHome, err := ioutil.TempDir("", "config-test")
 	if err != nil {
 		t.Fatal(err)
@@ -343,7 +343,7 @@ func TestNewJsonNoEmail(t *testing.T) {
 	}
 }
 
-func TestJsonWithPsFormat(t *testing.T) {
+func TestJSONWithPsFormat(t *testing.T) {
 	tmpHome, err := ioutil.TempDir("", "config-test")
 	if err != nil {
 		t.Fatal(err)
@@ -376,6 +376,78 @@ func TestJsonWithPsFormat(t *testing.T) {
 	}
 }
 
+func TestJSONWithCredentialStore(t *testing.T) {
+	tmpHome, err := ioutil.TempDir("", "config-test")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(tmpHome)
+
+	fn := filepath.Join(tmpHome, ConfigFileName)
+	js := `{
+		"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } },
+		"credsStore": "crazy-secure-storage"
+}`
+	if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
+		t.Fatal(err)
+	}
+
+	config, err := Load(tmpHome)
+	if err != nil {
+		t.Fatalf("Failed loading on empty json file: %q", err)
+	}
+
+	if config.CredentialsStore != "crazy-secure-storage" {
+		t.Fatalf("Unknown credential store: %s\n", config.CredentialsStore)
+	}
+
+	// Now save it and make sure it shows up in new form
+	configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
+	if !strings.Contains(configStr, `"credsStore":`) ||
+		!strings.Contains(configStr, "crazy-secure-storage") {
+		t.Fatalf("Should have save in new form: %s", configStr)
+	}
+}
+
+func TestJSONWithCredentialHelpers(t *testing.T) {
+	tmpHome, err := ioutil.TempDir("", "config-test")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(tmpHome)
+
+	fn := filepath.Join(tmpHome, ConfigFileName)
+	js := `{
+		"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } },
+		"credHelpers": { "images.io": "images-io", "containers.com": "crazy-secure-storage" }
+}`
+	if err := ioutil.WriteFile(fn, []byte(js), 0600); err != nil {
+		t.Fatal(err)
+	}
+
+	config, err := Load(tmpHome)
+	if err != nil {
+		t.Fatalf("Failed loading on empty json file: %q", err)
+	}
+
+	if config.CredentialHelpers == nil {
+		t.Fatal("config.CredentialHelpers was nil")
+	} else if config.CredentialHelpers["images.io"] != "images-io" ||
+		config.CredentialHelpers["containers.com"] != "crazy-secure-storage" {
+		t.Fatalf("Credential helpers not deserialized properly: %v\n", config.CredentialHelpers)
+	}
+
+	// Now save it and make sure it shows up in new form
+	configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
+	if !strings.Contains(configStr, `"credHelpers":`) ||
+		!strings.Contains(configStr, "images.io") ||
+		!strings.Contains(configStr, "images-io") ||
+		!strings.Contains(configStr, "containers.com") ||
+		!strings.Contains(configStr, "crazy-secure-storage") {
+		t.Fatalf("Should have save in new form: %s", configStr)
+	}
+}
+
 // Save it and make sure it shows up in new form
 func saveConfigAndValidateNewFormat(t *testing.T, config *configfile.ConfigFile, homeFolder string) string {
 	if err := config.Save(); err != nil {
@@ -420,7 +492,7 @@ func TestConfigFile(t *testing.T) {
 	}
 }
 
-func TestJsonReaderNoFile(t *testing.T) {
+func TestJSONReaderNoFile(t *testing.T) {
 	js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } } }`
 
 	config, err := LoadFromReader(strings.NewReader(js))
@@ -435,7 +507,7 @@ func TestJsonReaderNoFile(t *testing.T) {
 
 }
 
-func TestOldJsonReaderNoFile(t *testing.T) {
+func TestOldJSONReaderNoFile(t *testing.T) {
 	js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}`
 
 	config, err := LegacyLoadFromReader(strings.NewReader(js))
@@ -449,7 +521,7 @@ func TestOldJsonReaderNoFile(t *testing.T) {
 	}
 }
 
-func TestJsonWithPsFormatNoFile(t *testing.T) {
+func TestJSONWithPsFormatNoFile(t *testing.T) {
 	js := `{
 		"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } },
 		"psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
@@ -465,7 +537,7 @@ func TestJsonWithPsFormatNoFile(t *testing.T) {
 
 }
 
-func TestJsonSaveWithNoFile(t *testing.T) {
+func TestJSONSaveWithNoFile(t *testing.T) {
 	js := `{
 		"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv" } },
 		"psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
@@ -507,7 +579,7 @@ func TestJsonSaveWithNoFile(t *testing.T) {
 	}
 }
 
-func TestLegacyJsonSaveWithNoFile(t *testing.T) {
+func TestLegacyJSONSaveWithNoFile(t *testing.T) {
 
 	js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}`
 	config, err := LegacyLoadFromReader(strings.NewReader(js))

+ 3 - 1
cliconfig/configfile/file.go

@@ -31,6 +31,7 @@ type ConfigFile struct {
 	StatsFormat          string                      `json:"statsFormat,omitempty"`
 	DetachKeys           string                      `json:"detachKeys,omitempty"`
 	CredentialsStore     string                      `json:"credsStore,omitempty"`
+	CredentialHelpers    map[string]string           `json:"credHelpers,omitempty"`
 	Filename             string                      `json:"-"` // Note: for internal use only
 	ServiceInspectFormat string                      `json:"serviceInspectFormat,omitempty"`
 }
@@ -96,7 +97,8 @@ func (configFile *ConfigFile) LoadFromReader(configData io.Reader) error {
 // in this file or not.
 func (configFile *ConfigFile) ContainsAuth() bool {
 	return configFile.CredentialsStore != "" ||
-		(configFile.AuthConfigs != nil && len(configFile.AuthConfigs) > 0)
+		len(configFile.CredentialHelpers) > 0 ||
+		len(configFile.AuthConfigs) > 0
 }
 
 // SaveToWriter encodes and writes out all the authorization information to

+ 2 - 2
cliconfig/credentials/native_store.go

@@ -22,8 +22,8 @@ type nativeStore struct {
 
 // NewNativeStore creates a new native store that
 // uses a remote helper program to manage credentials.
-func NewNativeStore(file *configfile.ConfigFile) Store {
-	name := remoteCredentialsPrefix + file.CredentialsStore
+func NewNativeStore(file *configfile.ConfigFile, helperSuffix string) Store {
+	name := remoteCredentialsPrefix + helperSuffix
 	return &nativeStore{
 		programFunc: client.NewShellProgramFunc(name),
 		fileStore:   NewFileStore(file),