github.com/sealerio/sealer@v0.11.1-0.20240507115618-f4f89c5853ae/pkg/image/save/distributionpkg/proxy/proxyregistry.go (about)

     1  // Copyright © 2021 https://github.com/distribution/distribution
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package proxy
    16  
    17  import (
    18  	"context"
    19  	"net"
    20  	"net/http"
    21  	"net/url"
    22  	"path/filepath"
    23  	"strings"
    24  	"sync"
    25  	"time"
    26  
    27  	"github.com/docker/docker/registry"
    28  	"github.com/docker/go-connections/tlsconfig"
    29  
    30  	"github.com/distribution/distribution/v3"
    31  	"github.com/distribution/distribution/v3/configuration"
    32  	dcontext "github.com/distribution/distribution/v3/context"
    33  	"github.com/distribution/distribution/v3/reference"
    34  	"github.com/distribution/distribution/v3/registry/client"
    35  	"github.com/distribution/distribution/v3/registry/client/auth"
    36  	"github.com/distribution/distribution/v3/registry/client/auth/challenge"
    37  	"github.com/distribution/distribution/v3/registry/client/transport"
    38  	"github.com/distribution/distribution/v3/registry/proxy/scheduler"
    39  	"github.com/distribution/distribution/v3/registry/storage"
    40  	"github.com/distribution/distribution/v3/registry/storage/driver"
    41  )
    42  
    43  // proxyingRegistry fetches content from a remote registry and caches it locally
    44  type proxyingRegistry struct {
    45  	embedded       distribution.Namespace // provides local registry functionality
    46  	scheduler      *scheduler.TTLExpirationScheduler
    47  	remoteURL      url.URL
    48  	authChallenger authChallenger
    49  }
    50  
    51  // NewRegistryPullThroughCache creates a registry acting as a pull through cache
    52  func NewRegistryPullThroughCache(ctx context.Context, registry distribution.Namespace, driver driver.StorageDriver, config configuration.Proxy) (distribution.Namespace, error) {
    53  	remoteURL, err := url.Parse(config.RemoteURL)
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  
    58  	cs, err := configureAuth(config.Username, config.Password, config.RemoteURL)
    59  	if err != nil {
    60  		return nil, err
    61  	}
    62  
    63  	return &proxyingRegistry{
    64  		embedded:  registry,
    65  		scheduler: nil,
    66  		remoteURL: *remoteURL,
    67  		authChallenger: &remoteAuthChallenger{
    68  			remoteURL: *remoteURL,
    69  			cm:        challenge.NewSimpleManager(),
    70  			cs:        cs,
    71  		},
    72  	}, nil
    73  }
    74  
    75  func (pr *proxyingRegistry) Scope() distribution.Scope {
    76  	return distribution.GlobalScope
    77  }
    78  
    79  func (pr *proxyingRegistry) Repositories(ctx context.Context, repos []string, last string) (n int, err error) {
    80  	return pr.embedded.Repositories(ctx, repos, last)
    81  }
    82  
    83  // #nosec
    84  func (pr *proxyingRegistry) Repository(ctx context.Context, name reference.Named) (distribution.Repository, error) {
    85  	c := pr.authChallenger
    86  	tlsConfig := tlsconfig.ServerDefault()
    87  	if err := registry.ReadCertsDirectory(tlsConfig, filepath.Join(registry.CertsDir(), pr.remoteURL.Host)); err != nil {
    88  		return nil, err
    89  	}
    90  	transSport := &http.Transport{
    91  		Proxy: http.ProxyFromEnvironment,
    92  		DialContext: (&net.Dialer{
    93  			Timeout:   30 * time.Second,
    94  			KeepAlive: 30 * time.Second,
    95  		}).DialContext,
    96  		ForceAttemptHTTP2:     true,
    97  		MaxIdleConns:          100,
    98  		IdleConnTimeout:       90 * time.Second,
    99  		TLSHandshakeTimeout:   10 * time.Second,
   100  		ExpectContinueTimeout: 1 * time.Second,
   101  		TLSClientConfig:       tlsConfig,
   102  	}
   103  	tkopts := auth.TokenHandlerOptions{
   104  		Transport:   transSport,
   105  		Credentials: c.credentialStore(),
   106  		Scopes: []auth.Scope{
   107  			auth.RepositoryScope{
   108  				Repository: name.Name(),
   109  				Actions:    []string{"pull"},
   110  			},
   111  		},
   112  		Logger: dcontext.GetLogger(ctx),
   113  	}
   114  	tr := transport.NewTransport(http.DefaultTransport,
   115  		auth.NewAuthorizer(c.challengeManager(),
   116  			auth.NewTokenHandlerWithOptions(tkopts)))
   117  
   118  	tryClient := &http.Client{Transport: tr}
   119  	_, err := tryClient.Get(pr.remoteURL.String())
   120  	if err != nil && strings.Contains(err.Error(), certUnknown) {
   121  		tlsConfig.InsecureSkipVerify = true
   122  		tr = transport.NewTransport(transSport,
   123  			auth.NewAuthorizer(c.challengeManager(),
   124  				auth.NewTokenHandlerWithOptions(tkopts)))
   125  	}
   126  
   127  	localRepo, err := pr.embedded.Repository(ctx, name)
   128  	if err != nil {
   129  		return nil, err
   130  	}
   131  	localManifests, err := localRepo.Manifests(ctx, storage.SkipLayerVerification())
   132  	if err != nil {
   133  		return nil, err
   134  	}
   135  
   136  	remoteRepo, err := client.NewRepository(name, pr.remoteURL.String(), tr)
   137  	if err != nil {
   138  		return nil, err
   139  	}
   140  
   141  	remoteManifests, err := remoteRepo.Manifests(ctx)
   142  	if err != nil {
   143  		return nil, err
   144  	}
   145  
   146  	return &proxiedRepository{
   147  		blobStore: &proxyBlobStore{
   148  			localStore:     localRepo.Blobs(ctx),
   149  			remoteStore:    remoteRepo.Blobs(ctx),
   150  			scheduler:      pr.scheduler,
   151  			repositoryName: name,
   152  			authChallenger: pr.authChallenger,
   153  		},
   154  		manifests: &proxyManifestStore{
   155  			repositoryName:  name,
   156  			localManifests:  localManifests, // Options?
   157  			remoteManifests: remoteManifests,
   158  			ctx:             ctx,
   159  			scheduler:       pr.scheduler,
   160  			authChallenger:  pr.authChallenger,
   161  		},
   162  		name: name,
   163  		tags: &proxyTagService{
   164  			localTags:      localRepo.Tags(ctx),
   165  			remoteTags:     remoteRepo.Tags(ctx),
   166  			authChallenger: pr.authChallenger,
   167  		},
   168  	}, nil
   169  }
   170  
   171  func (pr *proxyingRegistry) Blobs() distribution.BlobEnumerator {
   172  	return pr.embedded.Blobs()
   173  }
   174  
   175  func (pr *proxyingRegistry) BlobStatter() distribution.BlobStatter {
   176  	return pr.embedded.BlobStatter()
   177  }
   178  
   179  // authChallenger encapsulates a request to the upstream to establish credential challenges
   180  type authChallenger interface {
   181  	tryEstablishChallenges(context.Context) error
   182  	challengeManager() challenge.Manager
   183  	credentialStore() auth.CredentialStore
   184  }
   185  
   186  type remoteAuthChallenger struct {
   187  	remoteURL url.URL
   188  	sync.Mutex
   189  	cm challenge.Manager
   190  	cs auth.CredentialStore
   191  }
   192  
   193  func (r *remoteAuthChallenger) credentialStore() auth.CredentialStore {
   194  	return r.cs
   195  }
   196  
   197  func (r *remoteAuthChallenger) challengeManager() challenge.Manager {
   198  	return r.cm
   199  }
   200  
   201  // tryEstablishChallenges will attempt to get a challenge type for the upstream if none currently exist
   202  func (r *remoteAuthChallenger) tryEstablishChallenges(ctx context.Context) error {
   203  	r.Lock()
   204  	defer r.Unlock()
   205  
   206  	remoteURL := r.remoteURL
   207  	remoteURL.Path = "/v2/"
   208  	challenges, err := r.cm.GetChallenges(remoteURL)
   209  	if err != nil {
   210  		return err
   211  	}
   212  
   213  	if len(challenges) > 0 {
   214  		return nil
   215  	}
   216  
   217  	// establish challenge type with upstream
   218  	if err := ping(r.cm, remoteURL.String()); err != nil {
   219  		return err
   220  	}
   221  
   222  	//	dcontext.GetLogger(ctx).Infof("Challenge established with upstream : %s %s", remoteURL, r.cm)
   223  	return nil
   224  }
   225  
   226  // proxiedRepository uses proxying blob and manifest services to serve content
   227  // locally, or pulling it through from a remote and caching it locally if it doesn't
   228  // already exist
   229  type proxiedRepository struct {
   230  	blobStore distribution.BlobStore
   231  	manifests distribution.ManifestService
   232  	name      reference.Named
   233  	tags      distribution.TagService
   234  }
   235  
   236  func (pr *proxiedRepository) Manifests(ctx context.Context, options ...distribution.ManifestServiceOption) (distribution.ManifestService, error) {
   237  	return pr.manifests, nil
   238  }
   239  
   240  func (pr *proxiedRepository) Blobs(ctx context.Context) distribution.BlobStore {
   241  	return pr.blobStore
   242  }
   243  
   244  func (pr *proxiedRepository) Named() reference.Named {
   245  	return pr.name
   246  }
   247  
   248  func (pr *proxiedRepository) Tags(ctx context.Context) distribution.TagService {
   249  	return pr.tags
   250  }