github.com/miqui/docker@v1.9.1/registry/config.go (about)

     1  package registry
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"net"
     8  	"net/url"
     9  	"strings"
    10  
    11  	"github.com/docker/distribution/registry/api/v2"
    12  	"github.com/docker/docker/image"
    13  	"github.com/docker/docker/opts"
    14  	flag "github.com/docker/docker/pkg/mflag"
    15  )
    16  
    17  // Options holds command line options.
    18  type Options struct {
    19  	Mirrors            opts.ListOpts
    20  	InsecureRegistries opts.ListOpts
    21  }
    22  
    23  const (
    24  	// DefaultNamespace is the default namespace
    25  	DefaultNamespace = "docker.io"
    26  	// DefaultRegistryVersionHeader is the name of the default HTTP header
    27  	// that carries Registry version info
    28  	DefaultRegistryVersionHeader = "Docker-Distribution-Api-Version"
    29  
    30  	// IndexServer is the v1 registry server used for user auth + account creation
    31  	IndexServer = DefaultV1Registry + "/v1/"
    32  	// IndexName is the name of the index
    33  	IndexName = "docker.io"
    34  
    35  	// NotaryServer is the endpoint serving the Notary trust server
    36  	NotaryServer = "https://notary.docker.io"
    37  
    38  	// IndexServer = "https://registry-stage.hub.docker.com/v1/"
    39  )
    40  
    41  var (
    42  	// ErrInvalidRepositoryName is an error returned if the repository name did
    43  	// not have the correct form
    44  	ErrInvalidRepositoryName = errors.New("Invalid repository name (ex: \"registry.domain.tld/myrepos\")")
    45  
    46  	emptyServiceConfig = NewServiceConfig(nil)
    47  
    48  	// V2Only controls access to legacy registries.  If it is set to true via the
    49  	// command line flag the daemon will not attempt to contact v1 legacy registries
    50  	V2Only = false
    51  )
    52  
    53  // InstallFlags adds command-line options to the top-level flag parser for
    54  // the current process.
    55  func (options *Options) InstallFlags(cmd *flag.FlagSet, usageFn func(string) string) {
    56  	options.Mirrors = opts.NewListOpts(ValidateMirror)
    57  	cmd.Var(&options.Mirrors, []string{"-registry-mirror"}, usageFn("Preferred Docker registry mirror"))
    58  	options.InsecureRegistries = opts.NewListOpts(ValidateIndexName)
    59  	cmd.Var(&options.InsecureRegistries, []string{"-insecure-registry"}, usageFn("Enable insecure registry communication"))
    60  	cmd.BoolVar(&V2Only, []string{"-disable-legacy-registry"}, false, "Do not contact legacy registries")
    61  }
    62  
    63  type netIPNet net.IPNet
    64  
    65  func (ipnet *netIPNet) MarshalJSON() ([]byte, error) {
    66  	return json.Marshal((*net.IPNet)(ipnet).String())
    67  }
    68  
    69  func (ipnet *netIPNet) UnmarshalJSON(b []byte) (err error) {
    70  	var ipnetStr string
    71  	if err = json.Unmarshal(b, &ipnetStr); err == nil {
    72  		var cidr *net.IPNet
    73  		if _, cidr, err = net.ParseCIDR(ipnetStr); err == nil {
    74  			*ipnet = netIPNet(*cidr)
    75  		}
    76  	}
    77  	return
    78  }
    79  
    80  // ServiceConfig stores daemon registry services configuration.
    81  type ServiceConfig struct {
    82  	InsecureRegistryCIDRs []*netIPNet           `json:"InsecureRegistryCIDRs"`
    83  	IndexConfigs          map[string]*IndexInfo `json:"IndexConfigs"`
    84  	Mirrors               []string
    85  }
    86  
    87  // NewServiceConfig returns a new instance of ServiceConfig
    88  func NewServiceConfig(options *Options) *ServiceConfig {
    89  	if options == nil {
    90  		options = &Options{
    91  			Mirrors:            opts.NewListOpts(nil),
    92  			InsecureRegistries: opts.NewListOpts(nil),
    93  		}
    94  	}
    95  
    96  	// Localhost is by default considered as an insecure registry
    97  	// This is a stop-gap for people who are running a private registry on localhost (especially on Boot2docker).
    98  	//
    99  	// TODO: should we deprecate this once it is easier for people to set up a TLS registry or change
   100  	// daemon flags on boot2docker?
   101  	options.InsecureRegistries.Set("127.0.0.0/8")
   102  
   103  	config := &ServiceConfig{
   104  		InsecureRegistryCIDRs: make([]*netIPNet, 0),
   105  		IndexConfigs:          make(map[string]*IndexInfo, 0),
   106  		// Hack: Bypass setting the mirrors to IndexConfigs since they are going away
   107  		// and Mirrors are only for the official registry anyways.
   108  		Mirrors: options.Mirrors.GetAll(),
   109  	}
   110  	// Split --insecure-registry into CIDR and registry-specific settings.
   111  	for _, r := range options.InsecureRegistries.GetAll() {
   112  		// Check if CIDR was passed to --insecure-registry
   113  		_, ipnet, err := net.ParseCIDR(r)
   114  		if err == nil {
   115  			// Valid CIDR.
   116  			config.InsecureRegistryCIDRs = append(config.InsecureRegistryCIDRs, (*netIPNet)(ipnet))
   117  		} else {
   118  			// Assume `host:port` if not CIDR.
   119  			config.IndexConfigs[r] = &IndexInfo{
   120  				Name:     r,
   121  				Mirrors:  make([]string, 0),
   122  				Secure:   false,
   123  				Official: false,
   124  			}
   125  		}
   126  	}
   127  
   128  	// Configure public registry.
   129  	config.IndexConfigs[IndexName] = &IndexInfo{
   130  		Name:     IndexName,
   131  		Mirrors:  config.Mirrors,
   132  		Secure:   true,
   133  		Official: true,
   134  	}
   135  
   136  	return config
   137  }
   138  
   139  // isSecureIndex returns false if the provided indexName is part of the list of insecure registries
   140  // Insecure registries accept HTTP and/or accept HTTPS with certificates from unknown CAs.
   141  //
   142  // The list of insecure registries can contain an element with CIDR notation to specify a whole subnet.
   143  // If the subnet contains one of the IPs of the registry specified by indexName, the latter is considered
   144  // insecure.
   145  //
   146  // indexName should be a URL.Host (`host:port` or `host`) where the `host` part can be either a domain name
   147  // or an IP address. If it is a domain name, then it will be resolved in order to check if the IP is contained
   148  // in a subnet. If the resolving is not successful, isSecureIndex will only try to match hostname to any element
   149  // of insecureRegistries.
   150  func (config *ServiceConfig) isSecureIndex(indexName string) bool {
   151  	// Check for configured index, first.  This is needed in case isSecureIndex
   152  	// is called from anything besides NewIndexInfo, in order to honor per-index configurations.
   153  	if index, ok := config.IndexConfigs[indexName]; ok {
   154  		return index.Secure
   155  	}
   156  
   157  	host, _, err := net.SplitHostPort(indexName)
   158  	if err != nil {
   159  		// assume indexName is of the form `host` without the port and go on.
   160  		host = indexName
   161  	}
   162  
   163  	addrs, err := lookupIP(host)
   164  	if err != nil {
   165  		ip := net.ParseIP(host)
   166  		if ip != nil {
   167  			addrs = []net.IP{ip}
   168  		}
   169  
   170  		// if ip == nil, then `host` is neither an IP nor it could be looked up,
   171  		// either because the index is unreachable, or because the index is behind an HTTP proxy.
   172  		// So, len(addrs) == 0 and we're not aborting.
   173  	}
   174  
   175  	// Try CIDR notation only if addrs has any elements, i.e. if `host`'s IP could be determined.
   176  	for _, addr := range addrs {
   177  		for _, ipnet := range config.InsecureRegistryCIDRs {
   178  			// check if the addr falls in the subnet
   179  			if (*net.IPNet)(ipnet).Contains(addr) {
   180  				return false
   181  			}
   182  		}
   183  	}
   184  
   185  	return true
   186  }
   187  
   188  // ValidateMirror validates an HTTP(S) registry mirror
   189  func ValidateMirror(val string) (string, error) {
   190  	uri, err := url.Parse(val)
   191  	if err != nil {
   192  		return "", fmt.Errorf("%s is not a valid URI", val)
   193  	}
   194  
   195  	if uri.Scheme != "http" && uri.Scheme != "https" {
   196  		return "", fmt.Errorf("Unsupported scheme %s", uri.Scheme)
   197  	}
   198  
   199  	if uri.Path != "" || uri.RawQuery != "" || uri.Fragment != "" {
   200  		return "", fmt.Errorf("Unsupported path/query/fragment at end of the URI")
   201  	}
   202  
   203  	return fmt.Sprintf("%s://%s/", uri.Scheme, uri.Host), nil
   204  }
   205  
   206  // ValidateIndexName validates an index name.
   207  func ValidateIndexName(val string) (string, error) {
   208  	// 'index.docker.io' => 'docker.io'
   209  	if val == "index."+IndexName {
   210  		val = IndexName
   211  	}
   212  	if strings.HasPrefix(val, "-") || strings.HasSuffix(val, "-") {
   213  		return "", fmt.Errorf("Invalid index name (%s). Cannot begin or end with a hyphen.", val)
   214  	}
   215  	// *TODO: Check if valid hostname[:port]/ip[:port]?
   216  	return val, nil
   217  }
   218  
   219  func validateRemoteName(remoteName string) error {
   220  
   221  	if !strings.Contains(remoteName, "/") {
   222  
   223  		// the repository name must not be a valid image ID
   224  		if err := image.ValidateID(remoteName); err == nil {
   225  			return fmt.Errorf("Invalid repository name (%s), cannot specify 64-byte hexadecimal strings", remoteName)
   226  		}
   227  	}
   228  
   229  	return v2.ValidateRepositoryName(remoteName)
   230  }
   231  
   232  func validateNoSchema(reposName string) error {
   233  	if strings.Contains(reposName, "://") {
   234  		// It cannot contain a scheme!
   235  		return ErrInvalidRepositoryName
   236  	}
   237  	return nil
   238  }
   239  
   240  // ValidateRepositoryName validates a repository name
   241  func ValidateRepositoryName(reposName string) error {
   242  	_, _, err := loadRepositoryName(reposName, true)
   243  	return err
   244  }
   245  
   246  // loadRepositoryName returns the repo name splitted into index name
   247  // and remote repo name. It returns an error if the name is not valid.
   248  func loadRepositoryName(reposName string, checkRemoteName bool) (string, string, error) {
   249  	if err := validateNoSchema(reposName); err != nil {
   250  		return "", "", err
   251  	}
   252  	indexName, remoteName := splitReposName(reposName)
   253  
   254  	var err error
   255  	if indexName, err = ValidateIndexName(indexName); err != nil {
   256  		return "", "", err
   257  	}
   258  	if checkRemoteName {
   259  		if err = validateRemoteName(remoteName); err != nil {
   260  			return "", "", err
   261  		}
   262  	}
   263  	return indexName, remoteName, nil
   264  }
   265  
   266  // NewIndexInfo returns IndexInfo configuration from indexName
   267  func (config *ServiceConfig) NewIndexInfo(indexName string) (*IndexInfo, error) {
   268  	var err error
   269  	indexName, err = ValidateIndexName(indexName)
   270  	if err != nil {
   271  		return nil, err
   272  	}
   273  
   274  	// Return any configured index info, first.
   275  	if index, ok := config.IndexConfigs[indexName]; ok {
   276  		return index, nil
   277  	}
   278  
   279  	// Construct a non-configured index info.
   280  	index := &IndexInfo{
   281  		Name:     indexName,
   282  		Mirrors:  make([]string, 0),
   283  		Official: false,
   284  	}
   285  	index.Secure = config.isSecureIndex(indexName)
   286  	return index, nil
   287  }
   288  
   289  // GetAuthConfigKey special-cases using the full index address of the official
   290  // index as the AuthConfig key, and uses the (host)name[:port] for private indexes.
   291  func (index *IndexInfo) GetAuthConfigKey() string {
   292  	if index.Official {
   293  		return IndexServer
   294  	}
   295  	return index.Name
   296  }
   297  
   298  // splitReposName breaks a reposName into an index name and remote name
   299  func splitReposName(reposName string) (string, string) {
   300  	nameParts := strings.SplitN(reposName, "/", 2)
   301  	var indexName, remoteName string
   302  	if len(nameParts) == 1 || (!strings.Contains(nameParts[0], ".") &&
   303  		!strings.Contains(nameParts[0], ":") && nameParts[0] != "localhost") {
   304  		// This is a Docker Index repos (ex: samalba/hipache or ubuntu)
   305  		// 'docker.io'
   306  		indexName = IndexName
   307  		remoteName = reposName
   308  	} else {
   309  		indexName = nameParts[0]
   310  		remoteName = nameParts[1]
   311  	}
   312  	return indexName, remoteName
   313  }
   314  
   315  // NewRepositoryInfo validates and breaks down a repository name into a RepositoryInfo
   316  func (config *ServiceConfig) NewRepositoryInfo(reposName string, bySearch bool) (*RepositoryInfo, error) {
   317  	indexName, remoteName, err := loadRepositoryName(reposName, !bySearch)
   318  	if err != nil {
   319  		return nil, err
   320  	}
   321  
   322  	repoInfo := &RepositoryInfo{
   323  		RemoteName: remoteName,
   324  	}
   325  
   326  	repoInfo.Index, err = config.NewIndexInfo(indexName)
   327  	if err != nil {
   328  		return nil, err
   329  	}
   330  
   331  	if repoInfo.Index.Official {
   332  		normalizedName := normalizeLibraryRepoName(repoInfo.RemoteName)
   333  
   334  		repoInfo.LocalName = normalizedName
   335  		repoInfo.RemoteName = normalizedName
   336  		// If the normalized name does not contain a '/' (e.g. "foo")
   337  		// then it is an official repo.
   338  		if strings.IndexRune(normalizedName, '/') == -1 {
   339  			repoInfo.Official = true
   340  			// Fix up remote name for official repos.
   341  			repoInfo.RemoteName = "library/" + normalizedName
   342  		}
   343  
   344  		repoInfo.CanonicalName = "docker.io/" + repoInfo.RemoteName
   345  	} else {
   346  		repoInfo.LocalName = localNameFromRemote(repoInfo.Index.Name, repoInfo.RemoteName)
   347  		repoInfo.CanonicalName = repoInfo.LocalName
   348  
   349  	}
   350  
   351  	return repoInfo, nil
   352  }
   353  
   354  // GetSearchTerm special-cases using local name for official index, and
   355  // remote name for private indexes.
   356  func (repoInfo *RepositoryInfo) GetSearchTerm() string {
   357  	if repoInfo.Index.Official {
   358  		return repoInfo.LocalName
   359  	}
   360  	return repoInfo.RemoteName
   361  }
   362  
   363  // ParseRepositoryInfo performs the breakdown of a repository name into a RepositoryInfo, but
   364  // lacks registry configuration.
   365  func ParseRepositoryInfo(reposName string) (*RepositoryInfo, error) {
   366  	return emptyServiceConfig.NewRepositoryInfo(reposName, false)
   367  }
   368  
   369  // ParseIndexInfo will use repository name to get back an indexInfo.
   370  func ParseIndexInfo(reposName string) (*IndexInfo, error) {
   371  	indexName, _ := splitReposName(reposName)
   372  
   373  	indexInfo, err := emptyServiceConfig.NewIndexInfo(indexName)
   374  	if err != nil {
   375  		return nil, err
   376  	}
   377  	return indexInfo, nil
   378  }
   379  
   380  // NormalizeLocalName transforms a repository name into a normalize LocalName
   381  // Passes through the name without transformation on error (image id, etc)
   382  // It does not use the repository info because we don't want to load
   383  // the repository index and do request over the network.
   384  func NormalizeLocalName(name string) string {
   385  	indexName, remoteName, err := loadRepositoryName(name, true)
   386  	if err != nil {
   387  		return name
   388  	}
   389  
   390  	var officialIndex bool
   391  	// Return any configured index info, first.
   392  	if index, ok := emptyServiceConfig.IndexConfigs[indexName]; ok {
   393  		officialIndex = index.Official
   394  	}
   395  
   396  	if officialIndex {
   397  		return normalizeLibraryRepoName(remoteName)
   398  	}
   399  	return localNameFromRemote(indexName, remoteName)
   400  }
   401  
   402  // normalizeLibraryRepoName removes the library prefix from
   403  // the repository name for official repos.
   404  func normalizeLibraryRepoName(name string) string {
   405  	if strings.HasPrefix(name, "library/") {
   406  		// If pull "library/foo", it's stored locally under "foo"
   407  		name = strings.SplitN(name, "/", 2)[1]
   408  	}
   409  	return name
   410  }
   411  
   412  // localNameFromRemote combines the index name and the repo remote name
   413  // to generate a repo local name.
   414  func localNameFromRemote(indexName, remoteName string) string {
   415  	return indexName + "/" + remoteName
   416  }