mirror of
				https://github.com/moby/moby.git
				synced 2022-11-09 12:21:53 -05:00 
			
		
		
		
	Add registry-specific credential helper support
Signed-off-by: Jake Sanders <jsand@google.com>
This commit is contained in:
		
							parent
							
								
									0a5cb187b4
								
							
						
					
					
						commit
						07c4b4124b
					
				
					 8 changed files with 139 additions and 24 deletions
				
			
		| 
						 | 
				
			
			@ -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 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue