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 }