commit
96272e1c9a
5 changed files with 116 additions and 66 deletions
|
@ -2,7 +2,6 @@ package registry
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
@ -34,9 +33,9 @@ func scanForApiVersion(hostname string) (string, APIVersion) {
|
|||
return hostname, DefaultAPIVersion
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
|
@ -49,14 +48,27 @@ func NewEndpoint(hostname string) (*Endpoint, error) {
|
|||
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
|
||||
|
@ -65,6 +77,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
|
||||
|
@ -88,7 +101,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
|
||||
}
|
||||
|
@ -127,3 +140,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
|
||||
}
|
||||
|
|
106
docs/registry.go
106
docs/registry.go
|
@ -14,6 +14,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/pkg/log"
|
||||
"github.com/docker/docker/utils"
|
||||
)
|
||||
|
||||
|
@ -35,13 +36,21 @@ const (
|
|||
ConnectTimeout
|
||||
)
|
||||
|
||||
func newClient(jar http.CookieJar, roots *x509.CertPool, cert *tls.Certificate, timeout TimeoutType) *http.Client {
|
||||
tlsConfig := tls.Config{RootCAs: roots}
|
||||
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
|
||||
MinVersion: tls.VersionTLS10,
|
||||
}
|
||||
|
||||
if cert != nil {
|
||||
tlsConfig.Certificates = append(tlsConfig.Certificates, *cert)
|
||||
}
|
||||
|
||||
if !secure {
|
||||
tlsConfig.InsecureSkipVerify = true
|
||||
}
|
||||
|
||||
httpTransport := &http.Transport{
|
||||
DisableKeepAlives: true,
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
|
@ -78,69 +87,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 && res.StatusCode != 403 && res.StatusCode < 500 {
|
||||
|
|
|
@ -18,7 +18,7 @@ var (
|
|||
|
||||
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)
|
||||
}
|
||||
|
@ -30,7 +30,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)
|
||||
}
|
||||
|
|
|
@ -64,7 +64,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 a new issue