github.com/Heebron/moby@v0.0.0-20221111184709-6eab4f55faf7/registry/config.go (about)

     1  package registry // import "github.com/docker/docker/registry"
     2  
     3  import (
     4  	"net"
     5  	"net/url"
     6  	"regexp"
     7  	"strconv"
     8  	"strings"
     9  
    10  	"github.com/docker/distribution/reference"
    11  	"github.com/docker/docker/api/types/registry"
    12  	"github.com/sirupsen/logrus"
    13  )
    14  
    15  // ServiceOptions holds command line options.
    16  type ServiceOptions struct {
    17  	AllowNondistributableArtifacts []string `json:"allow-nondistributable-artifacts,omitempty"`
    18  	Mirrors                        []string `json:"registry-mirrors,omitempty"`
    19  	InsecureRegistries             []string `json:"insecure-registries,omitempty"`
    20  }
    21  
    22  // serviceConfig holds daemon configuration for the registry service.
    23  type serviceConfig registry.ServiceConfig
    24  
    25  // TODO(thaJeztah) both the "index.docker.io" and "registry-1.docker.io" domains
    26  // are here for historic reasons and backward-compatibility. These domains
    27  // are still supported by Docker Hub (and will continue to be supported), but
    28  // there are new domains already in use, and plans to consolidate all legacy
    29  // domains to new "canonical" domains. Once those domains are decided on, we
    30  // should update these consts (but making sure to preserve compatibility with
    31  // existing installs, clients, and user configuration).
    32  const (
    33  	// DefaultNamespace is the default namespace
    34  	DefaultNamespace = "docker.io"
    35  	// DefaultRegistryHost is the hostname for the default (Docker Hub) registry
    36  	// used for pushing and pulling images. This hostname is hard-coded to handle
    37  	// the conversion from image references without registry name (e.g. "ubuntu",
    38  	// or "ubuntu:latest"), as well as references using the "docker.io" domain
    39  	// name, which is used as canonical reference for images on Docker Hub, but
    40  	// does not match the domain-name of Docker Hub's registry.
    41  	DefaultRegistryHost = "registry-1.docker.io"
    42  	// IndexHostname is the index hostname, used for authentication and image search.
    43  	IndexHostname = "index.docker.io"
    44  	// IndexServer is used for user auth and image search
    45  	IndexServer = "https://" + IndexHostname + "/v1/"
    46  	// IndexName is the name of the index
    47  	IndexName = "docker.io"
    48  )
    49  
    50  var (
    51  	// DefaultV2Registry is the URI of the default (Docker Hub) registry.
    52  	DefaultV2Registry = &url.URL{
    53  		Scheme: "https",
    54  		Host:   DefaultRegistryHost,
    55  	}
    56  
    57  	emptyServiceConfig, _ = newServiceConfig(ServiceOptions{})
    58  	validHostPortRegex    = regexp.MustCompile(`^` + reference.DomainRegexp.String() + `$`)
    59  
    60  	// for mocking in unit tests
    61  	lookupIP = net.LookupIP
    62  
    63  	// certsDir is used to override defaultCertsDir.
    64  	certsDir string
    65  )
    66  
    67  // SetCertsDir allows the default certs directory to be changed. This function
    68  // is used at daemon startup to set the correct location when running in
    69  // rootless mode.
    70  func SetCertsDir(path string) {
    71  	certsDir = path
    72  }
    73  
    74  // CertsDir is the directory where certificates are stored.
    75  func CertsDir() string {
    76  	if certsDir != "" {
    77  		return certsDir
    78  	}
    79  	return defaultCertsDir
    80  }
    81  
    82  // newServiceConfig returns a new instance of ServiceConfig
    83  func newServiceConfig(options ServiceOptions) (*serviceConfig, error) {
    84  	config := &serviceConfig{}
    85  	if err := config.loadAllowNondistributableArtifacts(options.AllowNondistributableArtifacts); err != nil {
    86  		return nil, err
    87  	}
    88  	if err := config.loadMirrors(options.Mirrors); err != nil {
    89  		return nil, err
    90  	}
    91  	if err := config.loadInsecureRegistries(options.InsecureRegistries); err != nil {
    92  		return nil, err
    93  	}
    94  
    95  	return config, nil
    96  }
    97  
    98  // copy constructs a new ServiceConfig with a copy of the configuration in config.
    99  func (config *serviceConfig) copy() *registry.ServiceConfig {
   100  	ic := make(map[string]*registry.IndexInfo)
   101  	for key, value := range config.IndexConfigs {
   102  		ic[key] = value
   103  	}
   104  	return &registry.ServiceConfig{
   105  		AllowNondistributableArtifactsCIDRs:     append([]*registry.NetIPNet(nil), config.AllowNondistributableArtifactsCIDRs...),
   106  		AllowNondistributableArtifactsHostnames: append([]string(nil), config.AllowNondistributableArtifactsHostnames...),
   107  		InsecureRegistryCIDRs:                   append([]*registry.NetIPNet(nil), config.InsecureRegistryCIDRs...),
   108  		IndexConfigs:                            ic,
   109  		Mirrors:                                 append([]string(nil), config.Mirrors...),
   110  	}
   111  }
   112  
   113  // loadAllowNondistributableArtifacts loads allow-nondistributable-artifacts registries into config.
   114  func (config *serviceConfig) loadAllowNondistributableArtifacts(registries []string) error {
   115  	cidrs := map[string]*registry.NetIPNet{}
   116  	hostnames := map[string]bool{}
   117  
   118  	for _, r := range registries {
   119  		if _, err := ValidateIndexName(r); err != nil {
   120  			return err
   121  		}
   122  		if hasScheme(r) {
   123  			return invalidParamf("allow-nondistributable-artifacts registry %s should not contain '://'", r)
   124  		}
   125  
   126  		if _, ipnet, err := net.ParseCIDR(r); err == nil {
   127  			// Valid CIDR.
   128  			cidrs[ipnet.String()] = (*registry.NetIPNet)(ipnet)
   129  		} else if err = validateHostPort(r); err == nil {
   130  			// Must be `host:port` if not CIDR.
   131  			hostnames[r] = true
   132  		} else {
   133  			return invalidParamWrapf(err, "allow-nondistributable-artifacts registry %s is not valid", r)
   134  		}
   135  	}
   136  
   137  	config.AllowNondistributableArtifactsCIDRs = make([]*registry.NetIPNet, 0, len(cidrs))
   138  	for _, c := range cidrs {
   139  		config.AllowNondistributableArtifactsCIDRs = append(config.AllowNondistributableArtifactsCIDRs, c)
   140  	}
   141  
   142  	config.AllowNondistributableArtifactsHostnames = make([]string, 0, len(hostnames))
   143  	for h := range hostnames {
   144  		config.AllowNondistributableArtifactsHostnames = append(config.AllowNondistributableArtifactsHostnames, h)
   145  	}
   146  
   147  	return nil
   148  }
   149  
   150  // loadMirrors loads mirrors to config, after removing duplicates.
   151  // Returns an error if mirrors contains an invalid mirror.
   152  func (config *serviceConfig) loadMirrors(mirrors []string) error {
   153  	mMap := map[string]struct{}{}
   154  	unique := []string{}
   155  
   156  	for _, mirror := range mirrors {
   157  		m, err := ValidateMirror(mirror)
   158  		if err != nil {
   159  			return err
   160  		}
   161  		if _, exist := mMap[m]; !exist {
   162  			mMap[m] = struct{}{}
   163  			unique = append(unique, m)
   164  		}
   165  	}
   166  
   167  	config.Mirrors = unique
   168  
   169  	// Configure public registry since mirrors may have changed.
   170  	config.IndexConfigs = map[string]*registry.IndexInfo{
   171  		IndexName: {
   172  			Name:     IndexName,
   173  			Mirrors:  unique,
   174  			Secure:   true,
   175  			Official: true,
   176  		},
   177  	}
   178  
   179  	return nil
   180  }
   181  
   182  // loadInsecureRegistries loads insecure registries to config
   183  func (config *serviceConfig) loadInsecureRegistries(registries []string) error {
   184  	// Localhost is by default considered as an insecure registry. This is a
   185  	// stop-gap for people who are running a private registry on localhost.
   186  	registries = append(registries, "127.0.0.0/8")
   187  
   188  	var (
   189  		insecureRegistryCIDRs = make([]*registry.NetIPNet, 0)
   190  		indexConfigs          = make(map[string]*registry.IndexInfo)
   191  	)
   192  
   193  skip:
   194  	for _, r := range registries {
   195  		// validate insecure registry
   196  		if _, err := ValidateIndexName(r); err != nil {
   197  			return err
   198  		}
   199  		if strings.HasPrefix(strings.ToLower(r), "http://") {
   200  			logrus.Warnf("insecure registry %s should not contain 'http://' and 'http://' has been removed from the insecure registry config", r)
   201  			r = r[7:]
   202  		} else if strings.HasPrefix(strings.ToLower(r), "https://") {
   203  			logrus.Warnf("insecure registry %s should not contain 'https://' and 'https://' has been removed from the insecure registry config", r)
   204  			r = r[8:]
   205  		} else if hasScheme(r) {
   206  			return invalidParamf("insecure registry %s should not contain '://'", r)
   207  		}
   208  		// Check if CIDR was passed to --insecure-registry
   209  		_, ipnet, err := net.ParseCIDR(r)
   210  		if err == nil {
   211  			// Valid CIDR. If ipnet is already in config.InsecureRegistryCIDRs, skip.
   212  			data := (*registry.NetIPNet)(ipnet)
   213  			for _, value := range insecureRegistryCIDRs {
   214  				if value.IP.String() == data.IP.String() && value.Mask.String() == data.Mask.String() {
   215  					continue skip
   216  				}
   217  			}
   218  			// ipnet is not found, add it in config.InsecureRegistryCIDRs
   219  			insecureRegistryCIDRs = append(insecureRegistryCIDRs, data)
   220  		} else {
   221  			if err := validateHostPort(r); err != nil {
   222  				return invalidParamWrapf(err, "insecure registry %s is not valid", r)
   223  			}
   224  			// Assume `host:port` if not CIDR.
   225  			indexConfigs[r] = &registry.IndexInfo{
   226  				Name:     r,
   227  				Mirrors:  make([]string, 0),
   228  				Secure:   false,
   229  				Official: false,
   230  			}
   231  		}
   232  	}
   233  
   234  	// Configure public registry.
   235  	indexConfigs[IndexName] = &registry.IndexInfo{
   236  		Name:     IndexName,
   237  		Mirrors:  config.Mirrors,
   238  		Secure:   true,
   239  		Official: true,
   240  	}
   241  	config.InsecureRegistryCIDRs = insecureRegistryCIDRs
   242  	config.IndexConfigs = indexConfigs
   243  
   244  	return nil
   245  }
   246  
   247  // allowNondistributableArtifacts returns true if the provided hostname is part of the list of registries
   248  // that allow push of nondistributable artifacts.
   249  //
   250  // The list can contain elements with CIDR notation to specify a whole subnet. If the subnet contains an IP
   251  // of the registry specified by hostname, true is returned.
   252  //
   253  // hostname should be a URL.Host (`host:port` or `host`) where the `host` part can be either a domain name
   254  // or an IP address. If it is a domain name, then it will be resolved to IP addresses for matching. If
   255  // resolution fails, CIDR matching is not performed.
   256  func (config *serviceConfig) allowNondistributableArtifacts(hostname string) bool {
   257  	for _, h := range config.AllowNondistributableArtifactsHostnames {
   258  		if h == hostname {
   259  			return true
   260  		}
   261  	}
   262  
   263  	return isCIDRMatch(config.AllowNondistributableArtifactsCIDRs, hostname)
   264  }
   265  
   266  // isSecureIndex returns false if the provided indexName is part of the list of insecure registries
   267  // Insecure registries accept HTTP and/or accept HTTPS with certificates from unknown CAs.
   268  //
   269  // The list of insecure registries can contain an element with CIDR notation to specify a whole subnet.
   270  // If the subnet contains one of the IPs of the registry specified by indexName, the latter is considered
   271  // insecure.
   272  //
   273  // indexName should be a URL.Host (`host:port` or `host`) where the `host` part can be either a domain name
   274  // or an IP address. If it is a domain name, then it will be resolved in order to check if the IP is contained
   275  // in a subnet. If the resolving is not successful, isSecureIndex will only try to match hostname to any element
   276  // of insecureRegistries.
   277  func (config *serviceConfig) isSecureIndex(indexName string) bool {
   278  	// Check for configured index, first.  This is needed in case isSecureIndex
   279  	// is called from anything besides newIndexInfo, in order to honor per-index configurations.
   280  	if index, ok := config.IndexConfigs[indexName]; ok {
   281  		return index.Secure
   282  	}
   283  
   284  	return !isCIDRMatch(config.InsecureRegistryCIDRs, indexName)
   285  }
   286  
   287  // isCIDRMatch returns true if URLHost matches an element of cidrs. URLHost is a URL.Host (`host:port` or `host`)
   288  // where the `host` part can be either a domain name or an IP address. If it is a domain name, then it will be
   289  // resolved to IP addresses for matching. If resolution fails, false is returned.
   290  func isCIDRMatch(cidrs []*registry.NetIPNet, URLHost string) bool {
   291  	host, _, err := net.SplitHostPort(URLHost)
   292  	if err != nil {
   293  		// Assume URLHost is of the form `host` without the port and go on.
   294  		host = URLHost
   295  	}
   296  
   297  	addrs, err := lookupIP(host)
   298  	if err != nil {
   299  		ip := net.ParseIP(host)
   300  		if ip != nil {
   301  			addrs = []net.IP{ip}
   302  		}
   303  
   304  		// if ip == nil, then `host` is neither an IP nor it could be looked up,
   305  		// either because the index is unreachable, or because the index is behind an HTTP proxy.
   306  		// So, len(addrs) == 0 and we're not aborting.
   307  	}
   308  
   309  	// Try CIDR notation only if addrs has any elements, i.e. if `host`'s IP could be determined.
   310  	for _, addr := range addrs {
   311  		for _, ipnet := range cidrs {
   312  			// check if the addr falls in the subnet
   313  			if (*net.IPNet)(ipnet).Contains(addr) {
   314  				return true
   315  			}
   316  		}
   317  	}
   318  
   319  	return false
   320  }
   321  
   322  // ValidateMirror validates an HTTP(S) registry mirror
   323  func ValidateMirror(val string) (string, error) {
   324  	uri, err := url.Parse(val)
   325  	if err != nil {
   326  		return "", invalidParamWrapf(err, "invalid mirror: %q is not a valid URI", val)
   327  	}
   328  	if uri.Scheme != "http" && uri.Scheme != "https" {
   329  		return "", invalidParamf("invalid mirror: unsupported scheme %q in %q", uri.Scheme, uri)
   330  	}
   331  	if (uri.Path != "" && uri.Path != "/") || uri.RawQuery != "" || uri.Fragment != "" {
   332  		return "", invalidParamf("invalid mirror: path, query, or fragment at end of the URI %q", uri)
   333  	}
   334  	if uri.User != nil {
   335  		// strip password from output
   336  		uri.User = url.UserPassword(uri.User.Username(), "xxxxx")
   337  		return "", invalidParamf("invalid mirror: username/password not allowed in URI %q", uri)
   338  	}
   339  	return strings.TrimSuffix(val, "/") + "/", nil
   340  }
   341  
   342  // ValidateIndexName validates an index name.
   343  func ValidateIndexName(val string) (string, error) {
   344  	// TODO: upstream this to check to reference package
   345  	if val == "index.docker.io" {
   346  		val = "docker.io"
   347  	}
   348  	if strings.HasPrefix(val, "-") || strings.HasSuffix(val, "-") {
   349  		return "", invalidParamf("invalid index name (%s). Cannot begin or end with a hyphen", val)
   350  	}
   351  	return val, nil
   352  }
   353  
   354  func hasScheme(reposName string) bool {
   355  	return strings.Contains(reposName, "://")
   356  }
   357  
   358  func validateHostPort(s string) error {
   359  	// Split host and port, and in case s can not be splitted, assume host only
   360  	host, port, err := net.SplitHostPort(s)
   361  	if err != nil {
   362  		host = s
   363  		port = ""
   364  	}
   365  	// If match against the `host:port` pattern fails,
   366  	// it might be `IPv6:port`, which will be captured by net.ParseIP(host)
   367  	if !validHostPortRegex.MatchString(s) && net.ParseIP(host) == nil {
   368  		return invalidParamf("invalid host %q", host)
   369  	}
   370  	if port != "" {
   371  		v, err := strconv.Atoi(port)
   372  		if err != nil {
   373  			return err
   374  		}
   375  		if v < 0 || v > 65535 {
   376  			return invalidParamf("invalid port %q", port)
   377  		}
   378  	}
   379  	return nil
   380  }
   381  
   382  // newIndexInfo returns IndexInfo configuration from indexName
   383  func newIndexInfo(config *serviceConfig, indexName string) (*registry.IndexInfo, error) {
   384  	var err error
   385  	indexName, err = ValidateIndexName(indexName)
   386  	if err != nil {
   387  		return nil, err
   388  	}
   389  
   390  	// Return any configured index info, first.
   391  	if index, ok := config.IndexConfigs[indexName]; ok {
   392  		return index, nil
   393  	}
   394  
   395  	// Construct a non-configured index info.
   396  	return &registry.IndexInfo{
   397  		Name:     indexName,
   398  		Mirrors:  make([]string, 0),
   399  		Secure:   config.isSecureIndex(indexName),
   400  		Official: false,
   401  	}, nil
   402  }
   403  
   404  // GetAuthConfigKey special-cases using the full index address of the official
   405  // index as the AuthConfig key, and uses the (host)name[:port] for private indexes.
   406  func GetAuthConfigKey(index *registry.IndexInfo) string {
   407  	if index.Official {
   408  		return IndexServer
   409  	}
   410  	return index.Name
   411  }
   412  
   413  // newRepositoryInfo validates and breaks down a repository name into a RepositoryInfo
   414  func newRepositoryInfo(config *serviceConfig, name reference.Named) (*RepositoryInfo, error) {
   415  	index, err := newIndexInfo(config, reference.Domain(name))
   416  	if err != nil {
   417  		return nil, err
   418  	}
   419  	official := !strings.ContainsRune(reference.FamiliarName(name), '/')
   420  
   421  	return &RepositoryInfo{
   422  		Name:     reference.TrimNamed(name),
   423  		Index:    index,
   424  		Official: official,
   425  	}, nil
   426  }
   427  
   428  // ParseRepositoryInfo performs the breakdown of a repository name into a RepositoryInfo, but
   429  // lacks registry configuration.
   430  func ParseRepositoryInfo(reposName reference.Named) (*RepositoryInfo, error) {
   431  	return newRepositoryInfo(emptyServiceConfig, reposName)
   432  }
   433  
   434  // ParseSearchIndexInfo will use repository name to get back an indexInfo.
   435  //
   436  // TODO(thaJeztah) this function is only used by the CLI, and used to get
   437  // information of the registry (to provide credentials if needed). We should
   438  // move this function (or equivalent) to the CLI, as it's doing too much just
   439  // for that.
   440  func ParseSearchIndexInfo(reposName string) (*registry.IndexInfo, error) {
   441  	indexName, _ := splitReposSearchTerm(reposName)
   442  
   443  	indexInfo, err := newIndexInfo(emptyServiceConfig, indexName)
   444  	if err != nil {
   445  		return nil, err
   446  	}
   447  	return indexInfo, nil
   448  }