diff --git a/registry/handlers/api_test.go b/registry/handlers/api_test.go index 5fd1790c..30613e6b 100644 --- a/registry/handlers/api_test.go +++ b/registry/handlers/api_test.go @@ -23,7 +23,6 @@ import ( "github.com/distribution/distribution/v3/configuration" "github.com/distribution/distribution/v3/manifest" "github.com/distribution/distribution/v3/manifest/manifestlist" - "github.com/distribution/distribution/v3/manifest/schema1" //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility. "github.com/distribution/distribution/v3/manifest/schema2" "github.com/distribution/distribution/v3/reference" "github.com/distribution/distribution/v3/registry/api/errcode" @@ -421,7 +420,8 @@ func TestCatalogAPI(t *testing.T) { envWithLessImages := newTestEnv(t, false) for _, image := range allCatalog[0:(maxEntries - 1)] { - createRepository(envWithLessImages, t, image, "sometag") + imageName, _ := reference.WithName(image) + testManifestAPISchema2(t, envWithLessImages, imageName, "sometag") } catalogURL, err = envWithLessImages.builder.BuildCatalogURL(values) @@ -1245,21 +1245,18 @@ type manifestArgs struct { } func TestManifestAPI(t *testing.T) { - schema1Repo, _ := reference.WithName("foo/schema1") schema2Repo, _ := reference.WithName("foo/schema2") deleteEnabled := false env1 := newTestEnv(t, deleteEnabled) defer env1.Shutdown() - testManifestAPISchema1(t, env1, schema1Repo) - schema2Args := testManifestAPISchema2(t, env1, schema2Repo) + schema2Args := testManifestAPISchema2(t, env1, schema2Repo, "schema2tag") testManifestAPIManifestList(t, env1, schema2Args) deleteEnabled = true env2 := newTestEnv(t, deleteEnabled) defer env2.Shutdown() - testManifestAPISchema1(t, env2, schema1Repo) - schema2Args = testManifestAPISchema2(t, env2, schema2Repo) + schema2Args = testManifestAPISchema2(t, env2, schema2Repo, "schema2tag") testManifestAPIManifestList(t, env2, schema2Args) } @@ -1438,24 +1435,21 @@ func TestGetManifestWithStorageError(t *testing.T) { } func TestManifestDelete(t *testing.T) { - schema1Repo, _ := reference.WithName("foo/schema1") schema2Repo, _ := reference.WithName("foo/schema2") deleteEnabled := true env := newTestEnv(t, deleteEnabled) defer env.Shutdown() - schema1Args := testManifestAPISchema1(t, env, schema1Repo) - testManifestDelete(t, env, schema1Args) - schema2Args := testManifestAPISchema2(t, env, schema2Repo) + schema2Args := testManifestAPISchema2(t, env, schema2Repo, "schema2tag") testManifestDelete(t, env, schema2Args) } func TestManifestDeleteDisabled(t *testing.T) { - schema1Repo, _ := reference.WithName("foo/schema1") + schema2Repo, _ := reference.WithName("foo/schema2") deleteEnabled := false env := newTestEnv(t, deleteEnabled) defer env.Shutdown() - testManifestDeleteDisabled(t, env, schema1Repo) + testManifestDeleteDisabled(t, env, schema2Repo) } func testManifestDeleteDisabled(t *testing.T, env *testEnv, imageName reference.Named) { @@ -1493,324 +1487,7 @@ func testManifestWithStorageError(t *testing.T, env *testEnv, imageName referenc checkBodyHasErrorCodes(t, "getting non-existent manifest", resp, expectedErrorCode) } -func testManifestAPISchema1(t *testing.T, env *testEnv, imageName reference.Named) manifestArgs { - tag := "thetag" - args := manifestArgs{imageName: imageName} - - tagRef, _ := reference.WithTag(imageName, tag) - manifestURL, err := env.builder.BuildManifestURL(tagRef) - 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{ //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility. - Versioned: manifest.Versioned{ - SchemaVersion: 1, - }, - Name: imageName.Name(), - Tag: tag, - FSLayers: []schema1.FSLayer{ //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility. - { - BlobSum: "asdf", - }, - { - BlobSum: "qwer", - }, - }, - History: []schema1.History{ //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility. - { - 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, "putting unsigned manifest", 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) //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility. - 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 an unverified manifest - // error. - - // Push 2 random layers - expectedLayers := make(map[digest.Digest]io.ReadSeeker) - - for i := range unsignedManifest.FSLayers { - rs, dgst, err := testutil.CreateRandomTarFile() - if err != nil { - t.Fatalf("error creating random layer %d: %v", i, err) - } - - expectedLayers[dgst] = rs - unsignedManifest.FSLayers[i].BlobSum = dgst - - uploadURLBase, _ := startPushLayer(t, env, imageName) - pushLayer(t, env.builder, imageName, dgst, uploadURLBase, rs) - } - - // ------------------- - // Push the signed manifest with all layers pushed. - signedManifest, err := schema1.Sign(unsignedManifest, env.pk) //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility. - if err != nil { - t.Fatalf("unexpected error signing manifest: %v", err) - } - - dgst := digest.FromBytes(signedManifest.Canonical) - args.manifest = signedManifest - args.dgst = dgst - - digestRef, _ := reference.WithDigest(imageName, dgst) - manifestDigestURL, err := env.builder.BuildManifestURL(digestRef) - 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 //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility. - 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 //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility. - 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() //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility. - 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) //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility. - if err != nil { - t.Fatal(err) - } - - // Re-push with a few different Content-Types. The official schema1 - // content type should work, as should application/json with/without a - // charset. - resp = putManifest(t, "re-putting signed manifest", manifestDigestURL, schema1.MediaTypeSignedManifest, sm2) //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility. - checkResponse(t, "re-putting signed manifest", resp, http.StatusCreated) - resp = putManifest(t, "re-putting signed manifest", manifestDigestURL, "application/json", sm2) - checkResponse(t, "re-putting signed manifest", resp, http.StatusCreated) - resp = putManifest(t, "re-putting signed manifest", manifestDigestURL, "application/json", 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 only 1 signature is returned - signatures, err = fetchedManifestByDigest.Signatures() //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility. - if err != nil { - t.Fatal(err) - } - - if len(signatures) != 1 { - 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(http.MethodGet, 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(http.MethodGet, 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() - - checkResponse(t, "getting 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.Name() { - t.Fatalf("tags name should match image name: %v != %v", tagsResponse.Name, imageName.Name()) - } - - 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{ //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility. - V1Compatibility: "", - }) - invalidSigned, err := schema1.Sign(unsignedManifest, env.pk) //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility. - 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 args -} - -func testManifestAPISchema2(t *testing.T, env *testEnv, imageName reference.Named) manifestArgs { - tag := "schema2tag" +func testManifestAPISchema2(t *testing.T, env *testEnv, imageName reference.Named, tag string) manifestArgs { args := manifestArgs{ imageName: imageName, mediaType: schema2.MediaTypeManifest, @@ -2101,63 +1778,6 @@ func testManifestAPISchema2(t *testing.T, env *testEnv, imageName reference.Name t.Fatalf("tag not as expected: %q != %q", tagsResponse.Tags[0], tag) } - // ------------------ - // Fetch as a schema1 manifest - resp, err = http.Get(manifestURL) - if err != nil { - t.Fatalf("unexpected error fetching manifest as schema1: %v", err) - } - defer resp.Body.Close() - - manifestBytes, err := io.ReadAll(resp.Body) - if err != nil { - t.Fatalf("error reading response body: %v", err) - } - - checkResponse(t, "fetching uploaded manifest as schema1", resp, http.StatusOK) - - m, desc, err := distribution.UnmarshalManifest(schema1.MediaTypeManifest, manifestBytes) //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility. - if err != nil { - t.Fatalf("unexpected error unmarshalling manifest: %v", err) - } - - fetchedSchema1Manifest, ok := m.(*schema1.SignedManifest) //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility. - if !ok { - t.Fatalf("expecting schema1 manifest") - } - - checkHeaders(t, resp, http.Header{ - "Docker-Content-Digest": []string{desc.Digest.String()}, - "ETag": []string{fmt.Sprintf(`"%s"`, desc.Digest)}, - }) - - if fetchedSchema1Manifest.Manifest.SchemaVersion != 1 { - t.Fatal("wrong schema version") - } - if fetchedSchema1Manifest.Architecture != "amd64" { - t.Fatal("wrong architecture") - } - if fetchedSchema1Manifest.Name != imageName.Name() { - t.Fatal("wrong image name") - } - if fetchedSchema1Manifest.Tag != tag { - t.Fatal("wrong tag") - } - if len(fetchedSchema1Manifest.FSLayers) != 2 { - t.Fatal("wrong number of FSLayers") - } - for i := range manifest.Layers { - if fetchedSchema1Manifest.FSLayers[i].BlobSum != manifest.Layers[len(manifest.Layers)-i-1].Digest { - t.Fatalf("blob digest mismatch in schema1 manifest for layer %d", i) - } - } - if len(fetchedSchema1Manifest.History) != 2 { - t.Fatal("wrong number of History entries") - } - - // Don't check V1Compatibility fields because we're using randomly-generated - // layers. - return args } @@ -2246,7 +1866,7 @@ func testManifestAPIManifestList(t *testing.T, env *testEnv, args manifestArgs) t.Fatalf("Error constructing request: %s", err) } // multiple headers in mixed list format to ensure we parse correctly server-side - req.Header.Set("Accept", fmt.Sprintf(` %s ; q=0.8 , %s ; q=0.5 `, manifestlist.MediaTypeManifestList, schema1.MediaTypeSignedManifest)) //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility. + req.Header.Set("Accept", fmt.Sprintf(` %s ; q=0.8 `, manifestlist.MediaTypeManifestList)) req.Header.Add("Accept", schema2.MediaTypeManifest) resp, err = http.DefaultClient.Do(req) if err != nil { @@ -2334,64 +1954,6 @@ func testManifestAPIManifestList(t *testing.T, env *testEnv, args manifestArgs) } checkResponse(t, "fetching manifest by dgst with etag", resp, http.StatusNotModified) - - // ------------------ - // Fetch as a schema1 manifest - resp, err = http.Get(manifestURL) - if err != nil { - t.Fatalf("unexpected error fetching manifest list as schema1: %v", err) - } - defer resp.Body.Close() - - manifestBytes, err := io.ReadAll(resp.Body) - if err != nil { - t.Fatalf("error reading response body: %v", err) - } - - checkResponse(t, "fetching uploaded manifest list as schema1", resp, http.StatusOK) - - m, desc, err := distribution.UnmarshalManifest(schema1.MediaTypeManifest, manifestBytes) //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility. - if err != nil { - t.Fatalf("unexpected error unmarshalling manifest: %v", err) - } - - fetchedSchema1Manifest, ok := m.(*schema1.SignedManifest) //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility. - if !ok { - t.Fatalf("expecting schema1 manifest") - } - - checkHeaders(t, resp, http.Header{ - "Docker-Content-Digest": []string{desc.Digest.String()}, - "ETag": []string{fmt.Sprintf(`"%s"`, desc.Digest)}, - }) - - if fetchedSchema1Manifest.Manifest.SchemaVersion != 1 { - t.Fatal("wrong schema version") - } - if fetchedSchema1Manifest.Architecture != "amd64" { - t.Fatal("wrong architecture") - } - if fetchedSchema1Manifest.Name != imageName.Name() { - t.Fatal("wrong image name") - } - if fetchedSchema1Manifest.Tag != tag { - t.Fatal("wrong tag") - } - if len(fetchedSchema1Manifest.FSLayers) != 2 { - t.Fatal("wrong number of FSLayers") - } - layers := args.manifest.(*schema2.DeserializedManifest).Layers - for i := range layers { - if fetchedSchema1Manifest.FSLayers[i].BlobSum != layers[len(layers)-i-1].Digest { - t.Fatalf("blob digest mismatch in schema1 manifest for layer %d", i) - } - } - if len(fetchedSchema1Manifest.History) != 2 { - t.Fatal("wrong number of History entries") - } - - // Don't check V1Compatibility fields because we're using randomly-generated - // layers. } func testManifestDelete(t *testing.T, env *testEnv, args manifestArgs) { @@ -2556,7 +2118,6 @@ func newTestEnvMirror(t *testing.T, deleteEnabled bool) *testEnv { MaxEntries: 5, }, } - config.Compatibility.Schema1.Enabled = true //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility. return newTestEnvWithConfig(t, &config) } @@ -2575,7 +2136,6 @@ func newTestEnv(t *testing.T, deleteEnabled bool) *testEnv { }, } - config.Compatibility.Schema1.Enabled = true //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility. config.HTTP.Headers = headerConfig return newTestEnvWithConfig(t, &config) @@ -2615,12 +2175,6 @@ func putManifest(t *testing.T, msg, url, contentType string, v interface{}) *htt var body []byte switch m := v.(type) { - case *schema1.SignedManifest: //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility. - _, pl, err := m.Payload() //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility. - if err != nil { - t.Fatalf("error getting payload: %v", err) - } - body = pl case *manifestlist.DeserializedManifestList: _, pl, err := m.Payload() if err != nil { @@ -2968,61 +2522,89 @@ func createRepository(env *testEnv, t *testing.T, imageName string, tag string) t.Fatalf("unable to parse reference: %v", err) } - unsignedManifest := &schema1.Manifest{ //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility. + tagRef, _ := reference.WithTag(imageNameRef, tag) + manifestURL, err := env.builder.BuildManifestURL(tagRef) + if err != nil { + t.Fatalf("unexpected error getting manifest url: %v", err) + } + + manifest := &schema2.Manifest{ Versioned: manifest.Versioned{ - SchemaVersion: 1, + SchemaVersion: 2, + MediaType: schema2.MediaTypeManifest, }, - Name: imageName, - Tag: tag, - FSLayers: []schema1.FSLayer{ //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility. - { - BlobSum: "asdf", - }, + Config: distribution.Descriptor{ + Digest: "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b", + Size: 3253, + MediaType: schema2.MediaTypeImageConfig, }, - History: []schema1.History{ //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility. + Layers: []distribution.Descriptor{ { - V1Compatibility: "", + Digest: "sha256:463434349086340864309863409683460843608348608934092322395278926a", + Size: 6323, + MediaType: schema2.MediaTypeLayer, }, }, } - // Push 2 random layers - expectedLayers := make(map[digest.Digest]io.ReadSeeker) + // Push a config, and reference it in the manifest + sampleConfig := []byte(`{ + "architecture": "amd64", + "history": [ + { + "created": "2015-10-31T22:22:54.690851953Z", + "created_by": "/bin/sh -c #(nop) ADD file:a3bc1e842b69636f9df5256c49c5374fb4eef1e281fe3f282c65fb853ee171c5 in /" + }, + ], + "rootfs": { + "diff_ids": [ + "sha256:c6f988f4874bb0add23a778f753c65efe992244e148a1d2ec2a8b664fb66bbd1", + ], + "type": "layers" + } + }`) + sampleConfigDigest := digest.FromBytes(sampleConfig) - for i := range unsignedManifest.FSLayers { + uploadURLBase, _ := startPushLayer(t, env, imageNameRef) + pushLayer(t, env.builder, imageNameRef, sampleConfigDigest, uploadURLBase, bytes.NewReader(sampleConfig)) + manifest.Config.Digest = sampleConfigDigest + manifest.Config.Size = int64(len(sampleConfig)) + + // Push random layers + + for i := range manifest.Layers { rs, dgst, err := testutil.CreateRandomTarFile() if err != nil { t.Fatalf("error creating random layer %d: %v", i, err) } + manifest.Layers[i].Digest = dgst - expectedLayers[dgst] = rs - unsignedManifest.FSLayers[i].BlobSum = dgst uploadURLBase, _ := startPushLayer(t, env, imageNameRef) pushLayer(t, env.builder, imageNameRef, dgst, uploadURLBase, rs) } - signedManifest, err := schema1.Sign(unsignedManifest, env.pk) //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility. + // ------------------- + // Push the manifest with all layers pushed. + deserializedManifest, err := schema2.FromStruct(*manifest) if err != nil { - t.Fatalf("unexpected error signing manifest: %v", err) + t.Fatalf("could not create DeserializedManifest: %v", err) } - - dgst := digest.FromBytes(signedManifest.Canonical) - - // Create this repository by tag to ensure the tag mapping is made in the registry - tagRef, _ := reference.WithTag(imageNameRef, tag) - manifestDigestURL, err := env.builder.BuildManifestURL(tagRef) + _, canonical, err := deserializedManifest.Payload() + if err != nil { + t.Fatalf("could not get manifest payload: %v", err) + } + dgst := digest.FromBytes(canonical) + digestRef, _ := reference.WithDigest(imageNameRef, dgst) + manifestDigestURL, err := env.builder.BuildManifestURL(digestRef) checkErr(t, err, "building manifest url") - digestRef, _ := reference.WithDigest(imageNameRef, dgst) - location, err := env.builder.BuildManifestURL(digestRef) - checkErr(t, err, "building location URL") - - resp := putManifest(t, "putting signed manifest", manifestDigestURL, "", signedManifest) - checkResponse(t, "putting signed manifest", resp, http.StatusCreated) + resp := putManifest(t, "putting manifest no error", manifestURL, schema2.MediaTypeManifest, manifest) + checkResponse(t, "putting manifest no error", resp, http.StatusCreated) checkHeaders(t, resp, http.Header{ - "Location": []string{location}, + "Location": []string{manifestDigestURL}, "Docker-Content-Digest": []string{dgst.String()}, }) + return dgst } @@ -3042,27 +2624,36 @@ func TestRegistryAsCacheMutationAPIs(t *testing.T) { } // Manifest upload - m := &schema1.Manifest{ //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility. + manifest := &schema2.Manifest{ Versioned: manifest.Versioned{ - SchemaVersion: 1, + SchemaVersion: 2, + MediaType: schema2.MediaTypeManifest, + }, + Config: distribution.Descriptor{ + Digest: "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b", + Size: 3253, + MediaType: schema2.MediaTypeImageConfig, + }, + Layers: []distribution.Descriptor{ + { + Digest: "sha256:463434349086340864309863409683460843608348608934092322395278926a", + Size: 6323, + MediaType: schema2.MediaTypeLayer, + }, + { + Digest: "sha256:630923423623623423352523525237238023652897356239852383652aaaaaaa", + Size: 6863, + MediaType: schema2.MediaTypeLayer, + }, }, - Name: imageName.Name(), - Tag: tag, - FSLayers: []schema1.FSLayer{}, //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility. - History: []schema1.History{}, //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility. } - sm, err := schema1.Sign(m, env.pk) //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility. - if err != nil { - t.Fatalf("error signing manifest: %v", err) - } - - resp := putManifest(t, "putting unsigned manifest", manifestURL, "", sm) - checkResponse(t, "putting signed manifest to cache", resp, errcode.ErrorCodeUnsupported.Descriptor().HTTPStatusCode) + resp := putManifest(t, "putting missing config manifest", manifestURL, schema2.MediaTypeManifest, manifest) + checkResponse(t, "putting missing config manifest", resp, errcode.ErrorCodeUnsupported.Descriptor().HTTPStatusCode) // Manifest Delete resp, _ = httpDelete(manifestURL) - checkResponse(t, "deleting signed manifest from cache", resp, errcode.ErrorCodeUnsupported.Descriptor().HTTPStatusCode) + checkResponse(t, "deleting config manifest from cache", resp, errcode.ErrorCodeUnsupported.Descriptor().HTTPStatusCode) // Blob upload initialization layerUploadURL, err := env.builder.BuildBlobUploadURL(imageName) @@ -3094,7 +2685,6 @@ func TestProxyManifestGetByTag(t *testing.T) { }}, }, } - truthConfig.Compatibility.Schema1.Enabled = true //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility. truthConfig.HTTP.Headers = headerConfig imageName, _ := reference.WithName("foo/bar") @@ -3113,7 +2703,6 @@ func TestProxyManifestGetByTag(t *testing.T) { RemoteURL: truthEnv.server.URL, }, } - proxyConfig.Compatibility.Schema1.Enabled = true //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility. proxyConfig.HTTP.Headers = headerConfig proxyEnv := newTestEnvWithConfig(t, &proxyConfig) diff --git a/registry/handlers/app.go b/registry/handlers/app.go index 080b39b4..46c5f4ba 100644 --- a/registry/handlers/app.go +++ b/registry/handlers/app.go @@ -40,7 +40,6 @@ import ( "github.com/distribution/distribution/v3/version" events "github.com/docker/go-events" "github.com/docker/go-metrics" - "github.com/docker/libtrust" "github.com/gomodule/redigo/redis" "github.com/gorilla/mux" "github.com/sirupsen/logrus" @@ -79,11 +78,6 @@ type App struct { redis *redis.Pool - // trustKey is a deprecated key used to sign manifests converted to - // schema1 for backward compatibility. It should not be used for any - // other purposes. - trustKey libtrust.PrivateKey - // isCache is true if this registry is configured as a pull through cache isCache bool @@ -164,25 +158,6 @@ func NewApp(ctx context.Context, config *configuration.Configuration) *App { app.configureLogHook(config) options := registrymiddleware.GetRegistryOptions() - if config.Compatibility.Schema1.TrustKey != "" { - app.trustKey, err = libtrust.LoadKeyFile(config.Compatibility.Schema1.TrustKey) - if err != nil { - panic(fmt.Sprintf(`could not load schema1 "signingkey" parameter: %v`, err)) - } - } else { - // Generate an ephemeral key to be used for signing converted manifests - // for clients that don't support schema2. - app.trustKey, err = libtrust.GenerateECP256PrivateKey() - if err != nil { - panic(err) - } - } - - options = append(options, storage.Schema1SigningKey(app.trustKey)) - - if config.Compatibility.Schema1.Enabled { - options = append(options, storage.EnableSchema1) - } if config.HTTP.Host != "" { u, err := url.Parse(config.HTTP.Host) diff --git a/registry/handlers/manifests.go b/registry/handlers/manifests.go index 4434286a..ac297b63 100644 --- a/registry/handlers/manifests.go +++ b/registry/handlers/manifests.go @@ -11,7 +11,6 @@ import ( dcontext "github.com/distribution/distribution/v3/context" "github.com/distribution/distribution/v3/manifest/manifestlist" "github.com/distribution/distribution/v3/manifest/ocischema" - "github.com/distribution/distribution/v3/manifest/schema1" //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility. "github.com/distribution/distribution/v3/manifest/schema2" "github.com/distribution/distribution/v3/reference" "github.com/distribution/distribution/v3/registry/api/errcode" @@ -23,11 +22,7 @@ import ( v1 "github.com/opencontainers/image-spec/specs-go/v1" ) -// These constants determine which architecture and OS to choose from a -// manifest list when downconverting it to a schema1 manifest. const ( - defaultArch = "amd64" - defaultOS = "linux" maxManifestBodySize = 4 << 20 imageClass = "image" ) @@ -35,12 +30,11 @@ const ( type storageType int const ( - manifestSchema1 storageType = iota // 0 - manifestSchema2 // 1 - manifestlistSchema // 2 - ociSchema // 3 - ociImageIndexSchema // 4 - numStorageTypes // 5 + manifestSchema2 storageType = iota // 0 + manifestlistSchema // 1 + ociSchema // 2 + ociImageIndexSchema // 3 + numStorageTypes // 4 ) // manifestDispatcher takes the request context and builds the @@ -151,12 +145,9 @@ func (imh *manifestHandler) GetManifest(w http.ResponseWriter, r *http.Request) return } // determine the type of the returned manifest - manifestType := manifestSchema1 - schema2Manifest, isSchema2 := manifest.(*schema2.DeserializedManifest) + manifestType := manifestSchema2 manifestList, isManifestList := manifest.(*manifestlist.DeserializedManifestList) - if isSchema2 { - manifestType = manifestSchema2 - } else if _, isOCImanifest := manifest.(*ocischema.DeserializedManifest); isOCImanifest { + if _, isOCImanifest := manifest.(*ocischema.DeserializedManifest); isOCImanifest { manifestType = ociSchema } else if isManifestList { if manifestList.MediaType == manifestlist.MediaTypeManifestList { @@ -174,56 +165,6 @@ func (imh *manifestHandler) GetManifest(w http.ResponseWriter, r *http.Request) imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown.WithMessage("OCI index found, but accept header does not support OCI indexes")) return } - // Only rewrite schema2 manifests when they are being fetched by tag. - // If they are being fetched by digest, we can't return something not - // matching the digest. - if imh.Tag != "" && manifestType == manifestSchema2 && !supports[manifestSchema2] { - // Rewrite manifest in schema1 format - dcontext.GetLogger(imh).Infof("rewriting manifest %s in schema1 format to support old client", imh.Digest.String()) - - manifest, err = imh.convertSchema2Manifest(schema2Manifest) - if err != nil { - return - } - } else if imh.Tag != "" && manifestType == manifestlistSchema && !supports[manifestlistSchema] { - // Rewrite manifest in schema1 format - dcontext.GetLogger(imh).Infof("rewriting manifest list %s in schema1 format to support old client", imh.Digest.String()) - - // Find the image manifest corresponding to the default - // platform - var manifestDigest digest.Digest - for _, manifestDescriptor := range manifestList.Manifests { - if manifestDescriptor.Platform.Architecture == defaultArch && manifestDescriptor.Platform.OS == defaultOS { - manifestDigest = manifestDescriptor.Digest - break - } - } - - if manifestDigest == "" { - imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown) - return - } - - manifest, err = manifests.Get(imh, manifestDigest) - if err != nil { - if _, ok := err.(distribution.ErrManifestUnknownRevision); ok { - imh.Errors = append(imh.Errors, v2.ErrorCodeManifestUnknown.WithDetail(err)) - } else { - imh.Errors = append(imh.Errors, errcode.ErrorCodeUnknown.WithDetail(err)) - } - return - } - - // If necessary, convert the image manifest - if schema2Manifest, isSchema2 := manifest.(*schema2.DeserializedManifest); isSchema2 && !supports[manifestSchema2] { - manifest, err = imh.convertSchema2Manifest(schema2Manifest) - if err != nil { - return - } - } else { - imh.Digest = manifestDigest - } - } ct, p, err := manifest.Payload() if err != nil { @@ -237,46 +178,6 @@ func (imh *manifestHandler) GetManifest(w http.ResponseWriter, r *http.Request) w.Write(p) } -func (imh *manifestHandler) convertSchema2Manifest(schema2Manifest *schema2.DeserializedManifest) (distribution.Manifest, error) { - targetDescriptor := schema2Manifest.Target() - blobs := imh.Repository.Blobs(imh) - configJSON, err := blobs.Get(imh, targetDescriptor.Digest) - if err != nil { - if err == distribution.ErrBlobUnknown { - imh.Errors = append(imh.Errors, v2.ErrorCodeManifestInvalid.WithDetail(err)) - } else { - imh.Errors = append(imh.Errors, errcode.ErrorCodeUnknown.WithDetail(err)) - } - return nil, err - } - - ref := imh.Repository.Named() - - if imh.Tag != "" { - ref, err = reference.WithTag(ref, imh.Tag) - if err != nil { - imh.Errors = append(imh.Errors, v2.ErrorCodeTagInvalid.WithDetail(err)) - return nil, err - } - } - - builder := schema1.NewConfigManifestBuilder(imh.Repository.Blobs(imh), imh.Context.App.trustKey, ref, configJSON) //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility. - for _, d := range schema2Manifest.Layers { - if err := builder.AppendReference(d); err != nil { - imh.Errors = append(imh.Errors, v2.ErrorCodeManifestInvalid.WithDetail(err)) - return nil, err - } - } - manifest, err := builder.Build(imh) - if err != nil { - imh.Errors = append(imh.Errors, v2.ErrorCodeManifestInvalid.WithDetail(err)) - return nil, err - } - imh.Digest = digest.FromBytes(manifest.(*schema1.SignedManifest).Canonical) //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility. - - return manifest, nil -} - func etagMatch(r *http.Request, etag string) bool { for _, headerVal := range r.Header["If-None-Match"] { if headerVal == etag || headerVal == fmt.Sprintf(`"%s"`, etag) { // allow quoted or unquoted @@ -421,8 +322,6 @@ func (imh *manifestHandler) applyResourcePolicy(manifest distribution.Manifest) var class string switch m := manifest.(type) { - case *schema1.SignedManifest: //nolint:staticcheck // Ignore SA1019: "github.com/distribution/distribution/v3/manifest/schema1" is deprecated, as it's used for backward compatibility. - class = imageClass case *schema2.DeserializedManifest: switch m.Config.MediaType { case schema2.MediaTypeImageConfig: