瀏覽代碼

Smarter push/pull TLS fallback

With the --insecure-registry daemon option (or talking to a registry on
a local IP), the daemon will first try TLS, and then try plaintext if
something goes wrong with the push or pull. It doesn't make sense to try
plaintext if a HTTP request went through while using TLS. This commit
changes the logic to keep track of host/port combinations where a TLS
attempt managed to do at least one HTTP request (whether the response
code indicated success or not). If the host/port responded to a HTTP
using TLS, we won't try to make plaintext HTTP requests to it.

This will result in better error messages, which sometimes ended up
showing the result of the plaintext attempt, like this:

    Error response from daemon: Get
    http://myregistrydomain.com:5000/v2/: malformed HTTP response
    "\x15\x03\x01\x00\x02\x02"

Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
Aaron Lehmann 9 年之前
父節點
當前提交
5e8af46fda
共有 6 個文件被更改,包括 95 次插入11 次删除
  1. 4 0
      distribution/errors.go
  2. 23 0
      distribution/pull.go
  3. 16 4
      distribution/pull_v2.go
  4. 22 0
      distribution/push.go
  5. 6 2
      distribution/push_v2.go
  6. 24 5
      distribution/registry.go

+ 4 - 0
distribution/errors.go

@@ -31,6 +31,10 @@ type fallbackError struct {
 	// supports the v2 protocol. This is used to limit fallbacks to the v1
 	// supports the v2 protocol. This is used to limit fallbacks to the v1
 	// protocol.
 	// protocol.
 	confirmedV2 bool
 	confirmedV2 bool
+	// transportOK is set to true if we managed to speak HTTP with the
+	// registry. This confirms that we're using appropriate TLS settings
+	// (or lack of TLS).
+	transportOK bool
 }
 }
 
 
 // Error renders the FallbackError as a string.
 // Error renders the FallbackError as a string.

+ 23 - 0
distribution/pull.go

