github.com/stchris/docker@v1.4.2-0.20150106053530-1510a324dbd5/registry/endpoint.go (about) 1 package registry 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io/ioutil" 7 "net" 8 "net/http" 9 "net/url" 10 "strings" 11 12 log "github.com/Sirupsen/logrus" 13 ) 14 15 // for mocking in unit tests 16 var lookupIP = net.LookupIP 17 18 // scans string for api version in the URL path. returns the trimmed hostname, if version found, string and API version. 19 func scanForAPIVersion(hostname string) (string, APIVersion) { 20 var ( 21 chunks []string 22 apiVersionStr string 23 ) 24 if strings.HasSuffix(hostname, "/") { 25 chunks = strings.Split(hostname[:len(hostname)-1], "/") 26 apiVersionStr = chunks[len(chunks)-1] 27 } else { 28 chunks = strings.Split(hostname, "/") 29 apiVersionStr = chunks[len(chunks)-1] 30 } 31 for k, v := range apiVersions { 32 if apiVersionStr == v { 33 hostname = strings.Join(chunks[:len(chunks)-1], "/") 34 return hostname, k 35 } 36 } 37 return hostname, DefaultAPIVersion 38 } 39 40 func NewEndpoint(hostname string, insecureRegistries []string) (*Endpoint, error) { 41 endpoint, err := newEndpoint(hostname, insecureRegistries) 42 if err != nil { 43 return nil, err 44 } 45 46 // Try HTTPS ping to registry 47 endpoint.URL.Scheme = "https" 48 if _, err := endpoint.Ping(); err != nil { 49 50 //TODO: triggering highland build can be done there without "failing" 51 52 if endpoint.secure { 53 // If registry is secure and HTTPS failed, show user the error and tell them about `--insecure-registry` 54 // in case that's what they need. DO NOT accept unknown CA certificates, and DO NOT fallback to HTTP. 55 return nil, fmt.Errorf("Invalid registry endpoint %s: %v. If this private registry supports only HTTP or HTTPS with an unknown CA certificate, please add `--insecure-registry %s` to the daemon's arguments. In the case of HTTPS, if you have access to the registry's CA certificate, no need for the flag; simply place the CA certificate at /etc/docker/certs.d/%s/ca.crt", endpoint, err, endpoint.URL.Host, endpoint.URL.Host) 56 } 57 58 // If registry is insecure and HTTPS failed, fallback to HTTP. 59 log.Debugf("Error from registry %q marked as insecure: %v. Insecurely falling back to HTTP", endpoint, err) 60 endpoint.URL.Scheme = "http" 61 _, err2 := endpoint.Ping() 62 if err2 == nil { 63 return endpoint, nil 64 } 65 66 return nil, fmt.Errorf("Invalid registry endpoint %q. HTTPS attempt: %v. HTTP attempt: %v", endpoint, err, err2) 67 } 68 69 return endpoint, nil 70 } 71 func newEndpoint(hostname string, insecureRegistries []string) (*Endpoint, error) { 72 var ( 73 endpoint = Endpoint{} 74 trimmedHostname string 75 err error 76 ) 77 if !strings.HasPrefix(hostname, "http") { 78 hostname = "https://" + hostname 79 } 80 trimmedHostname, endpoint.Version = scanForAPIVersion(hostname) 81 endpoint.URL, err = url.Parse(trimmedHostname) 82 if err != nil { 83 return nil, err 84 } 85 endpoint.secure, err = isSecure(endpoint.URL.Host, insecureRegistries) 86 if err != nil { 87 return nil, err 88 } 89 return &endpoint, nil 90 } 91 92 type Endpoint struct { 93 URL *url.URL 94 Version APIVersion 95 secure bool 96 } 97 98 // Get the formated URL for the root of this registry Endpoint 99 func (e Endpoint) String() string { 100 return fmt.Sprintf("%s/v%d/", e.URL.String(), e.Version) 101 } 102 103 func (e Endpoint) VersionString(version APIVersion) string { 104 return fmt.Sprintf("%s/v%d/", e.URL.String(), version) 105 } 106 107 func (e Endpoint) Ping() (RegistryInfo, error) { 108 if e.String() == IndexServerAddress() { 109 // Skip the check, we now this one is valid 110 // (and we never want to fallback to http in case of error) 111 return RegistryInfo{Standalone: false}, nil 112 } 113 114 req, err := http.NewRequest("GET", e.String()+"_ping", nil) 115 if err != nil { 116 return RegistryInfo{Standalone: false}, err 117 } 118 119 resp, _, err := doRequest(req, nil, ConnectTimeout, e.secure) 120 if err != nil { 121 return RegistryInfo{Standalone: false}, err 122 } 123 124 defer resp.Body.Close() 125 126 jsonString, err := ioutil.ReadAll(resp.Body) 127 if err != nil { 128 return RegistryInfo{Standalone: false}, fmt.Errorf("Error while reading the http response: %s", err) 129 } 130 131 // If the header is absent, we assume true for compatibility with earlier 132 // versions of the registry. default to true 133 info := RegistryInfo{ 134 Standalone: true, 135 } 136 if err := json.Unmarshal(jsonString, &info); err != nil { 137 log.Debugf("Error unmarshalling the _ping RegistryInfo: %s", err) 138 // don't stop here. Just assume sane defaults 139 } 140 if hdr := resp.Header.Get("X-Docker-Registry-Version"); hdr != "" { 141 log.Debugf("Registry version header: '%s'", hdr) 142 info.Version = hdr 143 } 144 log.Debugf("RegistryInfo.Version: %q", info.Version) 145 146 standalone := resp.Header.Get("X-Docker-Registry-Standalone") 147 log.Debugf("Registry standalone header: '%s'", standalone) 148 // Accepted values are "true" (case-insensitive) and "1". 149 if strings.EqualFold(standalone, "true") || standalone == "1" { 150 info.Standalone = true 151 } else if len(standalone) > 0 { 152 // there is a header set, and it is not "true" or "1", so assume fails 153 info.Standalone = false 154 } 155 log.Debugf("RegistryInfo.Standalone: %t", info.Standalone) 156 return info, nil 157 } 158 159 // isSecure returns false if the provided hostname is part of the list of insecure registries. 160 // Insecure registries accept HTTP and/or accept HTTPS with certificates from unknown CAs. 161 // 162 // The list of insecure registries can contain an element with CIDR notation to specify a whole subnet. 163 // If the subnet contains one of the IPs of the registry specified by hostname, the latter is considered 164 // insecure. 165 // 166 // hostname should be a URL.Host (`host:port` or `host`) where the `host` part can be either a domain name 167 // or an IP address. If it is a domain name, then it will be resolved in order to check if the IP is contained 168 // in a subnet. If the resolving is not successful, isSecure will only try to match hostname to any element 169 // of insecureRegistries. 170 func isSecure(hostname string, insecureRegistries []string) (bool, error) { 171 if hostname == IndexServerURL.Host { 172 return true, nil 173 } 174 175 host, _, err := net.SplitHostPort(hostname) 176 if err != nil { 177 // assume hostname is of the form `host` without the port and go on. 178 host = hostname 179 } 180 addrs, err := lookupIP(host) 181 if err != nil { 182 ip := net.ParseIP(host) 183 if ip != nil { 184 addrs = []net.IP{ip} 185 } 186 187 // if ip == nil, then `host` is neither an IP nor it could be looked up, 188 // either because the index is unreachable, or because the index is behind an HTTP proxy. 189 // So, len(addrs) == 0 and we're not aborting. 190 } 191 192 for _, r := range insecureRegistries { 193 if hostname == r { 194 // hostname matches insecure registry 195 return false, nil 196 } 197 198 // Try CIDR notation only if addrs has any elements, i.e. if `host`'s IP could be determined. 199 for _, addr := range addrs { 200 201 // now assume a CIDR was passed to --insecure-registry 202 _, ipnet, err := net.ParseCIDR(r) 203 if err != nil { 204 // if we could not parse it as a CIDR, even after removing 205 // assume it's not a CIDR and go on with the next candidate 206 break 207 } 208 209 // check if the addr falls in the subnet 210 if ipnet.Contains(addr) { 211 return false, nil 212 } 213 } 214 } 215 216 return true, nil 217 }