registry/auth.go
8d88ea0c
 package registry
be20f3c5
 
 import (
 	"fmt"
 	"io/ioutil"
 	"net/http"
f2d481a2
 	"net/url"
be20f3c5
 	"strings"
f2d481a2
 	"time"
4e725484
 
6f4d8470
 	"github.com/Sirupsen/logrus"
f2d481a2
 	"github.com/docker/distribution/registry/client/auth"
 	"github.com/docker/distribution/registry/client/transport"
907407d0
 	"github.com/docker/engine-api/types"
 	registrytypes "github.com/docker/engine-api/types/registry"
be20f3c5
 )
 
e896d1d7
 const (
 	// AuthClientID is used the ClientID used for the token server
 	AuthClientID = "docker"
 )
 
f2d481a2
 // loginV1 tries to register/login to the v1 registry server.
e896d1d7
 func loginV1(authConfig *types.AuthConfig, apiEndpoint APIEndpoint, userAgent string) (string, string, error) {
f2d481a2
 	registryEndpoint, err := apiEndpoint.ToV1Endpoint(userAgent, nil)
 	if err != nil {
e896d1d7
 		return "", "", err
41e20cec
 	}
 
f2d481a2
 	serverAddress := registryEndpoint.String()
fcee6056
 
e42f8508
 	logrus.Debugf("attempting v1 login to registry endpoint %s", serverAddress)
41e20cec
 
fcee6056
 	if serverAddress == "" {
e896d1d7
 		return "", "", fmt.Errorf("Server Error: Server Address not set.")
fcee6056
 	}
 
4fcb9ac4
 	loginAgainstOfficialIndex := serverAddress == IndexServer
fcee6056
 
aee260d4
 	req, err := http.NewRequest("GET", serverAddress+"users/", nil)
f2d481a2
 	if err != nil {
e896d1d7
 		return "", "", err
f2d481a2
 	}
aee260d4
 	req.SetBasicAuth(authConfig.Username, authConfig.Password)
 	resp, err := registryEndpoint.client.Do(req)
0a35db8f
 	if err != nil {
f2d481a2
 		// fallback when request could not be completed
e896d1d7
 		return "", "", fallbackError{
f2d481a2
 			err: err,
 		}
0a35db8f
 	}
aee260d4
 	defer resp.Body.Close()
 	body, err := ioutil.ReadAll(resp.Body)
f1cf5074
 	if err != nil {
e896d1d7
 		return "", "", err
be20f3c5
 	}
aee260d4
 	if resp.StatusCode == http.StatusOK {
e896d1d7
 		return "Login Succeeded", "", nil
aee260d4
 	} else if resp.StatusCode == http.StatusUnauthorized {
fcee6056
 		if loginAgainstOfficialIndex {
e896d1d7
 			return "", "", fmt.Errorf("Wrong login/password, please try again. Haven't got a Docker ID? Create one at https://hub.docker.com")
fcee6056
 		}
e896d1d7
 		return "", "", fmt.Errorf("Wrong login/password, please try again")
aee260d4
 	} else if resp.StatusCode == http.StatusForbidden {
 		if loginAgainstOfficialIndex {
e896d1d7
 			return "", "", fmt.Errorf("Login: Account is not active. Please check your e-mail for a confirmation link.")
9be5db87
 		}
aee260d4
 		// *TODO: Use registry configuration to determine what this says, if anything?
e896d1d7
 		return "", "", fmt.Errorf("Login: Account is not active. Please see the documentation of the registry %s for instructions how to activate it.", serverAddress)
aee260d4
 	} else if resp.StatusCode == http.StatusInternalServerError { // Issue #14326
 		logrus.Errorf("%s returned status code %d. Response Body :\n%s", req.URL.String(), resp.StatusCode, body)
e896d1d7
 		return "", "", fmt.Errorf("Internal Server Error")
be20f3c5
 	}
e896d1d7
 	return "", "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body,
 		resp.StatusCode, resp.Header)
be20f3c5
 }
fcee6056
 
f2d481a2
 type loginCredentialStore struct {
 	authConfig *types.AuthConfig
 }
41e20cec
 
f2d481a2
 func (lcs loginCredentialStore) Basic(*url.URL) (string, string) {
 	return lcs.authConfig.Username, lcs.authConfig.Password
 }
41e20cec
 
e896d1d7
 func (lcs loginCredentialStore) RefreshToken(*url.URL, string) string {
 	return lcs.authConfig.IdentityToken
 }
 
 func (lcs loginCredentialStore) SetRefreshToken(u *url.URL, service, token string) {
 	lcs.authConfig.IdentityToken = token
 }
 