@@ -2,6 +2,7 @@ package distribution
 
 
 import (
 import (
 	"fmt"
 	"fmt"
+	"net/url"
 
 
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
 	"github.com/docker/docker/api"
 	"github.com/docker/docker/api"
@@ -109,12 +110,31 @@ func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullCo
 		// confirm that it was talking to a v2 registry. This will
 		// confirm that it was talking to a v2 registry. This will
 		// prevent fallback to the v1 protocol.
 		// prevent fallback to the v1 protocol.
 		confirmedV2 bool
 		confirmedV2 bool
+
+		// confirmedTLSRegistries is a map indicating which registries
+		// are known to be using TLS. There should never be a plaintext
+		// retry for any of these.
+		confirmedTLSRegistries = make(map[string]struct{})
 	)
 	)
 	for _, endpoint := range endpoints {
 	for _, endpoint := range endpoints {
 		if confirmedV2 && endpoint.Version == registry.APIVersion1 {
 		if confirmedV2 && endpoint.Version == registry.APIVersion1 {
 			logrus.Debugf("Skipping v1 endpoint %s because v2 registry was detected", endpoint.URL)
 			logrus.Debugf("Skipping v1 endpoint %s because v2 registry was detected", endpoint.URL)
 			continue
 			continue
 		}
 		}
+
+		parsedURL, urlErr := url.Parse(endpoint.URL)
+		if urlErr != nil {
+			logrus.Errorf("Failed to parse endpoint URL %s", endpoint.URL)
+			continue
+		}
+
+		if parsedURL.Scheme != "https" {
+			if _, confirmedTLS := confirmedTLSRegistries[parsedURL.Host]; confirmedTLS {
+				logrus.Debugf("Skipping non-TLS endpoint %s for host/port that appears to use TLS", endpoint.URL)
+				continue
+			}
+		}
+
 		logrus.Debugf("Trying to pull %s from %s %s", repoInfo.Name(), endpoint.URL, endpoint.Version)
 		logrus.Debugf("Trying to pull %s from %s %s", repoInfo.Name(), endpoint.URL, endpoint.Version)
 
 
 		puller, err := newPuller(endpoint, repoInfo, imagePullConfig)
 		puller, err := newPuller(endpoint, repoInfo, imagePullConfig)
@@ -132,6 +152,9 @@ func Pull(ctx context.Context, ref reference.Named, imagePullConfig *ImagePullCo
 				if fallbackErr, ok := err.(fallbackError); ok {
 				if fallbackErr, ok := err.(fallbackError); ok {
 					fallback = true
 					fallback = true
 					confirmedV2 = confirmedV2 || fallbackErr.confirmedV2
 					confirmedV2 = confirmedV2 || fallbackErr.confirmedV2
+					if fallbackErr.transportOK && parsedURL.Scheme == "https" {
+						confirmedTLSRegistries[parsedURL.Host] = struct{}{}
+					}
 					err = fallbackErr.err
 					err = fallbackErr.err
 				}
 				}
 			}
 			}

+ 16 - 4
distribution/pull_v2.go

@@ -62,7 +62,7 @@ func (p *v2Puller) Pull(ctx context.Context, ref reference.Named) (err error) {
 	p.repo, p.confirmedV2, err = NewV2Repository(ctx, p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "pull")
 	p.repo, p.confirmedV2, err = NewV2Repository(ctx, p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "pull")
 	if err != nil {
 	if err != nil {
 		logrus.Warnf("Error getting v2 registry: %v", err)
 		logrus.Warnf("Error getting v2 registry: %v", err)
-		return fallbackError{err: err, confirmedV2: p.confirmedV2}
+		return err
 	}
 	}
 
 
 	if err = p.pullV2Repository(ctx, ref); err != nil {
 	if err = p.pullV2Repository(ctx, ref); err != nil {
@@ -71,7 +71,11 @@ func (p *v2Puller) Pull(ctx context.Context, ref reference.Named) (err error) {
 		}
 		}
 		if continueOnError(err) {
 		if continueOnError(err) {
 			logrus.Errorf("Error trying v2 registry: %v", err)
 			logrus.Errorf("Error trying v2 registry: %v", err)
-			return fallbackError{err: err, confirmedV2: p.confirmedV2}
+			return fallbackError{
+				err:         err,
+				confirmedV2: p.confirmedV2,
+				transportOK: true,
+			}
 		}
 		}
 	}
 	}
 	return err
 	return err
@@ -716,12 +720,20 @@ func allowV1Fallback(err error) error {
 	case errcode.Errors:
 	case errcode.Errors:
 		if len(v) != 0 {
 		if len(v) != 0 {
 			if v0, ok := v[0].(errcode.Error); ok && shouldV2Fallback(v0) {
 			if v0, ok := v[0].(errcode.Error); ok && shouldV2Fallback(v0) {
-				return fallbackError{err: err, confirmedV2: false}
+				return fallbackError{
+					err:         err,
+					confirmedV2: false,
+					transportOK: true,
+				}
 			}
 			}
 		}
 		}
 	case errcode.Error:
 	case errcode.Error:
 		if shouldV2Fallback(v) {
 		if shouldV2Fallback(v) {
-			return fallbackError{err: err, confirmedV2: false}
+			return fallbackError{
+				err:         err,
+				confirmedV2: false,
+				transportOK: true,
+			}
 		}
 		}
 	case *url.Error:
 	case *url.Error:
 		if v.Err == auth.ErrNoBasicAuthCredentials {
 		if v.Err == auth.ErrNoBasicAuthCredentials {

+ 22 - 0
distribution/push.go

@@ -5,6 +5,7 @@ import (
 	"compress/gzip"
 	"compress/gzip"
 	"fmt"
 	"fmt"
 	"io"
 	"io"
+	"net/url"
 
 
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
 	"github.com/docker/docker/distribution/metadata"
 	"github.com/docker/docker/distribution/metadata"
@@ -119,6 +120,11 @@ func Push(ctx context.Context, ref reference.Named, imagePushConfig *ImagePushCo
 		// confirm that it was talking to a v2 registry. This will
 		// confirm that it was talking to a v2 registry. This will
 		// prevent fallback to the v1 protocol.
 		// prevent fallback to the v1 protocol.
 		confirmedV2 bool
 		confirmedV2 bool
+
+		// confirmedTLSRegistries is a map indicating which registries
+		// are known to be using TLS. There should never be a plaintext
+		// retry for any of these.
+		confirmedTLSRegistries = make(map[string]struct{})
 	)
 	)
 
 
 	for _, endpoint := range endpoints {
 	for _, endpoint := range endpoints {
@@ -127,6 +133,19 @@ func Push(ctx context.Context, ref reference.Named, imagePushConfig *ImagePushCo
 			continue
 			continue
 		}
 		}
 
 
+		parsedURL, urlErr := url.Parse(endpoint.URL)
+		if urlErr != nil {
+			logrus.Errorf("Failed to parse endpoint URL %s", endpoint.URL)
+			continue
+		}
+
+		if parsedURL.Scheme != "https" {
+			if _, confirmedTLS := confirmedTLSRegistries[parsedURL.Host]; confirmedTLS {
+				logrus.Debugf("Skipping non-TLS endpoint %s for host/port that appears to use TLS", endpoint.URL)
+				continue
+			}
+		}
+
 		logrus.Debugf("Trying to push %s to %s %s", repoInfo.FullName(), endpoint.URL, endpoint.Version)
 		logrus.Debugf("Trying to push %s to %s %s", repoInfo.FullName(), endpoint.URL, endpoint.Version)
 
 
 		pusher, err := NewPusher(ref, endpoint, repoInfo, imagePushConfig)
 		pusher, err := NewPusher(ref, endpoint, repoInfo, imagePushConfig)
@@ -142,6 +161,9 @@ func Push(ctx context.Context, ref reference.Named, imagePushConfig *ImagePushCo
 			default:
 			default:
 				if fallbackErr, ok := err.(fallbackError); ok {
 				if fallbackErr, ok := err.(fallbackError); ok {
 					confirmedV2 = confirmedV2 || fallbackErr.confirmedV2
 					confirmedV2 = confirmedV2 || fallbackErr.confirmedV2
+					if fallbackErr.transportOK && parsedURL.Scheme == "https" {
+						confirmedTLSRegistries[parsedURL.Host] = struct{}{}
+					}
 					err = fallbackErr.err
 					err = fallbackErr.err
 					lastErr = err
 					lastErr = err
 					logrus.Errorf("Attempting next endpoint for push after error: %v", err)
 					logrus.Errorf("Attempting next endpoint for push after error: %v", err)

+ 6 - 2
distribution/push_v2.go

@@ -64,12 +64,16 @@ func (p *v2Pusher) Push(ctx context.Context) (err error) {
 	p.repo, p.pushState.confirmedV2, err = NewV2Repository(ctx, p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "push", "pull")
 	p.repo, p.pushState.confirmedV2, err = NewV2Repository(ctx, p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "push", "pull")
 	if err != nil {
 	if err != nil {
 		logrus.Debugf("Error getting v2 registry: %v", err)
 		logrus.Debugf("Error getting v2 registry: %v", err)
-		return fallbackError{err: err, confirmedV2: p.pushState.confirmedV2}
+		return err
 	}
 	}
 
 
 	if err = p.pushV2Repository(ctx); err != nil {
 	if err = p.pushV2Repository(ctx); err != nil {
 		if continueOnError(err) {
 		if continueOnError(err) {
-			return fallbackError{err: err, confirmedV2: p.pushState.confirmedV2}
+			return fallbackError{
+				err:         err,
+				confirmedV2: p.pushState.confirmedV2,
+				transportOK: true,
+			}
 		}
 		}
 	}
 	}
 	return err
 	return err

+ 24 - 5
distribution/registry.go

@@ -60,14 +60,18 @@ func NewV2Repository(ctx context.Context, repoInfo *registry.RepositoryInfo, end
 	endpointStr := strings.TrimRight(endpoint.URL, "/") + "/v2/"
 	endpointStr := strings.TrimRight(endpoint.URL, "/") + "/v2/"
 	req, err := http.NewRequest("GET", endpointStr, nil)
 	req, err := http.NewRequest("GET", endpointStr, nil)
 	if err != nil {
 	if err != nil {
-		return nil, false, err
+		return nil, false, fallbackError{err: err}
 	}
 	}
 	resp, err := pingClient.Do(req)
 	resp, err := pingClient.Do(req)
 	if err != nil {
 	if err != nil {
-		return nil, false, err
+		return nil, false, fallbackError{err: err}
 	}
 	}
 	defer resp.Body.Close()
 	defer resp.Body.Close()
 
 
+	// We got a HTTP request through, so we're using the right TLS settings.
+	// From this point forward, set transportOK to true in any fallbackError
+	// we return.
+
 	v2Version := auth.APIVersion{
 	v2Version := auth.APIVersion{
 		Type:    "registry",
 		Type:    "registry",
 		Version: "2.0",
 		Version: "2.0",
@@ -87,7 +91,11 @@ func NewV2Repository(ctx context.Context, repoInfo *registry.RepositoryInfo, end
 
 
 	challengeManager := auth.NewSimpleChallengeManager()
 	challengeManager := auth.NewSimpleChallengeManager()
 	if err := challengeManager.AddResponse(resp); err != nil {
 	if err := challengeManager.AddResponse(resp); err != nil {
-		return nil, foundVersion, err
+		return nil, foundVersion, fallbackError{
+			err:         err,
+			confirmedV2: foundVersion,
+			transportOK: true,
+		}
 	}
 	}
 
 
 	if authConfig.RegistryToken != "" {
 	if authConfig.RegistryToken != "" {
@@ -103,11 +111,22 @@ func NewV2Repository(ctx context.Context, repoInfo *registry.RepositoryInfo, end
 
 
 	repoNameRef, err := distreference.ParseNamed(repoName)
 	repoNameRef, err := distreference.ParseNamed(repoName)
 	if err != nil {
 	if err != nil {
-		return nil, foundVersion, err
+		return nil, foundVersion, fallbackError{
+			err:         err,
+			confirmedV2: foundVersion,
+			transportOK: true,
+		}
 	}
 	}
 
 
 	repo, err = client.NewRepository(ctx, repoNameRef, endpoint.URL, tr)
 	repo, err = client.NewRepository(ctx, repoNameRef, endpoint.URL, tr)
-	return repo, foundVersion, err
+	if err != nil {
+		err = fallbackError{
+			err:         err,
+			confirmedV2: foundVersion,
+			transportOK: true,
+		}
+	}
+	return
 }
 }
 
 
 type existingTokenHandler struct {
 type existingTokenHandler struct {