github.com/toplink-cn/moby@v0.0.0-20240305205811-460b4aebdf81/registry/search_endpoint_v1.go (about) 1 package registry // import "github.com/docker/docker/registry" 2 3 import ( 4 "context" 5 "crypto/tls" 6 "encoding/json" 7 "net/http" 8 "net/url" 9 "strings" 10 11 "github.com/containerd/log" 12 "github.com/docker/distribution/registry/client/transport" 13 "github.com/docker/docker/api/types/registry" 14 ) 15 16 // v1PingResult contains the information returned when pinging a registry. It 17 // indicates whether the registry claims to be a standalone registry. 18 type v1PingResult struct { 19 // Standalone is set to true if the registry indicates it is a 20 // standalone registry in the X-Docker-Registry-Standalone 21 // header 22 Standalone bool `json:"standalone"` 23 } 24 25 // v1Endpoint stores basic information about a V1 registry endpoint. 26 type v1Endpoint struct { 27 client *http.Client 28 URL *url.URL 29 IsSecure bool 30 } 31 32 // newV1Endpoint parses the given address to return a registry endpoint. 33 // TODO: remove. This is only used by search. 34 func newV1Endpoint(index *registry.IndexInfo, headers http.Header) (*v1Endpoint, error) { 35 tlsConfig, err := newTLSConfig(index.Name, index.Secure) 36 if err != nil { 37 return nil, err 38 } 39 40 endpoint, err := newV1EndpointFromStr(GetAuthConfigKey(index), tlsConfig, headers) 41 if err != nil { 42 return nil, err 43 } 44 45 if endpoint.String() == IndexServer { 46 // Skip the check, we know this one is valid 47 // (and we never want to fall back to http in case of error) 48 return endpoint, nil 49 } 50 51 // Try HTTPS ping to registry 52 endpoint.URL.Scheme = "https" 53 if _, err := endpoint.ping(); err != nil { 54 if endpoint.IsSecure { 55 // If registry is secure and HTTPS failed, show user the error and tell them about `--insecure-registry` 56 // in case that's what they need. DO NOT accept unknown CA certificates, and DO NOT fall back to HTTP. 57 return nil, invalidParamf("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) 58 } 59 60 // registry is insecure and HTTPS failed, fallback to HTTP. 61 log.G(context.TODO()).WithError(err).Debugf("error from registry %q marked as insecure - insecurely falling back to HTTP", endpoint) 62 endpoint.URL.Scheme = "http" 63 if _, err2 := endpoint.ping(); err2 != nil { 64 return nil, invalidParamf("invalid registry endpoint %q. HTTPS attempt: %v. HTTP attempt: %v", endpoint, err, err2) 65 } 66 } 67 68 return endpoint, nil 69 } 70 71 // trimV1Address trims the "v1" version suffix off the address and returns 72 // the trimmed address. It returns an error on "v2" endpoints. 73 func trimV1Address(address string) (string, error) { 74 trimmed := strings.TrimSuffix(address, "/") 75 if strings.HasSuffix(trimmed, "/v2") { 76 return "", invalidParamf("search is not supported on v2 endpoints: %s", address) 77 } 78 return strings.TrimSuffix(trimmed, "/v1"), nil 79 } 80 81 func newV1EndpointFromStr(address string, tlsConfig *tls.Config, headers http.Header) (*v1Endpoint, error) { 82 if !strings.HasPrefix(address, "http://") && !strings.HasPrefix(address, "https://") { 83 address = "https://" + address 84 } 85 86 address, err := trimV1Address(address) 87 if err != nil { 88 return nil, err 89 } 90 91 uri, err := url.Parse(address) 92 if err != nil { 93 return nil, invalidParam(err) 94 } 95 96 // TODO(tiborvass): make sure a ConnectTimeout transport is used 97 tr := newTransport(tlsConfig) 98 99 return &v1Endpoint{ 100 IsSecure: tlsConfig == nil || !tlsConfig.InsecureSkipVerify, 101 URL: uri, 102 client: httpClient(transport.NewTransport(tr, Headers("", headers)...)), 103 }, nil 104 } 105 106 // Get the formatted URL for the root of this registry Endpoint 107 func (e *v1Endpoint) String() string { 108 return e.URL.String() + "/v1/" 109 } 110 111 // ping returns a v1PingResult which indicates whether the registry is standalone or not. 112 func (e *v1Endpoint) ping() (v1PingResult, error) { 113 if e.String() == IndexServer { 114 // Skip the check, we know this one is valid 115 // (and we never want to fallback to http in case of error) 116 return v1PingResult{}, nil 117 } 118 119 pingURL := e.String() + "_ping" 120 log.G(context.TODO()).WithField("url", pingURL).Debug("attempting v1 ping for registry endpoint") 121 req, err := http.NewRequest(http.MethodGet, pingURL, nil) 122 if err != nil { 123 return v1PingResult{}, invalidParam(err) 124 } 125 126 resp, err := e.client.Do(req) 127 if err != nil { 128 return v1PingResult{}, invalidParam(err) 129 } 130 131 defer resp.Body.Close() 132 133 if v := resp.Header.Get("X-Docker-Registry-Standalone"); v != "" { 134 info := v1PingResult{} 135 // Accepted values are "1", and "true" (case-insensitive). 136 if v == "1" || strings.EqualFold(v, "true") { 137 info.Standalone = true 138 } 139 log.G(context.TODO()).Debugf("v1PingResult.Standalone (from X-Docker-Registry-Standalone header): %t", info.Standalone) 140 return info, nil 141 } 142 143 // If the header is absent, we assume true for compatibility with earlier 144 // versions of the registry. default to true 145 info := v1PingResult{ 146 Standalone: true, 147 } 148 if err := json.NewDecoder(resp.Body).Decode(&info); err != nil { 149 log.G(context.TODO()).WithError(err).Debug("error unmarshaling _ping response") 150 // don't stop here. Just assume sane defaults 151 } 152 153 log.G(context.TODO()).Debugf("v1PingResult.Standalone: %t", info.Standalone) 154 return info, nil 155 } 156 157 // httpClient returns an HTTP client structure which uses the given transport 158 // and contains the necessary headers for redirected requests 159 func httpClient(transport http.RoundTripper) *http.Client { 160 return &http.Client{ 161 Transport: transport, 162 CheckRedirect: addRequiredHeadersToRedirectedRequests, 163 } 164 } 165 166 func trustedLocation(req *http.Request) bool { 167 var ( 168 trusteds = []string{"docker.com", "docker.io"} 169 hostname = strings.SplitN(req.Host, ":", 2)[0] 170 ) 171 if req.URL.Scheme != "https" { 172 return false 173 } 174 175 for _, trusted := range trusteds { 176 if hostname == trusted || strings.HasSuffix(hostname, "."+trusted) { 177 return true 178 } 179 } 180 return false 181 } 182 183 // addRequiredHeadersToRedirectedRequests adds the necessary redirection headers 184 // for redirected requests 185 func addRequiredHeadersToRedirectedRequests(req *http.Request, via []*http.Request) error { 186 if len(via) != 0 && via[0] != nil { 187 if trustedLocation(req) && trustedLocation(via[0]) { 188 req.Header = via[0].Header 189 return nil 190 } 191 for k, v := range via[0].Header { 192 if k != "Authorization" { 193 for _, vv := range v { 194 req.Header.Add(k, vv) 195 } 196 } 197 } 198 } 199 return nil 200 }