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"
19 // ServiceOptions holds command line options.
20 type ServiceOptions struct {
21 AllowNondistributableArtifacts []string `json:"allow-nondistributable-artifacts,omitempty"`
22 Mirrors []string `json:"registry-mirrors,omitempty"`
23 InsecureRegistries []string `json:"insecure-registries,omitempty"`
25 // V2Only controls access to legacy registries. If it is set to true via the
26 // command line flag the daemon will not attempt to contact v1 legacy registries
27 V2Only bool `json:"disable-legacy-registry,omitempty"`
30 // serviceConfig holds daemon configuration for the registry service.
31 type serviceConfig struct {
32 registrytypes.ServiceConfig
37 // DefaultNamespace is the default namespace
38 DefaultNamespace = "docker.io"
39 // DefaultRegistryVersionHeader is the name of the default HTTP header
40 // that carries Registry version info
41 DefaultRegistryVersionHeader = "Docker-Distribution-Api-Version"
43 // IndexHostname is the index hostname
44 IndexHostname = "index.docker.io"
45 // IndexServer is used for user auth and image search
46 IndexServer = "https://" + IndexHostname + "/v1/"
47 // IndexName is the name of the index
48 IndexName = "docker.io"
50 // NotaryServer is the endpoint serving the Notary trust server
51 NotaryServer = "https://notary.docker.io"
53 // DefaultV2Registry is the URI of the default v2 registry
54 DefaultV2Registry = &url.URL{
56 Host: "registry-1.docker.io",
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\")")
65 emptyServiceConfig = newServiceConfig(ServiceOptions{})
69 validHostPortRegex = regexp.MustCompile(`^` + reference.DomainRegexp.String() + `$`)
72 // for mocking in unit tests
73 var lookupIP = net.LookupIP
75 // InstallCliFlags adds command-line options to the top-level flag parser for
76 // the current process.
77 func (options *ServiceOptions) InstallCliFlags(flags *pflag.FlagSet) {
78 ana := opts.NewNamedListOptsRef("allow-nondistributable-artifacts", &options.AllowNondistributableArtifacts, ValidateIndexName)
79 mirrors := opts.NewNamedListOptsRef("registry-mirrors", &options.Mirrors, ValidateMirror)
80 insecureRegistries := opts.NewNamedListOptsRef("insecure-registries", &options.InsecureRegistries, ValidateIndexName)
82 flags.Var(ana, "allow-nondistributable-artifacts", "Allow push of nondistributable artifacts to registry")
83 flags.Var(mirrors, "registry-mirror", "Preferred Docker registry mirror")
84 flags.Var(insecureRegistries, "insecure-registry", "Enable insecure registry communication")
86 options.installCliPlatformFlags(flags)
89 // newServiceConfig returns a new instance of ServiceConfig
90 func newServiceConfig(options ServiceOptions) *serviceConfig {
91 config := &serviceConfig{
92 ServiceConfig: registrytypes.ServiceConfig{
93 InsecureRegistryCIDRs: make([]*registrytypes.NetIPNet, 0),
94 IndexConfigs: make(map[string]*registrytypes.IndexInfo, 0),
95 // Hack: Bypass setting the mirrors to IndexConfigs since they are going away
96 // and Mirrors are only for the official registry anyways.
98 V2Only: options.V2Only,
101 config.LoadAllowNondistributableArtifacts(options.AllowNondistributableArtifacts)
102 config.LoadMirrors(options.Mirrors)
103 config.LoadInsecureRegistries(options.InsecureRegistries)
108 // LoadAllowNondistributableArtifacts loads allow-nondistributable-artifacts registries into config.
109 func (config *serviceConfig) LoadAllowNondistributableArtifacts(registries []string) error {
110 cidrs := map[string]*registrytypes.NetIPNet{}
111 hostnames := map[string]bool{}
113 for _, r := range registries {
114 if _, err := ValidateIndexName(r); err != nil {
117 if validateNoScheme(r) != nil {
118 return fmt.Errorf("allow-nondistributable-artifacts registry %s should not contain '://'", r)
121 if _, ipnet, err := net.ParseCIDR(r); err == nil {
123 cidrs[ipnet.String()] = (*registrytypes.NetIPNet)(ipnet)
124 } else if err := validateHostPort(r); err == nil {
125 // Must be `host:port` if not CIDR.
128 return fmt.Errorf("allow-nondistributable-artifacts registry %s is not valid: %v", r, err)
132 config.AllowNondistributableArtifactsCIDRs = make([]*(registrytypes.NetIPNet), 0)
133 for _, c := range cidrs {
134 config.AllowNondistributableArtifactsCIDRs = append(config.AllowNondistributableArtifactsCIDRs, c)
137 config.AllowNondistributableArtifactsHostnames = make([]string, 0)
138 for h := range hostnames {
139 config.AllowNondistributableArtifactsHostnames = append(config.AllowNondistributableArtifactsHostnames, h)
145 // LoadMirrors loads mirrors to config, after removing duplicates.
146 // Returns an error if mirrors contains an invalid mirror.
147 func (config *serviceConfig) LoadMirrors(mirrors []string) error {
148 mMap := map[string]struct{}{}
151 for _, mirror := range mirrors {
152 m, err := ValidateMirror(mirror)
156 if _, exist := mMap[m]; !exist {
158 unique = append(unique, m)
162 config.Mirrors = unique
164 // Configure public registry since mirrors may have changed.
165 config.IndexConfigs[IndexName] = ®istrytypes.IndexInfo{
167 Mirrors: config.Mirrors,
175 // LoadInsecureRegistries loads insecure registries to config
176 func (config *serviceConfig) LoadInsecureRegistries(registries []string) error {
177 // Localhost is by default considered as an insecure registry
178 // This is a stop-gap for people who are running a private registry on localhost (especially on Boot2docker).
180 // TODO: should we deprecate this once it is easier for people to set up a TLS registry or change
181 // daemon flags on boot2docker?
182 registries = append(registries, "127.0.0.0/8")
184 // Store original InsecureRegistryCIDRs and IndexConfigs
185 // Clean InsecureRegistryCIDRs and IndexConfigs in config, as passed registries has all insecure registry info.
186 originalCIDRs := config.ServiceConfig.InsecureRegistryCIDRs
187 originalIndexInfos := config.ServiceConfig.IndexConfigs
189 config.ServiceConfig.InsecureRegistryCIDRs = make([]*registrytypes.NetIPNet, 0)
190 config.ServiceConfig.IndexConfigs = make(map[string]*registrytypes.IndexInfo, 0)
193 for _, r := range registries {
194 // validate insecure registry
195 if _, err := ValidateIndexName(r); err != nil {
196 // before returning err, roll back to original data
197 config.ServiceConfig.InsecureRegistryCIDRs = originalCIDRs
198 config.ServiceConfig.IndexConfigs = originalIndexInfos
201 if strings.HasPrefix(strings.ToLower(r), "http://") {
202 logrus.Warnf("insecure registry %s should not contain 'http://' and 'http://' has been removed from the insecure registry config", r)
204 } else if strings.HasPrefix(strings.ToLower(r), "https://") {
205 logrus.Warnf("insecure registry %s should not contain 'https://' and 'https://' has been removed from the insecure registry config", r)
207 } else if validateNoScheme(r) != nil {
208 // Insecure registry should not contain '://'
209 // before returning err, roll back to original data
210 config.ServiceConfig.InsecureRegistryCIDRs = originalCIDRs
211 config.ServiceConfig.IndexConfigs = originalIndexInfos
212 return fmt.Errorf("insecure registry %s should not contain '://'", r)
214 // Check if CIDR was passed to --insecure-registry
215 _, ipnet, err := net.ParseCIDR(r)
217 // Valid CIDR. If ipnet is already in config.InsecureRegistryCIDRs, skip.
218 data := (*registrytypes.NetIPNet)(ipnet)
219 for _, value := range config.InsecureRegistryCIDRs {
220 if value.IP.String() == data.IP.String() && value.Mask.String() == data.Mask.String() {
224 // ipnet is not found, add it in config.InsecureRegistryCIDRs
225 config.InsecureRegistryCIDRs = append(config.InsecureRegistryCIDRs, data)
228 if err := validateHostPort(r); err != nil {
229 config.ServiceConfig.InsecureRegistryCIDRs = originalCIDRs
230 config.ServiceConfig.IndexConfigs = originalIndexInfos
231 return fmt.Errorf("insecure registry %s is not valid: %v", r, err)
234 // Assume `host:port` if not CIDR.
235 config.IndexConfigs[r] = ®istrytypes.IndexInfo{
237 Mirrors: make([]string, 0),
244 // Configure public registry.
245 config.IndexConfigs[IndexName] = ®istrytypes.IndexInfo{
247 Mirrors: config.Mirrors,
255 // allowNondistributableArtifacts returns true if the provided hostname is part of the list of regsitries
256 // that allow push of nondistributable artifacts.
258 // The list can contain elements with CIDR notation to specify a whole subnet. If the subnet contains an IP
259 // of the registry specified by hostname, true is returned.
261 // hostname should be a URL.Host (`host:port` or `host`) where the `host` part can be either a domain name
262 // or an IP address. If it is a domain name, then it will be resolved to IP addresses for matching. If
263 // resolution fails, CIDR matching is not performed.
264 func allowNondistributableArtifacts(config *serviceConfig, hostname string) bool {
265 for _, h := range config.AllowNondistributableArtifactsHostnames {
271 return isCIDRMatch(config.AllowNondistributableArtifactsCIDRs, hostname)
274 // isSecureIndex returns false if the provided indexName is part of the list of insecure registries
275 // Insecure registries accept HTTP and/or accept HTTPS with certificates from unknown CAs.
277 // The list of insecure registries can contain an element with CIDR notation to specify a whole subnet.
278 // If the subnet contains one of the IPs of the registry specified by indexName, the latter is considered
281 // indexName should be a URL.Host (`host:port` or `host`) where the `host` part can be either a domain name
282 // or an IP address. If it is a domain name, then it will be resolved in order to check if the IP is contained
283 // in a subnet. If the resolving is not successful, isSecureIndex will only try to match hostname to any element
284 // of insecureRegistries.
285 func isSecureIndex(config *serviceConfig, indexName string) bool {
286 // Check for configured index, first. This is needed in case isSecureIndex
287 // is called from anything besides newIndexInfo, in order to honor per-index configurations.
288 if index, ok := config.IndexConfigs[indexName]; ok {
292 return !isCIDRMatch(config.InsecureRegistryCIDRs, indexName)
295 // isCIDRMatch returns true if URLHost matches an element of cidrs. URLHost is a URL.Host (`host:port` or `host`)
296 // where the `host` part can be either a domain name or an IP address. If it is a domain name, then it will be
297 // resolved to IP addresses for matching. If resolution fails, false is returned.
298 func isCIDRMatch(cidrs []*registrytypes.NetIPNet, URLHost string) bool {
299 host, _, err := net.SplitHostPort(URLHost)
301 // Assume URLHost is of the form `host` without the port and go on.
305 addrs, err := lookupIP(host)
307 ip := net.ParseIP(host)
312 // if ip == nil, then `host` is neither an IP nor it could be looked up,
313 // either because the index is unreachable, or because the index is behind an HTTP proxy.
314 // So, len(addrs) == 0 and we're not aborting.
317 // Try CIDR notation only if addrs has any elements, i.e. if `host`'s IP could be determined.
318 for _, addr := range addrs {
319 for _, ipnet := range cidrs {
320 // check if the addr falls in the subnet
321 if (*net.IPNet)(ipnet).Contains(addr) {
330 // ValidateMirror validates an HTTP(S) registry mirror
331 func ValidateMirror(val string) (string, error) {
332 uri, err := url.Parse(val)
334 return "", fmt.Errorf("invalid mirror: %q is not a valid URI", val)
336 if uri.Scheme != "http" && uri.Scheme != "https" {
337 return "", fmt.Errorf("invalid mirror: unsupported scheme %q in %q", uri.Scheme, uri)
339 if (uri.Path != "" && uri.Path != "/") || uri.RawQuery != "" || uri.Fragment != "" {
340 return "", fmt.Errorf("invalid mirror: path, query, or fragment at end of the URI %q", uri)
343 // strip password from output
344 uri.User = url.UserPassword(uri.User.Username(), "xxxxx")
345 return "", fmt.Errorf("invalid mirror: username/password not allowed in URI %q", uri)
347 return strings.TrimSuffix(val, "/") + "/", nil
350 // ValidateIndexName validates an index name.
351 func ValidateIndexName(val string) (string, error) {
352 // TODO: upstream this to check to reference package
353 if val == "index.docker.io" {
356 if strings.HasPrefix(val, "-") || strings.HasSuffix(val, "-") {
357 return "", fmt.Errorf("Invalid index name (%s). Cannot begin or end with a hyphen.", val)
362 func validateNoScheme(reposName string) error {
363 if strings.Contains(reposName, "://") {
364 // It cannot contain a scheme!
365 return ErrInvalidRepositoryName
370 func validateHostPort(s string) error {
371 // Split host and port, and in case s can not be splitted, assume host only
372 host, port, err := net.SplitHostPort(s)
377 // If match against the `host:port` pattern fails,
378 // it might be `IPv6:port`, which will be captured by net.ParseIP(host)
379 if !validHostPortRegex.MatchString(s) && net.ParseIP(host) == nil {
380 return fmt.Errorf("invalid host %q", host)
383 v, err := strconv.Atoi(port)
387 if v < 0 || v > 65535 {
388 return fmt.Errorf("invalid port %q", port)
394 // newIndexInfo returns IndexInfo configuration from indexName
395 func newIndexInfo(config *serviceConfig, indexName string) (*registrytypes.IndexInfo, error) {
397 indexName, err = ValidateIndexName(indexName)
402 // Return any configured index info, first.
403 if index, ok := config.IndexConfigs[indexName]; ok {
407 // Construct a non-configured index info.
408 index := ®istrytypes.IndexInfo{
410 Mirrors: make([]string, 0),
413 index.Secure = isSecureIndex(config, indexName)
417 // GetAuthConfigKey special-cases using the full index address of the official
418 // index as the AuthConfig key, and uses the (host)name[:port] for private indexes.
419 func GetAuthConfigKey(index *registrytypes.IndexInfo) string {
426 // newRepositoryInfo validates and breaks down a repository name into a RepositoryInfo
427 func newRepositoryInfo(config *serviceConfig, name reference.Named) (*RepositoryInfo, error) {
428 index, err := newIndexInfo(config, reference.Domain(name))
432 official := !strings.ContainsRune(reference.FamiliarName(name), '/')
434 return &RepositoryInfo{
435 Name: reference.TrimNamed(name),
441 // ParseRepositoryInfo performs the breakdown of a repository name into a RepositoryInfo, but
442 // lacks registry configuration.
443 func ParseRepositoryInfo(reposName reference.Named) (*RepositoryInfo, error) {
444 return newRepositoryInfo(emptyServiceConfig, reposName)
447 // ParseSearchIndexInfo will use repository name to get back an indexInfo.
448 func ParseSearchIndexInfo(reposName string) (*registrytypes.IndexInfo, error) {
449 indexName, _ := splitReposSearchTerm(reposName)
451 indexInfo, err := newIndexInfo(emptyServiceConfig, indexName)
455 return indexInfo, nil