github.com/fabiokung/docker@v0.11.2-0.20170222101415-4534dcd49497/registry/config.go (about)

     1  package registry
     2  
     3  import (
     4  	"fmt"
     5  	"net"
     6  	"net/url"
     7  	"regexp"
     8  	"strconv"
     9  	"strings"
    10  
    11  	"github.com/Sirupsen/logrus"
    12  	"github.com/docker/distribution/reference"
    13  	registrytypes "github.com/docker/docker/api/types/registry"
    14  	"github.com/docker/docker/opts"
    15  	"github.com/pkg/errors"
    16  	"github.com/spf13/pflag"
    17  )
    18  
    19  // ServiceOptions holds command line options.
    20  type ServiceOptions struct {
    21  	Mirrors            []string `json:"registry-mirrors,omitempty"`
    22  	InsecureRegistries []string `json:"insecure-registries,omitempty"`
    23  
    24  	// V2Only controls access to legacy registries.  If it is set to true via the
    25  	// command line flag the daemon will not attempt to contact v1 legacy registries
    26  	V2Only bool `json:"disable-legacy-registry,omitempty"`
    27  }
    28  
    29  // serviceConfig holds daemon configuration for the registry service.
    30  type serviceConfig struct {
    31  	registrytypes.ServiceConfig
    32  	V2Only bool
    33  }
    34  
    35  var (
    36  	// DefaultNamespace is the default namespace
    37  	DefaultNamespace = "docker.io"
    38  	// DefaultRegistryVersionHeader is the name of the default HTTP header
    39  	// that carries Registry version info
    40  	DefaultRegistryVersionHeader = "Docker-Distribution-Api-Version"
    41  
    42  	// IndexHostname is the index hostname
    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  	// NotaryServer is the endpoint serving the Notary trust server
    50  	NotaryServer = "https://notary.docker.io"
    51  
    52  	// DefaultV2Registry is the URI of the default v2 registry
    53  	DefaultV2Registry = &url.URL{
    54  		Scheme: "https",
    55  		Host:   "registry-1.docker.io",
    56  	}
    57  )
    58  
    59  var (
    60  	// ErrInvalidRepositoryName is an error returned if the repository name did
    61  	// not have the correct form
    62  	ErrInvalidRepositoryName = errors.New("Invalid repository name (ex: \"registry.domain.tld/myrepos\")")
    63  
    64  	emptyServiceConfig = newServiceConfig(ServiceOptions{})
    65  )
    66  
    67  var (
    68  	validHostPortRegex = regexp.MustCompile(`^` + reference.DomainRegexp.String() + `$`)
    69  )
    70  
    71  // for mocking in unit tests
    72  var lookupIP = net.LookupIP
    73  
    74  // InstallCliFlags adds command-line options to the top-level flag parser for
    75  // the current process.
    76  func (options *ServiceOptions) InstallCliFlags(flags *pflag.FlagSet) {
    77  	mirrors := opts.NewNamedListOptsRef("registry-mirrors", &options.Mirrors, ValidateMirror)
    78  	insecureRegistries := opts.NewNamedListOptsRef("insecure-registries", &options.InsecureRegistries, ValidateIndexName)
    79  
    80  	flags.Var(mirrors, "registry-mirror", "Preferred Docker registry mirror")
    81  	flags.Var(insecureRegistries, "insecure-registry", "Enable insecure registry communication")
    82  
    83  	options.installCliPlatformFlags(flags)
    84  }
    85  
    86  // newServiceConfig returns a new instance of ServiceConfig
    87  func newServiceConfig(options ServiceOptions) *serviceConfig {
    88  	config := &serviceConfig{
    89  		ServiceConfig: registrytypes.ServiceConfig{
    90  			InsecureRegistryCIDRs: make([]*registrytypes.NetIPNet, 0),
    91  			IndexConfigs:          make(map[string]*registrytypes.IndexInfo, 0),
    92  			// Hack: Bypass setting the mirrors to IndexConfigs since they are going away
    93  			// and Mirrors are only for the official registry anyways.
    94  		},
    95  		V2Only: options.V2Only,
    96  	}
    97  
    98  	config.LoadMirrors(options.Mirrors)
    99  	config.LoadInsecureRegistries(options.InsecureRegistries)
   100  
   101  	return config
   102  }
   103  
   104  // LoadMirrors loads mirrors to config, after removing duplicates.
   105  // Returns an error if mirrors contains an invalid mirror.
   106  func (config *serviceConfig) LoadMirrors(mirrors []string) error {
   107  	mMap := map[string]struct{}{}
   108  	unique := []string{}
   109  
   110  	for _, mirror := range mirrors {
   111  		m, err := ValidateMirror(mirror)
   112  		if err != nil {
   113  			return err
   114  		}
   115  		if _, exist := mMap[m]; !exist {
   116  			mMap[m] = struct{}{}
   117  			unique = append(unique, m)
   118  		}
   119  	}
   120  
   121  	config.Mirrors = unique
   122  
   123  	// Configure public registry since mirrors may have changed.
   124  	config.IndexConfigs[IndexName] = &registrytypes.IndexInfo{
   125  		Name:     IndexName,
   126  		Mirrors:  config.Mirrors,
   127  		Secure:   true,
   128  		Official: true,
   129  	}
   130  
   131  	return nil
   132  }
   133  
   134  // LoadInsecureRegistries loads insecure registries to config
   135  func (config *serviceConfig) LoadInsecureRegistries(registries []string) error {
   136  	// Localhost is by default considered as an insecure registry
   137  	// This is a stop-gap for people who are running a private registry on localhost (especially on Boot2docker).
   138  	//
   139  	// TODO: should we deprecate this once it is easier for people to set up a TLS registry or change
   140  	// daemon flags on boot2docker?
   141  	registries = append(registries, "127.0.0.0/8")
   142  
   143  	// Store original InsecureRegistryCIDRs and IndexConfigs
   144  	// Clean InsecureRegistryCIDRs and IndexConfigs in config, as passed registries has all insecure registry info.
   145  	originalCIDRs := config.ServiceConfig.InsecureRegistryCIDRs
   146  	originalIndexInfos := config.ServiceConfig.IndexConfigs
   147  
   148  	config.ServiceConfig.InsecureRegistryCIDRs = make([]*registrytypes.NetIPNet, 0)
   149  	config.ServiceConfig.IndexConfigs = make(map[string]*registrytypes.IndexInfo, 0)
   150  
   151  skip:
   152  	for _, r := range registries {
   153  		// validate insecure registry
   154  		if _, err := ValidateIndexName(r); err != nil {
   155  			// before returning err, roll back to original data
   156  			config.ServiceConfig.InsecureRegistryCIDRs = originalCIDRs
   157  			config.ServiceConfig.IndexConfigs = originalIndexInfos
   158  			return err
   159  		}
   160  		if strings.HasPrefix(strings.ToLower(r), "http://") {
   161  			logrus.Warnf("insecure registry %s should not contain 'http://' and 'http://' has been removed from the insecure registry config", r)
   162  			r = r[7:]
   163  		} else if strings.HasPrefix(strings.ToLower(r), "https://") {
   164  			logrus.Warnf("insecure registry %s should not contain 'https://' and 'https://' has been removed from the insecure registry config", r)
   165  			r = r[8:]
   166  		} else if validateNoScheme(r) != nil {
   167  			// Insecure registry should not contain '://'
   168  			// before returning err, roll back to original data
   169  			config.ServiceConfig.InsecureRegistryCIDRs = originalCIDRs
   170  			config.ServiceConfig.IndexConfigs = originalIndexInfos
   171  			return fmt.Errorf("insecure registry %s should not contain '://'", r)
   172  		}
   173  		// Check if CIDR was passed to --insecure-registry
   174  		_, ipnet, err := net.ParseCIDR(r)
   175  		if err == nil {
   176  			// Valid CIDR. If ipnet is already in config.InsecureRegistryCIDRs, skip.
   177  			data := (*registrytypes.NetIPNet)(ipnet)
   178  			for _, value := range config.InsecureRegistryCIDRs {
   179  				if value.IP.String() == data.IP.String() && value.Mask.String() == data.Mask.String() {
   180  					continue skip
   181  				}
   182  			}
   183  			// ipnet is not found, add it in config.InsecureRegistryCIDRs
   184  			config.InsecureRegistryCIDRs = append(config.InsecureRegistryCIDRs, data)
   185  
   186  		} else {
   187  			if err := validateHostPort(r); err != nil {
   188  				config.ServiceConfig.InsecureRegistryCIDRs = originalCIDRs
   189  				config.ServiceConfig.IndexConfigs = originalIndexInfos
   190  				return fmt.Errorf("insecure registry %s is not valid: %v", r, err)
   191  
   192  			}
   193  			// Assume `host:port` if not CIDR.
   194  			config.IndexConfigs[r] = &registrytypes.IndexInfo{
   195  				Name:     r,
   196  				Mirrors:  make([]string, 0),
   197  				Secure:   false,
   198  				Official: false,
   199  			}
   200  		}
   201  	}
   202  
   203  	// Configure public registry.
   204  	config.IndexConfigs[IndexName] = &registrytypes.IndexInfo{
   205  		Name:     IndexName,
   206  		Mirrors:  config.Mirrors,
   207  		Secure:   true,
   208  		Official: true,
   209  	}
   210  
   211  	return nil
   212  }
   213  
   214  // isSecureIndex returns false if the provided indexName is part of the list of insecure registries
   215  // Insecure registries accept HTTP and/or accept HTTPS with certificates from unknown CAs.
   216  //
   217  // The list of insecure registries can contain an element with CIDR notation to specify a whole subnet.
   218  // If the subnet contains one of the IPs of the registry specified by indexName, the latter is considered
   219  // insecure.
   220  //
   221  // indexName should be a URL.Host (`host:port` or `host`) where the `host` part can be either a domain name
   222  // or an IP address. If it is a domain name, then it will be resolved in order to check if the IP is contained
   223  // in a subnet. If the resolving is not successful, isSecureIndex will only try to match hostname to any element
   224  // of insecureRegistries.
   225  func isSecureIndex(config *serviceConfig, indexName string) bool {
   226  	// Check for configured index, first.  This is needed in case isSecureIndex
   227  	// is called from anything besides newIndexInfo, in order to honor per-index configurations.
   228  	if index, ok := config.IndexConfigs[indexName]; ok {
   229  		return index.Secure
   230  	}
   231  
   232  	host, _, err := net.SplitHostPort(indexName)
   233  	if err != nil {
   234  		// assume indexName is of the form `host` without the port and go on.
   235  		host = indexName
   236  	}
   237  
   238  	addrs, err := lookupIP(host)
   239  	if err != nil {
   240  		ip := net.ParseIP(host)
   241  		if ip != nil {
   242  			addrs = []net.IP{ip}
   243  		}
   244  
   245  		// if ip == nil, then `host` is neither an IP nor it could be looked up,
   246  		// either because the index is unreachable, or because the index is behind an HTTP proxy.
   247  		// So, len(addrs) == 0 and we're not aborting.
   248  	}
   249  
   250  	// Try CIDR notation only if addrs has any elements, i.e. if `host`'s IP could be determined.
   251  	for _, addr := range addrs {
   252  		for _, ipnet := range config.InsecureRegistryCIDRs {
   253  			// check if the addr falls in the subnet
   254  			if (*net.IPNet)(ipnet).Contains(addr) {
   255  				return false
   256  			}
   257  		}
   258  	}
   259  
   260  	return true
   261  }
   262  
   263  // ValidateMirror validates an HTTP(S) registry mirror
   264  func ValidateMirror(val string) (string, error) {
   265  	uri, err := url.Parse(val)
   266  	if err != nil {
   267  		return "", fmt.Errorf("invalid mirror: %q is not a valid URI", val)
   268  	}
   269  	if uri.Scheme != "http" && uri.Scheme != "https" {
   270  		return "", fmt.Errorf("invalid mirror: unsupported scheme %q in %q", uri.Scheme, uri)
   271  	}
   272  	if (uri.Path != "" && uri.Path != "/") || uri.RawQuery != "" || uri.Fragment != "" {
   273  		return "", fmt.Errorf("invalid mirror: path, query, or fragment at end of the URI %q", uri)
   274  	}
   275  	if uri.User != nil {
   276  		// strip password from output
   277  		uri.User = url.UserPassword(uri.User.Username(), "xxxxx")
   278  		return "", fmt.Errorf("invalid mirror: username/password not allowed in URI %q", uri)
   279  	}
   280  	return strings.TrimSuffix(val, "/") + "/", nil
   281  }
   282  
   283  // ValidateIndexName validates an index name.
   284  func ValidateIndexName(val string) (string, error) {
   285  	// TODO: upstream this to check to reference package
   286  	if val == "index.docker.io" {
   287  		val = "docker.io"
   288  	}
   289  	if strings.HasPrefix(val, "-") || strings.HasSuffix(val, "-") {
   290  		return "", fmt.Errorf("Invalid index name (%s). Cannot begin or end with a hyphen.", val)
   291  	}
   292  	return val, nil
   293  }
   294  
   295  func validateNoScheme(reposName string) error {
   296  	if strings.Contains(reposName, "://") {
   297  		// It cannot contain a scheme!
   298  		return ErrInvalidRepositoryName
   299  	}
   300  	return nil
   301  }
   302  
   303  func validateHostPort(s string) error {
   304  	// Split host and port, and in case s can not be splitted, assume host only
   305  	host, port, err := net.SplitHostPort(s)
   306  	if err != nil {
   307  		host = s
   308  		port = ""
   309  	}
   310  	// If match against the `host:port` pattern fails,
   311  	// it might be `IPv6:port`, which will be captured by net.ParseIP(host)
   312  	if !validHostPortRegex.MatchString(s) && net.ParseIP(host) == nil {
   313  		return fmt.Errorf("invalid host %q", host)
   314  	}
   315  	if port != "" {
   316  		v, err := strconv.Atoi(port)
   317  		if err != nil {
   318  			return err
   319  		}
   320  		if v < 0 || v > 65535 {
   321  			return fmt.Errorf("invalid port %q", port)
   322  		}
   323  	}
   324  	return nil
   325  }
   326  
   327  // newIndexInfo returns IndexInfo configuration from indexName
   328  func newIndexInfo(config *serviceConfig, indexName string) (*registrytypes.IndexInfo, error) {
   329  	var err error
   330  	indexName, err = ValidateIndexName(indexName)
   331  	if err != nil {
   332  		return nil, err
   333  	}
   334  
   335  	// Return any configured index info, first.
   336  	if index, ok := config.IndexConfigs[indexName]; ok {
   337  		return index, nil
   338  	}
   339  
   340  	// Construct a non-configured index info.
   341  	index := &registrytypes.IndexInfo{
   342  		Name:     indexName,
   343  		Mirrors:  make([]string, 0),
   344  		Official: false,
   345  	}
   346  	index.Secure = isSecureIndex(config, indexName)
   347  	return index, nil
   348  }
   349  
   350  // GetAuthConfigKey special-cases using the full index address of the official
   351  // index as the AuthConfig key, and uses the (host)name[:port] for private indexes.
   352  func GetAuthConfigKey(index *registrytypes.IndexInfo) string {
   353  	if index.Official {
   354  		return IndexServer
   355  	}
   356  	return index.Name
   357  }
   358  
   359  // newRepositoryInfo validates and breaks down a repository name into a RepositoryInfo
   360  func newRepositoryInfo(config *serviceConfig, name reference.Named) (*RepositoryInfo, error) {
   361  	index, err := newIndexInfo(config, reference.Domain(name))
   362  	if err != nil {
   363  		return nil, err
   364  	}
   365  	official := !strings.ContainsRune(reference.FamiliarName(name), '/')
   366  
   367  	return &RepositoryInfo{
   368  		Name:     reference.TrimNamed(name),
   369  		Index:    index,
   370  		Official: official,
   371  	}, nil
   372  }
   373  
   374  // ParseRepositoryInfo performs the breakdown of a repository name into a RepositoryInfo, but
   375  // lacks registry configuration.
   376  func ParseRepositoryInfo(reposName reference.Named) (*RepositoryInfo, error) {
   377  	return newRepositoryInfo(emptyServiceConfig, reposName)
   378  }
   379  
   380  // ParseSearchIndexInfo will use repository name to get back an indexInfo.
   381  func ParseSearchIndexInfo(reposName string) (*registrytypes.IndexInfo, error) {
   382  	indexName, _ := splitReposSearchTerm(reposName)
   383  
   384  	indexInfo, err := newIndexInfo(emptyServiceConfig, indexName)
   385  	if err != nil {
   386  		return nil, err
   387  	}
   388  	return indexInfo, nil
   389  }