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…
Reference in a new issue