瀏覽代碼

Allow v1 search to use v2 auth with identity token

Updates the v1 search endpoint to also support v2 auth when an identity token is given.
Only search v1 endpoint is supported since there is not v2 search currently defined to replace it.

Signed-off-by: Derek McGowan <derek@mcgstyle.net>
Derek McGowan 9 年之前
父節點
當前提交
19d48f0b8b
共有 4 個文件被更改,包括 124 次插入58 次删除
  1. 2 18
      distribution/registry.go
  2. 62 24
      registry/auth.go
  3. 37 3
      registry/service.go
  4. 23 13
      registry/session.go

+ 2 - 18
distribution/registry.go

@@ -4,7 +4,6 @@ import (
 	"fmt"
 	"fmt"
 	"net"
 	"net"
 	"net/http"
 	"net/http"
-	"net/url"
 	"time"
 	"time"
 
 
 	"github.com/docker/distribution"
 	"github.com/docker/distribution"
@@ -19,21 +18,6 @@ import (
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 )
 )
 
 
-type dumbCredentialStore struct {
-	auth *types.AuthConfig
-}
-
-func (dcs dumbCredentialStore) Basic(*url.URL) (string, string) {
-	return dcs.auth.Username, dcs.auth.Password
-}
-
-func (dcs dumbCredentialStore) RefreshToken(*url.URL, string) string {
-	return dcs.auth.IdentityToken
-}
-
-func (dcs dumbCredentialStore) SetRefreshToken(*url.URL, string, string) {
-}
-
 // NewV2Repository returns a repository (v2 only). It creates an HTTP transport
 // NewV2Repository returns a repository (v2 only). It creates an HTTP transport
 // providing timeout settings and authentication support, and also verifies the
 // providing timeout settings and authentication support, and also verifies the
 // remote API version.
 // remote API version.
