github.com/toplink-cn/moby@v0.0.0-20240305205811-460b4aebdf81/registry/auth.go (about)

     1  package registry // import "github.com/docker/docker/registry"
     2  
     3  import (
     4  	"context"
     5  	"net/http"
     6  	"net/url"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/containerd/log"
    11  	"github.com/docker/distribution/registry/client/auth"
    12  	"github.com/docker/distribution/registry/client/auth/challenge"
    13  	"github.com/docker/distribution/registry/client/transport"
    14  	"github.com/docker/docker/api/types/registry"
    15  	"github.com/pkg/errors"
    16  )
    17  
    18  // AuthClientID is used the ClientID used for the token server
    19  const AuthClientID = "docker"
    20  
    21  type loginCredentialStore struct {
    22  	authConfig *registry.AuthConfig
    23  }
    24  
    25  func (lcs loginCredentialStore) Basic(*url.URL) (string, string) {
    26  	return lcs.authConfig.Username, lcs.authConfig.Password
    27  }
    28  
    29  func (lcs loginCredentialStore) RefreshToken(*url.URL, string) string {
    30  	return lcs.authConfig.IdentityToken
    31  }
    32  
    33  func (lcs loginCredentialStore) SetRefreshToken(u *url.URL, service, token string) {
    34  	lcs.authConfig.IdentityToken = token
    35  }
    36  
    37  type staticCredentialStore struct {
    38  	auth *registry.AuthConfig
    39  }
    40  
    41  // NewStaticCredentialStore returns a credential store
    42  // which always returns the same credential values.
    43  func NewStaticCredentialStore(auth *registry.AuthConfig) auth.CredentialStore {
    44  	return staticCredentialStore{
    45  		auth: auth,
    46  	}
    47  }
    48  
    49  func (scs staticCredentialStore) Basic(*url.URL) (string, string) {
    50  	if scs.auth == nil {
    51  		return "", ""
    52  	}
    53  	return scs.auth.Username, scs.auth.Password
    54  }
    55  
    56  func (scs staticCredentialStore) RefreshToken(*url.URL, string) string {
    57  	if scs.auth == nil {
    58  		return ""
    59  	}
    60  	return scs.auth.IdentityToken
    61  }
    62  
    63  func (scs staticCredentialStore) SetRefreshToken(*url.URL, string, string) {
    64  }
    65  
    66  // loginV2 tries to login to the v2 registry server. The given registry
    67  // endpoint will be pinged to get authorization challenges. These challenges
    68  // will be used to authenticate against the registry to validate credentials.
    69  func loginV2(authConfig *registry.AuthConfig, endpoint APIEndpoint, userAgent string) (string, string, error) {
    70  	var (
    71  		endpointStr          = strings.TrimRight(endpoint.URL.String(), "/") + "/v2/"
    72  		modifiers            = Headers(userAgent, nil)
    73  		authTransport        = transport.NewTransport(newTransport(endpoint.TLSConfig), modifiers...)
    74  		credentialAuthConfig = *authConfig
    75  		creds                = loginCredentialStore{authConfig: &credentialAuthConfig}
    76  	)
    77  
    78  	log.G(context.TODO()).Debugf("attempting v2 login to registry endpoint %s", endpointStr)
    79  
    80  	loginClient, err := v2AuthHTTPClient(endpoint.URL, authTransport, modifiers, creds, nil)
    81  	if err != nil {
    82  		return "", "", err
    83  	}
    84  
    85  	req, err := http.NewRequest(http.MethodGet, endpointStr, nil)
    86  	if err != nil {
    87  		return "", "", err
    88  	}
    89  
    90  	resp, err := loginClient.Do(req)
    91  	if err != nil {
    92  		err = translateV2AuthError(err)
    93  		return "", "", err
    94  	}
    95  	defer resp.Body.Close()
    96  
    97  	if resp.StatusCode == http.StatusOK {
    98  		return "Login Succeeded", credentialAuthConfig.IdentityToken, nil
    99  	}
   100  
   101  	// TODO(dmcgowan): Attempt to further interpret result, status code and error code string
   102  	return "", "", errors.Errorf("login attempt to %s failed with status: %d %s", endpointStr, resp.StatusCode, http.StatusText(resp.StatusCode))
   103  }
   104  
   105  func v2AuthHTTPClient(endpoint *url.URL, authTransport http.RoundTripper, modifiers []transport.RequestModifier, creds auth.CredentialStore, scopes []auth.Scope) (*http.Client, error) {
   106  	challengeManager, err := PingV2Registry(endpoint, authTransport)
   107  	if err != nil {
   108  		return nil, err
   109  	}
   110  
   111  	tokenHandlerOptions := auth.TokenHandlerOptions{
   112  		Transport:     authTransport,
   113  		Credentials:   creds,
   114  		OfflineAccess: true,
   115  		ClientID:      AuthClientID,
   116  		Scopes:        scopes,
   117  	}
   118  	tokenHandler := auth.NewTokenHandlerWithOptions(tokenHandlerOptions)
   119  	basicHandler := auth.NewBasicHandler(creds)
   120  	modifiers = append(modifiers, auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler))
   121  
   122  	return &http.Client{
   123  		Transport: transport.NewTransport(authTransport, modifiers...),
   124  		Timeout:   15 * time.Second,
   125  	}, nil
   126  }
   127  
   128  // ConvertToHostname normalizes a registry URL which has http|https prepended
   129  // to just its hostname. It is used to match credentials, which may be either
   130  // stored as hostname or as hostname including scheme (in legacy configuration
   131  // files).
   132  func ConvertToHostname(url string) string {
   133  	stripped := url
   134  	if strings.HasPrefix(url, "http://") {
   135  		stripped = strings.TrimPrefix(url, "http://")
   136  	} else if strings.HasPrefix(url, "https://") {
   137  		stripped = strings.TrimPrefix(url, "https://")
   138  	}
   139  	return strings.SplitN(stripped, "/", 2)[0]
   140  }
   141  
   142  // ResolveAuthConfig matches an auth configuration to a server address or a URL
   143  func ResolveAuthConfig(authConfigs map[string]registry.AuthConfig, index *registry.IndexInfo) registry.AuthConfig {
   144  	configKey := GetAuthConfigKey(index)
   145  	// First try the happy case
   146  	if c, found := authConfigs[configKey]; found || index.Official {
   147  		return c
   148  	}
   149  
   150  	// Maybe they have a legacy config file, we will iterate the keys converting
   151  	// them to the new format and testing
   152  	for registryURL, ac := range authConfigs {
   153  		if configKey == ConvertToHostname(registryURL) {
   154  			return ac
   155  		}
   156  	}
   157  
   158  	// When all else fails, return an empty auth config
   159  	return registry.AuthConfig{}
   160  }
   161  
   162  // PingResponseError is used when the response from a ping
   163  // was received but invalid.
   164  type PingResponseError struct {
   165  	Err error
   166  }
   167  
   168  func (err PingResponseError) Error() string {
   169  	return err.Err.Error()
   170  }
   171  
   172  // PingV2Registry attempts to ping a v2 registry and on success return a
   173  // challenge manager for the supported authentication types.
   174  // If a response is received but cannot be interpreted, a PingResponseError will be returned.
   175  func PingV2Registry(endpoint *url.URL, transport http.RoundTripper) (challenge.Manager, error) {
   176  	pingClient := &http.Client{
   177  		Transport: transport,
   178  		Timeout:   15 * time.Second,
   179  	}
   180  	endpointStr := strings.TrimRight(endpoint.String(), "/") + "/v2/"
   181  	req, err := http.NewRequest(http.MethodGet, endpointStr, nil)
   182  	if err != nil {
   183  		return nil, err
   184  	}
   185  	resp, err := pingClient.Do(req)
   186  	if err != nil {
   187  		return nil, err
   188  	}
   189  	defer resp.Body.Close()
   190  
   191  	challengeManager := challenge.NewSimpleManager()
   192  	if err := challengeManager.AddResponse(resp); err != nil {
   193  		return nil, PingResponseError{
   194  			Err: err,
   195  		}
   196  	}
   197  
   198  	return challengeManager, nil
   199  }