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"
|
"runtime"
|
||||||
|
|
||||||
"github.com/docker/docker/api"
|
"github.com/docker/docker/api"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/versions"
|
"github.com/docker/docker/api/types/versions"
|
||||||
cliflags "github.com/docker/docker/cli/flags"
|
cliflags "github.com/docker/docker/cli/flags"
|
||||||
"github.com/docker/docker/cliconfig"
|
"github.com/docker/docker/cliconfig"
|
||||||
|
@ -86,15 +87,55 @@ func (cli *DockerCli) ConfigFile() *configfile.ConfigFile {
|
||||||
return cli.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
|
// CredentialsStore returns a new credentials store based
|
||||||
// on the settings provided in the configuration file.
|
// on the settings provided in the configuration file. Empty string returns
|
||||||
func (cli *DockerCli) CredentialsStore() credentials.Store {
|
// the default credential store.
|
||||||
if cli.configFile.CredentialsStore != "" {
|
func (cli *DockerCli) CredentialsStore(serverAddress string) credentials.Store {
|
||||||
return credentials.NewNativeStore(cli.configFile)
|
if helper := getConfiguredCredentialStore(cli.configFile, serverAddress); helper != "" {
|
||||||
|
return credentials.NewNativeStore(cli.configFile, helper)
|
||||||
}
|
}
|
||||||
return credentials.NewFileStore(cli.configFile)
|
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
|
// Initialize the dockerCli runs initialization that must happen after command
|
||||||
// line flags are parsed.
|
// line flags are parsed.
|
||||||
func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {
|
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{
|
buildOptions := types.ImageBuildOptions{
|
||||||
Memory: memory,
|
Memory: memory,
|
||||||
MemorySwap: memorySwap,
|
MemorySwap: memorySwap,
|
||||||
|
@ -301,7 +301,7 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
|
||||||
ShmSize: shmSize,
|
ShmSize: shmSize,
|
||||||
Ulimits: options.ulimits.GetList(),
|
Ulimits: options.ulimits.GetList(),
|
||||||
BuildArgs: runconfigopts.ConvertKVStringsToMap(options.buildArgs.GetAll()),
|
BuildArgs: runconfigopts.ConvertKVStringsToMap(options.buildArgs.GetAll()),
|
||||||
AuthConfigs: authConfig,
|
AuthConfigs: authConfigs,
|
||||||
Labels: runconfigopts.ConvertKVStringsToMap(options.labels.GetAll()),
|
Labels: runconfigopts.ConvertKVStringsToMap(options.labels.GetAll()),
|
||||||
CacheFrom: options.cacheFrom,
|
CacheFrom: options.cacheFrom,
|
||||||
SecurityOpt: options.securityOpt,
|
SecurityOpt: options.securityOpt,
|
||||||
|
|
|
@ -67,7 +67,7 @@ func ResolveAuthConfig(ctx context.Context, cli *DockerCli, index *registrytypes
|
||||||
configKey = ElectAuthServer(ctx, cli)
|
configKey = ElectAuthServer(ctx, cli)
|
||||||
}
|
}
|
||||||
|
|
||||||
a, _ := cli.CredentialsStore().Get(configKey)
|
a, _ := cli.CredentialsStore(configKey).Get(configKey)
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@ func ConfigureAuth(cli *DockerCli, flUser, flPassword, serverAddress string, isD
|
||||||
serverAddress = registry.ConvertToHostname(serverAddress)
|
serverAddress = registry.ConvertToHostname(serverAddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
authconfig, err := cli.CredentialsStore().Get(serverAddress)
|
authconfig, err := cli.CredentialsStore(serverAddress).Get(serverAddress)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return authconfig, err
|
return authconfig, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,7 +69,7 @@ func runLogin(dockerCli *command.DockerCli, opts loginOptions) error {
|
||||||
authConfig.Password = ""
|
authConfig.Password = ""
|
||||||
authConfig.IdentityToken = response.IdentityToken
|
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)
|
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)
|
fmt.Fprintf(dockerCli.Out(), "Removing login credentials for %s\n", hostnameAddress)
|
||||||
for _, r := range regsToLogout {
|
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)
|
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")
|
tmpHome, err := ioutil.TempDir("", "config-test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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")
|
tmpHome, err := ioutil.TempDir("", "config-test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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")
|
tmpHome, err := ioutil.TempDir("", "config-test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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")
|
tmpHome, err := ioutil.TempDir("", "config-test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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")
|
tmpHome, err := ioutil.TempDir("", "config-test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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")
|
tmpHome, err := ioutil.TempDir("", "config-test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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
|
// Save it and make sure it shows up in new form
|
||||||
func saveConfigAndValidateNewFormat(t *testing.T, config *configfile.ConfigFile, homeFolder string) string {
|
func saveConfigAndValidateNewFormat(t *testing.T, config *configfile.ConfigFile, homeFolder string) string {
|
||||||
if err := config.Save(); err != nil {
|
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" } } }`
|
js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } } }`
|
||||||
|
|
||||||
config, err := LoadFromReader(strings.NewReader(js))
|
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"}}`
|
js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}`
|
||||||
|
|
||||||
config, err := LegacyLoadFromReader(strings.NewReader(js))
|
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 := `{
|
js := `{
|
||||||
"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } },
|
"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } },
|
||||||
"psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
|
"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 := `{
|
js := `{
|
||||||
"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv" } },
|
"auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv" } },
|
||||||
"psFormat": "table {{.ID}}\\t{{.Label \"com.docker.label.cpu\"}}"
|
"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"}}`
|
js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}`
|
||||||
config, err := LegacyLoadFromReader(strings.NewReader(js))
|
config, err := LegacyLoadFromReader(strings.NewReader(js))
|
||||||
|
|
|
@ -31,6 +31,7 @@ type ConfigFile struct {
|
||||||
StatsFormat string `json:"statsFormat,omitempty"`
|
StatsFormat string `json:"statsFormat,omitempty"`
|
||||||
DetachKeys string `json:"detachKeys,omitempty"`
|
DetachKeys string `json:"detachKeys,omitempty"`
|
||||||
CredentialsStore string `json:"credsStore,omitempty"`
|
CredentialsStore string `json:"credsStore,omitempty"`
|
||||||
|
CredentialHelpers map[string]string `json:"credHelpers,omitempty"`
|
||||||
Filename string `json:"-"` // Note: for internal use only
|
Filename string `json:"-"` // Note: for internal use only
|
||||||
ServiceInspectFormat string `json:"serviceInspectFormat,omitempty"`
|
ServiceInspectFormat string `json:"serviceInspectFormat,omitempty"`
|
||||||
}
|
}
|
||||||
|
@ -96,7 +97,8 @@ func (configFile *ConfigFile) LoadFromReader(configData io.Reader) error {
|
||||||
// in this file or not.
|
// in this file or not.
|
||||||
func (configFile *ConfigFile) ContainsAuth() bool {
|
func (configFile *ConfigFile) ContainsAuth() bool {
|
||||||
return configFile.CredentialsStore != "" ||
|
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
|
// SaveToWriter encodes and writes out all the authorization information to
|
||||||
|
|
|
@ -22,8 +22,8 @@ type nativeStore struct {
|
||||||
|
|
||||||
// NewNativeStore creates a new native store that
|
// NewNativeStore creates a new native store that
|
||||||
// uses a remote helper program to manage credentials.
|
// uses a remote helper program to manage credentials.
|
||||||
func NewNativeStore(file *configfile.ConfigFile) Store {
|
func NewNativeStore(file *configfile.ConfigFile, helperSuffix string) Store {
|
||||||
name := remoteCredentialsPrefix + file.CredentialsStore
|
name := remoteCredentialsPrefix + helperSuffix
|
||||||
return &nativeStore{
|
return &nativeStore{
|
||||||
programFunc: client.NewShellProgramFunc(name),
|
programFunc: client.NewShellProgramFunc(name),
|
||||||
fileStore: NewFileStore(file),
|
fileStore: NewFileStore(file),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue