mirror of
https://github.com/docker/cli.git
synced 2026-09-25 17:02:07 -04:00
Client credentials store.
This change implements communication with an external credentials store, ala git-credential-helper. The client falls back the plain text store, what we're currently using, if there is no remote store configured. It shells out to helper program when a credential store is configured. Those programs can be implemented with any language as long as they follow the convention to pass arguments and information. There is an implementation for the OS X keychain in https://github.com/calavera/docker-credential-helpers. That package also provides basic structure to create other helpers. Signed-off-by: David Calavera <david.calavera@gmail.com> Upstream-commit: cf721c23e715e545eccf8484e145c2d18d6a6a23 Component: engine
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/docker/docker/api"
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/cliconfig"
|
||||
"github.com/docker/docker/cliconfig/credentials"
|
||||
"github.com/docker/docker/dockerversion"
|
||||
"github.com/docker/docker/opts"
|
||||
"github.com/docker/docker/pkg/term"
|
||||
@@ -125,6 +126,9 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, clientFlags *cli.ClientF
|
||||
if e != nil {
|
||||
fmt.Fprintf(cli.err, "WARNING: Error loading config file:%v\n", e)
|
||||
}
|
||||
if !configFile.ContainsAuth() {
|
||||
credentials.DetectDefaultStore(configFile)
|
||||
}
|
||||
cli.configFile = configFile
|
||||
|
||||
host, err := getServerHost(clientFlags.Common.Hosts, clientFlags.Common.TLSOptions)
|
||||
|
||||
@@ -42,7 +42,7 @@ func (cli *DockerCli) pullImageCustomOut(image string, out io.Writer) error {
|
||||
return err
|
||||
}
|
||||
|
||||
authConfig := cli.resolveAuthConfig(cli.configFile.AuthConfigs, repoInfo.Index)
|
||||
authConfig := cli.resolveAuthConfig(repoInfo.Index)
|
||||
encodedAuth, err := encodeAuthToBase64(authConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"strings"
|
||||
|
||||
Cli "github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/cliconfig"
|
||||
"github.com/docker/docker/cliconfig/credentials"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
"github.com/docker/docker/pkg/term"
|
||||
"github.com/docker/engine-api/client"
|
||||
@@ -50,18 +52,16 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
|
||||
response, err := cli.client.RegistryLogin(authConfig)
|
||||
if err != nil {
|
||||
if client.IsErrUnauthorized(err) {
|
||||
delete(cli.configFile.AuthConfigs, serverAddress)
|
||||
if err2 := cli.configFile.Save(); err2 != nil {
|
||||
fmt.Fprintf(cli.out, "WARNING: could not save config file: %v\n", err2)
|
||||
if err2 := eraseCredentials(cli.configFile, authConfig.ServerAddress); err2 != nil {
|
||||
fmt.Fprintf(cli.out, "WARNING: could not save credentials: %v\n", err2)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if err := cli.configFile.Save(); err != nil {
|
||||
return fmt.Errorf("Error saving config file: %v", err)
|
||||
if err := storeCredentials(cli.configFile, authConfig); err != nil {
|
||||
return fmt.Errorf("Error saving credentials: %v", err)
|
||||
}
|
||||
fmt.Fprintf(cli.out, "WARNING: login credentials saved in %s\n", cli.configFile.Filename())
|
||||
|
||||
if response.Status != "" {
|
||||
fmt.Fprintf(cli.out, "%s\n", response.Status)
|
||||
@@ -78,10 +78,11 @@ func (cli *DockerCli) promptWithDefault(prompt string, configDefault string) {
|
||||
}
|
||||
|
||||
func (cli *DockerCli) configureAuth(flUser, flPassword, flEmail, serverAddress string) (types.AuthConfig, error) {
|
||||
authconfig, ok := cli.configFile.AuthConfigs[serverAddress]
|
||||
if !ok {
|
||||
authconfig = types.AuthConfig{}
|
||||
authconfig, err := getCredentials(cli.configFile, serverAddress)
|
||||
if err != nil {
|
||||
return authconfig, err
|
||||
}
|
||||
|
||||
authconfig.Username = strings.TrimSpace(authconfig.Username)
|
||||
|
||||
if flUser = strings.TrimSpace(flUser); flUser == "" {
|
||||
@@ -133,11 +134,12 @@ func (cli *DockerCli) configureAuth(flUser, flPassword, flEmail, serverAddress s
|
||||
flEmail = authconfig.Email
|
||||
}
|
||||
}
|
||||
|
||||
authconfig.Username = flUser
|
||||
authconfig.Password = flPassword
|
||||
authconfig.Email = flEmail
|
||||
authconfig.ServerAddress = serverAddress
|
||||
cli.configFile.AuthConfigs[serverAddress] = authconfig
|
||||
|
||||
return authconfig, nil
|
||||
}
|
||||
|
||||
@@ -150,3 +152,33 @@ func readInput(in io.Reader, out io.Writer) string {
|
||||
}
|
||||
return string(line)
|
||||
}
|
||||
|
||||
// getCredentials loads the user credentials from a credentials store.
|
||||
// The store is determined by the config file settings.
|
||||
func getCredentials(c *cliconfig.ConfigFile, serverAddress string) (types.AuthConfig, error) {
|
||||
s := loadCredentialsStore(c)
|
||||
return s.Get(serverAddress)
|
||||
}
|
||||
|
||||
// storeCredentials saves the user credentials in a credentials store.
|
||||
// The store is determined by the config file settings.
|
||||
func storeCredentials(c *cliconfig.ConfigFile, auth types.AuthConfig) error {
|
||||
s := loadCredentialsStore(c)
|
||||
return s.Store(auth)
|
||||
}
|
||||
|
||||
// eraseCredentials removes the user credentials from a credentials store.
|
||||
// The store is determined by the config file settings.
|
||||
func eraseCredentials(c *cliconfig.ConfigFile, serverAddress string) error {
|
||||
s := loadCredentialsStore(c)
|
||||
return s.Erase(serverAddress)
|
||||
}
|
||||
|
||||
// loadCredentialsStore initializes a new credentials store based
|
||||
// in the settings provided in the configuration file.
|
||||
func loadCredentialsStore(c *cliconfig.ConfigFile) credentials.Store {
|
||||
if c.CredentialsStore != "" {
|
||||
return credentials.NewNativeStore(c)
|
||||
}
|
||||
return credentials.NewFileStore(c)
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ func (cli *DockerCli) CmdPull(args ...string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
authConfig := cli.resolveAuthConfig(cli.configFile.AuthConfigs, repoInfo.Index)
|
||||
authConfig := cli.resolveAuthConfig(repoInfo.Index)
|
||||
requestPrivilege := cli.registryAuthenticationPrivilegedFunc(repoInfo.Index, "pull")
|
||||
|
||||
if isTrusted() && !ref.HasDigest() {
|
||||
|
||||
@@ -44,7 +44,7 @@ func (cli *DockerCli) CmdPush(args ...string) error {
|
||||
return err
|
||||
}
|
||||
// Resolve the Auth config relevant for this server
|
||||
authConfig := cli.resolveAuthConfig(cli.configFile.AuthConfigs, repoInfo.Index)
|
||||
authConfig := cli.resolveAuthConfig(repoInfo.Index)
|
||||
|
||||
requestPrivilege := cli.registryAuthenticationPrivilegedFunc(repoInfo.Index, "push")
|
||||
if isTrusted() {
|
||||
|
||||
@@ -36,7 +36,7 @@ func (cli *DockerCli) CmdSearch(args ...string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
authConfig := cli.resolveAuthConfig(cli.configFile.AuthConfigs, indexInfo)
|
||||
authConfig := cli.resolveAuthConfig(indexInfo)
|
||||
requestPrivilege := cli.registryAuthenticationPrivilegedFunc(indexInfo, "search")
|
||||
|
||||
encodedAuth, err := encodeAuthToBase64(authConfig)
|
||||
|
||||
@@ -235,7 +235,7 @@ func (cli *DockerCli) trustedReference(ref reference.NamedTagged) (reference.Can
|
||||
}
|
||||
|
||||
// Resolve the Auth config relevant for this server
|
||||
authConfig := cli.resolveAuthConfig(cli.configFile.AuthConfigs, repoInfo.Index)
|
||||
authConfig := cli.resolveAuthConfig(repoInfo.Index)
|
||||
|
||||
notaryRepo, err := cli.getNotaryRepository(repoInfo, authConfig)
|
||||
if err != nil {
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
gosignal "os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
@@ -185,38 +184,12 @@ func copyToFile(outfile string, r io.Reader) error {
|
||||
// resolveAuthConfig is like registry.ResolveAuthConfig, but if using the
|
||||
// default index, it uses the default index name for the daemon's platform,
|
||||
// not the client's platform.
|
||||
func (cli *DockerCli) resolveAuthConfig(authConfigs map[string]types.AuthConfig, index *registrytypes.IndexInfo) types.AuthConfig {
|
||||
func (cli *DockerCli) resolveAuthConfig(index *registrytypes.IndexInfo) types.AuthConfig {
|
||||
configKey := index.Name
|
||||
if index.Official {
|
||||
configKey = cli.electAuthServer()
|
||||
}
|
||||
|
||||
// First try the happy case
|
||||
if c, found := authConfigs[configKey]; found || index.Official {
|
||||
return c
|
||||
}
|
||||
|
||||
convertToHostname := func(url string) string {
|
||||
stripped := url
|
||||
if strings.HasPrefix(url, "http://") {
|
||||
stripped = strings.Replace(url, "http://", "", 1)
|
||||
} else if strings.HasPrefix(url, "https://") {
|
||||
stripped = strings.Replace(url, "https://", "", 1)
|
||||
}
|
||||
|
||||
nameParts := strings.SplitN(stripped, "/", 2)
|
||||
|
||||
return nameParts[0]
|
||||
}
|
||||
|
||||
// Maybe they have a legacy config file, we will iterate the keys converting
|
||||
// them to the new format and testing
|
||||
for registry, ac := range authConfigs {
|
||||
if configKey == convertToHostname(registry) {
|
||||
return ac
|
||||
}
|
||||
}
|
||||
|
||||
// When all else fails, return an empty auth config
|
||||
return types.AuthConfig{}
|
||||
a, _ := getCredentials(cli.configFile, configKey)
|
||||
return a
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user