2018-02-05 21:05:59 +00:00
package registry // import "github.com/docker/docker/registry"
2014-08-26 23:21:04 +00:00
import (
2023-06-23 00:33:17 +00:00
"context"
2015-02-12 18:23:22 +00:00
"crypto/tls"
2014-08-26 23:21:04 +00:00
"encoding/json"
"net/http"
"net/url"
"strings"
2023-09-13 15:41:45 +00:00
"github.com/containerd/log"
2015-05-17 09:07:48 +00:00
"github.com/docker/distribution/registry/client/transport"
2022-02-26 18:13:43 +00:00
"github.com/docker/docker/api/types/registry"
2014-08-26 23:21:04 +00:00
)
2022-02-25 23:08:20 +00:00
// v1PingResult contains the information returned when pinging a registry. It
2023-09-08 09:55:53 +00:00
// indicates whether the registry claims to be a standalone registry.
2022-02-25 23:08:20 +00:00
type v1PingResult struct {
// Standalone is set to true if the registry indicates it is a
// standalone registry in the X-Docker-Registry-Standalone
// header
Standalone bool ` json:"standalone" `
}
2022-02-25 23:02:37 +00:00
// v1Endpoint stores basic information about a V1 registry endpoint.
type v1Endpoint struct {
2016-03-01 07:07:41 +00:00
client * http . Client
URL * url . URL
IsSecure bool
2014-08-26 23:21:04 +00:00
}
2022-02-25 23:02:37 +00:00
// newV1Endpoint parses the given address to return a registry endpoint.
2019-06-18 01:42:24 +00:00
// TODO: remove. This is only used by search.
2023-03-21 13:40:33 +00:00
func newV1Endpoint ( index * registry . IndexInfo , headers http . Header ) ( * v1Endpoint , error ) {
2015-07-28 17:36:57 +00:00
tlsConfig , err := newTLSConfig ( index . Name , index . Secure )
if err != nil {
return nil , err
}
2016-02-18 00:53:25 +00:00
2023-03-21 13:40:33 +00:00
endpoint , err := newV1EndpointFromStr ( GetAuthConfigKey ( index ) , tlsConfig , headers )
2014-08-26 23:21:04 +00:00
if err != nil {
return nil , err
}
2016-02-18 00:53:25 +00:00
2023-09-08 12:19:33 +00:00
if endpoint . String ( ) == IndexServer {
// Skip the check, we know this one is valid
// (and we never want to fall back to http in case of error)
return endpoint , nil
}
2014-10-11 03:22:12 +00:00
// Try HTTPS ping to registry
2014-08-26 23:21:04 +00:00
endpoint . URL . Scheme = "https"
2022-02-25 23:08:20 +00:00
if _ , err := endpoint . ping ( ) ; err != nil {
2014-12-23 21:40:06 +00:00
if endpoint . IsSecure {
2014-10-11 03:22:12 +00:00
// If registry is secure and HTTPS failed, show user the error and tell them about `--insecure-registry`
2023-09-08 13:38:20 +00:00
// in case that's what they need. DO NOT accept unknown CA certificates, and DO NOT fall back to HTTP.
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 )
2014-10-11 03:22:12 +00:00
}
2023-09-08 13:38:20 +00:00
// registry is insecure and HTTPS failed, fallback to HTTP.
2023-06-23 00:33:17 +00:00
log . G ( context . TODO ( ) ) . WithError ( err ) . Debugf ( "error from registry %q marked as insecure - insecurely falling back to HTTP" , endpoint )
2014-08-26 23:21:04 +00:00
endpoint . URL . Scheme = "http"
2023-09-08 13:38:20 +00:00
if _ , err2 := endpoint . ping ( ) ; err2 != nil {
return nil , invalidParamf ( "invalid registry endpoint %q. HTTPS attempt: %v. HTTP attempt: %v" , endpoint , err , err2 )
2014-08-26 23:21:04 +00:00
}
}
2023-09-08 13:38:20 +00:00
return endpoint , nil
2014-10-03 19:46:42 +00:00
}
2014-12-12 01:55:15 +00:00
2023-08-29 08:43:13 +00:00
// trimV1Address trims the "v1" version suffix off the address and returns
// the trimmed address. It returns an error on "v2" endpoints.
2016-03-01 07:07:41 +00:00
func trimV1Address ( address string ) ( string , error ) {
2023-08-29 08:43:13 +00:00
trimmed := strings . TrimSuffix ( address , "/" )
if strings . HasSuffix ( trimmed , "/v2" ) {
2023-08-29 08:47:29 +00:00
return "" , invalidParamf ( "search is not supported on v2 endpoints: %s" , address )
2016-03-01 07:07:41 +00:00
}
2023-08-29 08:43:13 +00:00
return strings . TrimSuffix ( trimmed , "/v1" ) , nil
2016-03-01 07:07:41 +00:00
}
2023-03-21 13:40:33 +00:00
func newV1EndpointFromStr ( address string , tlsConfig * tls . Config , headers http . Header ) ( * v1Endpoint , error ) {
2016-02-18 00:53:25 +00:00
if ! strings . HasPrefix ( address , "http://" ) && ! strings . HasPrefix ( address , "https://" ) {
2014-12-12 01:55:15 +00:00
address = "https://" + address
2014-10-03 19:46:42 +00:00
}
2014-12-12 01:55:15 +00:00
2016-03-01 07:07:41 +00:00
address , err := trimV1Address ( address )
if err != nil {
return nil , err
}
2015-02-12 18:23:22 +00:00
2016-03-01 07:07:41 +00:00
uri , err := url . Parse ( address )
2016-02-18 00:53:25 +00:00
if err != nil {
2022-02-26 12:45:12 +00:00
return nil , invalidParam ( err )
2016-02-18 00:53:25 +00:00
}
2014-12-12 01:55:15 +00:00
2021-10-05 18:49:33 +00:00
// TODO(tiborvass): make sure a ConnectTimeout transport is used
2022-02-25 22:58:25 +00:00
tr := newTransport ( tlsConfig )
2015-02-12 18:23:22 +00:00
2022-02-25 23:02:37 +00:00
return & v1Endpoint {
2021-10-05 18:49:33 +00:00
IsSecure : tlsConfig == nil || ! tlsConfig . InsecureSkipVerify ,
URL : uri ,
2023-03-21 13:40:33 +00:00
client : httpClient ( transport . NewTransport ( tr , Headers ( "" , headers ) ... ) ) ,
2021-10-05 18:49:33 +00:00
} , nil
2014-08-26 23:21:04 +00:00
}
2015-12-13 16:00:39 +00:00
// Get the formatted URL for the root of this registry Endpoint
2022-02-25 23:02:37 +00:00
func ( e * v1Endpoint ) String ( ) string {
2016-03-01 07:07:41 +00:00
return e . URL . String ( ) + "/v1/"
2014-08-26 23:21:04 +00:00
}
2022-02-25 23:08:20 +00:00
// ping returns a v1PingResult which indicates whether the registry is standalone or not.
func ( e * v1Endpoint ) ping ( ) ( v1PingResult , error ) {
2015-07-21 19:40:36 +00:00
if e . String ( ) == IndexServer {
2014-12-12 01:55:15 +00:00
// Skip the check, we know this one is valid
2014-08-26 23:21:04 +00:00
// (and we never want to fallback to http in case of error)
2022-02-25 23:08:20 +00:00
return v1PingResult { } , nil
2014-08-26 23:21:04 +00:00
}
2022-02-25 23:08:20 +00:00
pingURL := e . String ( ) + "_ping"
2023-09-08 11:21:28 +00:00
log . G ( context . TODO ( ) ) . WithField ( "url" , pingURL ) . Debug ( "attempting v1 ping for registry endpoint" )
2022-02-25 23:08:20 +00:00
req , err := http . NewRequest ( http . MethodGet , pingURL , nil )
2014-08-26 23:21:04 +00:00
if err != nil {
2022-02-26 12:45:12 +00:00
return v1PingResult { } , invalidParam ( err )
2014-08-26 23:21:04 +00:00
}
2015-05-16 01:35:04 +00:00
resp , err := e . client . Do ( req )
2014-08-26 23:21:04 +00:00
if err != nil {
2022-02-26 12:45:12 +00:00
return v1PingResult { } , invalidParam ( err )
2014-08-26 23:21:04 +00:00
}
defer resp . Body . Close ( )
2023-09-08 10:03:19 +00:00
if v := resp . Header . Get ( "X-Docker-Registry-Standalone" ) ; v != "" {
info := v1PingResult { }
// Accepted values are "1", and "true" (case-insensitive).
if v == "1" || strings . EqualFold ( v , "true" ) {
info . Standalone = true
}
log . G ( context . TODO ( ) ) . Debugf ( "v1PingResult.Standalone (from X-Docker-Registry-Standalone header): %t" , info . Standalone )
return info , nil
}
2014-08-26 23:21:04 +00:00
// If the header is absent, we assume true for compatibility with earlier
// versions of the registry. default to true
2022-02-25 23:08:20 +00:00
info := v1PingResult {
2014-08-26 23:21:04 +00:00
Standalone : true ,
}
2023-09-08 10:24:27 +00:00
if err := json . NewDecoder ( resp . Body ) . Decode ( & info ) ; err != nil {
2023-06-23 00:33:17 +00:00
log . G ( context . TODO ( ) ) . WithError ( err ) . Debug ( "error unmarshaling _ping response" )
2014-08-26 23:21:04 +00:00
// don't stop here. Just assume sane defaults
}
2023-06-23 00:33:17 +00:00
log . G ( context . TODO ( ) ) . Debugf ( "v1PingResult.Standalone: %t" , info . Standalone )
2014-08-26 23:21:04 +00:00
return info , nil
}
2022-08-25 08:13:03 +00:00
// httpClient returns an HTTP client structure which uses the given transport
// and contains the necessary headers for redirected requests
func httpClient ( transport http . RoundTripper ) * http . Client {
return & http . Client {
Transport : transport ,
CheckRedirect : addRequiredHeadersToRedirectedRequests ,
}
}
func trustedLocation ( req * http . Request ) bool {
var (
trusteds = [ ] string { "docker.com" , "docker.io" }
hostname = strings . SplitN ( req . Host , ":" , 2 ) [ 0 ]
)
if req . URL . Scheme != "https" {
return false
}
for _ , trusted := range trusteds {
if hostname == trusted || strings . HasSuffix ( hostname , "." + trusted ) {
return true
}
}
return false
}
// addRequiredHeadersToRedirectedRequests adds the necessary redirection headers
// for redirected requests
func addRequiredHeadersToRedirectedRequests ( req * http . Request , via [ ] * http . Request ) error {
if len ( via ) != 0 && via [ 0 ] != nil {
if trustedLocation ( req ) && trustedLocation ( via [ 0 ] ) {
req . Header = via [ 0 ] . Header
return nil
}
for k , v := range via [ 0 ] . Header {
if k != "Authorization" {
for _ , vv := range v {
req . Header . Add ( k , vv )
}
}
}
}
return nil
}