github.com/ld86/docker@v1.7.1-rc3/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/image" 13 "github.com/docker/docker/opts" 14 flag "github.com/docker/docker/pkg/mflag" 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"}, "Preferred Docker registry mirror") 52 options.InsecureRegistries = opts.NewListOpts(ValidateIndexName) 53 flag.Var(&options.InsecureRegistries, []string{"-insecure-registry"}, "Enable insecure registry communication") 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 ipnetStr string 64 if err = json.Unmarshal(b, &ipnetStr); err == nil { 65 var cidr *net.IPNet 66 if _, cidr, err = net.ParseCIDR(ipnetStr); 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/", 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 if strings.HasPrefix(val, "-") || strings.HasSuffix(val, "-") { 202 return "", fmt.Errorf("Invalid index name (%s). Cannot begin or end with a hyphen.", val) 203 } 204 // *TODO: Check if valid hostname[:port]/ip[:port]? 205 return val, nil 206 } 207 208 func validateRemoteName(remoteName string) error { 209 var ( 210 namespace string 211 name string 212 ) 213 nameParts := strings.SplitN(remoteName, "/", 2) 214 if len(nameParts) < 2 { 215 namespace = "library" 216 name = nameParts[0] 217 218 // the repository name must not be a valid image ID 219 if err := image.ValidateID(name); err == nil { 220 return fmt.Errorf("Invalid repository name (%s), cannot specify 64-byte hexadecimal strings", name) 221 } 222 } else { 223 namespace = nameParts[0] 224 name = nameParts[1] 225 } 226 if !validNamespaceChars.MatchString(namespace) { 227 return fmt.Errorf("Invalid namespace name (%s). Only [a-z0-9-_] are allowed.", namespace) 228 } 229 if len(namespace) < 2 || len(namespace) > 255 { 230 return fmt.Errorf("Invalid namespace name (%s). Cannot be fewer than 2 or more than 255 characters.", namespace) 231 } 232 if strings.HasPrefix(namespace, "-") || strings.HasSuffix(namespace, "-") { 233 return fmt.Errorf("Invalid namespace name (%s). Cannot begin or end with a hyphen.", namespace) 234 } 235 if strings.Contains(namespace, "--") { 236 return fmt.Errorf("Invalid namespace name (%s). Cannot contain consecutive hyphens.", namespace) 237 } 238 if !validRepo.MatchString(name) { 239 return fmt.Errorf("Invalid repository name (%s), only [a-z0-9-_.] are allowed", name) 240 } 241 if strings.HasPrefix(name, "-") || strings.HasSuffix(name, "-") { 242 return fmt.Errorf("Invalid repository name (%s). Cannot begin or end with a hyphen.", name) 243 } 244 return nil 245 } 246 247 func validateNoSchema(reposName string) error { 248 if strings.Contains(reposName, "://") { 249 // It cannot contain a scheme! 250 return ErrInvalidRepositoryName 251 } 252 return nil 253 } 254 255 // ValidateRepositoryName validates a repository name 256 func ValidateRepositoryName(reposName string) error { 257 var err error 258 if err = validateNoSchema(reposName); err != nil { 259 return err 260 } 261 indexName, remoteName := splitReposName(reposName) 262 if _, err = ValidateIndexName(indexName); err != nil { 263 return err 264 } 265 return validateRemoteName(remoteName) 266 } 267 268 // NewIndexInfo returns IndexInfo configuration from indexName 269 func (config *ServiceConfig) NewIndexInfo(indexName string) (*IndexInfo, error) { 270 var err error 271 indexName, err = ValidateIndexName(indexName) 272 if err != nil { 273 return nil, err 274 } 275 276 // Return any configured index info, first. 277 if index, ok := config.IndexConfigs[indexName]; ok { 278 return index, nil 279 } 280 281 // Construct a non-configured index info. 282 index := &IndexInfo{ 283 Name: indexName, 284 Mirrors: make([]string, 0), 285 Official: false, 286 } 287 index.Secure = config.isSecureIndex(indexName) 288 return index, nil 289 } 290 291 // GetAuthConfigKey special-cases using the full index address of the official 292 // index as the AuthConfig key, and uses the (host)name[:port] for private indexes. 293 func (index *IndexInfo) GetAuthConfigKey() string { 294 if index.Official { 295 return IndexServerAddress() 296 } 297 return index.Name 298 } 299 300 // splitReposName breaks a reposName into an index name and remote name 301 func splitReposName(reposName string) (string, string) { 302 nameParts := strings.SplitN(reposName, "/", 2) 303 var indexName, remoteName string 304 if len(nameParts) == 1 || (!strings.Contains(nameParts[0], ".") && 305 !strings.Contains(nameParts[0], ":") && nameParts[0] != "localhost") { 306 // This is a Docker Index repos (ex: samalba/hipache or ubuntu) 307 // 'docker.io' 308 indexName = IndexServerName() 309 remoteName = reposName 310 } else { 311 indexName = nameParts[0] 312 remoteName = nameParts[1] 313 } 314 return indexName, remoteName 315 } 316 317 // NewRepositoryInfo validates and breaks down a repository name into a RepositoryInfo 318 func (config *ServiceConfig) NewRepositoryInfo(reposName string) (*RepositoryInfo, error) { 319 if err := validateNoSchema(reposName); err != nil { 320 return nil, err 321 } 322 323 indexName, remoteName := splitReposName(reposName) 324 if err := validateRemoteName(remoteName); err != nil { 325 return nil, err 326 } 327 328 repoInfo := &RepositoryInfo{ 329 RemoteName: remoteName, 330 } 331 332 var err error 333 repoInfo.Index, err = config.NewIndexInfo(indexName) 334 if err != nil { 335 return nil, err 336 } 337 338 if repoInfo.Index.Official { 339 normalizedName := repoInfo.RemoteName 340 if strings.HasPrefix(normalizedName, "library/") { 341 // If pull "library/foo", it's stored locally under "foo" 342 normalizedName = strings.SplitN(normalizedName, "/", 2)[1] 343 } 344 345 repoInfo.LocalName = normalizedName 346 repoInfo.RemoteName = normalizedName 347 // If the normalized name does not contain a '/' (e.g. "foo") 348 // then it is an official repo. 349 if strings.IndexRune(normalizedName, '/') == -1 { 350 repoInfo.Official = true 351 // Fix up remote name for official repos. 352 repoInfo.RemoteName = "library/" + normalizedName 353 } 354 355 // *TODO: Prefix this with 'docker.io/'. 356 repoInfo.CanonicalName = repoInfo.LocalName 357 } else { 358 // *TODO: Decouple index name from hostname (via registry configuration?) 359 repoInfo.LocalName = repoInfo.Index.Name + "/" + repoInfo.RemoteName 360 repoInfo.CanonicalName = repoInfo.LocalName 361 362 } 363 364 return repoInfo, nil 365 } 366 367 // GetSearchTerm special-cases using local name for official index, and 368 // remote name for private indexes. 369 func (repoInfo *RepositoryInfo) GetSearchTerm() string { 370 if repoInfo.Index.Official { 371 return repoInfo.LocalName 372 } 373 return repoInfo.RemoteName 374 } 375 376 // ParseRepositoryInfo performs the breakdown of a repository name into a RepositoryInfo, but 377 // lacks registry configuration. 378 func ParseRepositoryInfo(reposName string) (*RepositoryInfo, error) { 379 return emptyServiceConfig.NewRepositoryInfo(reposName) 380 } 381 382 // NormalizeLocalName transforms a repository name into a normalize LocalName 383 // Passes through the name without transformation on error (image id, etc) 384 func NormalizeLocalName(name string) string { 385 repoInfo, err := ParseRepositoryInfo(name) 386 if err != nil { 387 return name 388 } 389 return repoInfo.LocalName 390 }