@@ -68,7 +52,7 @@ func NewV2Repository(ctx context.Context, repoInfo *registry.RepositoryInfo, end
 	modifiers := registry.DockerHeaders(dockerversion.DockerUserAgent(ctx), metaHeaders)
 	modifiers := registry.DockerHeaders(dockerversion.DockerUserAgent(ctx), metaHeaders)
 	authTransport := transport.NewTransport(base, modifiers...)
 	authTransport := transport.NewTransport(base, modifiers...)
 
 
-	challengeManager, foundVersion, err := registry.PingV2Registry(endpoint, authTransport)
+	challengeManager, foundVersion, err := registry.PingV2Registry(endpoint.URL, authTransport)
 	if err != nil {
 	if err != nil {
 		transportOK := false
 		transportOK := false
 		if responseErr, ok := err.(registry.PingResponseError); ok {
 		if responseErr, ok := err.(registry.PingResponseError); ok {
@@ -86,7 +70,7 @@ func NewV2Repository(ctx context.Context, repoInfo *registry.RepositoryInfo, end
 		passThruTokenHandler := &existingTokenHandler{token: authConfig.RegistryToken}
 		passThruTokenHandler := &existingTokenHandler{token: authConfig.RegistryToken}
 		modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, passThruTokenHandler))
 		modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, passThruTokenHandler))
 	} else {
 	} else {
-		creds := dumbCredentialStore{auth: authConfig}
+		creds := registry.NewStaticCredentialStore(authConfig)
 		tokenHandlerOptions := auth.TokenHandlerOptions{
 		tokenHandlerOptions := auth.TokenHandlerOptions{
 			Transport:   authTransport,
 			Transport:   authTransport,
 			Credentials: creds,
 			Credentials: creds,

+ 62 - 24
registry/auth.go

@@ -91,6 +91,35 @@ func (lcs loginCredentialStore) SetRefreshToken(u *url.URL, service, token strin
 	lcs.authConfig.IdentityToken = token
 	lcs.authConfig.IdentityToken = token
 }
 }
 
 
+type staticCredentialStore struct {
+	auth *types.AuthConfig
+}
+
+// NewStaticCredentialStore returns a credential store
+// which always returns the same credential values.
+func NewStaticCredentialStore(auth *types.AuthConfig) auth.CredentialStore {
+	return staticCredentialStore{
+		auth: auth,
+	}
+}
+
+func (scs staticCredentialStore) Basic(*url.URL) (string, string) {
+	if scs.auth == nil {
+		return "", ""
+	}
+	return scs.auth.Username, scs.auth.Password
+}
+
+func (scs staticCredentialStore) RefreshToken(*url.URL, string) string {
+	if scs.auth == nil {
+		return ""
+	}
+	return scs.auth.IdentityToken
+}
+
+func (scs staticCredentialStore) SetRefreshToken(*url.URL, string, string) {
+}
+
 type fallbackError struct {
 type fallbackError struct {
 	err error
 	err error
 }
 }
@@ -108,33 +137,14 @@ func loginV2(authConfig *types.AuthConfig, endpoint APIEndpoint, userAgent strin
 	modifiers := DockerHeaders(userAgent, nil)
 	modifiers := DockerHeaders(userAgent, nil)
 	authTransport := transport.NewTransport(NewTransport(endpoint.TLSConfig), modifiers...)
 	authTransport := transport.NewTransport(NewTransport(endpoint.TLSConfig), modifiers...)
 
 
-	challengeManager, foundV2, err := PingV2Registry(endpoint, authTransport)
-	if err != nil {
-		if !foundV2 {
-			err = fallbackError{err: err}
-		}
-		return "", "", err
-	}
-
 	credentialAuthConfig := *authConfig
 	credentialAuthConfig := *authConfig
 	creds := loginCredentialStore{
 	creds := loginCredentialStore{
 		authConfig: &credentialAuthConfig,
 		authConfig: &credentialAuthConfig,
 	}
 	}
 
 
-	tokenHandlerOptions := auth.TokenHandlerOptions{
-		Transport:     authTransport,
-		Credentials:   creds,
-		OfflineAccess: true,
-		ClientID:      AuthClientID,
-	}
-	tokenHandler := auth.NewTokenHandlerWithOptions(tokenHandlerOptions)
-	basicHandler := auth.NewBasicHandler(creds)
-	modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler))
-	tr := transport.NewTransport(authTransport, modifiers...)
-
-	loginClient := &http.Client{
-		Transport: tr,
-		Timeout:   15 * time.Second,
+	loginClient, foundV2, err := v2AuthHTTPClient(endpoint.URL, authTransport, modifiers, creds, nil)
+	if err != nil {
+		return "", "", err
 	}
 	}
 
 
 	endpointStr := strings.TrimRight(endpoint.URL.String(), "/") + "/v2/"
 	endpointStr := strings.TrimRight(endpoint.URL.String(), "/") + "/v2/"
@@ -168,6 +178,34 @@ func loginV2(authConfig *types.AuthConfig, endpoint APIEndpoint, userAgent strin
 
 
 }
 }
 
 
+func v2AuthHTTPClient(endpoint *url.URL, authTransport http.RoundTripper, modifiers []transport.RequestModifier, creds auth.CredentialStore, scopes []auth.Scope) (*http.Client, bool, error) {
+	challengeManager, foundV2, err := PingV2Registry(endpoint, authTransport)
+	if err != nil {
+		if !foundV2 {
+			err = fallbackError{err: err}
+		}
+		return nil, foundV2, err
+	}
+
+	tokenHandlerOptions := auth.TokenHandlerOptions{
+		Transport:     authTransport,
+		Credentials:   creds,
+		OfflineAccess: true,
+		ClientID:      AuthClientID,
+		Scopes:        scopes,
+	}
+	tokenHandler := auth.NewTokenHandlerWithOptions(tokenHandlerOptions)
+	basicHandler := auth.NewBasicHandler(creds)
+	modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler))
+	tr := transport.NewTransport(authTransport, modifiers...)
+
+	return &http.Client{
+		Transport: tr,
+		Timeout:   15 * time.Second,
+	}, foundV2, nil
+
+}
+
 // ResolveAuthConfig matches an auth configuration to a server address or a URL
 // ResolveAuthConfig matches an auth configuration to a server address or a URL
 func ResolveAuthConfig(authConfigs map[string]types.AuthConfig, index *registrytypes.IndexInfo) types.AuthConfig {
 func ResolveAuthConfig(authConfigs map[string]types.AuthConfig, index *registrytypes.IndexInfo) types.AuthConfig {
 	configKey := GetAuthConfigKey(index)
 	configKey := GetAuthConfigKey(index)
@@ -215,7 +253,7 @@ func (err PingResponseError) Error() string {
 // challenge manager for the supported authentication types and
 // challenge manager for the supported authentication types and
 // whether v2 was confirmed by the response. If a response is received but
 // whether v2 was confirmed by the response. If a response is received but
 // cannot be interpreted a PingResponseError will be returned.
 // cannot be interpreted a PingResponseError will be returned.
-func PingV2Registry(endpoint APIEndpoint, transport http.RoundTripper) (auth.ChallengeManager, bool, error) {
+func PingV2Registry(endpoint *url.URL, transport http.RoundTripper) (auth.ChallengeManager, bool, error) {
 	var (
 	var (
 		foundV2   = false
 		foundV2   = false
 		v2Version = auth.APIVersion{
 		v2Version = auth.APIVersion{
@@ -228,7 +266,7 @@ func PingV2Registry(endpoint APIEndpoint, transport http.RoundTripper) (auth.Cha
 		Transport: transport,
 		Transport: transport,
 		Timeout:   15 * time.Second,
 		Timeout:   15 * time.Second,
 	}
 	}
-	endpointStr := strings.TrimRight(endpoint.URL.String(), "/") + "/v2/"
+	endpointStr := strings.TrimRight(endpoint.String(), "/") + "/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, err

+ 37 - 3
registry/service.go

@@ -10,6 +10,7 @@ import (
 	"golang.org/x/net/context"
 	"golang.org/x/net/context"
 
 
 	"github.com/Sirupsen/logrus"
 	"github.com/Sirupsen/logrus"
+	"github.com/docker/distribution/registry/client/auth"
 	"github.com/docker/docker/reference"
 	"github.com/docker/docker/reference"
 	"github.com/docker/engine-api/types"
 	"github.com/docker/engine-api/types"
 	registrytypes "github.com/docker/engine-api/types/registry"
 	registrytypes "github.com/docker/engine-api/types/registry"
@@ -132,11 +133,44 @@ func (s *DefaultService) Search(ctx context.Context, term string, limit int, aut
 		return nil, err
 		return nil, err
 	}
 	}
 
 
-	r, err := NewSession(endpoint.client, authConfig, endpoint)
-	if err != nil {
-		return nil, err
+	var client *http.Client
+	if authConfig != nil && authConfig.IdentityToken != "" && authConfig.Username != "" {
+		creds := NewStaticCredentialStore(authConfig)
+		scopes := []auth.Scope{
+			auth.RegistryScope{
+				Name:    "catalog",
+				Actions: []string{"search"},
+			},
+		}
+
+		modifiers := DockerHeaders(userAgent, nil)
+		v2Client, foundV2, err := v2AuthHTTPClient(endpoint.URL, endpoint.client.Transport, modifiers, creds, scopes)
+		if err != nil {
+			if fErr, ok := err.(fallbackError); ok {
+				logrus.Errorf("Cannot use identity token for search, v2 auth not supported: %v", fErr.err)
+			} else {
+				return nil, err
+			}
+		} else if foundV2 {
+			// Copy non transport http client features
+			v2Client.Timeout = endpoint.client.Timeout
+			v2Client.CheckRedirect = endpoint.client.CheckRedirect
+			v2Client.Jar = endpoint.client.Jar
+
+			logrus.Debugf("using v2 client for search to %s", endpoint.URL)
+			client = v2Client
+		}
 	}
 	}
 
 
+	if client == nil {
+		client = endpoint.client
+		if err := authorizeClient(client, authConfig, endpoint); err != nil {
+			return nil, err
+		}
+	}
+
+	r := newSession(client, authConfig, endpoint)
+
 	if index.Official {
 	if index.Official {
 		localName := remoteName
 		localName := remoteName
 		if strings.HasPrefix(localName, "library/") {
 		if strings.HasPrefix(localName, "library/") {

+ 23 - 13
registry/session.go

@@ -161,16 +161,7 @@ func (tr *authTransport) CancelRequest(req *http.Request) {
 	}
 	}
 }
 }
 
 
-// NewSession creates a new session
-// TODO(tiborvass): remove authConfig param once registry client v2 is vendored
-func NewSession(client *http.Client, authConfig *types.AuthConfig, endpoint *V1Endpoint) (r *Session, err error) {
-	r = &Session{
-		authConfig:    authConfig,
-		client:        client,
-		indexEndpoint: endpoint,
-		id:            stringid.GenerateRandomID(),
-	}
-
+func authorizeClient(client *http.Client, authConfig *types.AuthConfig, endpoint *V1Endpoint) error {
 	var alwaysSetBasicAuth bool
 	var alwaysSetBasicAuth bool
 
 
 	// If we're working with a standalone private registry over HTTPS, send Basic Auth headers
 	// If we're working with a standalone private registry over HTTPS, send Basic Auth headers
@@ -178,7 +169,7 @@ func NewSession(client *http.Client, authConfig *types.AuthConfig, endpoint *V1E
 	if endpoint.String() != IndexServer && endpoint.URL.Scheme == "https" {
 	if endpoint.String() != IndexServer && endpoint.URL.Scheme == "https" {
 		info, err := endpoint.Ping()
 		info, err := endpoint.Ping()
 		if err != nil {
 		if err != nil {
-			return nil, err
+			return err
 		}
 		}
 		if info.Standalone && authConfig != nil {
 		if info.Standalone && authConfig != nil {
 			logrus.Debugf("Endpoint %s is eligible for private registry. Enabling decorator.", endpoint.String())
 			logrus.Debugf("Endpoint %s is eligible for private registry. Enabling decorator.", endpoint.String())
@@ -192,11 +183,30 @@ func NewSession(client *http.Client, authConfig *types.AuthConfig, endpoint *V1E
 
 
 	jar, err := cookiejar.New(nil)
 	jar, err := cookiejar.New(nil)
 	if err != nil {
 	if err != nil {
-		return nil, errors.New("cookiejar.New is not supposed to return an error")
+		return errors.New("cookiejar.New is not supposed to return an error")
 	}
 	}
 	client.Jar = jar
 	client.Jar = jar
 
 
-	return r, nil
+	return nil
+}
+
+func newSession(client *http.Client, authConfig *types.AuthConfig, endpoint *V1Endpoint) *Session {
+	return &Session{
+		authConfig:    authConfig,
+		client:        client,
+		indexEndpoint: endpoint,
+		id:            stringid.GenerateRandomID(),
+	}
+}
+
+// NewSession creates a new session
+// TODO(tiborvass): remove authConfig param once registry client v2 is vendored
+func NewSession(client *http.Client, authConfig *types.AuthConfig, endpoint *V1Endpoint) (*Session, error) {
+	if err := authorizeClient(client, authConfig, endpoint); err != nil {
+		return nil, err
+	}
+
+	return newSession(client, authConfig, endpoint), nil
 }
 }
 
 
 // ID returns this registry session's ID.
 // ID returns this registry session's ID.