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-11-11 22:37:44 +00:00
func NewEndpoint ( hostname string , insecureRegistries [ ] string ) ( * Endpoint , error ) {
2014-11-13 14:56:36 +00:00
endpoint , err := newEndpoint ( hostname , insecureRegistries )
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-11-13 14:56:36 +00:00
if endpoint . 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-11-13 14:56:36 +00:00
func newEndpoint ( hostname string , insecureRegistries [ ] string ) ( * 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-11-11 21:31:15 +00:00
endpoint . secure , err = isSecure ( endpoint . URL . Host , insecureRegistries )
if err != nil {
return nil , err
}
2014-08-26 23:21:04 +00:00
return & endpoint , nil
}
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-11-11 22:37:44 +00:00
// isSecure returns false if the provided hostname 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.
// If the subnet contains one of the IPs of the registry specified by hostname, the latter is considered
// insecure.
//
2014-12-19 00:13:56 +00:00
// hostname should be a URL.Host (`host:port` or `host`) where the `host` part can be either a domain name
// or an IP address. If it is a domain name, then it will be resolved in order to check if the IP is contained
// in a subnet. If the resolving is not successful, isSecure will only try to match hostname to any element
// of insecureRegistries.
2014-11-11 21:31:15 +00:00
func isSecure ( hostname string , insecureRegistries [ ] string ) ( bool , error ) {
2014-11-13 14:56:36 +00:00
if hostname == IndexServerURL . Host {
2014-11-11 21:31:15 +00:00
return true , nil
2014-10-11 03:22:12 +00:00
}
2014-11-12 17:08:45 +00:00
host , _ , err := net . SplitHostPort ( hostname )
if err != nil {
2014-11-11 21:31:15 +00:00
// assume hostname is of the form `host` without the port and go on.
2014-11-12 17:08:45 +00:00
host = hostname
}
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-12-19 00:13:56 +00:00
for _ , r := range insecureRegistries {
if hostname == r || host == r {
2014-11-11 21:31:15 +00:00
// hostname matches insecure registry
2014-12-19 00:13:56 +00:00
return false , nil
}
// Try CIDR notation only if addrs has any elements, i.e. if `host`'s IP could be determined.
for _ , addr := range addrs {
2014-11-11 21:31:15 +00:00
// now assume a CIDR was passed to --insecure-registry
_ , ipnet , err := net . ParseCIDR ( r )
if err != nil {
2014-12-19 00:13:56 +00:00
// if we could not parse it as a CIDR, even after removing
2014-11-11 21:31:15 +00:00
// assume it's not a CIDR and go on with the next candidate
2014-12-19 00:13:56 +00:00
break
2014-11-11 21:31:15 +00:00
}
// check if the addr falls in the subnet
if ipnet . Contains ( addr ) {
return false , nil
}
2014-10-11 03:22:12 +00:00
}
}
2014-11-11 21:31:15 +00:00
return true , nil
2014-10-11 03:22:12 +00:00
}