github.com/endophage/docker@v1.4.2-0.20161027011718-242853499895/registry/auth.go (about)

     1  package registry
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"net/http"
     7  	"net/url"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/Sirupsen/logrus"
    12  	"github.com/docker/distribution/registry/client/auth"
    13  	"github.com/docker/distribution/registry/client/transport"
    14  	"github.com/docker/docker/api/types"
    15  	registrytypes "github.com/docker/docker/api/types/registry"
    16  )
    17  
    18  const (
    19  	// AuthClientID is used the ClientID used for the token server
    20  	AuthClientID = "docker"
    21  )
    22  
    23  // loginV1 tries to register/login to the v1 registry server.
    24  func loginV1(authConfig *types.AuthConfig, apiEndpoint APIEndpoint, userAgent string) (string, string, error) {
    25  	registryEndpoint, err := apiEndpoint.ToV1Endpoint(userAgent, nil)
    26  	if err != nil {
    27  		return "", "", err
    28  	}
    29  
    30  	serverAddress := registryEndpoint.String()
    31  
    32  	logrus.Debugf("attempting v1 login to registry endpoint %s", serverAddress)
    33  
    34  	if serverAddress == "" {
    35  		return "", "", fmt.Errorf("Server Error: Server Address not set.")
    36  	}
    37  
    38  	loginAgainstOfficialIndex := serverAddress == IndexServer
    39  
    40  	req, err := http.NewRequest("GET", serverAddress+"users/", nil)
    41  	if err != nil {
    42  		return "", "", err
    43  	}
    44  	req.SetBasicAuth(authConfig.Username, authConfig.Password)
    45  	resp, err := registryEndpoint.client.Do(req)
    46  	if err != nil {
    47  		// fallback when request could not be completed
    48  		return "", "", fallbackError{
    49  			err: err,
    50  		}
    51  	}
    52  	defer resp.Body.Close()
    53  	body, err := ioutil.ReadAll(resp.Body)
    54  	if err != nil {
    55  		return "", "", err
    56  	}
    57  	if resp.StatusCode == http.StatusOK {
    58  		return "Login Succeeded", "", nil
    59  	} else if resp.StatusCode == http.StatusUnauthorized {
    60  		if loginAgainstOfficialIndex {
    61  			return "", "", fmt.Errorf("Wrong login/password, please try again. Haven't got a Docker ID? Create one at https://hub.docker.com")
    62  		}
    63  		return "", "", fmt.Errorf("Wrong login/password, please try again")
    64  	} else if resp.StatusCode == http.StatusForbidden {
    65  		if loginAgainstOfficialIndex {
    66  			return "", "", fmt.Errorf("Login: Account is not active. Please check your e-mail for a confirmation link.")
    67  		}
    68  		// *TODO: Use registry configuration to determine what this says, if anything?
    69  		return "", "", fmt.Errorf("Login: Account is not active. Please see the documentation of the registry %s for instructions how to activate it.", serverAddress)
    70  	} else if resp.StatusCode == http.StatusInternalServerError { // Issue #14326
    71  		logrus.Errorf("%s returned status code %d. Response Body :\n%s", req.URL.String(), resp.StatusCode, body)
    72  		return "", "", fmt.Errorf("Internal Server Error")
    73  	}
    74  	return "", "", fmt.Errorf("Login: %s (Code: %d; Headers: %s)", body,
    75  		resp.StatusCode, resp.Header)
    76  }
    77  
    78  type loginCredentialStore struct {
    79  	authConfig *types.AuthConfig
    80  }
    81  
    82  func (lcs loginCredentialStore) Basic(*url.URL) (string, string) {
    83  	return lcs.authConfig.Username, lcs.authConfig.Password
    84  }
    85  
    86  func (lcs loginCredentialStore) RefreshToken(*url.URL, string) string {
    87  	return lcs.authConfig.IdentityToken
    88  }
    89  
    90  func (lcs loginCredentialStore) SetRefreshToken(u *url.URL, service, token string) {
    91  	lcs.authConfig.IdentityToken = token
    92  }
    93  
    94  type staticCredentialStore struct {
    95  	auth *types.AuthConfig
    96  }
    97  
    98  // NewStaticCredentialStore returns a credential store
    99  // which always returns the same credential values.
   100  func NewStaticCredentialStore(auth *types.AuthConfig) auth.CredentialStore {
   101  	return staticCredentialStore{
   102  		auth: auth,
   103  	}
   104  }
   105  
   106  func (scs staticCredentialStore) Basic(*url.URL) (string, string) {
   107  	if scs.auth == nil {
   108  		return "", ""
   109  	}
   110  	return scs.auth.Username, scs.auth.Password
   111  }
   112  
   113  func (scs staticCredentialStore) RefreshToken(*url.URL, string) string {
   114  	if scs.auth == nil {
   115  		return ""
   116  	}
   117  	return scs.auth.IdentityToken
   118  }
   119  
   120  func (scs staticCredentialStore) SetRefreshToken(*url.URL, string, string) {
   121  }
   122  
   123  type fallbackError struct {
   124  	err error
   125  }
   126  
   127  func (err fallbackError) Error() string {
   128  	return err.err.Error()
   129  }
   130  
   131  // loginV2 tries to login to the v2 registry server. The given registry
   132  // endpoint will be pinged to get authorization challenges. These challenges
   133  // will be used to authenticate against the registry to validate credentials.
   134  func loginV2(authConfig *types.AuthConfig, endpoint APIEndpoint, userAgent string) (string, string, error) {
   135  	logrus.Debugf("attempting v2 login to registry endpoint %s", strings.TrimRight(endpoint.URL.String(), "/")+"/v2/")
   136  
   137  	modifiers := DockerHeaders(userAgent, nil)
   138  	authTransport := transport.NewTransport(NewTransport(endpoint.TLSConfig), modifiers...)
   139  
   140  	credentialAuthConfig := *authConfig
   141  	creds := loginCredentialStore{
   142  		authConfig: &credentialAuthConfig,
   143  	}
   144  
   145  	loginClient, foundV2, err := v2AuthHTTPClient(endpoint.URL, authTransport, modifiers, creds, nil)
   146  	if err != nil {
   147  		return "", "", err
   148  	}
   149  
   150  	endpointStr := strings.TrimRight(endpoint.URL.String(), "/") + "/v2/"
   151  	req, err := http.NewRequest("GET", endpointStr, nil)
   152  	if err != nil {
   153  		if !foundV2 {
   154  			err = fallbackError{err: err}
   155  		}
   156  		return "", "", err
   157  	}
   158  
   159  	resp, err := loginClient.Do(req)
   160  	if err != nil {
   161  		if !foundV2 {
   162  			err = fallbackError{err: err}
   163  		}
   164  		return "", "", err
   165  	}
   166  	defer resp.Body.Close()
   167  
   168  	if resp.StatusCode != http.StatusOK {
   169  		// TODO(dmcgowan): Attempt to further interpret result, status code and error code string
   170  		err := fmt.Errorf("login attempt to %s failed with status: %d %s", endpointStr, resp.StatusCode, http.StatusText(resp.StatusCode))
   171  		if !foundV2 {
   172  			err = fallbackError{err: err}
   173  		}
   174  		return "", "", err
   175  	}
   176  
   177  	return "Login Succeeded", credentialAuthConfig.IdentityToken, nil
   178  
   179  }
   180  
   181  func v2AuthHTTPClient(endpoint *url.URL, authTransport http.RoundTripper, modifiers []transport.RequestModifier, creds auth.CredentialStore, scopes []auth.Scope) (*http.Client, bool, error) {
   182  	challengeManager, foundV2, err := PingV2Registry(endpoint, authTransport)
   183  	if err != nil {
   184  		if !foundV2 {
   185  			err = fallbackError{err: err}
   186  		}
   187  		return nil, foundV2, err
   188  	}
   189  
   190  	tokenHandlerOptions := auth.TokenHandlerOptions{
   191  		Transport:     authTransport,
   192  		Credentials:   creds,
   193  		OfflineAccess: true,
   194  		ClientID:      AuthClientID,
   195  		Scopes:        scopes,
   196  	}
   197  	tokenHandler := auth.NewTokenHandlerWithOptions(tokenHandlerOptions)
   198  	basicHandler := auth.NewBasicHandler(creds)
   199  	modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler))
   200  	tr := transport.NewTransport(authTransport, modifiers...)
   201  
   202  	return &http.Client{
   203  		Transport: tr,
   204  		Timeout:   15 * time.Second,
   205  	}, foundV2, nil
   206  
   207  }
   208  
   209  // ConvertToHostname converts a registry url which has http|https prepended
   210  // to just an hostname.
   211  func ConvertToHostname(url string) string {
   212  	stripped := url
   213  	if strings.HasPrefix(url, "http://") {
   214  		stripped = strings.TrimPrefix(url, "http://")
   215  	} else if strings.HasPrefix(url, "https://") {
   216  		stripped = strings.TrimPrefix(url, "https://")
   217  	}
   218  
   219  	nameParts := strings.SplitN(stripped, "/", 2)
   220  
   221  	return nameParts[0]
   222  }
   223  
   224  // ResolveAuthConfig matches an auth configuration to a server address or a URL
   225  func ResolveAuthConfig(authConfigs map[string]types.AuthConfig, index *registrytypes.IndexInfo) types.AuthConfig {
   226  	configKey := GetAuthConfigKey(index)
   227  	// First try the happy case
   228  	if c, found := authConfigs[configKey]; found || index.Official {
   229  		return c
   230  	}
   231  
   232  	// Maybe they have a legacy config file, we will iterate the keys converting
   233  	// them to the new format and testing
   234  	for registry, ac := range authConfigs {
   235  		if configKey == ConvertToHostname(registry) {
   236  			return ac
   237  		}
   238  	}
   239  
   240  	// When all else fails, return an empty auth config
   241  	return types.AuthConfig{}
   242  }
   243  
   244  // PingResponseError is used when the response from a ping
   245  // was received but invalid.
   246  type PingResponseError struct {
   247  	Err error
   248  }
   249  
   250  func (err PingResponseError) Error() string {
   251  	return err.Error()
   252  }
   253  
   254  // PingV2Registry attempts to ping a v2 registry and on success return a
   255  // challenge manager for the supported authentication types and
   256  // whether v2 was confirmed by the response. If a response is received but
   257  // cannot be interpreted a PingResponseError will be returned.
   258  func PingV2Registry(endpoint *url.URL, transport http.RoundTripper) (auth.ChallengeManager, bool, error) {
   259  	var (
   260  		foundV2   = false
   261  		v2Version = auth.APIVersion{
   262  			Type:    "registry",
   263  			Version: "2.0",
   264  		}
   265  	)
   266  
   267  	pingClient := &http.Client{
   268  		Transport: transport,
   269  		Timeout:   15 * time.Second,
   270  	}
   271  	endpointStr := strings.TrimRight(endpoint.String(), "/") + "/v2/"
   272  	req, err := http.NewRequest("GET", endpointStr, nil)
   273  	if err != nil {
   274  		return nil, false, err
   275  	}
   276  	resp, err := pingClient.Do(req)
   277  	if err != nil {
   278  		return nil, false, err
   279  	}
   280  	defer resp.Body.Close()
   281  
   282  	versions := auth.APIVersions(resp, DefaultRegistryVersionHeader)
   283  	for _, pingVersion := range versions {
   284  		if pingVersion == v2Version {
   285  			// The version header indicates we're definitely
   286  			// talking to a v2 registry. So don't allow future
   287  			// fallbacks to the v1 protocol.
   288  
   289  			foundV2 = true
   290  			break
   291  		}
   292  	}
   293  
   294  	challengeManager := auth.NewSimpleChallengeManager()
   295  	if err := challengeManager.AddResponse(resp); err != nil {
   296  		return nil, foundV2, PingResponseError{
   297  			Err: err,
   298  		}
   299  	}
   300  
   301  	return challengeManager, foundV2, nil
   302  }