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