service.go 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. package registry // import "github.com/docker/docker/registry"
  2. import (
  3. "context"
  4. "crypto/tls"
  5. "net/http"
  6. "net/url"
  7. "strings"
  8. "sync"
  9. "github.com/docker/distribution/reference"
  10. "github.com/docker/distribution/registry/client/auth"
  11. "github.com/docker/docker/api/types/registry"
  12. "github.com/docker/docker/errdefs"
  13. "github.com/sirupsen/logrus"
  14. )
  15. // Service is the interface defining what a registry service should implement.
  16. type Service interface {
  17. Auth(ctx context.Context, authConfig *registry.AuthConfig, userAgent string) (status, token string, err error)
  18. LookupPullEndpoints(hostname string) (endpoints []APIEndpoint, err error)
  19. LookupPushEndpoints(hostname string) (endpoints []APIEndpoint, err error)
  20. ResolveRepository(name reference.Named) (*RepositoryInfo, error)
  21. Search(ctx context.Context, term string, limit int, authConfig *registry.AuthConfig, userAgent string, headers map[string][]string) (*registry.SearchResults, error)
  22. ServiceConfig() *registry.ServiceConfig
  23. LoadAllowNondistributableArtifacts([]string) error
  24. LoadMirrors([]string) error
  25. LoadInsecureRegistries([]string) error
  26. }
  27. // defaultService is a registry service. It tracks configuration data such as a list
  28. // of mirrors.
  29. type defaultService struct {
  30. config *serviceConfig
  31. mu sync.RWMutex
  32. }
  33. // NewService returns a new instance of defaultService ready to be
  34. // installed into an engine.
  35. func NewService(options ServiceOptions) (Service, error) {
  36. config, err := newServiceConfig(options)
  37. return &defaultService{config: config}, err
  38. }
  39. // ServiceConfig returns a copy of the public registry service's configuration.
  40. func (s *defaultService) ServiceConfig() *registry.ServiceConfig {
  41. s.mu.RLock()
  42. defer s.mu.RUnlock()
  43. return s.config.copy()
  44. }
  45. // LoadAllowNondistributableArtifacts loads allow-nondistributable-artifacts registries for Service.
  46. func (s *defaultService) LoadAllowNondistributableArtifacts(registries []string) error {
  47. s.mu.Lock()
  48. defer s.mu.Unlock()
  49. return s.config.loadAllowNondistributableArtifacts(registries)
  50. }
  51. // LoadMirrors loads registry mirrors for Service
  52. func (s *defaultService) LoadMirrors(mirrors []string) error {
  53. s.mu.Lock()
  54. defer s.mu.Unlock()
  55. return s.config.loadMirrors(mirrors)
  56. }
  57. // LoadInsecureRegistries loads insecure registries for Service
  58. func (s *defaultService) LoadInsecureRegistries(registries []string) error {
  59. s.mu.Lock()
  60. defer s.mu.Unlock()
  61. return s.config.loadInsecureRegistries(registries)
  62. }
  63. // Auth contacts the public registry with the provided credentials,
  64. // and returns OK if authentication was successful.
  65. // It can be used to verify the validity of a client's credentials.
  66. func (s *defaultService) Auth(ctx context.Context, authConfig *registry.AuthConfig, userAgent string) (status, token string, err error) {
  67. // TODO Use ctx when searching for repositories
  68. var registryHostName = IndexHostname
  69. if authConfig.ServerAddress != "" {
  70. serverAddress := authConfig.ServerAddress
  71. if !strings.HasPrefix(serverAddress, "https://") && !strings.HasPrefix(serverAddress, "http://") {
  72. serverAddress = "https://" + serverAddress
  73. }
  74. u, err := url.Parse(serverAddress)
  75. if err != nil {
  76. return "", "", invalidParamWrapf(err, "unable to parse server address")
  77. }
  78. registryHostName = u.Host
  79. }
  80. // Lookup endpoints for authentication using "LookupPushEndpoints", which
  81. // excludes mirrors to prevent sending credentials of the upstream registry
  82. // to a mirror.
  83. endpoints, err := s.LookupPushEndpoints(registryHostName)
  84. if err != nil {
  85. return "", "", invalidParam(err)
  86. }
  87. for _, endpoint := range endpoints {
  88. status, token, err = loginV2(authConfig, endpoint, userAgent)
  89. if err == nil {
  90. return
  91. }
  92. if errdefs.IsUnauthorized(err) {
  93. // Failed to authenticate; don't continue with (non-TLS) endpoints.
  94. return status, token, err
  95. }
  96. logrus.WithError(err).Infof("Error logging in to endpoint, trying next endpoint")
  97. }
  98. return "", "", err
  99. }
  100. // splitReposSearchTerm breaks a search term into an index name and remote name
  101. func splitReposSearchTerm(reposName string) (string, string) {
  102. nameParts := strings.SplitN(reposName, "/", 2)
  103. if len(nameParts) == 1 || (!strings.Contains(nameParts[0], ".") &&
  104. !strings.Contains(nameParts[0], ":") && nameParts[0] != "localhost") {
  105. // This is a Docker Hub repository (ex: samalba/hipache or ubuntu),
  106. // use the default Docker Hub registry (docker.io)
  107. return IndexName, reposName
  108. }
  109. return nameParts[0], nameParts[1]
  110. }
  111. // Search queries the public registry for images matching the specified
  112. // search terms, and returns the results.
  113. func (s *defaultService) Search(ctx context.Context, term string, limit int, authConfig *registry.AuthConfig, userAgent string, headers map[string][]string) (*registry.SearchResults, error) {
  114. // TODO Use ctx when searching for repositories
  115. if hasScheme(term) {
  116. return nil, invalidParamf("invalid repository name: repository name (%s) should not have a scheme", term)
  117. }
  118. indexName, remoteName := splitReposSearchTerm(term)
  119. // Search is a long-running operation, just lock s.config to avoid block others.
  120. s.mu.RLock()
  121. index, err := newIndexInfo(s.config, indexName)
  122. s.mu.RUnlock()
  123. if err != nil {
  124. return nil, err
  125. }
  126. if index.Official {
  127. // If pull "library/foo", it's stored locally under "foo"
  128. remoteName = strings.TrimPrefix(remoteName, "library/")
  129. }
  130. endpoint, err := newV1Endpoint(index, userAgent, headers)
  131. if err != nil {
  132. return nil, err
  133. }
  134. var client *http.Client
  135. if authConfig != nil && authConfig.IdentityToken != "" && authConfig.Username != "" {
  136. creds := NewStaticCredentialStore(authConfig)
  137. scopes := []auth.Scope{
  138. auth.RegistryScope{
  139. Name: "catalog",
  140. Actions: []string{"search"},
  141. },
  142. }
  143. modifiers := Headers(userAgent, nil)
  144. v2Client, err := v2AuthHTTPClient(endpoint.URL, endpoint.client.Transport, modifiers, creds, scopes)
  145. if err != nil {
  146. return nil, err
  147. }
  148. // Copy non transport http client features
  149. v2Client.Timeout = endpoint.client.Timeout
  150. v2Client.CheckRedirect = endpoint.client.CheckRedirect
  151. v2Client.Jar = endpoint.client.Jar
  152. logrus.Debugf("using v2 client for search to %s", endpoint.URL)
  153. client = v2Client
  154. } else {
  155. client = endpoint.client
  156. if err := authorizeClient(client, authConfig, endpoint); err != nil {
  157. return nil, err
  158. }
  159. }
  160. return newSession(client, endpoint).searchRepositories(remoteName, limit)
  161. }
  162. // ResolveRepository splits a repository name into its components
  163. // and configuration of the associated registry.
  164. func (s *defaultService) ResolveRepository(name reference.Named) (*RepositoryInfo, error) {
  165. s.mu.RLock()
  166. defer s.mu.RUnlock()
  167. return newRepositoryInfo(s.config, name)
  168. }
  169. // APIEndpoint represents a remote API endpoint
  170. type APIEndpoint struct {
  171. Mirror bool
  172. URL *url.URL
  173. Version APIVersion
  174. AllowNondistributableArtifacts bool
  175. Official bool
  176. TrimHostname bool
  177. TLSConfig *tls.Config
  178. }
  179. // LookupPullEndpoints creates a list of v2 endpoints to try to pull from, in order of preference.
  180. // It gives preference to mirrors over the actual registry, and HTTPS over plain HTTP.
  181. func (s *defaultService) LookupPullEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
  182. s.mu.RLock()
  183. defer s.mu.RUnlock()
  184. return s.lookupV2Endpoints(hostname)
  185. }
  186. // LookupPushEndpoints creates a list of v2 endpoints to try to push to, in order of preference.
  187. // It gives preference to HTTPS over plain HTTP. Mirrors are not included.
  188. func (s *defaultService) LookupPushEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
  189. s.mu.RLock()
  190. defer s.mu.RUnlock()
  191. allEndpoints, err := s.lookupV2Endpoints(hostname)
  192. if err == nil {
  193. for _, endpoint := range allEndpoints {
  194. if !endpoint.Mirror {
  195. endpoints = append(endpoints, endpoint)
  196. }
  197. }
  198. }
  199. return endpoints, err
  200. }