commit
						eba996acfb
					
				|  | @ -2,7 +2,6 @@ package registry | |||
| 
 | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"net/http" | ||||
|  | @ -34,27 +33,40 @@ func scanForAPIVersion(hostname string) (string, APIVersion) { | |||
| 	return hostname, DefaultAPIVersion | ||||
| } | ||||
| 
 | ||||
| func NewEndpoint(hostname string) (*Endpoint, error) { | ||||
| 	endpoint, err := newEndpoint(hostname) | ||||
| func NewEndpoint(hostname string, secure bool) (*Endpoint, error) { | ||||
| 	endpoint, err := newEndpoint(hostname, secure) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	// Try HTTPS ping to registry
 | ||||
| 	endpoint.URL.Scheme = "https" | ||||
| 	if _, err := endpoint.Ping(); err != nil { | ||||
| 		log.Debugf("Registry %s does not work (%s), falling back to http", endpoint, err) | ||||
| 		// TODO: Check if http fallback is enabled
 | ||||
| 		endpoint.URL.Scheme = "http" | ||||
| 		if _, err = endpoint.Ping(); err != nil { | ||||
| 			return nil, errors.New("Invalid Registry endpoint: " + err.Error()) | ||||
| 
 | ||||
| 		//TODO: triggering highland build can be done there without "failing"
 | ||||
| 
 | ||||
| 		if secure { | ||||
| 			// 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) | ||||
| 		endpoint.URL.Scheme = "http" | ||||
| 		_, err2 := endpoint.Ping() | ||||
| 		if err2 == nil { | ||||
| 			return endpoint, nil | ||||
| 		} | ||||
| 
 | ||||
| 		return nil, fmt.Errorf("Invalid registry endpoint %q. HTTPS attempt: %v. HTTP attempt: %v", endpoint, err, err2) | ||||
| 	} | ||||
| 
 | ||||
| 	return endpoint, nil | ||||
| } | ||||
| func newEndpoint(hostname string) (*Endpoint, error) { | ||||
| func newEndpoint(hostname string, secure bool) (*Endpoint, error) { | ||||
| 	var ( | ||||
| 		endpoint        Endpoint | ||||
| 		endpoint        = Endpoint{secure: secure} | ||||
| 		trimmedHostname string | ||||
| 		err             error | ||||
| 	) | ||||
|  | @ -72,6 +84,7 @@ func newEndpoint(hostname string) (*Endpoint, error) { | |||
| type Endpoint struct { | ||||
| 	URL     *url.URL | ||||
| 	Version APIVersion | ||||
| 	secure  bool | ||||
| } | ||||
| 
 | ||||
| // Get the formated URL for the root of this registry Endpoint
 | ||||
|  | @ -95,7 +108,7 @@ func (e Endpoint) Ping() (RegistryInfo, error) { | |||
| 		return RegistryInfo{Standalone: false}, err | ||||
| 	} | ||||
| 
 | ||||
| 	resp, _, err := doRequest(req, nil, ConnectTimeout) | ||||
| 	resp, _, err := doRequest(req, nil, ConnectTimeout, e.secure) | ||||
| 	if err != nil { | ||||
| 		return RegistryInfo{Standalone: false}, err | ||||
| 	} | ||||
|  | @ -134,3 +147,19 @@ func (e Endpoint) Ping() (RegistryInfo, error) { | |||
| 	log.Debugf("RegistryInfo.Standalone: %t", info.Standalone) | ||||
| 	return info, nil | ||||
| } | ||||
| 
 | ||||
| // IsSecure returns false if the provided hostname is part of the list of insecure registries.
 | ||||
| // Insecure registries accept HTTP and/or accept HTTPS with certificates from unknown CAs.
 | ||||
| func IsSecure(hostname string, insecureRegistries []string) bool { | ||||
| 	if hostname == IndexServerAddress() { | ||||
| 		return true | ||||
| 	} | ||||
| 
 | ||||
| 	for _, h := range insecureRegistries { | ||||
| 		if hostname == h { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return true | ||||
| } | ||||
|  |  | |||
|  | @ -12,7 +12,7 @@ func TestEndpointParse(t *testing.T) { | |||
| 		{"0.0.0.0:5000", "https://0.0.0.0:5000/v1/"}, | ||||
| 	} | ||||
| 	for _, td := range testData { | ||||
| 		e, err := newEndpoint(td.str) | ||||
| 		e, err := newEndpoint(td.str, true) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("%q: %s", td.str, err) | ||||
| 		} | ||||
|  |  | |||
							
								
								
									
										100
									
								
								docs/registry.go
								
								
								
								
							
							
						
						
									
										100
									
								
								docs/registry.go
								
								
								
								
							|  | @ -14,6 +14,7 @@ import ( | |||
| 	"strings" | ||||
| 	"time" | ||||
| 
 | ||||
| 	log "github.com/Sirupsen/logrus" | ||||
| 	"github.com/docker/docker/utils" | ||||
| ) | ||||
| 
 | ||||
|  | @ -35,7 +36,7 @@ const ( | |||
| 	ConnectTimeout | ||||
| ) | ||||
| 
 | ||||
| func newClient(jar http.CookieJar, roots *x509.CertPool, cert *tls.Certificate, timeout TimeoutType) *http.Client { | ||||
| func newClient(jar http.CookieJar, roots *x509.CertPool, cert *tls.Certificate, timeout TimeoutType, secure bool) *http.Client { | ||||
| 	tlsConfig := tls.Config{ | ||||
| 		RootCAs: roots, | ||||
| 		// Avoid fallback to SSL protocols < TLS1.0
 | ||||
|  | @ -46,6 +47,10 @@ func newClient(jar http.CookieJar, roots *x509.CertPool, cert *tls.Certificate, | |||
| 		tlsConfig.Certificates = append(tlsConfig.Certificates, *cert) | ||||
| 	} | ||||
| 
 | ||||
| 	if !secure { | ||||
| 		tlsConfig.InsecureSkipVerify = true | ||||
| 	} | ||||
| 
 | ||||
| 	httpTransport := &http.Transport{ | ||||
| 		DisableKeepAlives: true, | ||||
| 		Proxy:             http.ProxyFromEnvironment, | ||||
|  | @ -86,69 +91,76 @@ func newClient(jar http.CookieJar, roots *x509.CertPool, cert *tls.Certificate, | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| func doRequest(req *http.Request, jar http.CookieJar, timeout TimeoutType) (*http.Response, *http.Client, error) { | ||||
| 	hasFile := func(files []os.FileInfo, name string) bool { | ||||
| 		for _, f := range files { | ||||
| 			if f.Name() == name { | ||||
| 				return true | ||||
| 			} | ||||
| 		} | ||||
| 		return false | ||||
| 	} | ||||
| 
 | ||||
| 	hostDir := path.Join("/etc/docker/certs.d", req.URL.Host) | ||||
| 	fs, err := ioutil.ReadDir(hostDir) | ||||
| 	if err != nil && !os.IsNotExist(err) { | ||||
| 		return nil, nil, err | ||||
| 	} | ||||
| 
 | ||||
| func doRequest(req *http.Request, jar http.CookieJar, timeout TimeoutType, secure bool) (*http.Response, *http.Client, error) { | ||||
| 	var ( | ||||
| 		pool  *x509.CertPool | ||||
| 		certs []*tls.Certificate | ||||
| 	) | ||||
| 
 | ||||
| 	for _, f := range fs { | ||||
| 		if strings.HasSuffix(f.Name(), ".crt") { | ||||
| 			if pool == nil { | ||||
| 				pool = x509.NewCertPool() | ||||
| 	if secure && req.URL.Scheme == "https" { | ||||
| 		hasFile := func(files []os.FileInfo, name string) bool { | ||||
| 			for _, f := range files { | ||||
| 				if f.Name() == name { | ||||
| 					return true | ||||
| 				} | ||||
| 			} | ||||
| 			data, err := ioutil.ReadFile(path.Join(hostDir, f.Name())) | ||||
| 			if err != nil { | ||||
| 				return nil, nil, err | ||||
| 			} | ||||
| 			pool.AppendCertsFromPEM(data) | ||||
| 			return false | ||||
| 		} | ||||
| 		if strings.HasSuffix(f.Name(), ".cert") { | ||||
| 			certName := f.Name() | ||||
| 			keyName := certName[:len(certName)-5] + ".key" | ||||
| 			if !hasFile(fs, keyName) { | ||||
| 				return nil, nil, fmt.Errorf("Missing key %s for certificate %s", keyName, certName) | ||||
| 			} | ||||
| 			cert, err := tls.LoadX509KeyPair(path.Join(hostDir, certName), path.Join(hostDir, keyName)) | ||||
| 			if err != nil { | ||||
| 				return nil, nil, err | ||||
| 			} | ||||
| 			certs = append(certs, &cert) | ||||
| 
 | ||||
| 		hostDir := path.Join("/etc/docker/certs.d", req.URL.Host) | ||||
| 		log.Debugf("hostDir: %s", hostDir) | ||||
| 		fs, err := ioutil.ReadDir(hostDir) | ||||
| 		if err != nil && !os.IsNotExist(err) { | ||||
| 			return nil, nil, err | ||||
| 		} | ||||
| 		if strings.HasSuffix(f.Name(), ".key") { | ||||
| 			keyName := f.Name() | ||||
| 			certName := keyName[:len(keyName)-4] + ".cert" | ||||
| 			if !hasFile(fs, certName) { | ||||
| 				return nil, nil, fmt.Errorf("Missing certificate %s for key %s", certName, keyName) | ||||
| 
 | ||||
| 		for _, f := range fs { | ||||
| 			if strings.HasSuffix(f.Name(), ".crt") { | ||||
| 				if pool == nil { | ||||
| 					pool = x509.NewCertPool() | ||||
| 				} | ||||
| 				log.Debugf("crt: %s", hostDir+"/"+f.Name()) | ||||
| 				data, err := ioutil.ReadFile(path.Join(hostDir, f.Name())) | ||||
| 				if err != nil { | ||||
| 					return nil, nil, err | ||||
| 				} | ||||
| 				pool.AppendCertsFromPEM(data) | ||||
| 			} | ||||
| 			if strings.HasSuffix(f.Name(), ".cert") { | ||||
| 				certName := f.Name() | ||||
| 				keyName := certName[:len(certName)-5] + ".key" | ||||
| 				log.Debugf("cert: %s", hostDir+"/"+f.Name()) | ||||
| 				if !hasFile(fs, keyName) { | ||||
| 					return nil, nil, fmt.Errorf("Missing key %s for certificate %s", keyName, certName) | ||||
| 				} | ||||
| 				cert, err := tls.LoadX509KeyPair(path.Join(hostDir, certName), path.Join(hostDir, keyName)) | ||||
| 				if err != nil { | ||||
| 					return nil, nil, err | ||||
| 				} | ||||
| 				certs = append(certs, &cert) | ||||
| 			} | ||||
| 			if strings.HasSuffix(f.Name(), ".key") { | ||||
| 				keyName := f.Name() | ||||
| 				certName := keyName[:len(keyName)-4] + ".cert" | ||||
| 				log.Debugf("key: %s", hostDir+"/"+f.Name()) | ||||
| 				if !hasFile(fs, certName) { | ||||
| 					return nil, nil, fmt.Errorf("Missing certificate %s for key %s", certName, keyName) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if len(certs) == 0 { | ||||
| 		client := newClient(jar, pool, nil, timeout) | ||||
| 		client := newClient(jar, pool, nil, timeout, secure) | ||||
| 		res, err := client.Do(req) | ||||
| 		if err != nil { | ||||
| 			return nil, nil, err | ||||
| 		} | ||||
| 		return res, client, nil | ||||
| 	} | ||||
| 
 | ||||
| 	for i, cert := range certs { | ||||
| 		client := newClient(jar, pool, cert, timeout) | ||||
| 		client := newClient(jar, pool, cert, timeout, secure) | ||||
| 		res, err := client.Do(req) | ||||
| 		// If this is the last cert, otherwise, continue to next cert if 403 or 5xx
 | ||||
| 		if i == len(certs)-1 || err == nil && | ||||
|  |  | |||
|  | @ -21,7 +21,7 @@ const ( | |||
| 
 | ||||
| func spawnTestRegistrySession(t *testing.T) *Session { | ||||
| 	authConfig := &AuthConfig{} | ||||
| 	endpoint, err := NewEndpoint(makeURL("/v1/")) | ||||
| 	endpoint, err := NewEndpoint(makeURL("/v1/"), false) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | @ -33,7 +33,7 @@ func spawnTestRegistrySession(t *testing.T) *Session { | |||
| } | ||||
| 
 | ||||
| func TestPingRegistryEndpoint(t *testing.T) { | ||||
| 	ep, err := NewEndpoint(makeURL("/v1/")) | ||||
| 	ep, err := NewEndpoint(makeURL("/v1/"), false) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  |  | |||
|  | @ -13,12 +13,15 @@ import ( | |||
| //  'pull': Download images from any registry (TODO)
 | ||||
| //  'push': Upload images to any registry (TODO)
 | ||||
| type Service struct { | ||||
| 	insecureRegistries []string | ||||
| } | ||||
| 
 | ||||
| // NewService returns a new instance of Service ready to be
 | ||||
| // installed no an engine.
 | ||||
| func NewService() *Service { | ||||
| 	return &Service{} | ||||
| func NewService(insecureRegistries []string) *Service { | ||||
| 	return &Service{ | ||||
| 		insecureRegistries: insecureRegistries, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Install installs registry capabilities to eng.
 | ||||
|  | @ -32,15 +35,12 @@ func (s *Service) Install(eng *engine.Engine) error { | |||
| // and returns OK if authentication was sucessful.
 | ||||
| // It can be used to verify the validity of a client's credentials.
 | ||||
| func (s *Service) Auth(job *engine.Job) engine.Status { | ||||
| 	var ( | ||||
| 		err        error | ||||
| 		authConfig = &AuthConfig{} | ||||
| 	) | ||||
| 	var authConfig = new(AuthConfig) | ||||
| 
 | ||||
| 	job.GetenvJson("authConfig", authConfig) | ||||
| 	// TODO: this is only done here because auth and registry need to be merged into one pkg
 | ||||
| 
 | ||||
| 	if addr := authConfig.ServerAddress; addr != "" && addr != IndexServerAddress() { | ||||
| 		endpoint, err := NewEndpoint(addr) | ||||
| 		endpoint, err := NewEndpoint(addr, IsSecure(addr, s.insecureRegistries)) | ||||
| 		if err != nil { | ||||
| 			return job.Error(err) | ||||
| 		} | ||||
|  | @ -49,11 +49,13 @@ func (s *Service) Auth(job *engine.Job) engine.Status { | |||
| 		} | ||||
| 		authConfig.ServerAddress = endpoint.String() | ||||
| 	} | ||||
| 
 | ||||
| 	status, err := Login(authConfig, HTTPRequestFactory(nil)) | ||||
| 	if err != nil { | ||||
| 		return job.Error(err) | ||||
| 	} | ||||
| 	job.Printf("%s\n", status) | ||||
| 
 | ||||
| 	return engine.StatusOK | ||||
| } | ||||
| 
 | ||||
|  | @ -89,7 +91,10 @@ func (s *Service) Search(job *engine.Job) engine.Status { | |||
| 	if err != nil { | ||||
| 		return job.Error(err) | ||||
| 	} | ||||
| 	endpoint, err := NewEndpoint(hostname) | ||||
| 
 | ||||
| 	secure := IsSecure(hostname, s.insecureRegistries) | ||||
| 
 | ||||
| 	endpoint, err := NewEndpoint(hostname, secure) | ||||
| 	if err != nil { | ||||
| 		return job.Error(err) | ||||
| 	} | ||||
|  |  | |||
|  | @ -65,7 +65,7 @@ func NewSession(authConfig *AuthConfig, factory *utils.HTTPRequestFactory, endpo | |||
| } | ||||
| 
 | ||||
| func (r *Session) doRequest(req *http.Request) (*http.Response, *http.Client, error) { | ||||
| 	return doRequest(req, r.jar, r.timeout) | ||||
| 	return doRequest(req, r.jar, r.timeout, r.indexEndpoint.secure) | ||||
| } | ||||
| 
 | ||||
| // Retrieve the history of a given image from the Registry.
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue