mirror of
https://github.com/distribution/distribution.git
synced 2026-01-29 21:40:32 +00:00
Login update and endpoint refactor
Further differentiate the APIEndpoint used with V2 with the endpoint type which is only used for v1 registry interactions Rename Endpoint to V1Endpoint and remove version ambiguity Use distribution token handler for login Signed-off-by: Derek McGowan <derek@mcgstyle.net> Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
This commit is contained in:
committed by
Aaron Lehmann
parent
e123ca925e
commit
065ddf0186
232
docs/auth.go
232
docs/auth.go
@@ -4,28 +4,25 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/distribution/registry/client/auth"
|
||||
"github.com/docker/distribution/registry/client/transport"
|
||||
"github.com/docker/engine-api/types"
|
||||
registrytypes "github.com/docker/engine-api/types/registry"
|
||||
)
|
||||
|
||||
// Login tries to register/login to the registry server.
|
||||
func Login(authConfig *types.AuthConfig, registryEndpoint *Endpoint) (string, error) {
|
||||
// Separates the v2 registry login logic from the v1 logic.
|
||||
if registryEndpoint.Version == APIVersion2 {
|
||||
return loginV2(authConfig, registryEndpoint, "" /* scope */)
|
||||
}
|
||||
return loginV1(authConfig, registryEndpoint)
|
||||
}
|
||||
|
||||
// loginV1 tries to register/login to the v1 registry server.
|
||||
func loginV1(authConfig *types.AuthConfig, registryEndpoint *Endpoint) (string, error) {
|
||||
var (
|
||||
err error
|
||||
serverAddress = authConfig.ServerAddress
|
||||
)
|
||||
func loginV1(authConfig *types.AuthConfig, apiEndpoint APIEndpoint, userAgent string) (string, error) {
|
||||
registryEndpoint, err := apiEndpoint.ToV1Endpoint(userAgent, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
serverAddress := registryEndpoint.String()
|
||||
|
||||
logrus.Debugf("attempting v1 login to registry endpoint %s", registryEndpoint)
|
||||
|
||||
@@ -36,10 +33,16 @@ func loginV1(authConfig *types.AuthConfig, registryEndpoint *Endpoint) (string,
|
||||
loginAgainstOfficialIndex := serverAddress == IndexServer
|
||||
|
||||
req, err := http.NewRequest("GET", serverAddress+"users/", nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
req.SetBasicAuth(authConfig.Username, authConfig.Password)
|
||||
resp, err := registryEndpoint.client.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
// fallback when request could not be completed
|
||||
return "", fallbackError{
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
@@ -68,97 +71,82 @@ func loginV1(authConfig *types.AuthConfig, registryEndpoint *Endpoint) (string,
|
||||
}
|
||||
}
|
||||
|
||||
// loginV2 tries to login to the v2 registry server. The given registry endpoint has been
|
||||
// pinged or setup with a list of authorization challenges. Each of these challenges are
|
||||
// tried until one of them succeeds. Currently supported challenge schemes are:
|
||||
// HTTP Basic Authorization
|
||||
// Token Authorization with a separate token issuing server
|
||||
// NOTE: the v2 logic does not attempt to create a user account if one doesn't exist. For
|
||||
// now, users should create their account through other means like directly from a web page
|
||||
// served by the v2 registry service provider. Whether this will be supported in the future
|
||||
// is to be determined.
|
||||
func loginV2(authConfig *types.AuthConfig, registryEndpoint *Endpoint, scope string) (string, error) {
|
||||
logrus.Debugf("attempting v2 login to registry endpoint %s", registryEndpoint)
|
||||
var (
|
||||
err error
|
||||
allErrors []error
|
||||
)
|
||||
|
||||
for _, challenge := range registryEndpoint.AuthChallenges {
|
||||
params := make(map[string]string, len(challenge.Parameters)+1)
|
||||
for k, v := range challenge.Parameters {
|
||||
params[k] = v
|
||||
}
|
||||
params["scope"] = scope
|
||||
logrus.Debugf("trying %q auth challenge with params %v", challenge.Scheme, params)
|
||||
|
||||
switch strings.ToLower(challenge.Scheme) {
|
||||
case "basic":
|
||||
err = tryV2BasicAuthLogin(authConfig, params, registryEndpoint)
|
||||
case "bearer":
|
||||
err = tryV2TokenAuthLogin(authConfig, params, registryEndpoint)
|
||||
default:
|
||||
// Unsupported challenge types are explicitly skipped.
|
||||
err = fmt.Errorf("unsupported auth scheme: %q", challenge.Scheme)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
return "Login Succeeded", nil
|
||||
}
|
||||
|
||||
logrus.Debugf("error trying auth challenge %q: %s", challenge.Scheme, err)
|
||||
|
||||
allErrors = append(allErrors, err)
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("no successful auth challenge for %s - errors: %s", registryEndpoint, allErrors)
|
||||
type loginCredentialStore struct {
|
||||
authConfig *types.AuthConfig
|
||||
}
|
||||
|
||||
func tryV2BasicAuthLogin(authConfig *types.AuthConfig, params map[string]string, registryEndpoint *Endpoint) error {
|
||||
req, err := http.NewRequest("GET", registryEndpoint.Path(""), nil)
|
||||
func (lcs loginCredentialStore) Basic(*url.URL) (string, string) {
|
||||
return lcs.authConfig.Username, lcs.authConfig.Password
|
||||
}
|
||||
|
||||
type fallbackError struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (err fallbackError) Error() string {
|
||||
return err.err.Error()
|
||||
}
|
||||
|
||||
// loginV2 tries to login to the v2 registry server. The given registry
|
||||
// endpoint will be pinged to get authorization challenges. These challenges
|
||||
// will be used to authenticate against the registry to validate credentials.
|
||||
func loginV2(authConfig *types.AuthConfig, endpoint APIEndpoint, userAgent string) (string, error) {
|
||||
logrus.Debugf("attempting v2 login to registry endpoint %s", endpoint)
|
||||
|
||||
modifiers := DockerHeaders(userAgent, nil)
|
||||
authTransport := transport.NewTransport(NewTransport(endpoint.TLSConfig), modifiers...)
|
||||
|
||||
challengeManager, foundV2, err := PingV2Registry(endpoint, authTransport)
|
||||
if err != nil {
|
||||
return err
|
||||
if !foundV2 {
|
||||
err = fallbackError{err: err}
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
req.SetBasicAuth(authConfig.Username, authConfig.Password)
|
||||
creds := loginCredentialStore{
|
||||
authConfig: authConfig,
|
||||
}
|
||||
|
||||
resp, err := registryEndpoint.client.Do(req)
|
||||
tokenHandler := auth.NewTokenHandler(authTransport, creds, "")
|
||||
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/"
|
||||
req, err := http.NewRequest("GET", endpointStr, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
if !foundV2 {
|
||||
err = fallbackError{err: err}
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
resp, err := loginClient.Do(req)
|
||||
if err != nil {
|
||||
if !foundV2 {
|
||||
err = fallbackError{err: err}
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("basic auth attempt to %s realm %q failed with status: %d %s", registryEndpoint, params["realm"], resp.StatusCode, http.StatusText(resp.StatusCode))
|
||||
// TODO(dmcgowan): Attempt to further interpret result, status code and error code string
|
||||
err := fmt.Errorf("login attempt to %s failed with status: %d %s", endpointStr, resp.StatusCode, http.StatusText(resp.StatusCode))
|
||||
if !foundV2 {
|
||||
err = fallbackError{err: err}
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
return "Login Succeeded", nil
|
||||
|
||||
func tryV2TokenAuthLogin(authConfig *types.AuthConfig, params map[string]string, registryEndpoint *Endpoint) error {
|
||||
token, err := getToken(authConfig.Username, authConfig.Password, params, registryEndpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", registryEndpoint.Path(""), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||
|
||||
resp, err := registryEndpoint.client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("token auth attempt to %s realm %q failed with status: %d %s", registryEndpoint, params["realm"], resp.StatusCode, http.StatusText(resp.StatusCode))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ResolveAuthConfig matches an auth configuration to a server address or a URL
|
||||
@@ -193,3 +181,63 @@ func ResolveAuthConfig(authConfigs map[string]types.AuthConfig, index *registryt
|
||||
// When all else fails, return an empty auth config
|
||||
return types.AuthConfig{}
|
||||
}
|
||||
|
||||
// PingResponseError is used when the response from a ping
|
||||
// was received but invalid.
|
||||
type PingResponseError struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
func (err PingResponseError) Error() string {
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
// PingV2Registry attempts to ping a v2 registry and on success return a
|
||||
// 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) {
|
||||
var (
|
||||
foundV2 = false
|
||||
v2Version = auth.APIVersion{
|
||||
Type: "registry",
|
||||
Version: "2.0",
|
||||
}
|
||||
)
|
||||
|
||||
pingClient := &http.Client{
|
||||
Transport: transport,
|
||||
Timeout: 15 * time.Second,
|
||||
}
|
||||
endpointStr := strings.TrimRight(endpoint.URL.String(), "/") + "/v2/"
|
||||
req, err := http.NewRequest("GET", endpointStr, nil)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
resp, err := pingClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
versions := auth.APIVersions(resp, DefaultRegistryVersionHeader)
|
||||
for _, pingVersion := range versions {
|
||||
if pingVersion == v2Version {
|
||||
// The version header indicates we're definitely
|
||||
// talking to a v2 registry. So don't allow future
|
||||
// fallbacks to the v1 protocol.
|
||||
|
||||
foundV2 = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
challengeManager := auth.NewSimpleChallengeManager()
|
||||
if err := challengeManager.AddResponse(resp); err != nil {
|
||||
return nil, foundV2, PingResponseError{
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
return challengeManager, foundV2, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user