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