2014-08-26 19:21:04 -04:00
package registry
import (
2015-02-12 13:23:22 -05:00
"crypto/tls"
2014-08-26 19:21:04 -04:00
"encoding/json"
"fmt"
"io/ioutil"
2014-10-31 16:00:49 -04:00
"net"
2014-08-26 19:21:04 -04:00
"net/http"
"net/url"
"strings"
2015-03-26 18:22:04 -04:00
"github.com/Sirupsen/logrus"
2015-03-31 18:02:27 -04:00
"github.com/docker/distribution/registry/api/v2"
2015-05-17 05:07:48 -04:00
"github.com/docker/distribution/registry/client/transport"
2016-01-04 19:05:26 -05:00
registrytypes "github.com/docker/engine-api/types/registry"
2014-08-26 19:21:04 -04:00
)
2014-11-11 16:31:15 -05:00
// for mocking in unit tests
var lookupIP = net . LookupIP
2014-12-11 20:55:15 -05:00
// scans string for api version in the URL path. returns the trimmed address, if version found, string and API version.
func scanForAPIVersion ( address string ) ( string , APIVersion ) {
2014-08-26 19:21:04 -04:00
var (
chunks [ ] string
apiVersionStr string
)
2014-12-11 20:55:15 -05:00
if strings . HasSuffix ( address , "/" ) {
address = address [ : len ( address ) - 1 ]
2014-08-26 19:21:04 -04:00
}
2014-12-11 20:55:15 -05:00
chunks = strings . Split ( address , "/" )
apiVersionStr = chunks [ len ( chunks ) - 1 ]
2014-08-26 19:21:04 -04:00
for k , v := range apiVersions {
if apiVersionStr == v {
2014-12-11 20:55:15 -05:00
address = strings . Join ( chunks [ : len ( chunks ) - 1 ] , "/" )
return address , k
2014-08-26 19:21:04 -04:00
}
}
2014-12-11 20:55:15 -05:00
return address , APIVersionUnknown
2014-08-26 19:21:04 -04:00
}
2015-09-16 13:42:17 -04:00
// NewEndpoint parses the given address to return a registry endpoint. v can be used to
// specify a specific endpoint version
2016-01-04 13:36:01 -05:00
func NewEndpoint ( index * registrytypes . IndexInfo , userAgent string , metaHeaders http . Header , v APIVersion ) ( * Endpoint , error ) {
2015-07-28 13:36:57 -04:00
tlsConfig , err := newTLSConfig ( index . Name , index . Secure )
if err != nil {
return nil , err
}
2016-01-04 13:36:01 -05:00
endpoint , err := newEndpoint ( GetAuthConfigKey ( index ) , tlsConfig , userAgent , metaHeaders )
2014-08-26 19:21:04 -04:00
if err != nil {
return nil , err
}
2015-09-16 13:42:17 -04:00
if v != APIVersionUnknown {
endpoint . Version = v
}
2014-12-23 16:40:06 -05:00
if err := validateEndpoint ( endpoint ) ; err != nil {
return nil , err
}
return endpoint , nil
}
2014-08-26 19:21:04 -04:00
2014-12-23 16:40:06 -05:00
func validateEndpoint ( endpoint * Endpoint ) 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"
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.
2014-12-23 16:40:06 -05:00
return 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 )
2014-10-10 23:22:12 -04:00
}
// If registry is insecure and HTTPS failed, fallback to HTTP.
2015-03-26 18:22:04 -04:00
logrus . Debugf ( "Error from registry %q marked as insecure: %v. Insecurely falling back to HTTP" , endpoint , err )
2014-08-26 19:21:04 -04:00
endpoint . URL . Scheme = "http"
2014-12-11 20:55:15 -05:00
var err2 error
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
2014-12-23 16:40:06 -05:00
return fmt . Errorf ( "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-01-04 13:36:01 -05:00
func newEndpoint ( address string , tlsConfig * tls . Config , userAgent string , metaHeaders http . Header ) ( * Endpoint , error ) {
2014-10-03 15:46:42 -04:00
var (
2014-12-11 20:55:15 -05:00
endpoint = new ( Endpoint )
trimmedAddress string
err error
2014-10-03 15:46:42 -04:00
)
2014-12-11 20:55:15 -05:00
if ! strings . HasPrefix ( address , "http" ) {
address = "https://" + address
2014-10-03 15:46:42 -04:00
}
2014-12-11 20:55:15 -05:00
2015-02-12 13:23:22 -05:00
endpoint . IsSecure = ( tlsConfig == nil || ! tlsConfig . InsecureSkipVerify )
2014-12-11 20:55:15 -05:00
trimmedAddress , endpoint . Version = scanForAPIVersion ( address )
if endpoint . URL , err = url . Parse ( trimmedAddress ) ; err != nil {
2014-10-03 15:46:42 -04:00
return nil , err
}
2015-02-12 13:23:22 -05:00
// TODO(tiborvass): make sure a ConnectTimeout transport is used
tr := NewTransport ( tlsConfig )
2016-01-04 13:36:01 -05:00
endpoint . client = HTTPClient ( transport . NewTransport ( tr , DockerHeaders ( userAgent , metaHeaders ) ... ) )
2014-12-11 20:55:15 -05:00
return endpoint , nil
2014-08-26 19:21:04 -04:00
}
2014-12-11 20:55:15 -05:00
// Endpoint stores basic information about a registry endpoint.
2014-08-26 19:21:04 -04:00
type Endpoint struct {
2015-05-14 10:12:54 -04:00
client * http . Client
2014-12-11 20:55:15 -05:00
URL * url . URL
Version APIVersion
IsSecure bool
AuthChallenges [ ] * AuthorizationChallenge
2014-12-19 17:44:18 -05:00
URLBuilder * v2 . URLBuilder
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
2014-12-11 20:55:15 -05:00
func ( e * Endpoint ) String ( ) string {
return fmt . Sprintf ( "%s/v%d/" , e . URL , e . Version )
}
// VersionString returns a formatted string of this
// endpoint address using the given API Version.
func ( e * Endpoint ) VersionString ( version APIVersion ) string {
return fmt . Sprintf ( "%s/v%d/" , e . URL , version )
2014-08-26 19:21:04 -04:00
}
2014-12-11 20:55:15 -05:00
// Path returns a formatted string for the URL
// of this endpoint with the given path appended.
func ( e * Endpoint ) Path ( path string ) string {
return fmt . Sprintf ( "%s/v%d/%s" , e . URL , e . Version , path )
2014-08-26 19:21:04 -04:00
}
2015-07-21 15:40:36 -04:00
// Ping pings the remote endpoint with v2 and v1 pings to determine the API
// version. It returns a PingResult containing the discovered version. The
// PingResult also indicates whether the registry is standalone or not.
func ( e * Endpoint ) Ping ( ) ( PingResult , error ) {
2014-12-11 20:55:15 -05:00
// The ping logic to use is determined by the registry endpoint version.
switch e . Version {
case APIVersion1 :
2015-05-14 10:12:54 -04:00
return e . pingV1 ( )
2014-12-11 20:55:15 -05:00
case APIVersion2 :
2015-05-14 10:12:54 -04:00
return e . pingV2 ( )
2014-12-11 20:55:15 -05:00
}
// APIVersionUnknown
// We should try v2 first...
e . Version = APIVersion2
2015-05-14 10:12:54 -04:00
regInfo , errV2 := e . pingV2 ( )
2014-12-11 20:55:15 -05:00
if errV2 == nil {
return regInfo , nil
}
// ... then fallback to v1.
e . Version = APIVersion1
2015-05-14 10:12:54 -04:00
regInfo , errV1 := e . pingV1 ( )
2014-12-11 20:55:15 -05:00
if errV1 == nil {
return regInfo , nil
}
e . Version = APIVersionUnknown
2015-07-21 15:40:36 -04:00
return PingResult { } , fmt . Errorf ( "unable to ping registry endpoint %s\nv2 ping attempt failed with error: %s\n v1 ping attempt failed with error: %s" , e , errV2 , errV1 )
2014-12-11 20:55:15 -05:00
}
2015-07-21 15:40:36 -04:00
func ( e * Endpoint ) pingV1 ( ) ( PingResult , error ) {
2015-03-26 18:22:04 -04:00
logrus . Debugf ( "attempting v1 ping for registry endpoint %s" , e )
2014-12-11 20:55:15 -05:00
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)
2015-07-21 15:40:36 -04:00
return PingResult { Standalone : false } , nil
2014-08-26 19:21:04 -04:00
}
2015-05-14 10:12:54 -04:00
req , err := http . NewRequest ( "GET" , e . Path ( "_ping" ) , nil )
2014-08-26 19:21:04 -04:00
if err != nil {
2015-07-21 15:40:36 -04:00
return PingResult { Standalone : false } , 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 {
2015-07-21 15:40:36 -04:00
return PingResult { Standalone : false } , err
2014-08-26 19:21:04 -04:00
}
defer resp . Body . Close ( )
jsonString , err := ioutil . ReadAll ( resp . Body )
if err != nil {
2015-07-21 15:40:36 -04:00
return PingResult { Standalone : false } , fmt . Errorf ( "error while reading the http response: %s" , err )
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
2015-07-21 15:40:36 -04:00
info := PingResult {
2014-08-26 19:21:04 -04:00
Standalone : true ,
}
if err := json . Unmarshal ( jsonString , & info ) ; err != nil {
2015-07-21 15:40:36 -04:00
logrus . Debugf ( "Error unmarshalling the _ping PingResult: %s" , err )
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 != "" {
2015-03-26 18:22:04 -04:00
logrus . Debugf ( "Registry version header: '%s'" , hdr )
2014-08-26 19:21:04 -04:00
info . Version = hdr
}
2015-07-21 15:40:36 -04:00
logrus . Debugf ( "PingResult.Version: %q" , info . Version )
2014-08-26 19:21:04 -04:00
standalone := resp . Header . Get ( "X-Docker-Registry-Standalone" )
2015-03-26 18:22:04 -04:00
logrus . Debugf ( "Registry standalone header: '%s'" , standalone )
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
}
2015-07-21 15:40:36 -04:00
logrus . Debugf ( "PingResult.Standalone: %t" , info . Standalone )
2014-08-26 19:21:04 -04:00
return info , nil
}
2014-12-11 20:55:15 -05:00
2015-07-21 15:40:36 -04:00
func ( e * Endpoint ) pingV2 ( ) ( PingResult , error ) {
2015-03-26 18:22:04 -04:00
logrus . Debugf ( "attempting v2 ping for registry endpoint %s" , e )
2014-12-11 20:55:15 -05:00
2015-05-14 10:12:54 -04:00
req , err := http . NewRequest ( "GET" , e . Path ( "" ) , nil )
2014-12-11 20:55:15 -05:00
if err != nil {
2015-07-21 15:40:36 -04:00
return PingResult { } , err
2014-12-11 20:55:15 -05:00
}
2015-05-15 21:35:04 -04:00
resp , err := e . client . Do ( req )
2014-12-11 20:55:15 -05:00
if err != nil {
2015-07-21 15:40:36 -04:00
return PingResult { } , err
2014-12-11 20:55:15 -05:00
}
defer resp . Body . Close ( )
2015-01-20 22:37:21 -05:00
// The endpoint may have multiple supported versions.
// Ensure it supports the v2 Registry API.
var supportsV2 bool
2015-01-21 15:11:53 -05:00
HeaderLoop :
for _ , supportedVersions := range resp . Header [ http . CanonicalHeaderKey ( "Docker-Distribution-API-Version" ) ] {
for _ , versionName := range strings . Fields ( supportedVersions ) {
if versionName == "registry/2.0" {
supportsV2 = true
break HeaderLoop
}
2015-01-20 22:37:21 -05:00
}
}
if ! supportsV2 {
2015-07-21 15:40:36 -04:00
return PingResult { } , fmt . Errorf ( "%s does not appear to be a v2 registry endpoint" , e )
2015-01-20 22:37:21 -05:00
}
2014-12-11 20:55:15 -05:00
if resp . StatusCode == http . StatusOK {
// It would seem that no authentication/authorization is required.
// So we don't need to parse/add any authorization schemes.
2015-07-21 15:40:36 -04:00
return PingResult { Standalone : true } , nil
2014-12-11 20:55:15 -05:00
}
if resp . StatusCode == http . StatusUnauthorized {
// Parse the WWW-Authenticate Header and store the challenges
// on this endpoint object.
e . AuthChallenges = parseAuthHeader ( resp . Header )
2015-07-21 15:40:36 -04:00
return PingResult { } , nil
2014-12-11 20:55:15 -05:00
}
2015-07-21 15:40:36 -04:00
return PingResult { } , fmt . Errorf ( "v2 registry endpoint returned status %d: %q" , resp . StatusCode , http . StatusText ( resp . StatusCode ) )
2014-12-11 20:55:15 -05:00
}