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