github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/registry/auth.go (about)

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