gopkg.in/docker/docker.v23@v23.0.11/registry/auth.go (about)

     1  package registry // import "github.com/docker/docker/registry"
     2  
     3  import (
     4  	"net/http"
     5  	"net/url"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/docker/distribution/registry/client/auth"
    10  	"github.com/docker/distribution/registry/client/auth/challenge"
    11  	"github.com/docker/distribution/registry/client/transport"
    12  	"github.com/docker/docker/api/types"
    13  	"github.com/docker/docker/api/types/registry"
    14  	"github.com/pkg/errors"
    15  	"github.com/sirupsen/logrus"
    16  )
    17  
    18  // AuthClientID is used the ClientID used for the token server
    19  const AuthClientID = "docker"
    20  
    21  type loginCredentialStore struct {
    22  	authConfig *types.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 *types.AuthConfig
    39  }
    40  
    41  // NewStaticCredentialStore returns a credential store
    42  // which always returns the same credential values.
    43  func NewStaticCredentialStore(auth *types.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 *types.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  	logrus.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 converts a registry url which has http|https prepended
   129  // to just an hostname.
   130  func ConvertToHostname(url string) string {
   131  	stripped := url
   132  	if strings.HasPrefix(url, "http://") {
   133  		stripped = strings.TrimPrefix(url, "http://")
   134  	} else if strings.HasPrefix(url, "https://") {
   135  		stripped = strings.TrimPrefix(url, "https://")
   136  	}
   137  	return strings.SplitN(stripped, "/", 2)[0]
   138  }
   139  
   140  // ResolveAuthConfig matches an auth configuration to a server address or a URL
   141  func ResolveAuthConfig(authConfigs map[string]types.AuthConfig, index *registry.IndexInfo) types.AuthConfig {
   142  	configKey := GetAuthConfigKey(index)
   143  	// First try the happy case
   144  	if c, found := authConfigs[configKey]; found || index.Official {
   145  		return c
   146  	}
   147  
   148  	// Maybe they have a legacy config file, we will iterate the keys converting
   149  	// them to the new format and testing
   150  	for registry, ac := range authConfigs {
   151  		if configKey == ConvertToHostname(registry) {
   152  			return ac
   153  		}
   154  	}
   155  
   156  	// When all else fails, return an empty auth config
   157  	return types.AuthConfig{}
   158  }
   159  
   160  // PingResponseError is used when the response from a ping
   161  // was received but invalid.
   162  type PingResponseError struct {
   163  	Err error
   164  }
   165  
   166  func (err PingResponseError) Error() string {
   167  	return err.Err.Error()
   168  }
   169  
   170  // PingV2Registry attempts to ping a v2 registry and on success return a
   171  // challenge manager for the supported authentication types.
   172  // If a response is received but cannot be interpreted, a PingResponseError will be returned.
   173  func PingV2Registry(endpoint *url.URL, transport http.RoundTripper) (challenge.Manager, error) {
   174  	pingClient := &http.Client{
   175  		Transport: transport,
   176  		Timeout:   15 * time.Second,
   177  	}
   178  	endpointStr := strings.TrimRight(endpoint.String(), "/") + "/v2/"
   179  	req, err := http.NewRequest(http.MethodGet, endpointStr, nil)
   180  	if err != nil {
   181  		return nil, err
   182  	}
   183  	resp, err := pingClient.Do(req)
   184  	if err != nil {
   185  		return nil, err
   186  	}
   187  	defer resp.Body.Close()
   188  
   189  	challengeManager := challenge.NewSimpleManager()
   190  	if err := challengeManager.AddResponse(resp); err != nil {
   191  		return nil, PingResponseError{
   192  			Err: err,
   193  		}
   194  	}
   195  
   196  	return challengeManager, nil
   197  }