github.com/ssdev-go/moby@v17.12.1-ce-rc2+incompatible/registry/service.go (about)

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