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 }