github.com/sijibomii/docker@v0.0.0-20231230191044-5cf6ca554647/distribution/pull_v2.go (about) 1 package distribution 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "net/url" 10 "os" 11 "runtime" 12 13 "github.com/Sirupsen/logrus" 14 "github.com/docker/distribution" 15 "github.com/docker/distribution/digest" 16 "github.com/docker/distribution/manifest/manifestlist" 17 "github.com/docker/distribution/manifest/schema1" 18 "github.com/docker/distribution/manifest/schema2" 19 "github.com/docker/distribution/registry/api/errcode" 20 "github.com/docker/distribution/registry/client/auth" 21 "github.com/docker/distribution/registry/client/transport" 22 "github.com/docker/docker/distribution/metadata" 23 "github.com/docker/docker/distribution/xfer" 24 "github.com/docker/docker/image" 25 "github.com/docker/docker/image/v1" 26 "github.com/docker/docker/layer" 27 "github.com/docker/docker/pkg/ioutils" 28 "github.com/docker/docker/pkg/progress" 29 "github.com/docker/docker/pkg/stringid" 30 "github.com/docker/docker/reference" 31 "github.com/docker/docker/registry" 32 "golang.org/x/net/context" 33 ) 34 35 var errRootFSMismatch = errors.New("layers from manifest don't match image configuration") 36 37 // ImageConfigPullError is an error pulling the image config blob 38 // (only applies to schema2). 39 type ImageConfigPullError struct { 40 Err error 41 } 42 43 // Error returns the error string for ImageConfigPullError. 44 func (e ImageConfigPullError) Error() string { 45 return "error pulling image configuration: " + e.Err.Error() 46 } 47 48 type v2Puller struct { 49 V2MetadataService *metadata.V2MetadataService 50 endpoint registry.APIEndpoint 51 config *ImagePullConfig 52 repoInfo *registry.RepositoryInfo 53 repo distribution.Repository 54 // confirmedV2 is set to true if we confirm we're talking to a v2 55 // registry. This is used to limit fallbacks to the v1 protocol. 56 confirmedV2 bool 57 } 58 59 func (p *v2Puller) Pull(ctx context.Context, ref reference.Named) (err error) { 60 // TODO(tiborvass): was ReceiveTimeout 61 p.repo, p.confirmedV2, err = NewV2Repository(ctx, p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "pull") 62 if err != nil { 63 logrus.Warnf("Error getting v2 registry: %v", err) 64 return err 65 } 66 67 if err = p.pullV2Repository(ctx, ref); err != nil { 68 if _, ok := err.(fallbackError); ok { 69 return err 70 } 71 if continueOnError(err) { 72 logrus.Errorf("Error trying v2 registry: %v", err) 73 return fallbackError{ 74 err: err, 75 confirmedV2: p.confirmedV2, 76 transportOK: true, 77 } 78 } 79 } 80 return err 81 } 82 83 func (p *v2Puller) pullV2Repository(ctx context.Context, ref reference.Named) (err error) { 84 var layersDownloaded bool 85 if !reference.IsNameOnly(ref) { 86 layersDownloaded, err = p.pullV2Tag(ctx, ref) 87 if err != nil { 88 return err 89 } 90 } else { 91 tags, err := p.repo.Tags(ctx).All(ctx) 92 if err != nil { 93 // If this repository doesn't exist on V2, we should 94 // permit a fallback to V1. 95 return allowV1Fallback(err) 96 } 97 98 // The v2 registry knows about this repository, so we will not 99 // allow fallback to the v1 protocol even if we encounter an 100 // error later on. 101 p.confirmedV2 = true 102 103 for _, tag := range tags { 104 tagRef, err := reference.WithTag(ref, tag) 105 if err != nil { 106 return err 107 } 108 pulledNew, err := p.pullV2Tag(ctx, tagRef) 109 if err != nil { 110 // Since this is the pull-all-tags case, don't 111 // allow an error pulling a particular tag to 112 // make the whole pull fall back to v1. 113 if fallbackErr, ok := err.(fallbackError); ok { 114 return fallbackErr.err 115 } 116 return err 117 } 118 // pulledNew is true if either new layers were downloaded OR if existing images were newly tagged 119 // TODO(tiborvass): should we change the name of `layersDownload`? What about message in WriteStatus? 120 layersDownloaded = layersDownloaded || pulledNew 121 } 122 } 123 124 writeStatus(ref.String(), p.config.ProgressOutput, layersDownloaded) 125 126 return nil 127 } 128 129 type v2LayerDescriptor struct { 130 digest digest.Digest 131 repoInfo *registry.RepositoryInfo 132 repo distribution.Repository 133 V2MetadataService *metadata.V2MetadataService 134 tmpFile *os.File 135 verifier digest.Verifier 136 } 137 138 func (ld *v2LayerDescriptor) Key() string { 139 return "v2:" + ld.digest.String() 140 } 141 142 func (ld *v2LayerDescriptor) ID() string { 143 return stringid.TruncateID(ld.digest.String()) 144 } 145 146 func (ld *v2LayerDescriptor) DiffID() (layer.DiffID, error) { 147 return ld.V2MetadataService.GetDiffID(ld.digest) 148 } 149 150 func (ld *v2LayerDescriptor) Download(ctx context.Context, progressOutput progress.Output) (io.ReadCloser, int64, error) { 151 logrus.Debugf("pulling blob %q", ld.digest) 152 153 var ( 154 err error 155 offset int64 156 ) 157 158 if ld.tmpFile == nil { 159 ld.tmpFile, err = createDownloadFile() 160 if err != nil { 161 return nil, 0, xfer.DoNotRetry{Err: err} 162 } 163 } else { 164 offset, err = ld.tmpFile.Seek(0, os.SEEK_END) 165 if err != nil { 166 logrus.Debugf("error seeking to end of download file: %v", err) 167 offset = 0 168 169 ld.tmpFile.Close() 170 if err := os.Remove(ld.tmpFile.Name()); err != nil { 171 logrus.Errorf("Failed to remove temp file: %s", ld.tmpFile.Name()) 172 } 173 ld.tmpFile, err = createDownloadFile() 174 if err != nil { 175 return nil, 0, xfer.DoNotRetry{Err: err} 176 } 177 } else if offset != 0 { 178 logrus.Debugf("attempting to resume download of %q from %d bytes", ld.digest, offset) 179 } 180 } 181 182 tmpFile := ld.tmpFile 183 blobs := ld.repo.Blobs(ctx) 184 185 layerDownload, err := blobs.Open(ctx, ld.digest) 186 if err != nil { 187 logrus.Errorf("Error initiating layer download: %v", err) 188 if err == distribution.ErrBlobUnknown { 189 return nil, 0, xfer.DoNotRetry{Err: err} 190 } 191 return nil, 0, retryOnError(err) 192 } 193 194 if offset != 0 { 195 _, err := layerDownload.Seek(offset, os.SEEK_SET) 196 if err != nil { 197 if err := ld.truncateDownloadFile(); err != nil { 198 return nil, 0, xfer.DoNotRetry{Err: err} 199 } 200 return nil, 0, err 201 } 202 } 203 size, err := layerDownload.Seek(0, os.SEEK_END) 204 if err != nil { 205 // Seek failed, perhaps because there was no Content-Length 206 // header. This shouldn't fail the download, because we can 207 // still continue without a progress bar. 208 size = 0 209 } else { 210 if size != 0 && offset > size { 211 logrus.Debugf("Partial download is larger than full blob. Starting over") 212 offset = 0 213 if err := ld.truncateDownloadFile(); err != nil { 214 return nil, 0, xfer.DoNotRetry{Err: err} 215 } 216 } 217 218 // Restore the seek offset either at the beginning of the 219 // stream, or just after the last byte we have from previous 220 // attempts. 221 _, err = layerDownload.Seek(offset, os.SEEK_SET) 222 if err != nil { 223 return nil, 0, err 224 } 225 } 226 227 reader := progress.NewProgressReader(ioutils.NewCancelReadCloser(ctx, layerDownload), progressOutput, size-offset, ld.ID(), "Downloading") 228 defer reader.Close() 229 230 if ld.verifier == nil { 231 ld.verifier, err = digest.NewDigestVerifier(ld.digest) 232 if err != nil { 233 return nil, 0, xfer.DoNotRetry{Err: err} 234 } 235 } 236 237 _, err = io.Copy(tmpFile, io.TeeReader(reader, ld.verifier)) 238 if err != nil { 239 if err == transport.ErrWrongCodeForByteRange { 240 if err := ld.truncateDownloadFile(); err != nil { 241 return nil, 0, xfer.DoNotRetry{Err: err} 242 } 243 return nil, 0, err 244 } 245 return nil, 0, retryOnError(err) 246 } 247 248 progress.Update(progressOutput, ld.ID(), "Verifying Checksum") 249 250 if !ld.verifier.Verified() { 251 err = fmt.Errorf("filesystem layer verification failed for digest %s", ld.digest) 252 logrus.Error(err) 253 254 // Allow a retry if this digest verification error happened 255 // after a resumed download. 256 if offset != 0 { 257 if err := ld.truncateDownloadFile(); err != nil { 258 return nil, 0, xfer.DoNotRetry{Err: err} 259 } 260 261 return nil, 0, err 262 } 263 return nil, 0, xfer.DoNotRetry{Err: err} 264 } 265 266 progress.Update(progressOutput, ld.ID(), "Download complete") 267 268 logrus.Debugf("Downloaded %s to tempfile %s", ld.ID(), tmpFile.Name()) 269 270 _, err = tmpFile.Seek(0, os.SEEK_SET) 271 if err != nil { 272 tmpFile.Close() 273 if err := os.Remove(tmpFile.Name()); err != nil { 274 logrus.Errorf("Failed to remove temp file: %s", tmpFile.Name()) 275 } 276 ld.tmpFile = nil 277 ld.verifier = nil 278 return nil, 0, xfer.DoNotRetry{Err: err} 279 } 280 281 // hand off the temporary file to the download manager, so it will only 282 // be closed once 283 ld.tmpFile = nil 284 285 return ioutils.NewReadCloserWrapper(tmpFile, func() error { 286 tmpFile.Close() 287 err := os.RemoveAll(tmpFile.Name()) 288 if err != nil { 289 logrus.Errorf("Failed to remove temp file: %s", tmpFile.Name()) 290 } 291 return err 292 }), size, nil 293 } 294 295 func (ld *v2LayerDescriptor) Close() { 296 if ld.tmpFile != nil { 297 ld.tmpFile.Close() 298 if err := os.RemoveAll(ld.tmpFile.Name()); err != nil { 299 logrus.Errorf("Failed to remove temp file: %s", ld.tmpFile.Name()) 300 } 301 } 302 } 303 304 func (ld *v2LayerDescriptor) truncateDownloadFile() error { 305 // Need a new hash context since we will be redoing the download 306 ld.verifier = nil 307 308 if _, err := ld.tmpFile.Seek(0, os.SEEK_SET); err != nil { 309 logrus.Errorf("error seeking to beginning of download file: %v", err) 310 return err 311 } 312 313 if err := ld.tmpFile.Truncate(0); err != nil { 314 logrus.Errorf("error truncating download file: %v", err) 315 return err 316 } 317 318 return nil 319 } 320 321 func (ld *v2LayerDescriptor) Registered(diffID layer.DiffID) { 322 // Cache mapping from this layer's DiffID to the blobsum 323 ld.V2MetadataService.Add(diffID, metadata.V2Metadata{Digest: ld.digest, SourceRepository: ld.repoInfo.FullName()}) 324 } 325 326 func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named) (tagUpdated bool, err error) { 327 manSvc, err := p.repo.Manifests(ctx) 328 if err != nil { 329 return false, err 330 } 331 332 var ( 333 manifest distribution.Manifest 334 tagOrDigest string // Used for logging/progress only 335 ) 336 if tagged, isTagged := ref.(reference.NamedTagged); isTagged { 337 // NOTE: not using TagService.Get, since it uses HEAD requests 338 // against the manifests endpoint, which are not supported by 339 // all registry versions. 340 manifest, err = manSvc.Get(ctx, "", distribution.WithTag(tagged.Tag())) 341 if err != nil { 342 return false, allowV1Fallback(err) 343 } 344 tagOrDigest = tagged.Tag() 345 } else if digested, isDigested := ref.(reference.Canonical); isDigested { 346 manifest, err = manSvc.Get(ctx, digested.Digest()) 347 if err != nil { 348 return false, err 349 } 350 tagOrDigest = digested.Digest().String() 351 } else { 352 return false, fmt.Errorf("internal error: reference has neither a tag nor a digest: %s", ref.String()) 353 } 354 355 if manifest == nil { 356 return false, fmt.Errorf("image manifest does not exist for tag or digest %q", tagOrDigest) 357 } 358 359 // If manSvc.Get succeeded, we can be confident that the registry on 360 // the other side speaks the v2 protocol. 361 p.confirmedV2 = true 362 363 logrus.Debugf("Pulling ref from V2 registry: %s", ref.String()) 364 progress.Message(p.config.ProgressOutput, tagOrDigest, "Pulling from "+p.repo.Named().Name()) 365 366 var ( 367 imageID image.ID 368 manifestDigest digest.Digest 369 ) 370 371 switch v := manifest.(type) { 372 case *schema1.SignedManifest: 373 imageID, manifestDigest, err = p.pullSchema1(ctx, ref, v) 374 if err != nil { 375 return false, err 376 } 377 case *schema2.DeserializedManifest: 378 imageID, manifestDigest, err = p.pullSchema2(ctx, ref, v) 379 if err != nil { 380 return false, err 381 } 382 case *manifestlist.DeserializedManifestList: 383 imageID, manifestDigest, err = p.pullManifestList(ctx, ref, v) 384 if err != nil { 385 return false, err 386 } 387 default: 388 return false, errors.New("unsupported manifest format") 389 } 390 391 progress.Message(p.config.ProgressOutput, "", "Digest: "+manifestDigest.String()) 392 393 oldTagImageID, err := p.config.ReferenceStore.Get(ref) 394 if err == nil { 395 if oldTagImageID == imageID { 396 return false, nil 397 } 398 } else if err != reference.ErrDoesNotExist { 399 return false, err 400 } 401 402 if canonical, ok := ref.(reference.Canonical); ok { 403 if err = p.config.ReferenceStore.AddDigest(canonical, imageID, true); err != nil { 404 return false, err 405 } 406 } else if err = p.config.ReferenceStore.AddTag(ref, imageID, true); err != nil { 407 return false, err 408 } 409 410 return true, nil 411 } 412 413 func (p *v2Puller) pullSchema1(ctx context.Context, ref reference.Named, unverifiedManifest *schema1.SignedManifest) (imageID image.ID, manifestDigest digest.Digest, err error) { 414 var verifiedManifest *schema1.Manifest 415 verifiedManifest, err = verifySchema1Manifest(unverifiedManifest, ref) 416 if err != nil { 417 return "", "", err 418 } 419 420 rootFS := image.NewRootFS() 421 422 if err := detectBaseLayer(p.config.ImageStore, verifiedManifest, rootFS); err != nil { 423 return "", "", err 424 } 425 426 // remove duplicate layers and check parent chain validity 427 err = fixManifestLayers(verifiedManifest) 428 if err != nil { 429 return "", "", err 430 } 431 432 var descriptors []xfer.DownloadDescriptor 433 434 // Image history converted to the new format 435 var history []image.History 436 437 // Note that the order of this loop is in the direction of bottom-most 438 // to top-most, so that the downloads slice gets ordered correctly. 439 for i := len(verifiedManifest.FSLayers) - 1; i >= 0; i-- { 440 blobSum := verifiedManifest.FSLayers[i].BlobSum 441 442 var throwAway struct { 443 ThrowAway bool `json:"throwaway,omitempty"` 444 } 445 if err := json.Unmarshal([]byte(verifiedManifest.History[i].V1Compatibility), &throwAway); err != nil { 446 return "", "", err 447 } 448 449 h, err := v1.HistoryFromConfig([]byte(verifiedManifest.History[i].V1Compatibility), throwAway.ThrowAway) 450 if err != nil { 451 return "", "", err 452 } 453 history = append(history, h) 454 455 if throwAway.ThrowAway { 456 continue 457 } 458 459 layerDescriptor := &v2LayerDescriptor{ 460 digest: blobSum, 461 repoInfo: p.repoInfo, 462 repo: p.repo, 463 V2MetadataService: p.V2MetadataService, 464 } 465 466 descriptors = append(descriptors, layerDescriptor) 467 } 468 469 resultRootFS, release, err := p.config.DownloadManager.Download(ctx, *rootFS, descriptors, p.config.ProgressOutput) 470 if err != nil { 471 return "", "", err 472 } 473 defer release() 474 475 config, err := v1.MakeConfigFromV1Config([]byte(verifiedManifest.History[0].V1Compatibility), &resultRootFS, history) 476 if err != nil { 477 return "", "", err 478 } 479 480 imageID, err = p.config.ImageStore.Create(config) 481 if err != nil { 482 return "", "", err 483 } 484 485 manifestDigest = digest.FromBytes(unverifiedManifest.Canonical) 486 487 return imageID, manifestDigest, nil 488 } 489 490 func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *schema2.DeserializedManifest) (imageID image.ID, manifestDigest digest.Digest, err error) { 491 manifestDigest, err = schema2ManifestDigest(ref, mfst) 492 if err != nil { 493 return "", "", err 494 } 495 496 target := mfst.Target() 497 imageID = image.ID(target.Digest) 498 if _, err := p.config.ImageStore.Get(imageID); err == nil { 499 // If the image already exists locally, no need to pull 500 // anything. 501 return imageID, manifestDigest, nil 502 } 503 504 configChan := make(chan []byte, 1) 505 errChan := make(chan error, 1) 506 var cancel func() 507 ctx, cancel = context.WithCancel(ctx) 508 509 // Pull the image config 510 go func() { 511 configJSON, err := p.pullSchema2ImageConfig(ctx, target.Digest) 512 if err != nil { 513 errChan <- ImageConfigPullError{Err: err} 514 cancel() 515 return 516 } 517 configChan <- configJSON 518 }() 519 520 var descriptors []xfer.DownloadDescriptor 521 522 // Note that the order of this loop is in the direction of bottom-most 523 // to top-most, so that the downloads slice gets ordered correctly. 524 for _, d := range mfst.References() { 525 layerDescriptor := &v2LayerDescriptor{ 526 digest: d.Digest, 527 repo: p.repo, 528 repoInfo: p.repoInfo, 529 V2MetadataService: p.V2MetadataService, 530 } 531 532 descriptors = append(descriptors, layerDescriptor) 533 } 534 535 var ( 536 configJSON []byte // raw serialized image config 537 unmarshalledConfig image.Image // deserialized image config 538 downloadRootFS image.RootFS // rootFS to use for registering layers. 539 ) 540 if runtime.GOOS == "windows" { 541 configJSON, unmarshalledConfig, err = receiveConfig(configChan, errChan) 542 if err != nil { 543 return "", "", err 544 } 545 if unmarshalledConfig.RootFS == nil { 546 return "", "", errors.New("image config has no rootfs section") 547 } 548 downloadRootFS = *unmarshalledConfig.RootFS 549 downloadRootFS.DiffIDs = []layer.DiffID{} 550 } else { 551 downloadRootFS = *image.NewRootFS() 552 } 553 554 rootFS, release, err := p.config.DownloadManager.Download(ctx, downloadRootFS, descriptors, p.config.ProgressOutput) 555 if err != nil { 556 if configJSON != nil { 557 // Already received the config 558 return "", "", err 559 } 560 select { 561 case err = <-errChan: 562 return "", "", err 563 default: 564 cancel() 565 select { 566 case <-configChan: 567 case <-errChan: 568 } 569 return "", "", err 570 } 571 } 572 defer release() 573 574 if configJSON == nil { 575 configJSON, unmarshalledConfig, err = receiveConfig(configChan, errChan) 576 if err != nil { 577 return "", "", err 578 } 579 } 580 581 // The DiffIDs returned in rootFS MUST match those in the config. 582 // Otherwise the image config could be referencing layers that aren't 583 // included in the manifest. 584 if len(rootFS.DiffIDs) != len(unmarshalledConfig.RootFS.DiffIDs) { 585 return "", "", errRootFSMismatch 586 } 587 588 for i := range rootFS.DiffIDs { 589 if rootFS.DiffIDs[i] != unmarshalledConfig.RootFS.DiffIDs[i] { 590 return "", "", errRootFSMismatch 591 } 592 } 593 594 imageID, err = p.config.ImageStore.Create(configJSON) 595 if err != nil { 596 return "", "", err 597 } 598 599 return imageID, manifestDigest, nil 600 } 601 602 func receiveConfig(configChan <-chan []byte, errChan <-chan error) ([]byte, image.Image, error) { 603 select { 604 case configJSON := <-configChan: 605 var unmarshalledConfig image.Image 606 if err := json.Unmarshal(configJSON, &unmarshalledConfig); err != nil { 607 return nil, image.Image{}, err 608 } 609 return configJSON, unmarshalledConfig, nil 610 case err := <-errChan: 611 return nil, image.Image{}, err 612 // Don't need a case for ctx.Done in the select because cancellation 613 // will trigger an error in p.pullSchema2ImageConfig. 614 } 615 } 616 617 // pullManifestList handles "manifest lists" which point to various 618 // platform-specifc manifests. 619 func (p *v2Puller) pullManifestList(ctx context.Context, ref reference.Named, mfstList *manifestlist.DeserializedManifestList) (imageID image.ID, manifestListDigest digest.Digest, err error) { 620 manifestListDigest, err = schema2ManifestDigest(ref, mfstList) 621 if err != nil { 622 return "", "", err 623 } 624 625 var manifestDigest digest.Digest 626 for _, manifestDescriptor := range mfstList.Manifests { 627 // TODO(aaronl): The manifest list spec supports optional 628 // "features" and "variant" fields. These are not yet used. 629 // Once they are, their values should be interpreted here. 630 if manifestDescriptor.Platform.Architecture == runtime.GOARCH && manifestDescriptor.Platform.OS == runtime.GOOS { 631 manifestDigest = manifestDescriptor.Digest 632 break 633 } 634 } 635 636 if manifestDigest == "" { 637 return "", "", errors.New("no supported platform found in manifest list") 638 } 639 640 manSvc, err := p.repo.Manifests(ctx) 641 if err != nil { 642 return "", "", err 643 } 644 645 manifest, err := manSvc.Get(ctx, manifestDigest) 646 if err != nil { 647 return "", "", err 648 } 649 650 manifestRef, err := reference.WithDigest(ref, manifestDigest) 651 if err != nil { 652 return "", "", err 653 } 654 655 switch v := manifest.(type) { 656 case *schema1.SignedManifest: 657 imageID, _, err = p.pullSchema1(ctx, manifestRef, v) 658 if err != nil { 659 return "", "", err 660 } 661 case *schema2.DeserializedManifest: 662 imageID, _, err = p.pullSchema2(ctx, manifestRef, v) 663 if err != nil { 664 return "", "", err 665 } 666 default: 667 return "", "", errors.New("unsupported manifest format") 668 } 669 670 return imageID, manifestListDigest, err 671 } 672 673 func (p *v2Puller) pullSchema2ImageConfig(ctx context.Context, dgst digest.Digest) (configJSON []byte, err error) { 674 blobs := p.repo.Blobs(ctx) 675 configJSON, err = blobs.Get(ctx, dgst) 676 if err != nil { 677 return nil, err 678 } 679 680 // Verify image config digest 681 verifier, err := digest.NewDigestVerifier(dgst) 682 if err != nil { 683 return nil, err 684 } 685 if _, err := verifier.Write(configJSON); err != nil { 686 return nil, err 687 } 688 if !verifier.Verified() { 689 err := fmt.Errorf("image config verification failed for digest %s", dgst) 690 logrus.Error(err) 691 return nil, err 692 } 693 694 return configJSON, nil 695 } 696 697 // schema2ManifestDigest computes the manifest digest, and, if pulling by 698 // digest, ensures that it matches the requested digest. 699 func schema2ManifestDigest(ref reference.Named, mfst distribution.Manifest) (digest.Digest, error) { 700 _, canonical, err := mfst.Payload() 701 if err != nil { 702 return "", err 703 } 704 705 // If pull by digest, then verify the manifest digest. 706 if digested, isDigested := ref.(reference.Canonical); isDigested { 707 verifier, err := digest.NewDigestVerifier(digested.Digest()) 708 if err != nil { 709 return "", err 710 } 711 if _, err := verifier.Write(canonical); err != nil { 712 return "", err 713 } 714 if !verifier.Verified() { 715 err := fmt.Errorf("manifest verification failed for digest %s", digested.Digest()) 716 logrus.Error(err) 717 return "", err 718 } 719 return digested.Digest(), nil 720 } 721 722 return digest.FromBytes(canonical), nil 723 } 724 725 // allowV1Fallback checks if the error is a possible reason to fallback to v1 726 // (even if confirmedV2 has been set already), and if so, wraps the error in 727 // a fallbackError with confirmedV2 set to false. Otherwise, it returns the 728 // error unmodified. 729 func allowV1Fallback(err error) error { 730 switch v := err.(type) { 731 case errcode.Errors: 732 if len(v) != 0 { 733 if v0, ok := v[0].(errcode.Error); ok && shouldV2Fallback(v0) { 734 return fallbackError{ 735 err: err, 736 confirmedV2: false, 737 transportOK: true, 738 } 739 } 740 } 741 case errcode.Error: 742 if shouldV2Fallback(v) { 743 return fallbackError{ 744 err: err, 745 confirmedV2: false, 746 transportOK: true, 747 } 748 } 749 case *url.Error: 750 if v.Err == auth.ErrNoBasicAuthCredentials { 751 return fallbackError{err: err, confirmedV2: false} 752 } 753 } 754 755 return err 756 } 757 758 func verifySchema1Manifest(signedManifest *schema1.SignedManifest, ref reference.Named) (m *schema1.Manifest, err error) { 759 // If pull by digest, then verify the manifest digest. NOTE: It is 760 // important to do this first, before any other content validation. If the 761 // digest cannot be verified, don't even bother with those other things. 762 if digested, isCanonical := ref.(reference.Canonical); isCanonical { 763 verifier, err := digest.NewDigestVerifier(digested.Digest()) 764 if err != nil { 765 return nil, err 766 } 767 if _, err := verifier.Write(signedManifest.Canonical); err != nil { 768 return nil, err 769 } 770 if !verifier.Verified() { 771 err := fmt.Errorf("image verification failed for digest %s", digested.Digest()) 772 logrus.Error(err) 773 return nil, err 774 } 775 } 776 m = &signedManifest.Manifest 777 778 if m.SchemaVersion != 1 { 779 return nil, fmt.Errorf("unsupported schema version %d for %q", m.SchemaVersion, ref.String()) 780 } 781 if len(m.FSLayers) != len(m.History) { 782 return nil, fmt.Errorf("length of history not equal to number of layers for %q", ref.String()) 783 } 784 if len(m.FSLayers) == 0 { 785 return nil, fmt.Errorf("no FSLayers in manifest for %q", ref.String()) 786 } 787 return m, nil 788 } 789 790 // fixManifestLayers removes repeated layers from the manifest and checks the 791 // correctness of the parent chain. 792 func fixManifestLayers(m *schema1.Manifest) error { 793 imgs := make([]*image.V1Image, len(m.FSLayers)) 794 for i := range m.FSLayers { 795 img := &image.V1Image{} 796 797 if err := json.Unmarshal([]byte(m.History[i].V1Compatibility), img); err != nil { 798 return err 799 } 800 801 imgs[i] = img 802 if err := v1.ValidateID(img.ID); err != nil { 803 return err 804 } 805 } 806 807 if imgs[len(imgs)-1].Parent != "" && runtime.GOOS != "windows" { 808 // Windows base layer can point to a base layer parent that is not in manifest. 809 return errors.New("Invalid parent ID in the base layer of the image.") 810 } 811 812 // check general duplicates to error instead of a deadlock 813 idmap := make(map[string]struct{}) 814 815 var lastID string 816 for _, img := range imgs { 817 // skip IDs that appear after each other, we handle those later 818 if _, exists := idmap[img.ID]; img.ID != lastID && exists { 819 return fmt.Errorf("ID %+v appears multiple times in manifest", img.ID) 820 } 821 lastID = img.ID 822 idmap[lastID] = struct{}{} 823 } 824 825 // backwards loop so that we keep the remaining indexes after removing items 826 for i := len(imgs) - 2; i >= 0; i-- { 827 if imgs[i].ID == imgs[i+1].ID { // repeated ID. remove and continue 828 m.FSLayers = append(m.FSLayers[:i], m.FSLayers[i+1:]...) 829 m.History = append(m.History[:i], m.History[i+1:]...) 830 } else if imgs[i].Parent != imgs[i+1].ID { 831 return fmt.Errorf("Invalid parent ID. Expected %v, got %v.", imgs[i+1].ID, imgs[i].Parent) 832 } 833 } 834 835 return nil 836 } 837 838 func createDownloadFile() (*os.File, error) { 839 return ioutil.TempFile("", "GetImageBlob") 840 }