registry.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. package command
  2. import (
  3. "bufio"
  4. "encoding/base64"
  5. "encoding/json"
  6. "fmt"
  7. "io"
  8. "os"
  9. "runtime"
  10. "strings"
  11. "golang.org/x/net/context"
  12. "github.com/docker/docker/api/types"
  13. registrytypes "github.com/docker/docker/api/types/registry"
  14. "github.com/docker/docker/pkg/term"
  15. "github.com/docker/docker/reference"
  16. "github.com/docker/docker/registry"
  17. )
  18. // ElectAuthServer returns the default registry to use (by asking the daemon)
  19. func ElectAuthServer(ctx context.Context, cli *DockerCli) string {
  20. // The daemon `/info` endpoint informs us of the default registry being
  21. // used. This is essential in cross-platforms environment, where for
  22. // example a Linux client might be interacting with a Windows daemon, hence
  23. // the default registry URL might be Windows specific.
  24. serverAddress := registry.IndexServer
  25. if info, err := cli.Client().Info(ctx); err != nil {
  26. fmt.Fprintf(cli.Out(), "Warning: failed to get default registry endpoint from daemon (%v). Using system default: %s\n", err, serverAddress)
  27. } else {
  28. serverAddress = info.IndexServerAddress
  29. }
  30. return serverAddress
  31. }
  32. // EncodeAuthToBase64 serializes the auth configuration as JSON base64 payload
  33. func EncodeAuthToBase64(authConfig types.AuthConfig) (string, error) {
  34. buf, err := json.Marshal(authConfig)
  35. if err != nil {
  36. return "", err
  37. }
  38. return base64.URLEncoding.EncodeToString(buf), nil
  39. }
  40. // RegistryAuthenticationPrivilegedFunc returns a RequestPrivilegeFunc from the specified registry index info
  41. // for the given command.
  42. func RegistryAuthenticationPrivilegedFunc(cli *DockerCli, index *registrytypes.IndexInfo, cmdName string) types.RequestPrivilegeFunc {
  43. return func() (string, error) {
  44. fmt.Fprintf(cli.Out(), "\nPlease login prior to %s:\n", cmdName)
  45. indexServer := registry.GetAuthConfigKey(index)
  46. isDefaultRegistry := indexServer == ElectAuthServer(context.Background(), cli)
  47. authConfig, err := ConfigureAuth(cli, "", "", indexServer, isDefaultRegistry)
  48. if err != nil {
  49. return "", err
  50. }
  51. return EncodeAuthToBase64(authConfig)
  52. }
  53. }
  54. // ResolveAuthConfig is like registry.ResolveAuthConfig, but if using the
  55. // default index, it uses the default index name for the daemon's platform,
  56. // not the client's platform.
  57. func ResolveAuthConfig(ctx context.Context, cli *DockerCli, index *registrytypes.IndexInfo) types.AuthConfig {
  58. configKey := index.Name
  59. if index.Official {
  60. configKey = ElectAuthServer(ctx, cli)
  61. }
  62. a, _ := cli.CredentialsStore(configKey).Get(configKey)
  63. return a
  64. }
  65. // ConfigureAuth returns an AuthConfig from the specified user, password and server.
  66. func ConfigureAuth(cli *DockerCli, flUser, flPassword, serverAddress string, isDefaultRegistry bool) (types.AuthConfig, error) {
  67. // On Windows, force the use of the regular OS stdin stream. Fixes #14336/#14210
  68. if runtime.GOOS == "windows" {
  69. cli.in = NewInStream(os.Stdin)
  70. }
  71. if !isDefaultRegistry {
  72. serverAddress = registry.ConvertToHostname(serverAddress)
  73. }
  74. authconfig, err := cli.CredentialsStore(serverAddress).Get(serverAddress)
  75. if err != nil {
  76. return authconfig, err
  77. }
  78. // Some links documenting this:
  79. // - https://code.google.com/archive/p/mintty/issues/56
  80. // - https://github.com/docker/docker/issues/15272
  81. // - https://mintty.github.io/ (compatibility)
  82. // Linux will hit this if you attempt `cat | docker login`, and Windows
  83. // will hit this if you attempt docker login from mintty where stdin
  84. // is a pipe, not a character based console.
  85. if flPassword == "" && !cli.In().IsTerminal() {
  86. return authconfig, fmt.Errorf("Error: Cannot perform an interactive login from a non TTY device")
  87. }
  88. authconfig.Username = strings.TrimSpace(authconfig.Username)
  89. if flUser = strings.TrimSpace(flUser); flUser == "" {
  90. if isDefaultRegistry {
  91. // if this is a default registry (docker hub), then display the following message.
  92. fmt.Fprintln(cli.Out(), "Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.")
  93. }
  94. promptWithDefault(cli.Out(), "Username", authconfig.Username)
  95. flUser = readInput(cli.In(), cli.Out())
  96. flUser = strings.TrimSpace(flUser)
  97. if flUser == "" {
  98. flUser = authconfig.Username
  99. }
  100. }
  101. if flUser == "" {
  102. return authconfig, fmt.Errorf("Error: Non-null Username Required")
  103. }
  104. if flPassword == "" {
  105. oldState, err := term.SaveState(cli.In().FD())
  106. if err != nil {
  107. return authconfig, err
  108. }
  109. fmt.Fprintf(cli.Out(), "Password: ")
  110. term.DisableEcho(cli.In().FD(), oldState)
  111. flPassword = readInput(cli.In(), cli.Out())
  112. fmt.Fprint(cli.Out(), "\n")
  113. term.RestoreTerminal(cli.In().FD(), oldState)
  114. if flPassword == "" {
  115. return authconfig, fmt.Errorf("Error: Password Required")
  116. }
  117. }
  118. authconfig.Username = flUser
  119. authconfig.Password = flPassword
  120. authconfig.ServerAddress = serverAddress
  121. authconfig.IdentityToken = ""
  122. return authconfig, nil
  123. }
  124. func readInput(in io.Reader, out io.Writer) string {
  125. reader := bufio.NewReader(in)
  126. line, _, err := reader.ReadLine()
  127. if err != nil {
  128. fmt.Fprintln(out, err.Error())
  129. os.Exit(1)
  130. }
  131. return string(line)
  132. }
  133. func promptWithDefault(out io.Writer, prompt string, configDefault string) {
  134. if configDefault == "" {
  135. fmt.Fprintf(out, "%s: ", prompt)
  136. } else {
  137. fmt.Fprintf(out, "%s (%s): ", prompt, configDefault)
  138. }
  139. }
  140. // RetrieveAuthTokenFromImage retrieves an encoded auth token given a complete image
  141. func RetrieveAuthTokenFromImage(ctx context.Context, cli *DockerCli, image string) (string, error) {
  142. // Retrieve encoded auth token from the image reference
  143. authConfig, err := resolveAuthConfigFromImage(ctx, cli, image)
  144. if err != nil {
  145. return "", err
  146. }
  147. encodedAuth, err := EncodeAuthToBase64(authConfig)
  148. if err != nil {
  149. return "", err
  150. }
  151. return encodedAuth, nil
  152. }
  153. // resolveAuthConfigFromImage retrieves that AuthConfig using the image string
  154. func resolveAuthConfigFromImage(ctx context.Context, cli *DockerCli, image string) (types.AuthConfig, error) {
  155. registryRef, err := reference.ParseNamed(image)
  156. if err != nil {
  157. return types.AuthConfig{}, err
  158. }
  159. repoInfo, err := registry.ParseRepositoryInfo(registryRef)
  160. if err != nil {
  161. return types.AuthConfig{}, err
  162. }
  163. return ResolveAuthConfig(ctx, cli, repoInfo.Index), nil
  164. }