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>
(cherry picked from commit 19d48f0b8ba59eea9f2cac4ad1c7977712a6b7ac)
Signed-off-by: Tibor Vass <tibor@docker.com>
This commit is contained in:
Derek McGowan 2016-07-13 13:30:24 -07:00 committed by Tibor Vass
parent a58c74303c
commit 93f029e87c
3 changed files with 122 additions and 40 deletions

View file

@ -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{ loginClient, foundV2, err := v2AuthHTTPClient(endpoint.URL, authTransport, modifiers, creds, nil)
Transport: authTransport, if err != nil {
Credentials: creds, return "", "", err
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,
} }
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

View file

@ -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,10 +133,43 @@ 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) 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 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 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

View file

@ -161,16 +161,7 @@ func (tr *authTransport) CancelRequest(req *http.Request) {
} }
} }
// NewSession creates a new session func authorizeClient(client *http.Client, authConfig *types.AuthConfig, endpoint *V1Endpoint) error {
// 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(),
}
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.