github.com/sijibomii/docker@v0.0.0-20231230191044-5cf6ca554647/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 fallbackError struct {
    95  	err error
    96  }
    97  
    98  func (err fallbackError) Error() string {
    99  	return err.err.Error()
   100  }
   101  
   102  // loginV2 tries to login to the v2 registry server. The given registry
   103  // endpoint will be pinged to get authorization challenges. These challenges
   104  // will be used to authenticate against the registry to validate credentials.
   105  func loginV2(authConfig *types.AuthConfig, endpoint APIEndpoint, userAgent string) (string, string, error) {
   106  	logrus.Debugf("attempting v2 login to registry endpoint %s", strings.TrimRight(endpoint.URL.String(), "/")+"/v2/")
   107  
   108  	modifiers := DockerHeaders(userAgent, nil)
   109  	authTransport := transport.NewTransport(NewTransport(endpoint.TLSConfig), modifiers...)
   110  
   111  	challengeManager, foundV2, err := PingV2Registry(endpoint, authTransport)
   112  	if err != nil {
   113  		if !foundV2 {
   114  			err = fallbackError{err: err}
   115  		}
   116  		return "", "", err
   117  	}
   118  
   119  	credentialAuthConfig := *authConfig
   120  	creds := loginCredentialStore{
   121  		authConfig: &credentialAuthConfig,
   122  	}
   123  
   124  	tokenHandlerOptions := auth.TokenHandlerOptions{
   125  		Transport:     authTransport,
   126  		Credentials:   creds,
   127  		OfflineAccess: true,
   128  		ClientID:      AuthClientID,
   129  	}
   130  	tokenHandler := auth.NewTokenHandlerWithOptions(tokenHandlerOptions)
   131  	basicHandler := auth.NewBasicHandler(creds)
   132  	modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler))
   133  	tr := transport.NewTransport(authTransport, modifiers...)
   134  
   135  	loginClient := &http.Client{
   136  		Transport: tr,
   137  		Timeout:   15 * time.Second,
   138  	}
   139  
   140  	endpointStr := strings.TrimRight(endpoint.URL.String(), "/") + "/v2/"
   141  	req, err := http.NewRequest("GET", endpointStr, nil)
   142  	if err != nil {
   143  		if !foundV2 {
   144  			err = fallbackError{err: err}
   145  		}
   146  		return "", "", err
   147  	}
   148  
   149  	resp, err := loginClient.Do(req)
   150  	if err != nil {
   151  		if !foundV2 {
   152  			err = fallbackError{err: err}
   153  		}
   154  		return "", "", err
   155  	}
   156  	defer resp.Body.Close()
   157  
   158  	if resp.StatusCode != http.StatusOK {
   159  		// TODO(dmcgowan): Attempt to further interpret result, status code and error code string
   160  		err := fmt.Errorf("login attempt to %s failed with status: %d %s", endpointStr, resp.StatusCode, http.StatusText(resp.StatusCode))
   161  		if !foundV2 {
   162  			err = fallbackError{err: err}
   163  		}
   164  		return "", "", err
   165  	}
   166  
   167  	return "Login Succeeded", credentialAuthConfig.IdentityToken, nil
   168  
   169  }
   170  
   171  // ResolveAuthConfig matches an auth configuration to a server address or a URL
   172  func ResolveAuthConfig(authConfigs map[string]types.AuthConfig, index *registrytypes.IndexInfo) types.AuthConfig {
   173  	configKey := GetAuthConfigKey(index)
   174  	// First try the happy case
   175  	if c, found := authConfigs[configKey]; found || index.Official {
   176  		return c
   177  	}
   178  
   179  	convertToHostname := func(url string) string {
   180  		stripped := url
   181  		if strings.HasPrefix(url, "http://") {
   182  			stripped = strings.Replace(url, "http://", "", 1)
   183  		} else if strings.HasPrefix(url, "https://") {
   184  			stripped = strings.Replace(url, "https://", "", 1)
   185  		}
   186  
   187  		nameParts := strings.SplitN(stripped, "/", 2)
   188  
   189  		return nameParts[0]
   190  	}
   191  
   192  	// Maybe they have a legacy config file, we will iterate the keys converting
   193  	// them to the new format and testing
   194  	for registry, ac := range authConfigs {
   195  		if configKey == convertToHostname(registry) {
   196  			return ac
   197  		}
   198  	}
   199  
   200  	// When all else fails, return an empty auth config
   201  	return types.AuthConfig{}
   202  }
   203  
   204  // PingResponseError is used when the response from a ping
   205  // was received but invalid.
   206  type PingResponseError struct {
   207  	Err error
   208  }
   209  
   210  func (err PingResponseError) Error() string {
   211  	return err.Error()
   212  }
   213  
   214  // PingV2Registry attempts to ping a v2 registry and on success return a
   215  // challenge manager for the supported authentication types and
   216  // whether v2 was confirmed by the response. If a response is received but
   217  // cannot be interpreted a PingResponseError will be returned.
   218  func PingV2Registry(endpoint APIEndpoint, transport http.RoundTripper) (auth.ChallengeManager, bool, error) {
   219  	var (
   220  		foundV2   = false
   221  		v2Version = auth.APIVersion{
   222  			Type:    "registry",
   223  			Version: "2.0",
   224  		}
   225  	)
   226  
   227  	pingClient := &http.Client{
   228  		Transport: transport,
   229  		Timeout:   15 * time.Second,
   230  	}
   231  	endpointStr := strings.TrimRight(endpoint.URL.String(), "/") + "/v2/"
   232  	req, err := http.NewRequest("GET", endpointStr, nil)
   233  	if err != nil {
   234  		return nil, false, err
   235  	}
   236  	resp, err := pingClient.Do(req)
   237  	if err != nil {
   238  		return nil, false, err
   239  	}
   240  	defer resp.Body.Close()
   241  
   242  	versions := auth.APIVersions(resp, DefaultRegistryVersionHeader)
   243  	for _, pingVersion := range versions {
   244  		if pingVersion == v2Version {
   245  			// The version header indicates we're definitely
   246  			// talking to a v2 registry. So don't allow future
   247  			// fallbacks to the v1 protocol.
   248  
   249  			foundV2 = true
   250  			break
   251  		}
   252  	}
   253  
   254  	challengeManager := auth.NewSimpleChallengeManager()
   255  	if err := challengeManager.AddResponse(resp); err != nil {
   256  		return nil, foundV2, PingResponseError{
   257  			Err: err,
   258  		}
   259  	}
   260  
   261  	return challengeManager, foundV2, nil
   262  }