service.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. package registry
  2. import (
  3. "crypto/tls"
  4. "net/http"
  5. "net/url"
  6. "strings"
  7. "sync"
  8. "golang.org/x/net/context"
  9. "github.com/docker/distribution/reference"
  10. "github.com/docker/distribution/registry/client/auth"
  11. "github.com/docker/docker/api/types"
  12. registrytypes "github.com/docker/docker/api/types/registry"
  13. "github.com/pkg/errors"
  14. "github.com/sirupsen/logrus"
  15. )
  16. const (
  17. // DefaultSearchLimit is the default value for maximum number of returned search results.
  18. DefaultSearchLimit = 25
  19. )
  20. // Service is the interface defining what a registry service should implement.
  21. type Service interface {
  22. Auth(ctx context.Context, authConfig *types.AuthConfig, userAgent string) (status, token string, err error)
  23. LookupPullEndpoints(hostname string) (endpoints []APIEndpoint, err error)
  24. LookupPushEndpoints(hostname string) (endpoints []APIEndpoint, err error)
  25. ResolveRepository(name reference.Named) (*RepositoryInfo, error)
  26. Search(ctx context.Context, term string, limit int, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error)
  27. ServiceConfig() *registrytypes.ServiceConfig
  28. TLSConfig(hostname string) (*tls.Config, error)
  29. LoadAllowNondistributableArtifacts([]string) error
  30. LoadMirrors([]string) error
  31. LoadInsecureRegistries([]string) error
  32. }
  33. // DefaultService is a registry service. It tracks configuration data such as a list
  34. // of mirrors.
  35. type DefaultService struct {
  36. config *serviceConfig
  37. mu sync.Mutex
  38. }
  39. // NewService returns a new instance of DefaultService ready to be
  40. // installed into an engine.
  41. func NewService(options ServiceOptions) (*DefaultService, error) {
  42. config, err := newServiceConfig(options)
  43. return &DefaultService{config: config}, err
  44. }
  45. // ServiceConfig returns the public registry service configuration.
  46. func (s *DefaultService) ServiceConfig() *registrytypes.ServiceConfig {
  47. s.mu.Lock()
  48. defer s.mu.Unlock()
  49. servConfig := registrytypes.ServiceConfig{
  50. AllowNondistributableArtifactsCIDRs: make([]*(registrytypes.NetIPNet), 0),
  51. AllowNondistributableArtifactsHostnames: make([]string, 0),
  52. InsecureRegistryCIDRs: make([]*(registrytypes.NetIPNet), 0),
  53. IndexConfigs: make(map[string]*(registrytypes.IndexInfo)),
  54. Mirrors: make([]string, 0),
  55. }
  56. // construct a new ServiceConfig which will not retrieve s.Config directly,
  57. // and look up items in s.config with mu locked
  58. servConfig.AllowNondistributableArtifactsCIDRs = append(servConfig.AllowNondistributableArtifactsCIDRs, s.config.ServiceConfig.AllowNondistributableArtifactsCIDRs...)
  59. servConfig.AllowNondistributableArtifactsHostnames = append(servConfig.AllowNondistributableArtifactsHostnames, s.config.ServiceConfig.AllowNondistributableArtifactsHostnames...)
  60. servConfig.InsecureRegistryCIDRs = append(servConfig.InsecureRegistryCIDRs, s.config.ServiceConfig.InsecureRegistryCIDRs...)
  61. for key, value := range s.config.ServiceConfig.IndexConfigs {
  62. servConfig.IndexConfigs[key] = value
  63. }
  64. servConfig.Mirrors = append(servConfig.Mirrors, s.config.ServiceConfig.Mirrors...)
  65. return &servConfig
  66. }
  67. // LoadAllowNondistributableArtifacts loads allow-nondistributable-artifacts registries for Service.
  68. func (s *DefaultService) LoadAllowNondistributableArtifacts(registries []string) error {
  69. s.mu.Lock()
  70. defer s.mu.Unlock()
  71. return s.config.LoadAllowNondistributableArtifacts(registries)
  72. }
  73. // LoadMirrors loads registry mirrors for Service
  74. func (s *DefaultService) LoadMirrors(mirrors []string) error {
  75. s.mu.Lock()
  76. defer s.mu.Unlock()
  77. return s.config.LoadMirrors(mirrors)
  78. }
  79. // LoadInsecureRegistries loads insecure registries for Service
  80. func (s *DefaultService) LoadInsecureRegistries(registries []string) error {
  81. s.mu.Lock()
  82. defer s.mu.Unlock()
  83. return s.config.LoadInsecureRegistries(registries)
  84. }
  85. // Auth contacts the public registry with the provided credentials,
  86. // and returns OK if authentication was successful.
  87. // It can be used to verify the validity of a client's credentials.
  88. func (s *DefaultService) Auth(ctx context.Context, authConfig *types.AuthConfig, userAgent string) (status, token string, err error) {
  89. // TODO Use ctx when searching for repositories
  90. serverAddress := authConfig.ServerAddress
  91. if serverAddress == "" {
  92. serverAddress = IndexServer
  93. }
  94. if !strings.HasPrefix(serverAddress, "https://") && !strings.HasPrefix(serverAddress, "http://") {
  95. serverAddress = "https://" + serverAddress
  96. }
  97. u, err := url.Parse(serverAddress)
  98. if err != nil {
  99. return "", "", validationError{errors.Errorf("unable to parse server address: %v", err)}
  100. }
  101. endpoints, err := s.LookupPushEndpoints(u.Host)
  102. if err != nil {
  103. return "", "", validationError{err}
  104. }
  105. for _, endpoint := range endpoints {
  106. login := loginV2
  107. if endpoint.Version == APIVersion1 {
  108. login = loginV1
  109. }
  110. status, token, err = login(authConfig, endpoint, userAgent)
  111. if err == nil {
  112. return
  113. }
  114. if fErr, ok := err.(fallbackError); ok {
  115. err = fErr.err
  116. logrus.Infof("Error logging in to %s endpoint, trying next endpoint: %v", endpoint.Version, err)
  117. continue
  118. }
  119. return "", "", err
  120. }
  121. return "", "", err
  122. }
  123. // splitReposSearchTerm breaks a search term into an index name and remote name
  124. func splitReposSearchTerm(reposName string) (string, string) {
  125. nameParts := strings.SplitN(reposName, "/", 2)
  126. var indexName, remoteName string
  127. if len(nameParts) == 1 || (!strings.Contains(nameParts[0], ".") &&
  128. !strings.Contains(nameParts[0], ":") && nameParts[0] != "localhost") {
  129. // This is a Docker Index repos (ex: samalba/hipache or ubuntu)
  130. // 'docker.io'
  131. indexName = IndexName
  132. remoteName = reposName
  133. } else {
  134. indexName = nameParts[0]
  135. remoteName = nameParts[1]
  136. }
  137. return indexName, remoteName
  138. }
  139. // Search queries the public registry for images matching the specified
  140. // search terms, and returns the results.
  141. func (s *DefaultService) Search(ctx context.Context, term string, limit int, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error) {
  142. // TODO Use ctx when searching for repositories
  143. if err := validateNoScheme(term); err != nil {
  144. return nil, err
  145. }
  146. indexName, remoteName := splitReposSearchTerm(term)
  147. // Search is a long-running operation, just lock s.config to avoid block others.
  148. s.mu.Lock()
  149. index, err := newIndexInfo(s.config, indexName)
  150. s.mu.Unlock()
  151. if err != nil {
  152. return nil, err
  153. }
  154. // *TODO: Search multiple indexes.
  155. endpoint, err := NewV1Endpoint(index, userAgent, http.Header(headers))
  156. if err != nil {
  157. return nil, err
  158. }
  159. var client *http.Client
  160. if authConfig != nil && authConfig.IdentityToken != "" && authConfig.Username != "" {
  161. creds := NewStaticCredentialStore(authConfig)
  162. scopes := []auth.Scope{
  163. auth.RegistryScope{
  164. Name: "catalog",
  165. Actions: []string{"search"},
  166. },
  167. }
  168. modifiers := DockerHeaders(userAgent, nil)
  169. v2Client, foundV2, err := v2AuthHTTPClient(endpoint.URL, endpoint.client.Transport, modifiers, creds, scopes)
  170. if err != nil {
  171. if fErr, ok := err.(fallbackError); ok {
  172. logrus.Errorf("Cannot use identity token for search, v2 auth not supported: %v", fErr.err)
  173. } else {
  174. return nil, err
  175. }
  176. } else if foundV2 {
  177. // Copy non transport http client features
  178. v2Client.Timeout = endpoint.client.Timeout
  179. v2Client.CheckRedirect = endpoint.client.CheckRedirect
  180. v2Client.Jar = endpoint.client.Jar
  181. logrus.Debugf("using v2 client for search to %s", endpoint.URL)
  182. client = v2Client
  183. }
  184. }
  185. if client == nil {
  186. client = endpoint.client
  187. if err := authorizeClient(client, authConfig, endpoint); err != nil {
  188. return nil, err
  189. }
  190. }
  191. r := newSession(client, authConfig, endpoint)
  192. if index.Official {
  193. localName := remoteName
  194. if strings.HasPrefix(localName, "library/") {
  195. // If pull "library/foo", it's stored locally under "foo"
  196. localName = strings.SplitN(localName, "/", 2)[1]
  197. }
  198. return r.SearchRepositories(localName, limit)
  199. }
  200. return r.SearchRepositories(remoteName, limit)
  201. }
  202. // ResolveRepository splits a repository name into its components
  203. // and configuration of the associated registry.
  204. func (s *DefaultService) ResolveRepository(name reference.Named) (*RepositoryInfo, error) {
  205. s.mu.Lock()
  206. defer s.mu.Unlock()
  207. return newRepositoryInfo(s.config, name)
  208. }
  209. // APIEndpoint represents a remote API endpoint
  210. type APIEndpoint struct {
  211. Mirror bool
  212. URL *url.URL
  213. Version APIVersion
  214. AllowNondistributableArtifacts bool
  215. Official bool
  216. TrimHostname bool
  217. TLSConfig *tls.Config
  218. }
  219. // ToV1Endpoint returns a V1 API endpoint based on the APIEndpoint
  220. func (e APIEndpoint) ToV1Endpoint(userAgent string, metaHeaders http.Header) *V1Endpoint {
  221. return newV1Endpoint(*e.URL, e.TLSConfig, userAgent, metaHeaders)
  222. }
  223. // TLSConfig constructs a client TLS configuration based on server defaults
  224. func (s *DefaultService) TLSConfig(hostname string) (*tls.Config, error) {
  225. s.mu.Lock()
  226. defer s.mu.Unlock()
  227. return newTLSConfig(hostname, isSecureIndex(s.config, hostname))
  228. }
  229. // tlsConfig constructs a client TLS configuration based on server defaults
  230. func (s *DefaultService) tlsConfig(hostname string) (*tls.Config, error) {
  231. return newTLSConfig(hostname, isSecureIndex(s.config, hostname))
  232. }
  233. func (s *DefaultService) tlsConfigForMirror(mirrorURL *url.URL) (*tls.Config, error) {
  234. return s.tlsConfig(mirrorURL.Host)
  235. }
  236. // LookupPullEndpoints creates a list of endpoints to try to pull from, in order of preference.
  237. // It gives preference to v2 endpoints over v1, mirrors over the actual
  238. // registry, and HTTPS over plain HTTP.
  239. func (s *DefaultService) LookupPullEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
  240. s.mu.Lock()
  241. defer s.mu.Unlock()
  242. return s.lookupEndpoints(hostname)
  243. }
  244. // LookupPushEndpoints creates a list of endpoints to try to push to, in order of preference.
  245. // It gives preference to v2 endpoints over v1, and HTTPS over plain HTTP.
  246. // Mirrors are not included.
  247. func (s *DefaultService) LookupPushEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
  248. s.mu.Lock()
  249. defer s.mu.Unlock()
  250. allEndpoints, err := s.lookupEndpoints(hostname)
  251. if err == nil {
  252. for _, endpoint := range allEndpoints {
  253. if !endpoint.Mirror {
  254. endpoints = append(endpoints, endpoint)
  255. }
  256. }
  257. }
  258. return endpoints, err
  259. }
  260. func (s *DefaultService) lookupEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
  261. endpoints, err = s.lookupV2Endpoints(hostname)
  262. if err != nil {
  263. return nil, err
  264. }
  265. if s.config.V2Only {
  266. return endpoints, nil
  267. }
  268. legacyEndpoints, err := s.lookupV1Endpoints(hostname)
  269. if err != nil {
  270. return nil, err
  271. }
  272. endpoints = append(endpoints, legacyEndpoints...)
  273. return endpoints, nil
  274. }