github.com/endophage/docker@v1.4.2-0.20161027011718-242853499895/registry/config.go (about)

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