Merge pull request #8870 from tiborvass/merge_release_v1.3.1

Merge release v1.3.1
This commit is contained in:
Tibor Vass 2014-10-30 20:24:34 -04:00
commit eba996acfb
6 changed files with 114 additions and 68 deletions

View File

@ -2,7 +2,6 @@ package registry
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
@ -34,27 +33,40 @@ func scanForAPIVersion(hostname string) (string, APIVersion) {
return hostname, DefaultAPIVersion return hostname, DefaultAPIVersion
} }
func NewEndpoint(hostname string) (*Endpoint, error) { func NewEndpoint(hostname string, secure bool) (*Endpoint, error) {
endpoint, err := newEndpoint(hostname) endpoint, err := newEndpoint(hostname, secure)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Try HTTPS ping to registry
endpoint.URL.Scheme = "https" endpoint.URL.Scheme = "https"
if _, err := endpoint.Ping(); err != nil { 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 //TODO: triggering highland build can be done there without "failing"
endpoint.URL.Scheme = "http"
if _, err = endpoint.Ping(); err != nil { if secure {
return nil, errors.New("Invalid Registry endpoint: " + err.Error()) // 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 return endpoint, nil
} }
func newEndpoint(hostname string) (*Endpoint, error) { func newEndpoint(hostname string, secure bool) (*Endpoint, error) {
var ( var (
endpoint Endpoint endpoint = Endpoint{secure: secure}
trimmedHostname string trimmedHostname string
err error err error
) )
@ -72,6 +84,7 @@ func newEndpoint(hostname string) (*Endpoint, error) {
type Endpoint struct { type Endpoint struct {
URL *url.URL URL *url.URL
Version APIVersion Version APIVersion
secure bool
} }
// Get the formated URL for the root of this registry Endpoint // 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 return RegistryInfo{Standalone: false}, err
} }
resp, _, err := doRequest(req, nil, ConnectTimeout) resp, _, err := doRequest(req, nil, ConnectTimeout, e.secure)
if err != nil { if err != nil {
return RegistryInfo{Standalone: false}, err return RegistryInfo{Standalone: false}, err
} }
@ -134,3 +147,19 @@ func (e Endpoint) Ping() (RegistryInfo, error) {
log.Debugf("RegistryInfo.Standalone: %t", info.Standalone) log.Debugf("RegistryInfo.Standalone: %t", info.Standalone)
return info, nil 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
}

View File

@ -12,7 +12,7 @@ func TestEndpointParse(t *testing.T) {
{"0.0.0.0:5000", "https://0.0.0.0:5000/v1/"}, {"0.0.0.0:5000", "https://0.0.0.0:5000/v1/"},
} }
for _, td := range testData { for _, td := range testData {
e, err := newEndpoint(td.str) e, err := newEndpoint(td.str, true)
if err != nil { if err != nil {
t.Errorf("%q: %s", td.str, err) t.Errorf("%q: %s", td.str, err)
} }

View File

@ -14,6 +14,7 @@ import (
"strings" "strings"
"time" "time"
log "github.com/Sirupsen/logrus"
"github.com/docker/docker/utils" "github.com/docker/docker/utils"
) )
@ -35,7 +36,7 @@ const (
ConnectTimeout 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{ tlsConfig := tls.Config{
RootCAs: roots, RootCAs: roots,
// Avoid fallback to SSL protocols < TLS1.0 // 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) tlsConfig.Certificates = append(tlsConfig.Certificates, *cert)
} }
if !secure {
tlsConfig.InsecureSkipVerify = true
}
httpTransport := &http.Transport{ httpTransport := &http.Transport{
DisableKeepAlives: true, DisableKeepAlives: true,
Proxy: http.ProxyFromEnvironment, 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) { func doRequest(req *http.Request, jar http.CookieJar, timeout TimeoutType, secure bool) (*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
}
var ( var (
pool *x509.CertPool pool *x509.CertPool
certs []*tls.Certificate certs []*tls.Certificate
) )
for _, f := range fs { if secure && req.URL.Scheme == "https" {
if strings.HasSuffix(f.Name(), ".crt") { hasFile := func(files []os.FileInfo, name string) bool {
if pool == nil { for _, f := range files {
pool = x509.NewCertPool() if f.Name() == name {
return true
}
} }
data, err := ioutil.ReadFile(path.Join(hostDir, f.Name())) return false
if err != nil {
return nil, nil, err
}
pool.AppendCertsFromPEM(data)
} }
if strings.HasSuffix(f.Name(), ".cert") {
certName := f.Name() hostDir := path.Join("/etc/docker/certs.d", req.URL.Host)
keyName := certName[:len(certName)-5] + ".key" log.Debugf("hostDir: %s", hostDir)
if !hasFile(fs, keyName) { fs, err := ioutil.ReadDir(hostDir)
return nil, nil, fmt.Errorf("Missing key %s for certificate %s", keyName, certName) if err != nil && !os.IsNotExist(err) {
} return nil, nil, err
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() for _, f := range fs {
certName := keyName[:len(keyName)-4] + ".cert" if strings.HasSuffix(f.Name(), ".crt") {
if !hasFile(fs, certName) { if pool == nil {
return nil, nil, fmt.Errorf("Missing certificate %s for key %s", certName, keyName) 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 { if len(certs) == 0 {
client := newClient(jar, pool, nil, timeout) client := newClient(jar, pool, nil, timeout, secure)
res, err := client.Do(req) res, err := client.Do(req)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
return res, client, nil return res, client, nil
} }
for i, cert := range certs { for i, cert := range certs {
client := newClient(jar, pool, cert, timeout) client := newClient(jar, pool, cert, timeout, secure)
res, err := client.Do(req) res, err := client.Do(req)
// If this is the last cert, otherwise, continue to next cert if 403 or 5xx // If this is the last cert, otherwise, continue to next cert if 403 or 5xx
if i == len(certs)-1 || err == nil && if i == len(certs)-1 || err == nil &&

View File

@ -21,7 +21,7 @@ const (
func spawnTestRegistrySession(t *testing.T) *Session { func spawnTestRegistrySession(t *testing.T) *Session {
authConfig := &AuthConfig{} authConfig := &AuthConfig{}
endpoint, err := NewEndpoint(makeURL("/v1/")) endpoint, err := NewEndpoint(makeURL("/v1/"), false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -33,7 +33,7 @@ func spawnTestRegistrySession(t *testing.T) *Session {
} }
func TestPingRegistryEndpoint(t *testing.T) { func TestPingRegistryEndpoint(t *testing.T) {
ep, err := NewEndpoint(makeURL("/v1/")) ep, err := NewEndpoint(makeURL("/v1/"), false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -13,12 +13,15 @@ import (
// 'pull': Download images from any registry (TODO) // 'pull': Download images from any registry (TODO)
// 'push': Upload images to any registry (TODO) // 'push': Upload images to any registry (TODO)
type Service struct { type Service struct {
insecureRegistries []string
} }
// NewService returns a new instance of Service ready to be // NewService returns a new instance of Service ready to be
// installed no an engine. // installed no an engine.
func NewService() *Service { func NewService(insecureRegistries []string) *Service {
return &Service{} return &Service{
insecureRegistries: insecureRegistries,
}
} }
// Install installs registry capabilities to eng. // 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. // and returns OK if authentication was sucessful.
// It can be used to verify the validity of a client's credentials. // It can be used to verify the validity of a client's credentials.
func (s *Service) Auth(job *engine.Job) engine.Status { func (s *Service) Auth(job *engine.Job) engine.Status {
var ( var authConfig = new(AuthConfig)
err error
authConfig = &AuthConfig{}
)
job.GetenvJson("authConfig", 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() { if addr := authConfig.ServerAddress; addr != "" && addr != IndexServerAddress() {
endpoint, err := NewEndpoint(addr) endpoint, err := NewEndpoint(addr, IsSecure(addr, s.insecureRegistries))
if err != nil { if err != nil {
return job.Error(err) return job.Error(err)
} }
@ -49,11 +49,13 @@ func (s *Service) Auth(job *engine.Job) engine.Status {
} }
authConfig.ServerAddress = endpoint.String() authConfig.ServerAddress = endpoint.String()
} }
status, err := Login(authConfig, HTTPRequestFactory(nil)) status, err := Login(authConfig, HTTPRequestFactory(nil))
if err != nil { if err != nil {
return job.Error(err) return job.Error(err)
} }
job.Printf("%s\n", status) job.Printf("%s\n", status)
return engine.StatusOK return engine.StatusOK
} }
@ -89,7 +91,10 @@ func (s *Service) Search(job *engine.Job) engine.Status {
if err != nil { if err != nil {
return job.Error(err) return job.Error(err)
} }
endpoint, err := NewEndpoint(hostname)
secure := IsSecure(hostname, s.insecureRegistries)
endpoint, err := NewEndpoint(hostname, secure)
if err != nil { if err != nil {
return job.Error(err) return job.Error(err)
} }

View File

@ -65,7 +65,7 @@ func NewSession(authConfig *AuthConfig, factory *utils.HTTPRequestFactory, endpo
} }
func (r *Session) doRequest(req *http.Request) (*http.Response, *http.Client, error) { 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. // Retrieve the history of a given image from the Registry.