auth.go 11 KB

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