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:
parent
a58c74303c
commit
93f029e87c
3 changed files with 122 additions and 40 deletions
86
docs/auth.go
86
docs/auth.go
|
@ -91,6 +91,35 @@ func (lcs loginCredentialStore) SetRefreshToken(u *url.URL, service, token strin
|
|||
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 {
|
||||
err error
|
||||
}
|
||||
|
@ -108,33 +137,14 @@ func loginV2(authConfig *types.AuthConfig, endpoint APIEndpoint, userAgent strin
|
|||
modifiers := DockerHeaders(userAgent, nil)
|
||||
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
|
||||
creds := loginCredentialStore{
|
||||
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/"
|
||||
|
@ -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
|
||||
func ResolveAuthConfig(authConfigs map[string]types.AuthConfig, index *registrytypes.IndexInfo) types.AuthConfig {
|
||||
configKey := GetAuthConfigKey(index)
|
||||
|
@ -215,7 +253,7 @@ func (err PingResponseError) Error() string {
|
|||
// challenge manager for the supported authentication types and
|
||||
// whether v2 was confirmed by the response. If a response is received but
|
||||
// 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 (
|
||||
foundV2 = false
|
||||
v2Version = auth.APIVersion{
|
||||
|
@ -228,7 +266,7 @@ func PingV2Registry(endpoint APIEndpoint, transport http.RoundTripper) (auth.Cha
|
|||
Transport: transport,
|
||||
Timeout: 15 * time.Second,
|
||||
}
|
||||
endpointStr := strings.TrimRight(endpoint.URL.String(), "/") + "/v2/"
|
||||
endpointStr := strings.TrimRight(endpoint.String(), "/") + "/v2/"
|
||||
req, err := http.NewRequest("GET", endpointStr, nil)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/distribution/registry/client/auth"
|
||||
"github.com/docker/docker/reference"
|
||||
"github.com/docker/engine-api/types"
|
||||
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
|
||||
}
|
||||
|
||||
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 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 {
|
||||
localName := remoteName
|
||||
|
|
|
@ -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
|
||||
|
||||
// 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" {
|
||||
info, err := endpoint.Ping()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
if info.Standalone && authConfig != nil {
|
||||
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)
|
||||
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
|
||||
|
||||
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.
|
||||
|
|
Loading…
Reference in a new issue