github.com/pwn-term/docker@v0.0.0-20210616085119-6e977cce2565/moby/registry/service.go (about)

     1  package registry // import "github.com/docker/docker/registry"
     2  
     3  import (
     4  	"context"
     5  	"crypto/tls"
     6  	"net/http"
     7  	"net/url"
     8  	"strings"
     9  	"sync"
    10  
    11  	"github.com/docker/distribution/reference"
    12  	"github.com/docker/distribution/registry/client/auth"
    13  	"github.com/docker/docker/api/types"
    14  	registrytypes "github.com/docker/docker/api/types/registry"
    15  	"github.com/docker/docker/errdefs"
    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  	var registryHostName = IndexHostname
   112  
   113  	if authConfig.ServerAddress != "" {
   114  		serverAddress := authConfig.ServerAddress
   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 "", "", errdefs.InvalidParameter(errors.Errorf("unable to parse server address: %v", err))
   121  		}
   122  		registryHostName = u.Host
   123  	}
   124  
   125  	// Lookup endpoints for authentication using "LookupPushEndpoints", which
   126  	// excludes mirrors to prevent sending credentials of the upstream registry
   127  	// to a mirror.
   128  	endpoints, err := s.LookupPushEndpoints(registryHostName)
   129  	if err != nil {
   130  		return "", "", errdefs.InvalidParameter(err)
   131  	}
   132  
   133  	for _, endpoint := range endpoints {
   134  		status, token, err = loginV2(authConfig, endpoint, userAgent)
   135  		if err == nil {
   136  			return
   137  		}
   138  		if fErr, ok := err.(fallbackError); ok {
   139  			logrus.WithError(fErr.err).Infof("Error logging in to endpoint, trying next endpoint")
   140  			continue
   141  		}
   142  
   143  		return "", "", err
   144  	}
   145  
   146  	return "", "", err
   147  }
   148  
   149  // splitReposSearchTerm breaks a search term into an index name and remote name
   150  func splitReposSearchTerm(reposName string) (string, string) {
   151  	nameParts := strings.SplitN(reposName, "/", 2)
   152  	if len(nameParts) == 1 || (!strings.Contains(nameParts[0], ".") &&
   153  		!strings.Contains(nameParts[0], ":") && nameParts[0] != "localhost") {
   154  		// This is a Docker Hub repository (ex: samalba/hipache or ubuntu),
   155  		// use the default Docker Hub registry (docker.io)
   156  		return IndexName, reposName
   157  	}
   158  	return nameParts[0], nameParts[1]
   159  }
   160  
   161  // Search queries the public registry for images matching the specified
   162  // search terms, and returns the results.
   163  func (s *DefaultService) Search(ctx context.Context, term string, limit int, authConfig *types.AuthConfig, userAgent string, headers map[string][]string) (*registrytypes.SearchResults, error) {
   164  	// TODO Use ctx when searching for repositories
   165  	if err := validateNoScheme(term); err != nil {
   166  		return nil, err
   167  	}
   168  
   169  	indexName, remoteName := splitReposSearchTerm(term)
   170  
   171  	// Search is a long-running operation, just lock s.config to avoid block others.
   172  	s.mu.Lock()
   173  	index, err := newIndexInfo(s.config, indexName)
   174  	s.mu.Unlock()
   175  
   176  	if err != nil {
   177  		return nil, err
   178  	}
   179  
   180  	// *TODO: Search multiple indexes.
   181  	endpoint, err := NewV1Endpoint(index, userAgent, headers)
   182  	if err != nil {
   183  		return nil, err
   184  	}
   185  
   186  	var client *http.Client
   187  	if authConfig != nil && authConfig.IdentityToken != "" && authConfig.Username != "" {
   188  		creds := NewStaticCredentialStore(authConfig)
   189  		scopes := []auth.Scope{
   190  			auth.RegistryScope{
   191  				Name:    "catalog",
   192  				Actions: []string{"search"},
   193  			},
   194  		}
   195  
   196  		modifiers := Headers(userAgent, nil)
   197  		v2Client, foundV2, err := v2AuthHTTPClient(endpoint.URL, endpoint.client.Transport, modifiers, creds, scopes)
   198  		if err != nil {
   199  			if fErr, ok := err.(fallbackError); ok {
   200  				logrus.Errorf("Cannot use identity token for search, v2 auth not supported: %v", fErr.err)
   201  			} else {
   202  				return nil, err
   203  			}
   204  		} else if foundV2 {
   205  			// Copy non transport http client features
   206  			v2Client.Timeout = endpoint.client.Timeout
   207  			v2Client.CheckRedirect = endpoint.client.CheckRedirect
   208  			v2Client.Jar = endpoint.client.Jar
   209  
   210  			logrus.Debugf("using v2 client for search to %s", endpoint.URL)
   211  			client = v2Client
   212  		}
   213  	}
   214  
   215  	if client == nil {
   216  		client = endpoint.client
   217  		if err := authorizeClient(client, authConfig, endpoint); err != nil {
   218  			return nil, err
   219  		}
   220  	}
   221  
   222  	r := newSession(client, authConfig, endpoint)
   223  
   224  	if index.Official {
   225  		// If pull "library/foo", it's stored locally under "foo"
   226  		remoteName = strings.TrimPrefix(remoteName, "library/")
   227  	}
   228  	return r.SearchRepositories(remoteName, limit)
   229  }
   230  
   231  // ResolveRepository splits a repository name into its components
   232  // and configuration of the associated registry.
   233  func (s *DefaultService) ResolveRepository(name reference.Named) (*RepositoryInfo, error) {
   234  	s.mu.Lock()
   235  	defer s.mu.Unlock()
   236  	return newRepositoryInfo(s.config, name)
   237  }
   238  
   239  // APIEndpoint represents a remote API endpoint
   240  type APIEndpoint struct {
   241  	Mirror                         bool
   242  	URL                            *url.URL
   243  	Version                        APIVersion
   244  	AllowNondistributableArtifacts bool
   245  	Official                       bool
   246  	TrimHostname                   bool
   247  	TLSConfig                      *tls.Config
   248  }
   249  
   250  // ToV1Endpoint returns a V1 API endpoint based on the APIEndpoint
   251  // Deprecated: this function is deprecated and will be removed in a future update
   252  func (e APIEndpoint) ToV1Endpoint(userAgent string, metaHeaders http.Header) *V1Endpoint {
   253  	return newV1Endpoint(*e.URL, e.TLSConfig, userAgent, metaHeaders)
   254  }
   255  
   256  // TLSConfig constructs a client TLS configuration based on server defaults
   257  func (s *DefaultService) TLSConfig(hostname string) (*tls.Config, error) {
   258  	s.mu.Lock()
   259  	defer s.mu.Unlock()
   260  
   261  	return newTLSConfig(hostname, isSecureIndex(s.config, hostname))
   262  }
   263  
   264  // tlsConfig constructs a client TLS configuration based on server defaults
   265  func (s *DefaultService) tlsConfig(hostname string) (*tls.Config, error) {
   266  	return newTLSConfig(hostname, isSecureIndex(s.config, hostname))
   267  }
   268  
   269  func (s *DefaultService) tlsConfigForMirror(mirrorURL *url.URL) (*tls.Config, error) {
   270  	return s.tlsConfig(mirrorURL.Host)
   271  }
   272  
   273  // LookupPullEndpoints creates a list of v2 endpoints to try to pull from, in order of preference.
   274  // It gives preference to mirrors over the actual registry, and HTTPS over plain HTTP.
   275  func (s *DefaultService) LookupPullEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
   276  	s.mu.Lock()
   277  	defer s.mu.Unlock()
   278  
   279  	return s.lookupV2Endpoints(hostname)
   280  }
   281  
   282  // LookupPushEndpoints creates a list of v2 endpoints to try to push to, in order of preference.
   283  // It gives preference to HTTPS over plain HTTP. Mirrors are not included.
   284  func (s *DefaultService) LookupPushEndpoints(hostname string) (endpoints []APIEndpoint, err error) {
   285  	s.mu.Lock()
   286  	defer s.mu.Unlock()
   287  
   288  	allEndpoints, err := s.lookupV2Endpoints(hostname)
   289  	if err == nil {
   290  		for _, endpoint := range allEndpoints {
   291  			if !endpoint.Mirror {
   292  				endpoints = append(endpoints, endpoint)
   293  			}
   294  		}
   295  	}
   296  	return endpoints, err
   297  }