diff --git a/pkg/compat/compat.go b/pkg/compat/compat.go index a2b61f0e..ae2db659 100644 --- a/pkg/compat/compat.go +++ b/pkg/compat/compat.go @@ -45,6 +45,20 @@ func IsCompatibleManifestListMediaType(mediatype string) bool { return false } +func CompatibleConfigMediaTypes() []string { + return []string{docker.MediaTypeImageConfig} +} + +func IsCompatibleConfigMediaType(mediatype string) bool { + for _, mt := range CompatibleConfigMediaTypes() { + if mt == mediatype { + return true + } + } + + return false +} + func Validate(body []byte, mediaType string) ([]v1.Descriptor, error) { switch mediaType { case docker.MediaTypeManifest: diff --git a/pkg/extensions/search/cve/cve.go b/pkg/extensions/search/cve/cve.go index f897e494..922a9879 100644 --- a/pkg/extensions/search/cve/cve.go +++ b/pkg/extensions/search/cve/cve.go @@ -331,7 +331,8 @@ func getConfigAndDigest(metaDB mTypes.MetaDB, manifestDigestStr string) (ispec.I } // we'll fail the execution if the config is not compatible with ispec.Image because we can't scan this type of images. - if manifestData.Manifests[0].Manifest.Config.MediaType != ispec.MediaTypeImageConfig { + configMediaType := manifestData.Manifests[0].Manifest.Config.MediaType + if configMediaType != ispec.MediaTypeImageConfig && !compat.IsCompatibleConfigMediaType(configMediaType) { return ispec.Image{}, "", zerr.ErrUnexpectedMediaType } diff --git a/pkg/extensions/search/search_test.go b/pkg/extensions/search/search_test.go index 0e3a3f8c..5b1c2c93 100644 --- a/pkg/extensions/search/search_test.go +++ b/pkg/extensions/search/search_test.go @@ -1749,6 +1749,257 @@ func TestExpandedRepoInfo(t *testing.T) { So(found, ShouldBeTrue) }) + + Convey("Test expanded repo info for docker media type", t, func() { + subpath := "/a" + rootDir := t.TempDir() + subRootDir := t.TempDir() + port := GetFreePort() + baseURL := GetBaseURL(port) + conf := config.New() + conf.HTTP.Port = port + conf.Storage.RootDirectory = rootDir + conf.Storage.GC = false + conf.Storage.SubPaths = make(map[string]config.StorageConfig) + conf.Storage.SubPaths[subpath] = config.StorageConfig{RootDirectory: subRootDir} + defaultVal := true + conf.Extensions = &extconf.ExtensionConfig{ + Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}}, + } + + conf.Extensions.Search.CVE = nil + + ctlr := api.NewController(conf) + ctlrManager := NewControllerManager(ctlr) + ctlrManager.StartAndWait(port) + + defer ctlrManager.StopServer() + + annotations := make(map[string]string) + annotations["org.opencontainers.image.vendor"] = "zot" + + configBlob, err := json.Marshal(GetDefaultConfig()) + So(err, ShouldBeNil) + + uploadedImage := CreateImageWith().RandomLayers(1, 100). + CustomConfigBlob(configBlob, "application/vnd.docker.container.image.v1+json"). + Annotations(annotations).Build() + + err = UploadImage(uploadedImage, baseURL, "zot-cve-test", "0.0.1") + So(err, ShouldBeNil) + + err = UploadImage(uploadedImage, baseURL, "a/zot-cve-test", "0.0.1") + So(err, ShouldBeNil) + + err = UploadImage(uploadedImage, baseURL, "zot-test", "0.0.1") + So(err, ShouldBeNil) + + err = UploadImage(uploadedImage, baseURL, "a/zot-test", "0.0.1") + So(err, ShouldBeNil) + + log := log.NewLogger("debug", "") + metrics := monitoring.NewMetricsServer(false, log) + testStorage := local.NewImageStore(rootDir, false, false, log, metrics, nil, nil, nil) + + resp, err := resty.R().Get(baseURL + "/v2/") + So(resp, ShouldNotBeNil) + So(err, ShouldBeNil) + So(resp.StatusCode(), ShouldEqual, 200) + + resp, err = resty.R().Get(baseURL + graphqlQueryPrefix) + So(resp, ShouldNotBeNil) + So(err, ShouldBeNil) + So(resp.StatusCode(), ShouldEqual, 422) + + query := `{ + ExpandedRepoInfo(repo:"zot-cve-test"){ + Summary { + Name LastUpdated Size + } + } + }` + + resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) + So(resp, ShouldNotBeNil) + So(err, ShouldBeNil) + So(resp.StatusCode(), ShouldEqual, 200) + + responseStruct := &zcommon.ExpandedRepoInfoResp{} + + err = json.Unmarshal(resp.Body(), responseStruct) + So(err, ShouldBeNil) + So(responseStruct.Summary, ShouldNotBeEmpty) + So(responseStruct.Summary.Name, ShouldEqual, "zot-cve-test") + + query = `{ + ExpandedRepoInfo(repo:"zot-cve-test"){ + Images { + Tag + Manifests { + Digest + Layers {Size Digest} + Platform {Os Arch} + } + IsSigned + } + } + }` + + resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) + So(resp, ShouldNotBeNil) + So(err, ShouldBeNil) + So(resp.StatusCode(), ShouldEqual, 200) + + responseStruct = &zcommon.ExpandedRepoInfoResp{} + + err = json.Unmarshal(resp.Body(), responseStruct) + So(err, ShouldBeNil) + So(len(responseStruct.ImageSummaries), ShouldNotEqual, 0) + So(len(responseStruct.ImageSummaries[0].Manifests[0].Layers), ShouldNotEqual, 0) + + _, testManifestDigest, _, err := testStorage.GetImageManifest("zot-cve-test", "0.0.1") + So(err, ShouldBeNil) + + found := false + + for _, imageSummary := range responseStruct.ImageSummaries { + if imageSummary.Manifests[0].Digest == testManifestDigest.String() { + found = true + + So(imageSummary.IsSigned, ShouldEqual, false) + So(imageSummary.Manifests[0].Platform.Os, ShouldEqual, "linux") + So(imageSummary.Manifests[0].Platform.Arch, ShouldEqual, "amd64") + } + } + + So(found, ShouldEqual, true) + + err = signature.SignImageUsingCosign("zot-cve-test:0.0.1", port, false) + So(err, ShouldBeNil) + + resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) + So(resp, ShouldNotBeNil) + So(err, ShouldBeNil) + So(resp.StatusCode(), ShouldEqual, 200) + + err = json.Unmarshal(resp.Body(), responseStruct) + So(err, ShouldBeNil) + So(len(responseStruct.ImageSummaries), ShouldNotEqual, 0) + So(len(responseStruct.ImageSummaries[0].Manifests[0].Layers), ShouldNotEqual, 0) + + _, testManifestDigest, _, err = testStorage.GetImageManifest("zot-cve-test", "0.0.1") + So(err, ShouldBeNil) + + found = false + + for _, imageSummary := range responseStruct.ImageSummaries { + if imageSummary.Manifests[0].Digest == testManifestDigest.String() { + found = true + + So(imageSummary.IsSigned, ShouldEqual, true) + So(imageSummary.Manifests[0].Platform.Os, ShouldEqual, "linux") + So(imageSummary.Manifests[0].Platform.Arch, ShouldEqual, "amd64") + } + } + + So(found, ShouldEqual, true) + + query = `{ + ExpandedRepoInfo(repo:""){ + Images { + Tag + } + } + }` + + resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) + So(resp, ShouldNotBeNil) + So(err, ShouldBeNil) + So(resp.StatusCode(), ShouldEqual, 200) + + query = `{ + ExpandedRepoInfo(repo:"zot-test"){ + Images { + RepoName + Tag IsSigned + Manifests{ + Digest + Layers {Size Digest} + Platform {Os Arch} + } + } + } + }` + resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) + So(resp, ShouldNotBeNil) + So(err, ShouldBeNil) + So(resp.StatusCode(), ShouldEqual, 200) + + err = json.Unmarshal(resp.Body(), responseStruct) + So(err, ShouldBeNil) + So(len(responseStruct.ImageSummaries), ShouldNotEqual, 0) + So(len(responseStruct.ImageSummaries[0].Manifests[0].Layers), ShouldNotEqual, 0) + + _, testManifestDigest, _, err = testStorage.GetImageManifest("zot-test", "0.0.1") + So(err, ShouldBeNil) + + found = false + + for _, imageSummary := range responseStruct.ImageSummaries { + if imageSummary.Manifests[0].Digest == testManifestDigest.String() { + found = true + + So(imageSummary.IsSigned, ShouldEqual, false) + So(imageSummary.Manifests[0].Platform.Os, ShouldEqual, "linux") + So(imageSummary.Manifests[0].Platform.Arch, ShouldEqual, "amd64") + } + } + + So(found, ShouldEqual, true) + + err = signature.SignImageUsingCosign("zot-test@"+testManifestDigest.String(), port, false) + So(err, ShouldBeNil) + + resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "/query?query=" + url.QueryEscape(query)) + So(resp, ShouldNotBeNil) + So(err, ShouldBeNil) + So(resp.StatusCode(), ShouldEqual, 200) + + err = json.Unmarshal(resp.Body(), responseStruct) + So(err, ShouldBeNil) + So(len(responseStruct.ImageSummaries), ShouldNotEqual, 0) + So(len(responseStruct.ImageSummaries[0].Manifests[0].Layers), ShouldNotEqual, 0) + + _, testManifestDigest, _, err = testStorage.GetImageManifest("zot-test", "0.0.1") + So(err, ShouldBeNil) + + found = false + + for _, imageSummary := range responseStruct.ImageSummaries { + if imageSummary.Manifests[0].Digest == testManifestDigest.String() { + found = true + + So(imageSummary.IsSigned, ShouldEqual, true) + So(imageSummary.Manifests[0].Platform.Os, ShouldEqual, "linux") + So(imageSummary.Manifests[0].Platform.Arch, ShouldEqual, "amd64") + } + } + + So(found, ShouldEqual, true) + + manifestDigest := uploadedImage.ManifestDescriptor.Digest + + err = os.Remove(path.Join(rootDir, "zot-test/blobs/sha256", manifestDigest.Encoded())) + So(err, ShouldBeNil) + + resp, err = resty.R().Get(baseURL + graphqlQueryPrefix + "?query=" + url.QueryEscape(query)) + So(resp, ShouldNotBeNil) + So(err, ShouldBeNil) + So(resp.StatusCode(), ShouldEqual, 200) + + err = json.Unmarshal(resp.Body(), responseStruct) + So(err, ShouldBeNil) + }) } func TestDerivedImageList(t *testing.T) { diff --git a/pkg/meta/hooks.go b/pkg/meta/hooks.go index ccd373d9..98660c55 100644 --- a/pkg/meta/hooks.go +++ b/pkg/meta/hooks.go @@ -7,6 +7,7 @@ import ( v1 "github.com/opencontainers/image-spec/specs-go/v1" zcommon "zotregistry.dev/zot/pkg/common" + "zotregistry.dev/zot/pkg/compat" "zotregistry.dev/zot/pkg/log" mTypes "zotregistry.dev/zot/pkg/meta/types" "zotregistry.dev/zot/pkg/storage" @@ -117,7 +118,8 @@ func OnGetManifest(name, reference, mediaType string, body []byte, return nil } - if !(mediaType == v1.MediaTypeImageManifest || mediaType == v1.MediaTypeImageIndex) { + if !(mediaType == v1.MediaTypeImageManifest || mediaType == v1.MediaTypeImageIndex || + compat.IsCompatibleManifestMediaType(mediaType) || compat.IsCompatibleManifestListMediaType(mediaType)) { return nil } diff --git a/pkg/meta/parse.go b/pkg/meta/parse.go index 056b0e1e..8212015b 100644 --- a/pkg/meta/parse.go +++ b/pkg/meta/parse.go @@ -321,7 +321,8 @@ func SetImageMetaFromInput(ctx context.Context, repo, reference, mediaType strin return err } - if manifestContent.Config.MediaType == ispec.MediaTypeImageConfig { + if manifestContent.Config.MediaType == ispec.MediaTypeImageConfig || + compat.IsCompatibleConfigMediaType(manifestContent.Config.MediaType) { configBlob, err := imageStore.GetBlobContent(repo, manifestContent.Config.Digest) if err != nil { return err