github.com/portworx/docker@v1.12.1/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/engine-api/types"
    15  	registrytypes "github.com/docker/engine-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  // ResolveAuthConfig matches an auth configuration to a server address or a URL
   210  func ResolveAuthConfig(authConfigs map[string]types.AuthConfig, index *registrytypes.IndexInfo) types.AuthConfig {
   211  	configKey := GetAuthConfigKey(index)
   212  	// First try the happy case
   213  	if c, found := authConfigs[configKey]; found || index.Official {
   214  		return c
   215  	}
   216  
   217  	convertToHostname := func(url string) string {
   218  		stripped := url
   219  		if strings.HasPrefix(url, "http://") {
   220  			stripped = strings.Replace(url, "http://", "", 1)
   221  		} else if strings.HasPrefix(url, "https://") {
   222  			stripped = strings.Replace(url, "https://", "", 1)
   223  		}
   224  
   225  		nameParts := strings.SplitN(stripped, "/", 2)
   226  
   227  		return nameParts[0]
   228  	}
   229  
   230  	// Maybe they have a legacy config file, we will iterate the keys converting
   231  	// them to the new format and testing
   232  	for registry, ac := range authConfigs {
   233  		if configKey == convertToHostname(registry) {
   234  			return ac
   235  		}
   236  	}
   237  
   238  	// When all else fails, return an empty auth config
   239  	return types.AuthConfig{}
   240  }
   241  
   242  // PingResponseError is used when the response from a ping
   243  // was received but invalid.
   244  type PingResponseError struct {
   245  	Err error
   246  }
   247  
   248  func (err PingResponseError) Error() string {
   249  	return err.Error()
   250  }
   251  
   252  // PingV2Registry attempts to ping a v2 registry and on success return a
   253  // challenge manager for the supported authentication types and
   254  // whether v2 was confirmed by the response. If a response is received but
   255  // cannot be interpreted a PingResponseError will be returned.
   256  func PingV2Registry(endpoint *url.URL, transport http.RoundTripper) (auth.ChallengeManager, bool, error) {
   257  	var (
   258  		foundV2   = false
   259  		v2Version = auth.APIVersion{
   260  			Type:    "registry",
   261  			Version: "2.0",
   262  		}
   263  	)
   264  
   265  	pingClient := &http.Client{
   266  		Transport: transport,
   267  		Timeout:   15 * time.Second,
   268  	}
   269  	endpointStr := strings.TrimRight(endpoint.String(), "/") + "/v2/"
   270  	req, err := http.NewRequest("GET", endpointStr, nil)
   271  	if err != nil {
   272  		return nil, false, err
   273  	}
   274  	resp, err := pingClient.Do(req)
   275  	if err != nil {
   276  		return nil, false, err
   277  	}
   278  	defer resp.Body.Close()
   279  
   280  	versions := auth.APIVersions(resp, DefaultRegistryVersionHeader)
   281  	for _, pingVersion := range versions {
   282  		if pingVersion == v2Version {
   283  			// The version header indicates we're definitely
   284  			// talking to a v2 registry. So don't allow future
   285  			// fallbacks to the v1 protocol.
   286  
   287  			foundV2 = true
   288  			break
   289  		}
   290  	}
   291  
   292  	challengeManager := auth.NewSimpleChallengeManager()
   293  	if err := challengeManager.AddResponse(resp); err != nil {
   294  		return nil, foundV2, PingResponseError{
   295  			Err: err,
   296  		}
   297  	}
   298  
   299  	return challengeManager, foundV2, nil
   300  }