github.com/ssdev-go/moby@v17.12.1-ce-rc2+incompatible/registry/auth.go (about)

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