2014-08-26 23:21:04 +00:00
package registry
import (
"encoding/json"
"fmt"
"io/ioutil"
2014-10-31 20:00:49 +00:00
"net"
2014-08-26 23:21:04 +00:00
"net/http"
"net/url"
"strings"
2014-10-24 17:12:35 +00:00
log "github.com/Sirupsen/logrus"
2014-08-26 23:21:04 +00:00
)
2014-11-11 21:31:15 +00:00
// for mocking in unit tests
var lookupIP = net . LookupIP
2014-08-26 23:21:04 +00:00
// scans string for api version in the URL path. returns the trimmed hostname, if version found, string and API version.
2014-10-06 19:34:39 +00:00
func scanForAPIVersion ( hostname string ) ( string , APIVersion ) {
2014-08-26 23:21:04 +00:00
var (
chunks [ ] string
apiVersionStr string
)
if strings . HasSuffix ( hostname , "/" ) {
chunks = strings . Split ( hostname [ : len ( hostname ) - 1 ] , "/" )
apiVersionStr = chunks [ len ( chunks ) - 1 ]
} else {
chunks = strings . Split ( hostname , "/" )
apiVersionStr = chunks [ len ( chunks ) - 1 ]
}
for k , v := range apiVersions {
if apiVersionStr == v {
hostname = strings . Join ( chunks [ : len ( chunks ) - 1 ] , "/" )
return hostname , k
}
}
return hostname , DefaultAPIVersion
}
2014-10-07 01:54:52 +00:00
func NewEndpoint ( index * IndexInfo ) ( * Endpoint , error ) {
// *TODO: Allow per-registry configuration of endpoints.
endpoint , err := newEndpoint ( index . GetAuthConfigKey ( ) , index . Secure )
2014-08-26 23:21:04 +00:00
if err != nil {
return nil , err
}
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"
if _ , err := endpoint . Ping ( ) ; err != nil {
2014-10-11 03:22:12 +00:00
//TODO: triggering highland build can be done there without "failing"
2014-10-07 01:54:52 +00:00
if index . Secure {
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`
// in case that's what they need. DO NOT accept unknown CA certificates, and DO NOT fallback to HTTP.
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 )
}
// If registry is insecure and HTTPS failed, fallback to HTTP.
log . Debugf ( "Error from registry %q marked as insecure: %v. Insecurely falling back to HTTP" , endpoint , err )
2014-08-26 23:21:04 +00:00
endpoint . URL . Scheme = "http"
2014-10-11 03:22:12 +00:00
_ , err2 := endpoint . Ping ( )
if err2 == nil {
return endpoint , nil
2014-08-26 23:21:04 +00:00
}
2014-10-11 03:22:12 +00:00
return nil , fmt . Errorf ( "Invalid registry endpoint %q. HTTPS attempt: %v. HTTP attempt: %v" , endpoint , err , err2 )
2014-08-26 23:21:04 +00:00
}
2014-10-03 19:46:42 +00:00
return endpoint , nil
}
2014-10-07 01:54:52 +00:00
func newEndpoint ( hostname string , secure bool ) ( * Endpoint , error ) {
2014-10-03 19:46:42 +00:00
var (
2014-11-13 14:56:36 +00:00
endpoint = Endpoint { }
2014-10-03 19:46:42 +00:00
trimmedHostname string
err error
)
if ! strings . HasPrefix ( hostname , "http" ) {
hostname = "https://" + hostname
}
trimmedHostname , endpoint . Version = scanForAPIVersion ( hostname )
endpoint . URL , err = url . Parse ( trimmedHostname )
if err != nil {
return nil , err
}
2014-10-07 01:54:52 +00:00
endpoint . secure = secure
2014-08-26 23:21:04 +00:00
return & endpoint , nil
}
2014-10-07 01:54:52 +00:00
func ( repoInfo * RepositoryInfo ) GetEndpoint ( ) ( * Endpoint , error ) {
return NewEndpoint ( repoInfo . Index )
}
2014-08-26 23:21:04 +00:00
type Endpoint struct {
URL * url . URL
Version APIVersion
2014-10-11 03:22:12 +00:00
secure bool
2014-08-26 23:21:04 +00:00
}
// Get the formated URL for the root of this registry Endpoint
func ( e Endpoint ) String ( ) string {
return fmt . Sprintf ( "%s/v%d/" , e . URL . String ( ) , e . Version )
}
func ( e Endpoint ) VersionString ( version APIVersion ) string {
return fmt . Sprintf ( "%s/v%d/" , e . URL . String ( ) , version )
}
func ( e Endpoint ) Ping ( ) ( RegistryInfo , error ) {
if e . String ( ) == IndexServerAddress ( ) {
// Skip the check, we now this one is valid
// (and we never want to fallback to http in case of error)
return RegistryInfo { Standalone : false } , nil
}
req , err := http . NewRequest ( "GET" , e . String ( ) + "_ping" , nil )
if err != nil {
return RegistryInfo { Standalone : false } , err
}
2014-10-11 03:22:12 +00:00
resp , _ , err := doRequest ( req , nil , ConnectTimeout , e . secure )
2014-08-26 23:21:04 +00:00
if err != nil {
return RegistryInfo { Standalone : false } , err
}
defer resp . Body . Close ( )
jsonString , err := ioutil . ReadAll ( resp . Body )
if err != nil {
return RegistryInfo { Standalone : false } , fmt . Errorf ( "Error while reading the http response: %s" , err )
}
// If the header is absent, we assume true for compatibility with earlier
// versions of the registry. default to true
info := RegistryInfo {
Standalone : true ,
}
if err := json . Unmarshal ( jsonString , & info ) ; err != nil {
log . Debugf ( "Error unmarshalling the _ping RegistryInfo: %s" , err )
// don't stop here. Just assume sane defaults
}
if hdr := resp . Header . Get ( "X-Docker-Registry-Version" ) ; hdr != "" {
log . Debugf ( "Registry version header: '%s'" , hdr )
info . Version = hdr
}
log . Debugf ( "RegistryInfo.Version: %q" , info . Version )
standalone := resp . Header . Get ( "X-Docker-Registry-Standalone" )
log . Debugf ( "Registry standalone header: '%s'" , standalone )
// 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
}
2014-10-03 00:41:57 +00:00
log . Debugf ( "RegistryInfo.Standalone: %t" , info . Standalone )
2014-08-26 23:21:04 +00:00
return info , nil
}
2014-10-11 03:22:12 +00:00
2014-10-07 01:54:52 +00:00
// isSecureIndex returns false if the provided indexName is part of the list of insecure registries
2014-10-11 03:22:12 +00:00
// Insecure registries accept HTTP and/or accept HTTPS with certificates from unknown CAs.
2014-11-11 21:31:15 +00:00
//
// The list of insecure registries can contain an element with CIDR notation to specify a whole subnet.
2014-10-07 01:54:52 +00:00
// If the subnet contains one of the IPs of the registry specified by indexName, the latter is considered
2014-11-11 21:31:15 +00:00
// insecure.
//
2014-10-07 01:54:52 +00:00
// indexName should be a URL.Host (`host:port` or `host`) where the `host` part can be either a domain name
2014-12-19 00:13:56 +00:00
// or an IP address. If it is a domain name, then it will be resolved in order to check if the IP is contained
2014-10-07 01:54:52 +00:00
// in a subnet. If the resolving is not successful, isSecureIndex will only try to match hostname to any element
2014-12-19 00:13:56 +00:00
// of insecureRegistries.
2014-10-07 01:54:52 +00:00
func ( config * ServiceConfig ) isSecureIndex ( indexName string ) bool {
// Check for configured index, first. This is needed in case isSecureIndex
// is called from anything besides NewIndexInfo, in order to honor per-index configurations.
if index , ok := config . IndexConfigs [ indexName ] ; ok {
return index . Secure
2014-10-11 03:22:12 +00:00
}
2014-11-12 17:08:45 +00:00
2014-10-07 01:54:52 +00:00
host , _ , err := net . SplitHostPort ( indexName )
2014-11-12 17:08:45 +00:00
if err != nil {
2014-10-07 01:54:52 +00:00
// assume indexName is of the form `host` without the port and go on.
host = indexName
2014-11-12 17:08:45 +00:00
}
2014-10-07 01:54:52 +00:00
2014-11-11 21:31:15 +00:00
addrs , err := lookupIP ( host )
if err != nil {
ip := net . ParseIP ( host )
2014-12-19 00:13:56 +00:00
if ip != nil {
addrs = [ ] net . IP { ip }
2014-11-11 21:31:15 +00:00
}
2014-12-19 00:13:56 +00:00
// if ip == nil, then `host` is neither an IP nor it could be looked up,
// either because the index is unreachable, or because the index is behind an HTTP proxy.
// So, len(addrs) == 0 and we're not aborting.
2014-11-11 21:31:15 +00:00
}
2014-10-07 01:54:52 +00:00
// Try CIDR notation only if addrs has any elements, i.e. if `host`'s IP could be determined.
for _ , addr := range addrs {
for _ , ipnet := range config . InsecureRegistryCIDRs {
2014-11-11 21:31:15 +00:00
// check if the addr falls in the subnet
2014-10-07 01:54:52 +00:00
if ( * net . IPNet ) ( ipnet ) . Contains ( addr ) {
return false
2014-11-11 21:31:15 +00:00
}
2014-10-11 03:22:12 +00:00
}
}
2014-10-07 01:54:52 +00:00
return true
2014-10-11 03:22:12 +00:00
}