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