2018-02-05 16:05:59 -05:00
package registry // import "github.com/docker/docker/registry"
2014-08-26 19:21:04 -04:00
import (
2015-02-12 13:23:22 -05:00
"crypto/tls"
2014-08-26 19:21:04 -04:00
"encoding/json"
2021-08-24 06:10:50 -04:00
"io"
2014-08-26 19:21:04 -04:00
"net/http"
"net/url"
"strings"
2015-05-17 05:07:48 -04:00
"github.com/docker/distribution/registry/client/transport"
2022-02-26 13:13:43 -05:00
"github.com/docker/docker/api/types/registry"
2017-07-26 17:42:13 -04:00
"github.com/sirupsen/logrus"
2014-08-26 19:21:04 -04:00
)
2022-02-25 18:08:20 -05:00
// v1PingResult contains the information returned when pinging a registry. It
// indicates the registry's version and whether the registry claims to be a
// standalone registry.
type v1PingResult struct {
// Version is the registry version supplied by the registry in an HTTP
// header
Version string ` json:"version" `
// 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 18:02:37 -05:00
// v1Endpoint stores basic information about a V1 registry endpoint.
type v1Endpoint struct {
2016-03-01 02:07:41 -05:00
client * http . Client
URL * url . URL
IsSecure bool
2014-08-26 19:21:04 -04:00
}
2022-02-25 18:02:37 -05:00
// newV1Endpoint parses the given address to return a registry endpoint.
2019-06-17 21:42:24 -04:00
// TODO: remove. This is only used by search.
2022-02-26 13:13:43 -05:00
func newV1Endpoint ( index * registry . IndexInfo , userAgent string , metaHeaders http . Header ) ( * v1Endpoint , error ) {
2015-07-28 13:36:57 -04:00
tlsConfig , err := newTLSConfig ( index . Name , index . Secure )
if err != nil {
return nil , err
}
2016-02-17 19:53:25 -05:00
2016-03-01 02:07:41 -05:00
endpoint , err := newV1EndpointFromStr ( GetAuthConfigKey ( index ) , tlsConfig , userAgent , metaHeaders )
2014-08-26 19:21:04 -04:00
if err != nil {
return nil , err
}
2016-02-17 19:53:25 -05:00
2021-10-05 14:49:33 -04:00
err = validateEndpoint ( endpoint )
if err != nil {
2014-12-23 16:40:06 -05:00
return nil , err
}
return endpoint , nil
}
2014-08-26 19:21:04 -04:00
2022-02-25 18:02:37 -05:00
func validateEndpoint ( endpoint * v1Endpoint ) error {
2015-03-26 18:22:04 -04:00
logrus . Debugf ( "pinging registry endpoint %s" , endpoint )
2014-12-11 20:55:15 -05:00
2014-10-10 23:22:12 -04:00
// Try HTTPS ping to registry
2014-08-26 19:21:04 -04:00
endpoint . URL . Scheme = "https"
2022-02-25 18:08:20 -05:00
if _ , err := endpoint . ping ( ) ; err != nil {
2014-12-23 16:40:06 -05:00
if endpoint . IsSecure {
2014-10-10 23:22:12 -04:00
// If registry is secure and HTTPS failed, show user the error and tell them about `--insecure-registry`
// in case that's what they need. DO NOT accept unknown CA certificates, and DO NOT fallback to HTTP.
2022-02-26 07:45:12 -05:00
return 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-10 23:22:12 -04:00
}
// If registry is insecure and HTTPS failed, fallback to HTTP.
2022-02-26 08:32:13 -05:00
logrus . WithError ( err ) . Debugf ( "error from registry %q marked as insecure - insecurely falling back to HTTP" , endpoint )
2014-08-26 19:21:04 -04:00
endpoint . URL . Scheme = "http"
2014-12-11 20:55:15 -05:00
var err2 error
2022-02-25 18:08:20 -05:00
if _ , err2 = endpoint . ping ( ) ; err2 == nil {
2014-12-23 16:40:06 -05:00
return nil
2014-08-26 19:21:04 -04:00
}
2014-10-10 23:22:12 -04:00
2022-02-26 07:45:12 -05:00
return invalidParamf ( "invalid registry endpoint %q. HTTPS attempt: %v. HTTP attempt: %v" , endpoint , err , err2 )
2014-08-26 19:21:04 -04:00
}
2014-12-23 16:40:06 -05:00
return nil
2014-10-03 15:46:42 -04:00
}
2014-12-11 20:55:15 -05:00
2016-03-01 02:07:41 -05:00
// trimV1Address trims the version off the address and returns the
// trimmed address or an error if there is a non-V1 version.
func trimV1Address ( address string ) ( string , error ) {
2021-10-05 15:12:10 -04:00
address = strings . TrimSuffix ( address , "/" )
2022-02-26 08:32:13 -05:00
chunks := strings . Split ( address , "/" )
apiVersionStr := chunks [ len ( chunks ) - 1 ]
2016-03-01 02:07:41 -05:00
if apiVersionStr == "v1" {
return strings . Join ( chunks [ : len ( chunks ) - 1 ] , "/" ) , nil
}
for k , v := range apiVersions {
if k != APIVersion1 && apiVersionStr == v {
2022-02-26 07:45:12 -05:00
return "" , invalidParamf ( "unsupported V1 version path %s" , apiVersionStr )
2016-03-01 02:07:41 -05:00
}
}
return address , nil
}
2022-02-25 18:02:37 -05:00
func newV1EndpointFromStr ( address string , tlsConfig * tls . Config , userAgent string , metaHeaders http . Header ) ( * v1Endpoint , error ) {
2016-02-17 19:53:25 -05:00
if ! strings . HasPrefix ( address , "http://" ) && ! strings . HasPrefix ( address , "https://" ) {
2014-12-11 20:55:15 -05:00
address = "https://" + address
2014-10-03 15:46:42 -04:00
}
2014-12-11 20:55:15 -05:00
2016-03-01 02:07:41 -05:00
address , err := trimV1Address ( address )
if err != nil {
return nil , err
}
2015-02-12 13:23:22 -05:00
2016-03-01 02:07:41 -05:00
uri , err := url . Parse ( address )
2016-02-17 19:53:25 -05:00
if err != nil {
2022-02-26 07:45:12 -05:00
return nil , invalidParam ( err )
2016-02-17 19:53:25 -05:00
}
2014-12-11 20:55:15 -05:00
2021-10-05 14:49:33 -04:00
// TODO(tiborvass): make sure a ConnectTimeout transport is used
2022-02-25 17:58:25 -05:00
tr := newTransport ( tlsConfig )
2015-02-12 13:23:22 -05:00
2022-02-25 18:02:37 -05:00
return & v1Endpoint {
2021-10-05 14:49:33 -04:00
IsSecure : tlsConfig == nil || ! tlsConfig . InsecureSkipVerify ,
URL : uri ,
2022-02-25 17:58:25 -05:00
client : httpClient ( transport . NewTransport ( tr , Headers ( userAgent , metaHeaders ) ... ) ) ,
2021-10-05 14:49:33 -04:00
} , nil
2014-08-26 19:21:04 -04:00
}
2015-12-13 11:00:39 -05:00
// Get the formatted URL for the root of this registry Endpoint
2022-02-25 18:02:37 -05:00
func ( e * v1Endpoint ) String ( ) string {
2016-03-01 02:07:41 -05:00
return e . URL . String ( ) + "/v1/"
2014-08-26 19:21:04 -04:00
}
2022-02-25 18:08:20 -05:00
// ping returns a v1PingResult which indicates whether the registry is standalone or not.
func ( e * v1Endpoint ) ping ( ) ( v1PingResult , error ) {
2015-07-21 15:40:36 -04:00
if e . String ( ) == IndexServer {
2014-12-11 20:55:15 -05:00
// Skip the check, we know this one is valid
2014-08-26 19:21:04 -04:00
// (and we never want to fallback to http in case of error)
2022-02-25 18:08:20 -05:00
return v1PingResult { } , nil
2014-08-26 19:21:04 -04:00
}
2021-10-07 10:26:39 -04:00
logrus . Debugf ( "attempting v1 ping for registry endpoint %s" , e )
2022-02-25 18:08:20 -05:00
pingURL := e . String ( ) + "_ping"
req , err := http . NewRequest ( http . MethodGet , pingURL , nil )
2014-08-26 19:21:04 -04:00
if err != nil {
2022-02-26 07:45:12 -05:00
return v1PingResult { } , invalidParam ( err )
2014-08-26 19:21:04 -04:00
}
2015-05-15 21:35:04 -04:00
resp , err := e . client . Do ( req )
2014-08-26 19:21:04 -04:00
if err != nil {
2022-02-26 07:45:12 -05:00
return v1PingResult { } , invalidParam ( err )
2014-08-26 19:21:04 -04:00
}
defer resp . Body . Close ( )
2021-08-24 06:10:50 -04:00
jsonString , err := io . ReadAll ( resp . Body )
2014-08-26 19:21:04 -04:00
if err != nil {
2022-02-26 07:45:12 -05:00
return v1PingResult { } , invalidParamWrapf ( err , "error while reading response from %s" , pingURL )
2014-08-26 19:21:04 -04:00
}
// If the header is absent, we assume true for compatibility with earlier
// versions of the registry. default to true
2022-02-25 18:08:20 -05:00
info := v1PingResult {
2014-08-26 19:21:04 -04:00
Standalone : true ,
}
if err := json . Unmarshal ( jsonString , & info ) ; err != nil {
2022-02-26 08:32:13 -05:00
logrus . WithError ( err ) . Debug ( "error unmarshaling _ping response" )
2014-08-26 19:21:04 -04:00
// don't stop here. Just assume sane defaults
}
if hdr := resp . Header . Get ( "X-Docker-Registry-Version" ) ; hdr != "" {
info . Version = hdr
}
2022-02-25 18:08:20 -05:00
logrus . Debugf ( "v1PingResult.Version: %q" , info . Version )
2014-08-26 19:21:04 -04:00
standalone := resp . Header . Get ( "X-Docker-Registry-Standalone" )
2021-10-07 10:26:39 -04:00
2014-08-26 19:21:04 -04:00
// Accepted values are "true" (case-insensitive) and "1".
if strings . EqualFold ( standalone , "true" ) || standalone == "1" {
info . Standalone = true
} else if len ( standalone ) > 0 {
// there is a header set, and it is not "true" or "1", so assume fails
info . Standalone = false
}
2022-02-25 18:08:20 -05:00
logrus . Debugf ( "v1PingResult.Standalone: %t" , info . Standalone )
2014-08-26 19:21:04 -04:00
return info , nil
}