auth.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. package registry // import "github.com/docker/docker/registry"
  2. import (
  3. "net/http"
  4. "net/url"
  5. "strings"
  6. "time"
  7. "github.com/docker/distribution/registry/client/auth"
  8. "github.com/docker/distribution/registry/client/auth/challenge"
  9. "github.com/docker/distribution/registry/client/transport"
  10. "github.com/docker/docker/api/types/registry"
  11. "github.com/pkg/errors"
  12. "github.com/sirupsen/logrus"
  13. )
  14. // AuthClientID is used the ClientID used for the token server
  15. const AuthClientID = "docker"
  16. type loginCredentialStore struct {
  17. authConfig *registry.AuthConfig
  18. }
  19. func (lcs loginCredentialStore) Basic(*url.URL) (string, string) {
  20. return lcs.authConfig.Username, lcs.authConfig.Password
  21. }
  22. func (lcs loginCredentialStore) RefreshToken(*url.URL, string) string {
  23. return lcs.authConfig.IdentityToken
  24. }
  25. func (lcs loginCredentialStore) SetRefreshToken(u *url.URL, service, token string) {
  26. lcs.authConfig.IdentityToken = token
  27. }
  28. type staticCredentialStore struct {
  29. auth *registry.AuthConfig
  30. }
  31. // NewStaticCredentialStore returns a credential store
  32. // which always returns the same credential values.
  33. func NewStaticCredentialStore(auth *registry.AuthConfig) auth.CredentialStore {
  34. return staticCredentialStore{
  35. auth: auth,
  36. }
  37. }
  38. func (scs staticCredentialStore) Basic(*url.URL) (string, string) {
  39. if scs.auth == nil {
  40. return "", ""
  41. }
  42. return scs.auth.Username, scs.auth.Password
  43. }
  44. func (scs staticCredentialStore) RefreshToken(*url.URL, string) string {
  45. if scs.auth == nil {
  46. return ""
  47. }
  48. return scs.auth.IdentityToken
  49. }
  50. func (scs staticCredentialStore) SetRefreshToken(*url.URL, string, string) {
  51. }
  52. // loginV2 tries to login to the v2 registry server. The given registry
  53. // endpoint will be pinged to get authorization challenges. These challenges
  54. // will be used to authenticate against the registry to validate credentials.
  55. func loginV2(authConfig *registry.AuthConfig, endpoint APIEndpoint, userAgent string) (string, string, error) {
  56. var (
  57. endpointStr = strings.TrimRight(endpoint.URL.String(), "/") + "/v2/"
  58. modifiers = Headers(userAgent, nil)
  59. authTransport = transport.NewTransport(newTransport(endpoint.TLSConfig), modifiers...)
  60. credentialAuthConfig = *authConfig
  61. creds = loginCredentialStore{authConfig: &credentialAuthConfig}
  62. )
  63. logrus.Debugf("attempting v2 login to registry endpoint %s", endpointStr)
  64. loginClient, err := v2AuthHTTPClient(endpoint.URL, authTransport, modifiers, creds, nil)
  65. if err != nil {
  66. return "", "", err
  67. }
  68. req, err := http.NewRequest(http.MethodGet, endpointStr, nil)
  69. if err != nil {
  70. return "", "", err
  71. }
  72. resp, err := loginClient.Do(req)
  73. if err != nil {
  74. err = translateV2AuthError(err)
  75. return "", "", err
  76. }
  77. defer resp.Body.Close()
  78. if resp.StatusCode == http.StatusOK {
  79. return "Login Succeeded", credentialAuthConfig.IdentityToken, nil
  80. }
  81. // TODO(dmcgowan): Attempt to further interpret result, status code and error code string
  82. return "", "", errors.Errorf("login attempt to %s failed with status: %d %s", endpointStr, resp.StatusCode, http.StatusText(resp.StatusCode))
  83. }
  84. func v2AuthHTTPClient(endpoint *url.URL, authTransport http.RoundTripper, modifiers []transport.RequestModifier, creds auth.CredentialStore, scopes []auth.Scope) (*http.Client, error) {
  85. challengeManager, err := PingV2Registry(endpoint, authTransport)
  86. if err != nil {
  87. return nil, err
  88. }
  89. tokenHandlerOptions := auth.TokenHandlerOptions{
  90. Transport: authTransport,
  91. Credentials: creds,
  92. OfflineAccess: true,
  93. ClientID: AuthClientID,
  94. Scopes: scopes,
  95. }
  96. tokenHandler := auth.NewTokenHandlerWithOptions(tokenHandlerOptions)
  97. basicHandler := auth.NewBasicHandler(creds)
  98. modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler))
  99. return &http.Client{
  100. Transport: transport.NewTransport(authTransport, modifiers...),
  101. Timeout: 15 * time.Second,
  102. }, nil
  103. }
  104. // ConvertToHostname converts a registry url which has http|https prepended
  105. // to just an hostname.
  106. func ConvertToHostname(url string) string {
  107. stripped := url
  108. if strings.HasPrefix(url, "http://") {
  109. stripped = strings.TrimPrefix(url, "http://")
  110. } else if strings.HasPrefix(url, "https://") {
  111. stripped = strings.TrimPrefix(url, "https://")
  112. }
  113. return strings.SplitN(stripped, "/", 2)[0]
  114. }
  115. // ResolveAuthConfig matches an auth configuration to a server address or a URL
  116. func ResolveAuthConfig(authConfigs map[string]registry.AuthConfig, index *registry.IndexInfo) registry.AuthConfig {
  117. configKey := GetAuthConfigKey(index)
  118. // First try the happy case
  119. if c, found := authConfigs[configKey]; found || index.Official {
  120. return c
  121. }
  122. // Maybe they have a legacy config file, we will iterate the keys converting
  123. // them to the new format and testing
  124. for registry, ac := range authConfigs {
  125. if configKey == ConvertToHostname(registry) {
  126. return ac
  127. }
  128. }
  129. // When all else fails, return an empty auth config
  130. return registry.AuthConfig{}
  131. }
  132. // PingResponseError is used when the response from a ping
  133. // was received but invalid.
  134. type PingResponseError struct {
  135. Err error
  136. }
  137. func (err PingResponseError) Error() string {
  138. return err.Err.Error()
  139. }
  140. // PingV2Registry attempts to ping a v2 registry and on success return a
  141. // challenge manager for the supported authentication types.
  142. // If a response is received but cannot be interpreted, a PingResponseError will be returned.
  143. func PingV2Registry(endpoint *url.URL, transport http.RoundTripper) (challenge.Manager, error) {
  144. pingClient := &http.Client{
  145. Transport: transport,
  146. Timeout: 15 * time.Second,
  147. }
  148. endpointStr := strings.TrimRight(endpoint.String(), "/") + "/v2/"
  149. req, err := http.NewRequest(http.MethodGet, endpointStr, nil)
  150. if err != nil {
  151. return nil, err
  152. }
  153. resp, err := pingClient.Do(req)
  154. if err != nil {
  155. return nil, err
  156. }
  157. defer resp.Body.Close()
  158. challengeManager := challenge.NewSimpleManager()
  159. if err := challengeManager.AddResponse(resp); err != nil {
  160. return nil, PingResponseError{
  161. Err: err,
  162. }
  163. }
  164. return challengeManager, nil
  165. }