diff --git a/cli/command/registry/login.go b/cli/command/registry/login.go index b538fb0084..83c1554d55 100644 --- a/cli/command/registry/login.go +++ b/cli/command/registry/login.go @@ -17,6 +17,11 @@ import ( "github.com/spf13/cobra" ) +const unencryptedWarning = `WARNING! Your password will be stored unencrypted in %s. +Configure a credential helper to remove this warning. See +https://docs.docker.com/engine/reference/commandline/login/#credentials-store +` + type loginOptions struct { serverAddress string user string @@ -50,6 +55,29 @@ func NewLoginCommand(dockerCli command.Cli) *cobra.Command { return cmd } +// unencryptedPrompt prompts the user to find out whether they want to continue +// with insecure credential storage. If stdin is not a terminal, we assume they +// want it (sadly), because people may have been scripting insecure logins and +// we don't want to break them. Maybe they'll see the warning in their logs and +// fix things. +func unencryptedPrompt(dockerCli command.Streams, filename string) error { + fmt.Fprintln(dockerCli.Err(), fmt.Sprintf(unencryptedWarning, filename)) + + if dockerCli.In().IsTerminal() { + if command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), "") { + return nil + } + return errors.Errorf("User refused unencrypted credentials storage.") + } + + return nil +} + +type isFileStore interface { + IsFileStore() bool + GetFilename() string +} + func verifyloginOptions(dockerCli command.Cli, opts *loginOptions) error { if opts.password != "" { fmt.Fprintln(dockerCli.Err(), "WARNING! Using --password via the CLI is insecure. Use --password-stdin.") @@ -74,7 +102,7 @@ func verifyloginOptions(dockerCli command.Cli, opts *loginOptions) error { return nil } -func runLogin(dockerCli command.Cli, opts loginOptions) error { +func runLogin(dockerCli command.Cli, opts loginOptions) error { //nolint: gocyclo ctx := context.Background() clnt := dockerCli.Client() if err := verifyloginOptions(dockerCli, &opts); err != nil { @@ -113,7 +141,18 @@ func runLogin(dockerCli command.Cli, opts loginOptions) error { authConfig.Password = "" authConfig.IdentityToken = response.IdentityToken } - if err := dockerCli.ConfigFile().GetCredentialsStore(serverAddress).Store(*authConfig); err != nil { + + creds := dockerCli.ConfigFile().GetCredentialsStore(serverAddress) + + store, isDefault := creds.(isFileStore) + if isDefault { + err = unencryptedPrompt(dockerCli, store.GetFilename()) + if err != nil { + return err + } + } + + if err := creds.Store(*authConfig); err != nil { return errors.Errorf("Error saving credentials: %v", err) } diff --git a/cli/config/configfile/file.go b/cli/config/configfile/file.go index 9338bc4e66..5653306622 100644 --- a/cli/config/configfile/file.go +++ b/cli/config/configfile/file.go @@ -307,3 +307,8 @@ func (configFile *ConfigFile) GetAllCredentials() (map[string]types.AuthConfig, } return auths, nil } + +// GetFilename returns the file name that this config file is based on. +func (configFile *ConfigFile) GetFilename() string { + return configFile.Filename +} diff --git a/cli/config/credentials/file_store.go b/cli/config/credentials/file_store.go index b186dbbed6..6ae681754a 100644 --- a/cli/config/credentials/file_store.go +++ b/cli/config/credentials/file_store.go @@ -8,6 +8,7 @@ import ( type store interface { Save() error GetAuthConfigs() map[string]types.AuthConfig + GetFilename() string } // fileStore implements a credentials store using @@ -53,3 +54,11 @@ func (c *fileStore) Store(authConfig types.AuthConfig) error { c.file.GetAuthConfigs()[authConfig.ServerAddress] = authConfig return c.file.Save() } + +func (c *fileStore) GetFilename() string { + return c.file.GetFilename() +} + +func (c *fileStore) IsFileStore() bool { + return true +} diff --git a/cli/config/credentials/file_store_test.go b/cli/config/credentials/file_store_test.go index b849d62456..984dc1fa69 100644 --- a/cli/config/credentials/file_store_test.go +++ b/cli/config/credentials/file_store_test.go @@ -20,6 +20,10 @@ func (f *fakeStore) GetAuthConfigs() map[string]types.AuthConfig { return f.configs } +func (f *fakeStore) GetFilename() string { + return "/tmp/docker-fakestore" +} + func newStore(auths map[string]types.AuthConfig) store { return &fakeStore{configs: auths} }