github.com/alibaba/sealer@v0.8.6-0.20220430115802-37a2bdaa8173/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  	// v := storage.NewVacuum(ctx, driver)
    59  	// s := scheduler.New(ctx, driver, "/scheduler-state.json")
    60  	// s.OnBlobExpire(func(ref reference.Reference) error {
    61  	// 	var r reference.Canonical
    62  	// 	var ok bool
    63  	// 	if r, ok = ref.(reference.Canonical); !ok {
    64  	// 		return fmt.Errorf("unexpected reference type : %T", ref)
    65  	// 	}
    66  
    67  	// 	repo, err := registry.Repository(ctx, r)
    68  	// 	if err != nil {
    69  	// 		return err
    70  	// 	}
    71  
    72  	// 	blobs := repo.Blobs(ctx)
    73  
    74  	// 	// Clear the repository reference and descriptor caches
    75  	// 	err = blobs.Delete(ctx, r.Digest())
    76  	// 	if err != nil {
    77  	// 		return err
    78  	// 	}
    79  
    80  	// 	err = v.RemoveBlob(r.Digest().String())
    81  	// 	if err != nil {
    82  	// 		return err
    83  	// 	}
    84  
    85  	// 	return nil
    86  	// })
    87  
    88  	// s.OnManifestExpire(func(ref reference.Reference) error {
    89  	// 	var r reference.Canonical
    90  	// 	var ok bool
    91  	// 	if r, ok = ref.(reference.Canonical); !ok {
    92  	// 		return fmt.Errorf("unexpected reference type : %T", ref)
    93  	// 	}
    94  
    95  	// 	repo, err := registry.Repository(ctx, r)
    96  	// 	if err != nil {
    97  	// 		return err
    98  	// 	}
    99  
   100  	// 	manifests, err := repo.Manifests(ctx)
   101  	// 	if err != nil {
   102  	// 		return err
   103  	// 	}
   104  	// 	err = manifests.Delete(ctx, r.Digest())
   105  	// 	if err != nil {
   106  	// 		return err
   107  	// 	}
   108  	// 	return nil
   109  	// })
   110  
   111  	// err = s.Start()
   112  	// if err != nil {
   113  	// 	return nil, err
   114  	// }
   115  
   116  	cs, err := configureAuth(config.Username, config.Password, config.RemoteURL)
   117  	if err != nil {
   118  		return nil, err
   119  	}
   120  
   121  	return &proxyingRegistry{
   122  		embedded:  registry,
   123  		scheduler: nil,
   124  		remoteURL: *remoteURL,
   125  		authChallenger: &remoteAuthChallenger{
   126  			remoteURL: *remoteURL,
   127  			cm:        challenge.NewSimpleManager(),
   128  			cs:        cs,
   129  		},
   130  	}, nil
   131  }
   132  
   133  func (pr *proxyingRegistry) Scope() distribution.Scope {
   134  	return distribution.GlobalScope
   135  }
   136  
   137  func (pr *proxyingRegistry) Repositories(ctx context.Context, repos []string, last string) (n int, err error) {
   138  	return pr.embedded.Repositories(ctx, repos, last)
   139  }
   140  
   141  // #nosec
   142  func (pr *proxyingRegistry) Repository(ctx context.Context, name reference.Named) (distribution.Repository, error) {
   143  	c := pr.authChallenger
   144  	tlsConfig := tlsconfig.ServerDefault()
   145  	if err := registry.ReadCertsDirectory(tlsConfig, filepath.Join(registry.CertsDir(), pr.remoteURL.Host)); err != nil {
   146  		return nil, err
   147  	}
   148  	transSport := &http.Transport{
   149  		Proxy: http.ProxyFromEnvironment,
   150  		DialContext: (&net.Dialer{
   151  			Timeout:   30 * time.Second,
   152  			KeepAlive: 30 * time.Second,
   153  		}).DialContext,
   154  		ForceAttemptHTTP2:     true,
   155  		MaxIdleConns:          100,
   156  		IdleConnTimeout:       90 * time.Second,
   157  		TLSHandshakeTimeout:   10 * time.Second,
   158  		ExpectContinueTimeout: 1 * time.Second,
   159  		TLSClientConfig:       tlsConfig,
   160  	}
   161  	tkopts := auth.TokenHandlerOptions{
   162  		Transport:   transSport,
   163  		Credentials: c.credentialStore(),
   164  		Scopes: []auth.Scope{
   165  			auth.RepositoryScope{
   166  				Repository: name.Name(),
   167  				Actions:    []string{"pull"},
   168  			},
   169  		},
   170  		Logger: dcontext.GetLogger(ctx),
   171  	}
   172  	tr := transport.NewTransport(http.DefaultTransport,
   173  		auth.NewAuthorizer(c.challengeManager(),
   174  			auth.NewTokenHandlerWithOptions(tkopts)))
   175  
   176  	tryClient := &http.Client{Transport: tr}
   177  	_, err := tryClient.Get(pr.remoteURL.String())
   178  	if err != nil && strings.Contains(err.Error(), certUnknown) {
   179  		tlsConfig.InsecureSkipVerify = true
   180  		tr = transport.NewTransport(transSport,
   181  			auth.NewAuthorizer(c.challengeManager(),
   182  				auth.NewTokenHandlerWithOptions(tkopts)))
   183  	}
   184  
   185  	localRepo, err := pr.embedded.Repository(ctx, name)
   186  	if err != nil {
   187  		return nil, err
   188  	}
   189  	localManifests, err := localRepo.Manifests(ctx, storage.SkipLayerVerification())
   190  	if err != nil {
   191  		return nil, err
   192  	}
   193  
   194  	remoteRepo, err := client.NewRepository(name, pr.remoteURL.String(), tr)
   195  	if err != nil {
   196  		return nil, err
   197  	}
   198  
   199  	remoteManifests, err := remoteRepo.Manifests(ctx)
   200  	if err != nil {
   201  		return nil, err
   202  	}
   203  
   204  	return &proxiedRepository{
   205  		blobStore: &proxyBlobStore{
   206  			localStore:     localRepo.Blobs(ctx),
   207  			remoteStore:    remoteRepo.Blobs(ctx),
   208  			scheduler:      pr.scheduler,
   209  			repositoryName: name,
   210  			authChallenger: pr.authChallenger,
   211  		},
   212  		manifests: &proxyManifestStore{
   213  			repositoryName:  name,
   214  			localManifests:  localManifests, // Options?
   215  			remoteManifests: remoteManifests,
   216  			ctx:             ctx,
   217  			scheduler:       pr.scheduler,
   218  			authChallenger:  pr.authChallenger,
   219  		},
   220  		name: name,
   221  		tags: &proxyTagService{
   222  			localTags:      localRepo.Tags(ctx),
   223  			remoteTags:     remoteRepo.Tags(ctx),
   224  			authChallenger: pr.authChallenger,
   225  		},
   226  	}, nil
   227  }
   228  
   229  func (pr *proxyingRegistry) Blobs() distribution.BlobEnumerator {
   230  	return pr.embedded.Blobs()
   231  }
   232  
   233  func (pr *proxyingRegistry) BlobStatter() distribution.BlobStatter {
   234  	return pr.embedded.BlobStatter()
   235  }
   236  
   237  // authChallenger encapsulates a request to the upstream to establish credential challenges
   238  type authChallenger interface {
   239  	tryEstablishChallenges(context.Context) error
   240  	challengeManager() challenge.Manager
   241  	credentialStore() auth.CredentialStore
   242  }
   243  
   244  type remoteAuthChallenger struct {
   245  	remoteURL url.URL
   246  	sync.Mutex
   247  	cm challenge.Manager
   248  	cs auth.CredentialStore
   249  }
   250  
   251  func (r *remoteAuthChallenger) credentialStore() auth.CredentialStore {
   252  	return r.cs
   253  }
   254  
   255  func (r *remoteAuthChallenger) challengeManager() challenge.Manager {
   256  	return r.cm
   257  }
   258  
   259  // tryEstablishChallenges will attempt to get a challenge type for the upstream if none currently exist
   260  func (r *remoteAuthChallenger) tryEstablishChallenges(ctx context.Context) error {
   261  	r.Lock()
   262  	defer r.Unlock()
   263  
   264  	remoteURL := r.remoteURL
   265  	remoteURL.Path = "/v2/"
   266  	challenges, err := r.cm.GetChallenges(remoteURL)
   267  	if err != nil {
   268  		return err
   269  	}
   270  
   271  	if len(challenges) > 0 {
   272  		return nil
   273  	}
   274  
   275  	// establish challenge type with upstream
   276  	if err := ping(r.cm, remoteURL.String()); err != nil {
   277  		return err
   278  	}
   279  
   280  	//	dcontext.GetLogger(ctx).Infof("Challenge established with upstream : %s %s", remoteURL, r.cm)
   281  	return nil
   282  }
   283  
   284  // proxiedRepository uses proxying blob and manifest services to serve content
   285  // locally, or pulling it through from a remote and caching it locally if it doesn't
   286  // already exist
   287  type proxiedRepository struct {
   288  	blobStore distribution.BlobStore
   289  	manifests distribution.ManifestService
   290  	name      reference.Named
   291  	tags      distribution.TagService
   292  }
   293  
   294  func (pr *proxiedRepository) Manifests(ctx context.Context, options ...distribution.ManifestServiceOption) (distribution.ManifestService, error) {
   295  	return pr.manifests, nil
   296  }
   297  
   298  func (pr *proxiedRepository) Blobs(ctx context.Context) distribution.BlobStore {
   299  	return pr.blobStore
   300  }
   301  
   302  func (pr *proxiedRepository) Named() reference.Named {
   303  	return pr.name
   304  }
   305  
   306  func (pr *proxiedRepository) Tags(ctx context.Context) distribution.TagService {
   307  	return pr.tags
   308  }