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