f2d481a2
 type fallbackError struct {
 	err error
 }
41e20cec
 
f2d481a2
 func (err fallbackError) Error() string {
 	return err.err.Error()
41e20cec
 }
 
f2d481a2
 // 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.
e896d1d7
 func loginV2(authConfig *types.AuthConfig, endpoint APIEndpoint, userAgent string) (string, string, error) {
e42f8508
 	logrus.Debugf("attempting v2 login to registry endpoint %s", strings.TrimRight(endpoint.URL.String(), "/")+"/v2/")
41e20cec
 
f2d481a2
 	modifiers := DockerHeaders(userAgent, nil)
 	authTransport := transport.NewTransport(NewTransport(endpoint.TLSConfig), modifiers...)
41e20cec
 
f2d481a2
 	challengeManager, foundV2, err := PingV2Registry(endpoint, authTransport)
41e20cec
 	if err != nil {
f2d481a2
 		if !foundV2 {
 			err = fallbackError{err: err}
 		}
e896d1d7
 		return "", "", err
41e20cec
 	}
 
e896d1d7
 	credentialAuthConfig := *authConfig
f2d481a2
 	creds := loginCredentialStore{
e896d1d7
 		authConfig: &credentialAuthConfig,
41e20cec
 	}
 
e896d1d7
 	tokenHandlerOptions := auth.TokenHandlerOptions{
 		Transport:     authTransport,
 		Credentials:   creds,
 		OfflineAccess: true,
 		ClientID:      AuthClientID,
 	}
 	tokenHandler := auth.NewTokenHandlerWithOptions(tokenHandlerOptions)
f2d481a2
 	basicHandler := auth.NewBasicHandler(creds)
 	modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler))
 	tr := transport.NewTransport(authTransport, modifiers...)
41e20cec
 
f2d481a2
 	loginClient := &http.Client{
 		Transport: tr,
 		Timeout:   15 * time.Second,
41e20cec
 	}
 
f2d481a2
 	endpointStr := strings.TrimRight(endpoint.URL.String(), "/") + "/v2/"
 	req, err := http.NewRequest("GET", endpointStr, nil)
41e20cec
 	if err != nil {
f2d481a2
 		if !foundV2 {
 			err = fallbackError{err: err}
 		}
e896d1d7
 		return "", "", err
41e20cec
 	}
 
f2d481a2
 	resp, err := loginClient.Do(req)
41e20cec
 	if err != nil {
f2d481a2
 		if !foundV2 {
 			err = fallbackError{err: err}
 		}
e896d1d7
 		return "", "", err
41e20cec
 	}
 	defer resp.Body.Close()
 
 	if resp.StatusCode != http.StatusOK {
f2d481a2
 		// 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}
 		}
e896d1d7
 		return "", "", err
41e20cec
 	}
 
e896d1d7
 	return "Login Succeeded", credentialAuthConfig.IdentityToken, nil
f2d481a2
 
41e20cec
 }
 
4fcb9ac4
 // ResolveAuthConfig matches an auth configuration to a server address or a URL
96c10098
 func ResolveAuthConfig(authConfigs map[string]types.AuthConfig, index *registrytypes.IndexInfo) types.AuthConfig {
 	configKey := GetAuthConfigKey(index)
90b0cce0
 	// First try the happy case
920ea135
 	if c, found := authConfigs[configKey]; found || index.Official {
90b0cce0
 		return c
fcee6056
 	}
 
90b0cce0
 	convertToHostname := func(url string) string {
 		stripped := url
 		if strings.HasPrefix(url, "http://") {
 			stripped = strings.Replace(url, "http://", "", 1)
 		} else if strings.HasPrefix(url, "https://") {
 			stripped = strings.Replace(url, "https://", "", 1)
fcee6056
 		}
 
90b0cce0
 		nameParts := strings.SplitN(stripped, "/", 2)
 
 		return nameParts[0]
fcee6056
 	}
 
90b0cce0
 	// Maybe they have a legacy config file, we will iterate the keys converting
 	// them to the new format and testing
920ea135
 	for registry, ac := range authConfigs {
568f86eb
 		if configKey == convertToHostname(registry) {
bb9da6ba
 			return ac
90b0cce0
 		}
fcee6056
 	}
90b0cce0
 
 	// When all else fails, return an empty auth config
5b321e32
 	return types.AuthConfig{}
18c9b6c6
 }
f2d481a2
 
 // 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
 }