// put stores the manifest in the repository, if not already present. Any // updated signatures will be stored, as well. func (rs *revisionStore) put(ctx context.Context, sm *schema1.SignedManifest) (distribution.Descriptor, error) { // Resolve the payload in the manifest. payload, err := sm.Payload() if err != nil { return distribution.Descriptor{}, err } // Digest and store the manifest payload in the blob store. revision, err := rs.blobStore.Put(ctx, schema1.ManifestMediaType, payload) if err != nil { context.GetLogger(ctx).Errorf("error putting payload into blobstore: %v", err) return distribution.Descriptor{}, err } // Link the revision into the repository. if err := rs.blobStore.linkBlob(ctx, revision); err != nil { return distribution.Descriptor{}, err } // Grab each json signature and store them. signatures, err := sm.Signatures() if err != nil { return distribution.Descriptor{}, err } if err := rs.repository.Signatures().Put(revision.Digest, signatures...); err != nil { return distribution.Descriptor{}, err } return revision, nil }
func schema1ToImage(manifest *schema1.SignedManifest, d digest.Digest) (*api.Image, error) { if len(manifest.History) == 0 { return nil, fmt.Errorf("image has no v1Compatibility history and cannot be used") } dockerImage, err := unmarshalDockerImage([]byte(manifest.History[0].V1Compatibility)) if err != nil { return nil, err } mediatype, payload, err := manifest.Payload() if err != nil { return nil, err } if len(d) > 0 { dockerImage.ID = d.String() } else { dockerImage.ID = digest.FromBytes(manifest.Canonical).String() } image := &api.Image{ ObjectMeta: kapi.ObjectMeta{ Name: dockerImage.ID, }, DockerImageMetadata: *dockerImage, DockerImageManifest: string(payload), DockerImageManifestMediaType: mediatype, DockerImageMetadataVersion: "1.0", } return image, nil }
func digestFromManifest(m *schema1.SignedManifest) (digest.Digest, error) { payload, err := m.Payload() if err != nil { return "", err } manifestDigest, err := digest.FromBytes(payload) return manifestDigest, nil }
// verifyManifest ensures that the manifest content is valid from the // perspective of the registry. It ensures that the signature is valid for the // enclosed payload. As a policy, the registry only tries to store valid // content, leaving trust policies of that content up to consumers. func (ms *signedManifestHandler) verifyManifest(ctx context.Context, mnfst schema1.SignedManifest, skipDependencyVerification bool) error { var errs distribution.ErrManifestVerification if len(mnfst.Name) > reference.NameTotalLengthMax { errs = append(errs, distribution.ErrManifestNameInvalid{ Name: mnfst.Name, Reason: fmt.Errorf("manifest name must not be more than %v characters", reference.NameTotalLengthMax), }) } if !reference.NameRegexp.MatchString(mnfst.Name) { errs = append(errs, distribution.ErrManifestNameInvalid{ Name: mnfst.Name, Reason: fmt.Errorf("invalid manifest name format"), }) } if len(mnfst.History) != len(mnfst.FSLayers) { errs = append(errs, fmt.Errorf("mismatched history and fslayer cardinality %d != %d", len(mnfst.History), len(mnfst.FSLayers))) } if _, err := schema1.Verify(&mnfst); err != nil { switch err { case libtrust.ErrMissingSignatureKey, libtrust.ErrInvalidJSONContent, libtrust.ErrMissingSignatureKey: errs = append(errs, distribution.ErrManifestUnverified{}) default: if err.Error() == "invalid signature" { // TODO(stevvooe): This should be exported by libtrust errs = append(errs, distribution.ErrManifestUnverified{}) } else { errs = append(errs, err) } } } if !skipDependencyVerification { for _, fsLayer := range mnfst.References() { _, err := ms.repository.Blobs(ctx).Stat(ctx, fsLayer.Digest) if err != nil { if err != distribution.ErrBlobUnknown { errs = append(errs, err) } // On error here, we always append unknown blob errors. errs = append(errs, distribution.ErrManifestBlobUnknown{Digest: fsLayer.Digest}) } } } if len(errs) != 0 { return errs } return nil }
func digestFromManifest(m *schema1.SignedManifest, localName string) (digest.Digest, int, error) { payload, err := m.Payload() if err != nil { // If this failed, the signatures section was corrupted // or missing. Treat the entire manifest as the payload. payload = m.Raw } manifestDigest, err := digest.FromBytes(payload) if err != nil { logrus.Infof("Could not compute manifest digest for %s:%s : %v", localName, m.Tag, err) } return manifestDigest, len(payload), nil }
func manifestDigest(sm *schema1.SignedManifest) (digest.Digest, error) { payload, err := sm.Payload() if err != nil { return "", err } dgst, err := digest.FromBytes(payload) if err != nil { return "", err } return dgst, nil }
// signedManifestFillImageMetadata fills a given image with metadata. It also corrects layer sizes with blob sizes. Newer // Docker client versions don't set layer sizes in the manifest at all. Origin master needs correct layer // sizes for proper image quota support. That's why we need to fill the metadata in the registry. func (r *repository) signedManifestFillImageMetadata(manifest *schema1.SignedManifest, image *imageapi.Image) error { signatures, err := manifest.Signatures() if err != nil { return err } for _, signDigest := range signatures { image.DockerImageSignatures = append(image.DockerImageSignatures, signDigest) } if err := imageapi.ImageWithMetadata(image); err != nil { return err } refs := manifest.References() blobSet := sets.NewString() image.DockerImageMetadata.Size = int64(0) blobs := r.Blobs(r.ctx) for i := range image.DockerImageLayers { layer := &image.DockerImageLayers[i] // DockerImageLayers represents manifest.Manifest.FSLayers in reversed order desc, err := blobs.Stat(r.ctx, refs[len(image.DockerImageLayers)-i-1].Digest) if err != nil { context.GetLogger(r.ctx).Errorf("failed to stat blobs %s of image %s", layer.Name, image.DockerImageReference) return err } if layer.MediaType == "" { if desc.MediaType != "" { layer.MediaType = desc.MediaType } else { layer.MediaType = schema1.MediaTypeManifestLayer } } layer.LayerSize = desc.Size // count empty layer just once (empty layer may actually have non-zero size) if !blobSet.Has(layer.Name) { image.DockerImageMetadata.Size += desc.Size blobSet.Insert(layer.Name) } } if len(image.DockerImageConfig) > 0 && !blobSet.Has(image.DockerImageMetadata.ID) { blobSet.Insert(image.DockerImageMetadata.ID) image.DockerImageMetadata.Size += int64(len(image.DockerImageConfig)) } return nil }
func verifyManifest(signedManifest *schema1.SignedManifest, tag string) (m *schema1.Manifest, err error) { // If pull by digest, then verify the manifest digest. NOTE: It is // important to do this first, before any other content validation. If the // digest cannot be verified, don't even bother with those other things. if manifestDigest, err := digest.ParseDigest(tag); err == nil { verifier, err := digest.NewDigestVerifier(manifestDigest) if err != nil { return nil, err } payload, err := signedManifest.Payload() if err != nil { // If this failed, the signatures section was corrupted // or missing. Treat the entire manifest as the payload. payload = signedManifest.Raw } if _, err := verifier.Write(payload); err != nil { return nil, err } if !verifier.Verified() { err := fmt.Errorf("image verification failed for digest %s", manifestDigest) logrus.Error(err) return nil, err } var verifiedManifest schema1.Manifest if err = json.Unmarshal(payload, &verifiedManifest); err != nil { return nil, err } m = &verifiedManifest } else { m = &signedManifest.Manifest } if m.SchemaVersion != 1 { return nil, fmt.Errorf("unsupported schema version %d for tag %q", m.SchemaVersion, tag) } if len(m.FSLayers) != len(m.History) { return nil, fmt.Errorf("length of history not equal to number of layers for tag %q", tag) } if len(m.FSLayers) == 0 { return nil, fmt.Errorf("no FSLayers in manifest for tag %q", tag) } return m, nil }
// ManifestMatchesImage returns true if the provided manifest matches the name of the image. func ManifestMatchesImage(image *Image, newManifest []byte) (bool, error) { dgst, err := digest.ParseDigest(image.Name) if err != nil { return false, err } v, err := digest.NewDigestVerifier(dgst) if err != nil { return false, err } sm := schema1.SignedManifest{Raw: newManifest} raw, err := sm.Payload() if err != nil { return false, err } if _, err := v.Write(raw); err != nil { return false, err } return v.Verified(), nil }
// digestManifest takes a digest of the given manifest. This belongs somewhere // better but we'll wait for a refactoring cycle to find that real somewhere. func digestManifest(ctx context.Context, sm *schema1.SignedManifest) (digest.Digest, error) { p, err := sm.Payload() if err != nil { if !strings.Contains(err.Error(), "missing signature key") { ctxu.GetLogger(ctx).Errorf("error getting manifest payload: %v", err) return "", err } // NOTE(stevvooe): There are no signatures but we still have a // payload. The request will fail later but this is not the // responsibility of this part of the code. p = sm.Raw } dgst, err := digest.FromBytes(p) if err != nil { ctxu.GetLogger(ctx).Errorf("error digesting manifest: %v", err) return "", err } return dgst, err }
func (registry *Registry) PutManifest(repository, reference string, signedManifest *manifestV1.SignedManifest) error { url := registry.url("/v2/%s/manifests/%s", repository, reference) registry.Logf("registry.manifest.put url=%s repository=%s reference=%s", url, repository, reference) body, err := signedManifest.MarshalJSON() if err != nil { return err } buffer := bytes.NewBuffer(body) req, err := http.NewRequest("PUT", url, buffer) if err != nil { return err } req.Header.Set("Content-Type", manifestV1.MediaTypeManifest) resp, err := registry.Client.Do(req) if resp != nil { defer resp.Body.Close() } return err }
func (b *bridge) createManifestEvent(action string, repo string, sm *schema1.SignedManifest) (*Event, error) { event := b.createEvent(action) event.Target.MediaType = schema1.ManifestMediaType event.Target.Repository = repo p, err := sm.Payload() if err != nil { return nil, err } event.Target.Length = int64(len(p)) event.Target.Size = int64(len(p)) event.Target.Digest, err = digest.FromBytes(p) if err != nil { return nil, err } event.Target.URL, err = b.ub.BuildManifestURL(sm.Name, event.Target.Digest.String()) if err != nil { return nil, err } return event, nil }
func schema1ToImage(manifest *schema1.SignedManifest, d digest.Digest) (*api.Image, error) { if len(manifest.History) == 0 { return nil, fmt.Errorf("image has no v1Compatibility history and cannot be used") } dockerImage, err := unmarshalDockerImage([]byte(manifest.History[0].V1Compatibility)) if err != nil { return nil, err } if len(d) > 0 { dockerImage.ID = d.String() } else { if p, err := manifest.Payload(); err == nil { d, err := digest.FromBytes(p) if err != nil { return nil, fmt.Errorf("unable to create digest from image payload: %v", err) } dockerImage.ID = d.String() } else { d, err := digest.FromBytes(manifest.Raw) if err != nil { return nil, fmt.Errorf("unable to create digest from image bytes: %v", err) } dockerImage.ID = d.String() } } image := &api.Image{ ObjectMeta: kapi.ObjectMeta{ Name: dockerImage.ID, }, DockerImageMetadata: *dockerImage, DockerImageManifest: string(manifest.Raw), DockerImageMetadataVersion: "1.0", } return image, nil }
// TestValidateManifest verifies the validateManifest function func TestValidateManifest(t *testing.T) { expectedDigest := "sha256:02fee8c3220ba806531f606525eceb83f4feb654f62b207191b1c9209188dedd" expectedFSLayer0 := digest.Digest("sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4") // Good manifest goodManifestBytes, err := ioutil.ReadFile("fixtures/validate_manifest/good_manifest") if err != nil { t.Fatal("error reading fixture:", err) } var goodSignedManifest schema1.SignedManifest err = json.Unmarshal(goodManifestBytes, &goodSignedManifest) if err != nil { t.Fatal("error unmarshaling manifest:", err) } verifiedManifest, err := verifyManifest(&goodSignedManifest, expectedDigest) if err != nil { t.Fatal("validateManifest failed:", err) } if verifiedManifest.FSLayers[0].BlobSum != expectedFSLayer0 { t.Fatal("unexpected FSLayer in good manifest") } // "Extra data" manifest extraDataManifestBytes, err := ioutil.ReadFile("fixtures/validate_manifest/extra_data_manifest") if err != nil { t.Fatal("error reading fixture:", err) } var extraDataSignedManifest schema1.SignedManifest err = json.Unmarshal(extraDataManifestBytes, &extraDataSignedManifest) if err != nil { t.Fatal("error unmarshaling manifest:", err) } verifiedManifest, err = verifyManifest(&extraDataSignedManifest, expectedDigest) if err != nil { t.Fatal("validateManifest failed:", err) } if verifiedManifest.FSLayers[0].BlobSum != expectedFSLayer0 { t.Fatal("unexpected FSLayer in extra data manifest") } // Bad manifest badManifestBytes, err := ioutil.ReadFile("fixtures/validate_manifest/bad_manifest") if err != nil { t.Fatal("error reading fixture:", err) } var badSignedManifest schema1.SignedManifest err = json.Unmarshal(badManifestBytes, &badSignedManifest) if err != nil { t.Fatal("error unmarshaling manifest:", err) } verifiedManifest, err = verifyManifest(&badSignedManifest, expectedDigest) if err == nil || !strings.HasPrefix(err.Error(), "image verification failed for digest") { t.Fatal("expected validateManifest to fail with digest error") } // Manifest with no signature expectedWholeFileDigest := "7ec3615a120efcdfc270e9c7ea4183330775a3e52a09e2efb194b9a7c18e5ff7" noSignatureManifestBytes, err := ioutil.ReadFile("fixtures/validate_manifest/no_signature_manifest") if err != nil { t.Fatal("error reading fixture:", err) } var noSignatureSignedManifest schema1.SignedManifest noSignatureSignedManifest.Raw = noSignatureManifestBytes err = json.Unmarshal(noSignatureManifestBytes, &noSignatureSignedManifest.Manifest) if err != nil { t.Fatal("error unmarshaling manifest:", err) } verifiedManifest, err = verifyManifest(&noSignatureSignedManifest, expectedWholeFileDigest) if err != nil { t.Fatal("validateManifest failed:", err) } if verifiedManifest.FSLayers[0].BlobSum != expectedFSLayer0 { t.Fatal("unexpected FSLayer in no-signature manifest") } }
// Put creates or updates the named manifest. func (r *repository) Put(manifest *schema1.SignedManifest) error { // Resolve the payload in the manifest. payload, err := manifest.Payload() if err != nil { return err } // Calculate digest dgst, err := digest.FromBytes(payload) if err != nil { return err } // Upload to openshift ism := imageapi.ImageStreamMapping{ ObjectMeta: kapi.ObjectMeta{ Namespace: r.namespace, Name: r.name, }, Tag: manifest.Tag, Image: imageapi.Image{ ObjectMeta: kapi.ObjectMeta{ Name: dgst.String(), Annotations: map[string]string{ imageapi.ManagedByOpenShiftAnnotation: "true", }, }, DockerImageReference: fmt.Sprintf("%s/%s/%s@%s", r.registryAddr, r.namespace, r.name, dgst.String()), DockerImageManifest: string(payload), }, } if err := r.registryClient.ImageStreamMappings(r.namespace).Create(&ism); err != nil { // if the error was that the image stream wasn't found, try to auto provision it statusErr, ok := err.(*kerrors.StatusError) if !ok { context.GetLogger(r.ctx).Errorf("Error creating ImageStreamMapping: %s", err) return err } status := statusErr.ErrStatus if status.Code != http.StatusNotFound || status.Details.Kind != "imageStream" || status.Details.Name != r.name { context.GetLogger(r.ctx).Errorf("Error creating ImageStreamMapping: %s", err) return err } stream := imageapi.ImageStream{ ObjectMeta: kapi.ObjectMeta{ Name: r.name, }, } client, ok := UserClientFrom(r.ctx) if !ok { context.GetLogger(r.ctx).Errorf("Error creating user client to auto provision image stream: Origin user client unavailable") return statusErr } if _, err := client.ImageStreams(r.namespace).Create(&stream); err != nil { context.GetLogger(r.ctx).Errorf("Error auto provisioning image stream: %s", err) return statusErr } // try to create the ISM again if err := r.registryClient.ImageStreamMappings(r.namespace).Create(&ism); err != nil { context.GetLogger(r.ctx).Errorf("Error creating image stream mapping: %s", err) return err } } // Grab each json signature and store them. signatures, err := manifest.Signatures() if err != nil { return err } for _, signature := range signatures { if err := r.Signatures().Put(dgst, signature); err != nil { context.GetLogger(r.ctx).Errorf("Error storing signature: %s", err) return err } } return nil }
func outputManifestFor(target string) { var pkey trust.PrivateKey if key != "" { var err error pkey, err = trust.LoadKeyFile(key) if err != nil { fmt.Printf("error loading key: %s\n", err.Error()) return } } if verbose { fmt.Errorf("signing with: %s\n", pkey.KeyID()) } f, err := os.Open(target) if err != nil { fmt.Printf("error opening file: %s\n", err.Error()) return } defer func() { if err := f.Close(); err != nil { panic(err) } }() var ( repo, tag string ) layers := LayerMap{} t := tar.NewReader(bufio.NewReader(f)) for { hdr, err := t.Next() if err == io.EOF { break } if strings.HasSuffix(hdr.Name, "layer.tar") { id := getLayerPrefix(hdr.Name) sum, _ := blobSumLayer(t) if _, ok := layers[id]; !ok { layers[id] = &Layer{Id: id} } else { layers[id].BlobSum = sum } } if strings.HasSuffix(hdr.Name, "json") { data, _ := ioutil.ReadAll(t) parent, id, _ := getLayerInfo(data) if _, ok := layers[id]; !ok { layers[id] = &Layer{Id: id, Parent: parent} } else { layers[id].Parent = parent } var img image.Image json.Unmarshal(data, &img) b, _ := json.Marshal(img) layers[id].Data = string(b) + "\n" } if hdr.Name == "repositories" { r, _ := ioutil.ReadAll(t) var raw map[string]interface{} if err := json.Unmarshal(r, &raw); err != nil { return } repo, tag = getRepoInfo(raw) if !strings.Contains(repo, "/") { repo = "library/" + repo } } } m := manifest.Manifest{ Versioned: versioned.Versioned{ SchemaVersion: 1, }, Name: repo, Tag: tag, Architecture: "amd64"} ll := getLayersFromMap(layers) for _, l := range getLayersInOrder(ll) { m.FSLayers = append(m.FSLayers, manifest.FSLayer{BlobSum: l.BlobSum}) m.History = append(m.History, manifest.History{V1Compatibility: l.Data}) } var x []byte if pkey != nil { var sm *manifest.SignedManifest sm, err = manifest.Sign(&m, pkey) x, err = sm.MarshalJSON() } else { x, err = json.MarshalIndent(m, "", " ") } if print_digest { dgstr, _ := digest.FromBytes(x) fmt.Println(string(dgstr)) } fmt.Println(string(x)) }
func testManifestAPI(t *testing.T, env *testEnv, args manifestArgs) (*testEnv, manifestArgs) { imageName := args.imageName tag := "thetag" manifestURL, err := env.builder.BuildManifestURL(imageName, tag) if err != nil { t.Fatalf("unexpected error getting manifest url: %v", err) } // ----------------------------- // Attempt to fetch the manifest resp, err := http.Get(manifestURL) if err != nil { t.Fatalf("unexpected error getting manifest: %v", err) } defer resp.Body.Close() checkResponse(t, "getting non-existent manifest", resp, http.StatusNotFound) checkBodyHasErrorCodes(t, "getting non-existent manifest", resp, v2.ErrorCodeManifestUnknown) tagsURL, err := env.builder.BuildTagsURL(imageName) if err != nil { t.Fatalf("unexpected error building tags url: %v", err) } resp, err = http.Get(tagsURL) if err != nil { t.Fatalf("unexpected error getting unknown tags: %v", err) } defer resp.Body.Close() // Check that we get an unknown repository error when asking for tags checkResponse(t, "getting unknown manifest tags", resp, http.StatusNotFound) checkBodyHasErrorCodes(t, "getting unknown manifest tags", resp, v2.ErrorCodeNameUnknown) // -------------------------------- // Attempt to push unsigned manifest with missing layers unsignedManifest := &schema1.Manifest{ Versioned: manifest.Versioned{ SchemaVersion: 1, }, Name: imageName, Tag: tag, FSLayers: []schema1.FSLayer{ { BlobSum: "asdf", }, { BlobSum: "qwer", }, }, History: []schema1.History{ { V1Compatibility: "", }, { V1Compatibility: "", }, }, } resp = putManifest(t, "putting unsigned manifest", manifestURL, unsignedManifest) defer resp.Body.Close() checkResponse(t, "putting unsigned manifest", resp, http.StatusBadRequest) _, p, counts := checkBodyHasErrorCodes(t, "getting unknown manifest tags", resp, v2.ErrorCodeManifestInvalid) expectedCounts := map[errcode.ErrorCode]int{ v2.ErrorCodeManifestInvalid: 1, } if !reflect.DeepEqual(counts, expectedCounts) { t.Fatalf("unexpected number of error codes encountered: %v\n!=\n%v\n---\n%s", counts, expectedCounts, string(p)) } // sign the manifest and still get some interesting errors. sm, err := schema1.Sign(unsignedManifest, env.pk) if err != nil { t.Fatalf("error signing manifest: %v", err) } resp = putManifest(t, "putting signed manifest with errors", manifestURL, sm) defer resp.Body.Close() checkResponse(t, "putting signed manifest with errors", resp, http.StatusBadRequest) _, p, counts = checkBodyHasErrorCodes(t, "putting signed manifest with errors", resp, v2.ErrorCodeManifestBlobUnknown, v2.ErrorCodeDigestInvalid) expectedCounts = map[errcode.ErrorCode]int{ v2.ErrorCodeManifestBlobUnknown: 2, v2.ErrorCodeDigestInvalid: 2, } if !reflect.DeepEqual(counts, expectedCounts) { t.Fatalf("unexpected number of error codes encountered: %v\n!=\n%v\n---\n%s", counts, expectedCounts, string(p)) } // TODO(stevvooe): Add a test case where we take a mostly valid registry, // tamper with the content and ensure that we get a unverified manifest // error. // Push 2 random layers expectedLayers := make(map[digest.Digest]io.ReadSeeker) for i := range unsignedManifest.FSLayers { rs, dgstStr, err := testutil.CreateRandomTarFile() if err != nil { t.Fatalf("error creating random layer %d: %v", i, err) } dgst := digest.Digest(dgstStr) expectedLayers[dgst] = rs unsignedManifest.FSLayers[i].BlobSum = dgst uploadURLBase, _ := startPushLayer(t, env.builder, imageName) pushLayer(t, env.builder, imageName, dgst, uploadURLBase, rs) } // ------------------- // Push the signed manifest with all layers pushed. signedManifest, err := schema1.Sign(unsignedManifest, env.pk) if err != nil { t.Fatalf("unexpected error signing manifest: %v", err) } dgst := digest.FromBytes(signedManifest.Canonical) args.signedManifest = signedManifest args.dgst = dgst manifestDigestURL, err := env.builder.BuildManifestURL(imageName, dgst.String()) checkErr(t, err, "building manifest url") resp = putManifest(t, "putting signed manifest no error", manifestURL, signedManifest) checkResponse(t, "putting signed manifest no error", resp, http.StatusCreated) checkHeaders(t, resp, http.Header{ "Location": []string{manifestDigestURL}, "Docker-Content-Digest": []string{dgst.String()}, }) // -------------------- // Push by digest -- should get same result resp = putManifest(t, "putting signed manifest", manifestDigestURL, signedManifest) checkResponse(t, "putting signed manifest", resp, http.StatusCreated) checkHeaders(t, resp, http.Header{ "Location": []string{manifestDigestURL}, "Docker-Content-Digest": []string{dgst.String()}, }) // ------------------ // Fetch by tag name resp, err = http.Get(manifestURL) if err != nil { t.Fatalf("unexpected error fetching manifest: %v", err) } defer resp.Body.Close() checkResponse(t, "fetching uploaded manifest", resp, http.StatusOK) checkHeaders(t, resp, http.Header{ "Docker-Content-Digest": []string{dgst.String()}, "ETag": []string{fmt.Sprintf(`"%s"`, dgst)}, }) var fetchedManifest schema1.SignedManifest dec := json.NewDecoder(resp.Body) if err := dec.Decode(&fetchedManifest); err != nil { t.Fatalf("error decoding fetched manifest: %v", err) } if !bytes.Equal(fetchedManifest.Canonical, signedManifest.Canonical) { t.Fatalf("manifests do not match") } // --------------- // Fetch by digest resp, err = http.Get(manifestDigestURL) checkErr(t, err, "fetching manifest by digest") defer resp.Body.Close() checkResponse(t, "fetching uploaded manifest", resp, http.StatusOK) checkHeaders(t, resp, http.Header{ "Docker-Content-Digest": []string{dgst.String()}, "ETag": []string{fmt.Sprintf(`"%s"`, dgst)}, }) var fetchedManifestByDigest schema1.SignedManifest dec = json.NewDecoder(resp.Body) if err := dec.Decode(&fetchedManifestByDigest); err != nil { t.Fatalf("error decoding fetched manifest: %v", err) } if !bytes.Equal(fetchedManifestByDigest.Canonical, signedManifest.Canonical) { t.Fatalf("manifests do not match") } // check signature was roundtripped signatures, err := fetchedManifestByDigest.Signatures() if err != nil { t.Fatal(err) } if len(signatures) != 1 { t.Fatalf("expected 1 signature from manifest, got: %d", len(signatures)) } // Re-sign, push and pull the same digest sm2, err := schema1.Sign(&fetchedManifestByDigest.Manifest, env.pk) if err != nil { t.Fatal(err) } resp = putManifest(t, "re-putting signed manifest", manifestDigestURL, sm2) checkResponse(t, "re-putting signed manifest", resp, http.StatusCreated) resp, err = http.Get(manifestDigestURL) checkErr(t, err, "re-fetching manifest by digest") defer resp.Body.Close() checkResponse(t, "re-fetching uploaded manifest", resp, http.StatusOK) checkHeaders(t, resp, http.Header{ "Docker-Content-Digest": []string{dgst.String()}, "ETag": []string{fmt.Sprintf(`"%s"`, dgst)}, }) dec = json.NewDecoder(resp.Body) if err := dec.Decode(&fetchedManifestByDigest); err != nil { t.Fatalf("error decoding fetched manifest: %v", err) } // check two signatures were roundtripped signatures, err = fetchedManifestByDigest.Signatures() if err != nil { t.Fatal(err) } if len(signatures) != 2 { t.Fatalf("expected 2 signature from manifest, got: %d", len(signatures)) } // Get by name with etag, gives 304 etag := resp.Header.Get("Etag") req, err := http.NewRequest("GET", manifestURL, nil) if err != nil { t.Fatalf("Error constructing request: %s", err) } req.Header.Set("If-None-Match", etag) resp, err = http.DefaultClient.Do(req) if err != nil { t.Fatalf("Error constructing request: %s", err) } checkResponse(t, "fetching manifest by name with etag", resp, http.StatusNotModified) // Get by digest with etag, gives 304 req, err = http.NewRequest("GET", manifestDigestURL, nil) if err != nil { t.Fatalf("Error constructing request: %s", err) } req.Header.Set("If-None-Match", etag) resp, err = http.DefaultClient.Do(req) if err != nil { t.Fatalf("Error constructing request: %s", err) } checkResponse(t, "fetching manifest by dgst with etag", resp, http.StatusNotModified) // Ensure that the tag is listed. resp, err = http.Get(tagsURL) if err != nil { t.Fatalf("unexpected error getting unknown tags: %v", err) } defer resp.Body.Close() // Check that we get an unknown repository error when asking for tags checkResponse(t, "getting unknown manifest tags", resp, http.StatusOK) dec = json.NewDecoder(resp.Body) var tagsResponse tagsAPIResponse if err := dec.Decode(&tagsResponse); err != nil { t.Fatalf("unexpected error decoding error response: %v", err) } if tagsResponse.Name != imageName { t.Fatalf("tags name should match image name: %v != %v", tagsResponse.Name, imageName) } if len(tagsResponse.Tags) != 1 { t.Fatalf("expected some tags in response: %v", tagsResponse.Tags) } if tagsResponse.Tags[0] != tag { t.Fatalf("tag not as expected: %q != %q", tagsResponse.Tags[0], tag) } // Attempt to put a manifest with mismatching FSLayer and History array cardinalities unsignedManifest.History = append(unsignedManifest.History, schema1.History{ V1Compatibility: "", }) invalidSigned, err := schema1.Sign(unsignedManifest, env.pk) if err != nil { t.Fatalf("error signing manifest") } resp = putManifest(t, "putting invalid signed manifest", manifestDigestURL, invalidSigned) checkResponse(t, "putting invalid signed manifest", resp, http.StatusBadRequest) return env, args }