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