auth.go 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. package registry
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "io/ioutil"
  6. "net/http"
  7. "strings"
  8. "github.com/Sirupsen/logrus"
  9. "github.com/docker/docker/cliconfig"
  10. )
  11. // Login tries to register/login to the registry server.
  12. func Login(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint) (string, error) {
  13. // Separates the v2 registry login logic from the v1 logic.
  14. if registryEndpoint.Version == APIVersion2 {
  15. return loginV2(authConfig, registryEndpoint, "" /* scope */)
  16. }
  17. return loginV1(authConfig, registryEndpoint)
  18. }
  19. // loginV1 tries to register/login to the v1 registry server.
  20. func loginV1(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint) (string, error) {
  21. var (
  22. status string
  23. reqBody []byte
  24. err error
  25. reqStatusCode = 0
  26. serverAddress = authConfig.ServerAddress
  27. )
  28. logrus.Debugf("attempting v1 login to registry endpoint %s", registryEndpoint)
  29. if serverAddress == "" {
  30. return "", fmt.Errorf("Server Error: Server Address not set.")
  31. }
  32. loginAgainstOfficialIndex := serverAddress == IndexServer
  33. // to avoid sending the server address to the server it should be removed before being marshalled
  34. authCopy := *authConfig
  35. authCopy.ServerAddress = ""
  36. jsonBody, err := json.Marshal(authCopy)
  37. if err != nil {
  38. return "", fmt.Errorf("Config Error: %s", err)
  39. }
  40. // using `bytes.NewReader(jsonBody)` here causes the server to respond with a 411 status.
  41. b := strings.NewReader(string(jsonBody))
  42. req1, err := registryEndpoint.client.Post(serverAddress+"users/", "application/json; charset=utf-8", b)
  43. if err != nil {
  44. return "", fmt.Errorf("Server Error: %s", err)
  45. }
  46. defer req1.Body.Close()
  47. reqStatusCode = req1.StatusCode
  48. reqBody, err = ioutil.ReadAll(req1.Body)
  49. if err != nil {
  50. return "", fmt.Errorf("Server Error: [%#v] %s", reqStatusCode, err)
  51. }
  52. if reqStatusCode == 201 {
  53. if loginAgainstOfficialIndex {
  54. status = "Account created. Please use the confirmation link we sent" +
  55. " to your e-mail to activate it."
  56. } else {
  57. // *TODO: Use registry configuration to determine what this says, if anything?
  58. status = "Account created. Please see the documentation of the registry " + serverAddress + " for instructions how to activate it."
  59. }
  60. } else if reqStatusCode == 400 {
  61. if string(reqBody) == "\"Username or email already exists\"" {
  62. req, err := http.NewRequest("GET", serverAddress+"users/", nil)
  63. req.SetBasicAuth(authConfig.Username, authConfig.Password)
  64. resp, err := registryEndpoint.client.Do(req)
  65. if err != nil {
  66. return "", err
  67. }
  68. defer resp.Body.Close()
  69. body, err := ioutil.ReadAll(resp.Body)
  70. if err != nil {
  71. return "", err
  72. }
  73. if resp.StatusCode == 200 {
  74. return "Login Succeeded", nil
  75. } else if resp.StatusCode == 401 {
  76. return "", fmt.Errorf("Wrong login/password, please try again")
  77. } else if resp.StatusCode == 403 {
  78. if loginAgainstOfficialIndex {
  79. return "", fmt.Errorf("Login: Account is not Active. Please check your e-mail for a confirmation link.")
  80. }
  81. // *TODO: Use registry configuration to determine what this says, if anything?
  82. return "", fmt.Errorf("Login: Account is not Active. Please see the documentation of the registry %s for instructions how to activate it.", serverAddress)
  83. } else if resp.StatusCode == 500 { // Issue #14326
  84. logrus.Errorf("%s returned status code %d. Response Body :\n%s", req.URL.String(), resp.StatusCode, body)
  85. return "", fmt.Errorf("Internal Server Error")
  86. }
  87. return "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body, resp.StatusCode, resp.Header)
  88. }
  89. return "", fmt.Errorf("Registration: %s", reqBody)
  90. } else if reqStatusCode == 401 {
  91. // This case would happen with private registries where /v1/users is
  92. // protected, so people can use `docker login` as an auth check.
  93. req, err := http.NewRequest("GET", serverAddress+"users/", nil)
  94. req.SetBasicAuth(authConfig.Username, authConfig.Password)
  95. resp, err := registryEndpoint.client.Do(req)
  96. if err != nil {
  97. return "", err
  98. }
  99. defer resp.Body.Close()
  100. body, err := ioutil.ReadAll(resp.Body)
  101. if err != nil {
  102. return "", err
  103. }
  104. if resp.StatusCode == 200 {
  105. return "Login Succeeded", nil
  106. } else if resp.StatusCode == 401 {
  107. return "", fmt.Errorf("Wrong login/password, please try again")
  108. } else {
  109. return "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body,
  110. resp.StatusCode, resp.Header)
  111. }
  112. } else {
  113. return "", fmt.Errorf("Unexpected status code [%d] : %s", reqStatusCode, reqBody)
  114. }
  115. return status, nil
  116. }
  117. // loginV2 tries to login to the v2 registry server. The given registry endpoint has been
  118. // pinged or setup with a list of authorization challenges. Each of these challenges are
  119. // tried until one of them succeeds. Currently supported challenge schemes are:
  120. // HTTP Basic Authorization
  121. // Token Authorization with a separate token issuing server
  122. // NOTE: the v2 logic does not attempt to create a user account if one doesn't exist. For
  123. // now, users should create their account through other means like directly from a web page
  124. // served by the v2 registry service provider. Whether this will be supported in the future
  125. // is to be determined.
  126. func loginV2(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint, scope string) (string, error) {
  127. logrus.Debugf("attempting v2 login to registry endpoint %s", registryEndpoint)
  128. var (
  129. err error
  130. allErrors []error
  131. )
  132. for _, challenge := range registryEndpoint.AuthChallenges {
  133. params := make(map[string]string, len(challenge.Parameters)+1)
  134. for k, v := range challenge.Parameters {
  135. params[k] = v
  136. }
  137. params["scope"] = scope
  138. logrus.Debugf("trying %q auth challenge with params %v", challenge.Scheme, params)
  139. switch strings.ToLower(challenge.Scheme) {
  140. case "basic":
  141. err = tryV2BasicAuthLogin(authConfig, params, registryEndpoint)
  142. case "bearer":
  143. err = tryV2TokenAuthLogin(authConfig, params, registryEndpoint)
  144. default:
  145. // Unsupported challenge types are explicitly skipped.
  146. err = fmt.Errorf("unsupported auth scheme: %q", challenge.Scheme)
  147. }
  148. if err == nil {
  149. return "Login Succeeded", nil
  150. }
  151. logrus.Debugf("error trying auth challenge %q: %s", challenge.Scheme, err)
  152. allErrors = append(allErrors, err)
  153. }
  154. return "", fmt.Errorf("no successful auth challenge for %s - errors: %s", registryEndpoint, allErrors)
  155. }
  156. func tryV2BasicAuthLogin(authConfig *cliconfig.AuthConfig, params map[string]string, registryEndpoint *Endpoint) error {
  157. req, err := http.NewRequest("GET", registryEndpoint.Path(""), nil)
  158. if err != nil {
  159. return err
  160. }
  161. req.SetBasicAuth(authConfig.Username, authConfig.Password)
  162. resp, err := registryEndpoint.client.Do(req)
  163. if err != nil {
  164. return err
  165. }
  166. defer resp.Body.Close()
  167. if resp.StatusCode != http.StatusOK {
  168. return fmt.Errorf("basic auth attempt to %s realm %q failed with status: %d %s", registryEndpoint, params["realm"], resp.StatusCode, http.StatusText(resp.StatusCode))
  169. }
  170. return nil
  171. }
  172. func tryV2TokenAuthLogin(authConfig *cliconfig.AuthConfig, params map[string]string, registryEndpoint *Endpoint) error {
  173. token, err := getToken(authConfig.Username, authConfig.Password, params, registryEndpoint)
  174. if err != nil {
  175. return err
  176. }
  177. req, err := http.NewRequest("GET", registryEndpoint.Path(""), nil)
  178. if err != nil {
  179. return err
  180. }
  181. req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
  182. resp, err := registryEndpoint.client.Do(req)
  183. if err != nil {
  184. return err
  185. }
  186. defer resp.Body.Close()
  187. if resp.StatusCode != http.StatusOK {
  188. return fmt.Errorf("token auth attempt to %s realm %q failed with status: %d %s", registryEndpoint, params["realm"], resp.StatusCode, http.StatusText(resp.StatusCode))
  189. }
  190. return nil
  191. }
  192. // ResolveAuthConfig matches an auth configuration to a server address or a URL
  193. func ResolveAuthConfig(config *cliconfig.ConfigFile, index *IndexInfo) cliconfig.AuthConfig {
  194. configKey := index.GetAuthConfigKey()
  195. // First try the happy case
  196. if c, found := config.AuthConfigs[configKey]; found || index.Official {
  197. return c
  198. }
  199. convertToHostname := func(url string) string {
  200. stripped := url
  201. if strings.HasPrefix(url, "http://") {
  202. stripped = strings.Replace(url, "http://", "", 1)
  203. } else if strings.HasPrefix(url, "https://") {
  204. stripped = strings.Replace(url, "https://", "", 1)
  205. }
  206. nameParts := strings.SplitN(stripped, "/", 2)
  207. return nameParts[0]
  208. }
  209. // Maybe they have a legacy config file, we will iterate the keys converting
  210. // them to the new format and testing
  211. for registry, ac := range config.AuthConfigs {
  212. if configKey == convertToHostname(registry) {
  213. return ac
  214. }
  215. }
  216. // When all else fails, return an empty auth config
  217. return cliconfig.AuthConfig{}
  218. }