mirror of
https://github.com/project-zot/zot.git
synced 2024-12-30 22:34:13 -05:00
feat(cve): implemented trivy image scan for multiarch images (#1510)
Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
This commit is contained in:
parent
96d9d318df
commit
0a04b2a4ed
32 changed files with 1617 additions and 370 deletions
1
Makefile
1
Makefile
|
@ -129,6 +129,7 @@ $(TESTDATA): check-skopeo
|
||||||
skopeo --insecure-policy copy -q docker://public.ecr.aws/t0x7q1g8/centos:7 oci:${TESTDATA}/zot-test:0.0.1; \
|
skopeo --insecure-policy copy -q docker://public.ecr.aws/t0x7q1g8/centos:7 oci:${TESTDATA}/zot-test:0.0.1; \
|
||||||
skopeo --insecure-policy copy -q docker://public.ecr.aws/t0x7q1g8/centos:8 oci:${TESTDATA}/zot-cve-test:0.0.1; \
|
skopeo --insecure-policy copy -q docker://public.ecr.aws/t0x7q1g8/centos:8 oci:${TESTDATA}/zot-cve-test:0.0.1; \
|
||||||
skopeo --insecure-policy copy -q docker://ghcr.io/project-zot/test-images/java:0.0.1 oci:${TESTDATA}/zot-cve-java-test:0.0.1; \
|
skopeo --insecure-policy copy -q docker://ghcr.io/project-zot/test-images/java:0.0.1 oci:${TESTDATA}/zot-cve-java-test:0.0.1; \
|
||||||
|
skopeo --insecure-policy copy -q docker://ghcr.io/project-zot/test-images/alpine:3.17.3 oci:${TESTDATA}/alpine:3.17.3; \
|
||||||
chmod -R a=rwx ${TESTDATA}
|
chmod -R a=rwx ${TESTDATA}
|
||||||
|
|
||||||
.PHONY: run-bench
|
.PHONY: run-bench
|
||||||
|
|
|
@ -73,7 +73,7 @@ var (
|
||||||
ErrEmptyRepoName = errors.New("repodb: repo name can't be empty string")
|
ErrEmptyRepoName = errors.New("repodb: repo name can't be empty string")
|
||||||
ErrEmptyTag = errors.New("repodb: tag can't be empty string")
|
ErrEmptyTag = errors.New("repodb: tag can't be empty string")
|
||||||
ErrEmptyDigest = errors.New("repodb: digest can't be empty string")
|
ErrEmptyDigest = errors.New("repodb: digest can't be empty string")
|
||||||
ErrInvalidRepoTagFormat = errors.New("invalid format for tag search, not following repo:tag")
|
ErrInvalidRepoRefFormat = errors.New("invalid image reference format")
|
||||||
ErrLimitIsNegative = errors.New("pageturner: limit has negative value")
|
ErrLimitIsNegative = errors.New("pageturner: limit has negative value")
|
||||||
ErrOffsetIsNegative = errors.New("pageturner: offset has negative value")
|
ErrOffsetIsNegative = errors.New("pageturner: offset has negative value")
|
||||||
ErrSortCriteriaNotSupported = errors.New("pageturner: the sort criteria is not supported")
|
ErrSortCriteriaNotSupported = errors.New("pageturner: the sort criteria is not supported")
|
||||||
|
@ -96,4 +96,5 @@ var (
|
||||||
ErrSyncPingRegistry = errors.New("sync: unable to ping any registry URLs")
|
ErrSyncPingRegistry = errors.New("sync: unable to ping any registry URLs")
|
||||||
ErrSyncImageNotSigned = errors.New("sync: image is not signed")
|
ErrSyncImageNotSigned = errors.New("sync: image is not signed")
|
||||||
ErrSyncImageFilteredOut = errors.New("sync: image is filtered out by sync config")
|
ErrSyncImageFilteredOut = errors.New("sync: image is filtered out by sync config")
|
||||||
|
ErrCallerInfo = errors.New("runtime: failed to get info regarding the current runtime")
|
||||||
)
|
)
|
||||||
|
|
|
@ -29,6 +29,7 @@ import (
|
||||||
zotErrors "zotregistry.io/zot/errors"
|
zotErrors "zotregistry.io/zot/errors"
|
||||||
"zotregistry.io/zot/pkg/api"
|
"zotregistry.io/zot/pkg/api"
|
||||||
"zotregistry.io/zot/pkg/api/config"
|
"zotregistry.io/zot/pkg/api/config"
|
||||||
|
zcommon "zotregistry.io/zot/pkg/common"
|
||||||
extconf "zotregistry.io/zot/pkg/extensions/config"
|
extconf "zotregistry.io/zot/pkg/extensions/config"
|
||||||
"zotregistry.io/zot/pkg/extensions/monitoring"
|
"zotregistry.io/zot/pkg/extensions/monitoring"
|
||||||
cveinfo "zotregistry.io/zot/pkg/extensions/search/cve"
|
cveinfo "zotregistry.io/zot/pkg/extensions/search/cve"
|
||||||
|
@ -1035,7 +1036,7 @@ func TestServerCVEResponse(t *testing.T) {
|
||||||
space := regexp.MustCompile(`\s+`)
|
space := regexp.MustCompile(`\s+`)
|
||||||
str := space.ReplaceAllString(buff.String(), " ")
|
str := space.ReplaceAllString(buff.String(), " ")
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(strings.TrimSpace(str), ShouldEqual,
|
So(strings.TrimSpace(str), ShouldResemble,
|
||||||
"IMAGE NAME TAG OS/ARCH DIGEST SIGNED SIZE zot-cve-test 0.0.1 linux/amd64 40d1f749 false 605B")
|
"IMAGE NAME TAG OS/ARCH DIGEST SIGNED SIZE zot-cve-test 0.0.1 linux/amd64 40d1f749 false 605B")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1172,7 +1173,8 @@ func getMockCveInfo(repoDB repodb.RepoDB, log log.Logger) cveinfo.CveInfo {
|
||||||
// Setup test CVE data in mock scanner
|
// Setup test CVE data in mock scanner
|
||||||
scanner := mocks.CveScannerMock{
|
scanner := mocks.CveScannerMock{
|
||||||
ScanImageFn: func(image string) (map[string]cvemodel.CVE, error) {
|
ScanImageFn: func(image string) (map[string]cvemodel.CVE, error) {
|
||||||
if image == "zot-cve-test:0.0.1" {
|
if image == "zot-cve-test@sha256:40d1f74918aefed733c590f798d7eafde8fc0a7ec63bb8bc52eaae133cf92495" ||
|
||||||
|
image == "zot-cve-test:0.0.1" {
|
||||||
return map[string]cvemodel.CVE{
|
return map[string]cvemodel.CVE{
|
||||||
"CVE-1": {
|
"CVE-1": {
|
||||||
ID: "CVE-1",
|
ID: "CVE-1",
|
||||||
|
@ -1223,12 +1225,20 @@ func getMockCveInfo(repoDB repodb.RepoDB, log log.Logger) cveinfo.CveInfo {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
manifestDigestStr, ok := repoMeta.Tags[inputTag]
|
manifestDigestStr := reference
|
||||||
|
|
||||||
|
if zcommon.IsTag(reference) {
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
descriptor, ok := repoMeta.Tags[inputTag]
|
||||||
if !ok {
|
if !ok {
|
||||||
return false, zotErrors.ErrTagMetaNotFound
|
return false, zotErrors.ErrTagMetaNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
manifestDigest, err := godigest.Parse(manifestDigestStr.Digest)
|
manifestDigestStr = descriptor.Digest
|
||||||
|
}
|
||||||
|
|
||||||
|
manifestDigest, err := godigest.Parse(manifestDigestStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1891,8 +1891,8 @@ func (service mockService) getTagsForCVEGQL(ctx context.Context, config searchCo
|
||||||
|
|
||||||
func (service mockService) getFixedTagsForCVEGQL(ctx context.Context, config searchConfig, username, password,
|
func (service mockService) getFixedTagsForCVEGQL(ctx context.Context, config searchConfig, username, password,
|
||||||
imageName, cveID string,
|
imageName, cveID string,
|
||||||
) (*common.FixedTags, error) {
|
) (*common.ImageListWithCVEFixedResponse, error) {
|
||||||
fixedTags := &common.FixedTags{
|
fixedTags := &common.ImageListWithCVEFixedResponse{
|
||||||
Errors: nil,
|
Errors: nil,
|
||||||
ImageListWithCVEFixed: struct {
|
ImageListWithCVEFixed: struct {
|
||||||
common.PaginatedImagesResult `json:"ImageListWithCVEFixed"` //nolint:tagliatelle // graphQL schema
|
common.PaginatedImagesResult `json:"ImageListWithCVEFixed"` //nolint:tagliatelle // graphQL schema
|
||||||
|
|
|
@ -43,7 +43,7 @@ type SearchService interface { //nolint:interfacebloat
|
||||||
getTagsForCVEGQL(ctx context.Context, config searchConfig, username, password, imageName,
|
getTagsForCVEGQL(ctx context.Context, config searchConfig, username, password, imageName,
|
||||||
cveID string) (*common.ImagesForCve, error)
|
cveID string) (*common.ImagesForCve, error)
|
||||||
getFixedTagsForCVEGQL(ctx context.Context, config searchConfig, username, password, imageName,
|
getFixedTagsForCVEGQL(ctx context.Context, config searchConfig, username, password, imageName,
|
||||||
cveID string) (*common.FixedTags, error)
|
cveID string) (*common.ImageListWithCVEFixedResponse, error)
|
||||||
getDerivedImageListGQL(ctx context.Context, config searchConfig, username, password string,
|
getDerivedImageListGQL(ctx context.Context, config searchConfig, username, password string,
|
||||||
derivedImage string) (*common.DerivedImageListResponse, error)
|
derivedImage string) (*common.DerivedImageListResponse, error)
|
||||||
getBaseImageListGQL(ctx context.Context, config searchConfig, username, password string,
|
getBaseImageListGQL(ctx context.Context, config searchConfig, username, password string,
|
||||||
|
@ -377,7 +377,7 @@ func (service searchService) getTagsForCVEGQL(ctx context.Context, config search
|
||||||
|
|
||||||
func (service searchService) getFixedTagsForCVEGQL(ctx context.Context, config searchConfig,
|
func (service searchService) getFixedTagsForCVEGQL(ctx context.Context, config searchConfig,
|
||||||
username, password, imageName, cveID string,
|
username, password, imageName, cveID string,
|
||||||
) (*common.FixedTags, error) {
|
) (*common.ImageListWithCVEFixedResponse, error) {
|
||||||
query := fmt.Sprintf(`
|
query := fmt.Sprintf(`
|
||||||
{
|
{
|
||||||
ImageListWithCVEFixed(id: "%s", image: "%s") {
|
ImageListWithCVEFixed(id: "%s", image: "%s") {
|
||||||
|
@ -398,7 +398,7 @@ func (service searchService) getFixedTagsForCVEGQL(ctx context.Context, config s
|
||||||
}`,
|
}`,
|
||||||
cveID, imageName)
|
cveID, imageName)
|
||||||
|
|
||||||
result := &common.FixedTags{}
|
result := &common.ImageListWithCVEFixedResponse{}
|
||||||
|
|
||||||
err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
|
err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
|
||||||
|
|
||||||
|
@ -847,7 +847,7 @@ func (service searchService) getFixedTagsForCVE(ctx context.Context, config sear
|
||||||
}
|
}
|
||||||
}`, cvid, imageName)
|
}`, cvid, imageName)
|
||||||
|
|
||||||
result := &common.FixedTags{}
|
result := &common.ImageListWithCVEFixedResponse{}
|
||||||
|
|
||||||
err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
|
err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -112,7 +112,7 @@ type Annotation struct {
|
||||||
Value string `json:"value"`
|
Value string `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FixedTags struct {
|
type ImageListWithCVEFixedResponse struct {
|
||||||
Errors []ErrorGQL `json:"errors"`
|
Errors []ErrorGQL `json:"errors"`
|
||||||
ImageListWithCVEFixed `json:"data"`
|
ImageListWithCVEFixed `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/opencontainers/go-digest"
|
||||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
|
||||||
zerr "zotregistry.io/zot/errors"
|
zerr "zotregistry.io/zot/errors"
|
||||||
|
@ -101,7 +102,7 @@ func GetRepoRefference(repo string) (string, string, bool, error) {
|
||||||
repoName, tag, found := strings.Cut(repo, ":")
|
repoName, tag, found := strings.Cut(repo, ":")
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
return "", "", false, zerr.ErrInvalidRepoTagFormat
|
return "", "", false, zerr.ErrInvalidRepoRefFormat
|
||||||
}
|
}
|
||||||
|
|
||||||
return repoName, tag, true, nil
|
return repoName, tag, true, nil
|
||||||
|
@ -109,3 +110,22 @@ func GetRepoRefference(repo string) (string, string, bool, error) {
|
||||||
|
|
||||||
return repoName, digest, false, nil
|
return repoName, digest, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetFullImageName returns the formated string for the given repo/tag or repo/digest.
|
||||||
|
func GetFullImageName(repo, ref string) string {
|
||||||
|
if IsTag(ref) {
|
||||||
|
return repo + ":" + ref
|
||||||
|
}
|
||||||
|
|
||||||
|
return repo + "@" + ref
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsDigest(ref string) bool {
|
||||||
|
_, err := digest.Parse(ref)
|
||||||
|
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsTag(ref string) bool {
|
||||||
|
return !IsDigest(ref)
|
||||||
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
|
||||||
"zotregistry.io/zot/pkg/extensions/search/convert"
|
"zotregistry.io/zot/pkg/extensions/search/convert"
|
||||||
cveinfo "zotregistry.io/zot/pkg/extensions/search/cve"
|
cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model"
|
||||||
"zotregistry.io/zot/pkg/extensions/search/gql_generated"
|
"zotregistry.io/zot/pkg/extensions/search/gql_generated"
|
||||||
"zotregistry.io/zot/pkg/log"
|
"zotregistry.io/zot/pkg/log"
|
||||||
"zotregistry.io/zot/pkg/meta/bolt"
|
"zotregistry.io/zot/pkg/meta/bolt"
|
||||||
|
@ -74,9 +74,9 @@ func TestConvertErrors(t *testing.T) {
|
||||||
map[string]repodb.IndexData{},
|
map[string]repodb.IndexData{},
|
||||||
convert.SkipQGLField{},
|
convert.SkipQGLField{},
|
||||||
mocks.CveInfoMock{
|
mocks.CveInfoMock{
|
||||||
GetCVESummaryForImageFn: func(repo string, reference string,
|
GetCVESummaryForImageMediaFn: func(repo string, digest, mediaType string,
|
||||||
) (cveinfo.ImageCVESummary, error) {
|
) (cvemodel.ImageCVESummary, error) {
|
||||||
return cveinfo.ImageCVESummary{}, ErrTestError
|
return cvemodel.ImageCVESummary{}, ErrTestError
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -120,9 +120,8 @@ func TestConvertErrors(t *testing.T) {
|
||||||
},
|
},
|
||||||
map[string]repodb.ManifestMetadata{},
|
map[string]repodb.ManifestMetadata{},
|
||||||
mocks.CveInfoMock{
|
mocks.CveInfoMock{
|
||||||
GetCVESummaryForImageFn: func(repo, reference string,
|
GetCVESummaryForImageMediaFn: func(repo, digest, mediaType string) (cvemodel.ImageCVESummary, error) {
|
||||||
) (cveinfo.ImageCVESummary, error) {
|
return cvemodel.ImageCVESummary{}, ErrTestError
|
||||||
return cveinfo.ImageCVESummary{}, ErrTestError
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -153,9 +152,8 @@ func TestConvertErrors(t *testing.T) {
|
||||||
ConfigBlob: configBlob,
|
ConfigBlob: configBlob,
|
||||||
},
|
},
|
||||||
mocks.CveInfoMock{
|
mocks.CveInfoMock{
|
||||||
GetCVESummaryForImageFn: func(repo, reference string,
|
GetCVESummaryForImageMediaFn: func(repo, digest, mediaType string) (cvemodel.ImageCVESummary, error) {
|
||||||
) (cveinfo.ImageCVESummary, error) {
|
return cvemodel.ImageCVESummary{}, ErrTestError
|
||||||
return cveinfo.ImageCVESummary{}, ErrTestError
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -187,12 +185,7 @@ func TestConvertErrors(t *testing.T) {
|
||||||
ConfigBlob: []byte("bad json"),
|
ConfigBlob: []byte("bad json"),
|
||||||
},
|
},
|
||||||
nil,
|
nil,
|
||||||
mocks.CveInfoMock{
|
mocks.CveInfoMock{},
|
||||||
GetCVESummaryForImageFn: func(repo, reference string,
|
|
||||||
) (cveinfo.ImageCVESummary, error) {
|
|
||||||
return cveinfo.ImageCVESummary{}, ErrTestError
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
So(err, ShouldNotBeNil)
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
@ -227,9 +220,8 @@ func TestConvertErrors(t *testing.T) {
|
||||||
},
|
},
|
||||||
nil,
|
nil,
|
||||||
mocks.CveInfoMock{
|
mocks.CveInfoMock{
|
||||||
GetCVESummaryForImageFn: func(repo, reference string,
|
GetCVESummaryForImageMediaFn: func(repo, digest, mediaType string) (cvemodel.ImageCVESummary, error) {
|
||||||
) (cveinfo.ImageCVESummary, error) {
|
return cvemodel.ImageCVESummary{}, ErrTestError
|
||||||
return cveinfo.ImageCVESummary{}, ErrTestError
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -259,9 +251,8 @@ func TestConvertErrors(t *testing.T) {
|
||||||
Vulnerabilities: false,
|
Vulnerabilities: false,
|
||||||
},
|
},
|
||||||
mocks.CveInfoMock{
|
mocks.CveInfoMock{
|
||||||
GetCVESummaryForImageFn: func(repo, reference string,
|
GetCVESummaryForImageMediaFn: func(repo, digest, mediaType string) (cvemodel.ImageCVESummary, error) {
|
||||||
) (cveinfo.ImageCVESummary, error) {
|
return cvemodel.ImageCVESummary{}, ErrTestError
|
||||||
return cveinfo.ImageCVESummary{}, ErrTestError
|
|
||||||
},
|
},
|
||||||
}, log.NewLogger("debug", ""),
|
}, log.NewLogger("debug", ""),
|
||||||
)
|
)
|
||||||
|
@ -286,9 +277,8 @@ func TestConvertErrors(t *testing.T) {
|
||||||
Vulnerabilities: false,
|
Vulnerabilities: false,
|
||||||
},
|
},
|
||||||
mocks.CveInfoMock{
|
mocks.CveInfoMock{
|
||||||
GetCVESummaryForImageFn: func(repo, reference string,
|
GetCVESummaryForImageMediaFn: func(repo, digest, mediaType string) (cvemodel.ImageCVESummary, error) {
|
||||||
) (cveinfo.ImageCVESummary, error) {
|
return cvemodel.ImageCVESummary{}, ErrTestError
|
||||||
return cveinfo.ImageCVESummary{}, ErrTestError
|
|
||||||
},
|
},
|
||||||
}, log.NewLogger("debug", ""),
|
}, log.NewLogger("debug", ""),
|
||||||
)
|
)
|
||||||
|
|
|
@ -103,7 +103,8 @@ func RepoMeta2RepoSummary(ctx context.Context, repoMeta repodb.RepoMetadata,
|
||||||
// We only scan the latest image on the repo for performance reasons
|
// We only scan the latest image on the repo for performance reasons
|
||||||
// Check if vulnerability scanning is disabled
|
// Check if vulnerability scanning is disabled
|
||||||
if cveInfo != nil && lastUpdatedImageSummary != nil && !skip.Vulnerabilities {
|
if cveInfo != nil && lastUpdatedImageSummary != nil && !skip.Vulnerabilities {
|
||||||
imageCveSummary, err := cveInfo.GetCVESummaryForImage(repoMeta.Name, *lastUpdatedImageSummary.Tag)
|
imageCveSummary, err := cveInfo.GetCVESummaryForImageMedia(repoMeta.Name, *lastUpdatedImageSummary.Digest,
|
||||||
|
*lastUpdatedImageSummary.MediaType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Log the error, but we should still include the image in results
|
// Log the error, but we should still include the image in results
|
||||||
graphql.AddError(
|
graphql.AddError(
|
||||||
|
@ -227,10 +228,10 @@ func ImageIndex2ImageSummary(ctx context.Context, repo, tag string, indexDigest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
imageCveSummary := cveinfo.ImageCVESummary{}
|
imageCveSummary := cvemodel.ImageCVESummary{}
|
||||||
|
|
||||||
if cveInfo != nil && !skipCVE {
|
if cveInfo != nil && !skipCVE {
|
||||||
imageCveSummary, err = cveInfo.GetCVESummaryForImage(repo, tag)
|
imageCveSummary, err = cveInfo.GetCVESummaryForImageMedia(repo, indexDigestStr, ispec.MediaTypeImageIndex)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Log the error, but we should still include the manifest in results
|
// Log the error, but we should still include the manifest in results
|
||||||
|
@ -345,10 +346,10 @@ func ImageManifest2ImageSummary(ctx context.Context, repo, tag string, digest go
|
||||||
"manifest digest: %s, error: %s", tag, repo, manifestDigest, err.Error()))
|
"manifest digest: %s, error: %s", tag, repo, manifestDigest, err.Error()))
|
||||||
}
|
}
|
||||||
|
|
||||||
imageCveSummary := cveinfo.ImageCVESummary{}
|
imageCveSummary := cvemodel.ImageCVESummary{}
|
||||||
|
|
||||||
if cveInfo != nil && !skipCVE {
|
if cveInfo != nil && !skipCVE {
|
||||||
imageCveSummary, err = cveInfo.GetCVESummaryForImage(repo, tag)
|
imageCveSummary, err = cveInfo.GetCVESummaryForImageMedia(repo, manifestDigest, ispec.MediaTypeImageManifest)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Log the error, but we should still include the manifest in results
|
// Log the error, but we should still include the manifest in results
|
||||||
|
@ -500,10 +501,10 @@ func ImageManifest2ManifestSummary(ctx context.Context, repo, tag string, descri
|
||||||
"manifest digest: %s, error: %s", tag, repo, manifestDigestStr, err.Error()))
|
"manifest digest: %s, error: %s", tag, repo, manifestDigestStr, err.Error()))
|
||||||
}
|
}
|
||||||
|
|
||||||
imageCveSummary := cveinfo.ImageCVESummary{}
|
imageCveSummary := cvemodel.ImageCVESummary{}
|
||||||
|
|
||||||
if cveInfo != nil && !skipCVE {
|
if cveInfo != nil && !skipCVE {
|
||||||
imageCveSummary, err = cveInfo.GetCVESummaryForImage(repo, tag)
|
imageCveSummary, err = cveInfo.GetCVESummaryForImageMedia(repo, manifestDigestStr, ispec.MediaTypeImageManifest)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Log the error, but we should still include the manifest in results
|
// Log the error, but we should still include the manifest in results
|
||||||
|
@ -662,7 +663,8 @@ func RepoMeta2ExpandedRepoInfo(ctx context.Context, repoMeta repodb.RepoMetadata
|
||||||
// We only scan the latest image on the repo for performance reasons
|
// We only scan the latest image on the repo for performance reasons
|
||||||
// Check if vulnerability scanning is disabled
|
// Check if vulnerability scanning is disabled
|
||||||
if cveInfo != nil && lastUpdatedImageSummary != nil && !skip.Vulnerabilities {
|
if cveInfo != nil && lastUpdatedImageSummary != nil && !skip.Vulnerabilities {
|
||||||
imageCveSummary, err := cveInfo.GetCVESummaryForImage(repoMeta.Name, *lastUpdatedImageSummary.Tag)
|
imageCveSummary, err := cveInfo.GetCVESummaryForImageMedia(repoMeta.Name, *lastUpdatedImageSummary.Digest,
|
||||||
|
*lastUpdatedImageSummary.MediaType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Log the error, but we should still include the image in results
|
// Log the error, but we should still include the image in results
|
||||||
graphql.AddError(
|
graphql.AddError(
|
||||||
|
|
|
@ -2,15 +2,14 @@ package cveinfo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
godigest "github.com/opencontainers/go-digest"
|
godigest "github.com/opencontainers/go-digest"
|
||||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
|
||||||
"zotregistry.io/zot/errors"
|
zcommon "zotregistry.io/zot/pkg/common"
|
||||||
"zotregistry.io/zot/pkg/common"
|
|
||||||
cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model"
|
cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model"
|
||||||
"zotregistry.io/zot/pkg/extensions/search/cve/trivy"
|
"zotregistry.io/zot/pkg/extensions/search/cve/trivy"
|
||||||
"zotregistry.io/zot/pkg/log"
|
"zotregistry.io/zot/pkg/log"
|
||||||
|
@ -21,24 +20,22 @@ import (
|
||||||
type CveInfo interface {
|
type CveInfo interface {
|
||||||
GetImageListForCVE(repo, cveID string) ([]cvemodel.TagInfo, error)
|
GetImageListForCVE(repo, cveID string) ([]cvemodel.TagInfo, error)
|
||||||
GetImageListWithCVEFixed(repo, cveID string) ([]cvemodel.TagInfo, error)
|
GetImageListWithCVEFixed(repo, cveID string) ([]cvemodel.TagInfo, error)
|
||||||
GetCVEListForImage(repo, tag string, searchedCVE string, pageinput PageInput) ([]cvemodel.CVE, common.PageInfo, error)
|
GetCVEListForImage(repo, tag string, searchedCVE string, pageinput cvemodel.PageInput,
|
||||||
GetCVESummaryForImage(repo, tag string) (ImageCVESummary, error)
|
) ([]cvemodel.CVE, zcommon.PageInfo, error)
|
||||||
|
GetCVESummaryForImage(repo, ref string) (cvemodel.ImageCVESummary, error)
|
||||||
|
GetCVESummaryForImageMedia(repo, digest, mediaType string) (cvemodel.ImageCVESummary, error)
|
||||||
CompareSeverities(severity1, severity2 string) int
|
CompareSeverities(severity1, severity2 string) int
|
||||||
UpdateDB() error
|
UpdateDB() error
|
||||||
}
|
}
|
||||||
|
|
||||||
type Scanner interface {
|
type Scanner interface {
|
||||||
ScanImage(image string) (map[string]cvemodel.CVE, error)
|
ScanImage(image string) (map[string]cvemodel.CVE, error)
|
||||||
IsImageFormatScannable(repo, tag string) (bool, error)
|
IsImageFormatScannable(repo, ref string) (bool, error)
|
||||||
|
IsImageMediaScannable(repo, digestStr, mediaType string) (bool, error)
|
||||||
CompareSeverities(severity1, severity2 string) int
|
CompareSeverities(severity1, severity2 string) int
|
||||||
UpdateDB() error
|
UpdateDB() error
|
||||||
}
|
}
|
||||||
|
|
||||||
type ImageCVESummary struct {
|
|
||||||
Count int
|
|
||||||
MaxSeverity string
|
|
||||||
}
|
|
||||||
|
|
||||||
type BaseCveInfo struct {
|
type BaseCveInfo struct {
|
||||||
Log log.Logger
|
Log log.Logger
|
||||||
Scanner Scanner
|
Scanner Scanner
|
||||||
|
@ -70,19 +67,19 @@ func (cveinfo BaseCveInfo) GetImageListForCVE(repo, cveID string) ([]cvemodel.Ta
|
||||||
|
|
||||||
for tag, descriptor := range repoMeta.Tags {
|
for tag, descriptor := range repoMeta.Tags {
|
||||||
switch descriptor.MediaType {
|
switch descriptor.MediaType {
|
||||||
case ispec.MediaTypeImageManifest:
|
case ispec.MediaTypeImageManifest, ispec.MediaTypeImageIndex:
|
||||||
manifestDigestStr := descriptor.Digest
|
manifestDigestStr := descriptor.Digest
|
||||||
|
|
||||||
manifestDigest := godigest.Digest(manifestDigestStr)
|
manifestDigest := godigest.Digest(manifestDigestStr)
|
||||||
|
|
||||||
isScanableImage, err := cveinfo.Scanner.IsImageFormatScannable(repo, tag)
|
isScanableImage, err := cveinfo.Scanner.IsImageFormatScannable(repo, manifestDigestStr)
|
||||||
if !isScanableImage || err != nil {
|
if !isScanableImage || err != nil {
|
||||||
cveinfo.Log.Info().Str("image", repo+":"+tag).Err(err).Msg("image is not scanable")
|
cveinfo.Log.Info().Str("image", repo+":"+tag).Err(err).Msg("image is not scanable")
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
cveMap, err := cveinfo.Scanner.ScanImage(getImageString(repo, tag))
|
cveMap, err := cveinfo.Scanner.ScanImage(zcommon.GetFullImageName(repo, tag))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cveinfo.Log.Info().Str("image", repo+":"+tag).Err(err).Msg("image scan failed")
|
cveinfo.Log.Info().Str("image", repo+":"+tag).Err(err).Msg("image scan failed")
|
||||||
|
|
||||||
|
@ -91,7 +88,7 @@ func (cveinfo BaseCveInfo) GetImageListForCVE(repo, cveID string) ([]cvemodel.Ta
|
||||||
|
|
||||||
if _, hasCVE := cveMap[cveID]; hasCVE {
|
if _, hasCVE := cveMap[cveID]; hasCVE {
|
||||||
imgList = append(imgList, cvemodel.TagInfo{
|
imgList = append(imgList, cvemodel.TagInfo{
|
||||||
Name: tag,
|
Tag: tag,
|
||||||
Descriptor: cvemodel.Descriptor{
|
Descriptor: cvemodel.Descriptor{
|
||||||
Digest: manifestDigest,
|
Digest: manifestDigest,
|
||||||
MediaType: descriptor.MediaType,
|
MediaType: descriptor.MediaType,
|
||||||
|
@ -118,87 +115,81 @@ func (cveinfo BaseCveInfo) GetImageListWithCVEFixed(repo, cveID string) ([]cvemo
|
||||||
vulnerableTags := make([]cvemodel.TagInfo, 0)
|
vulnerableTags := make([]cvemodel.TagInfo, 0)
|
||||||
allTags := make([]cvemodel.TagInfo, 0)
|
allTags := make([]cvemodel.TagInfo, 0)
|
||||||
|
|
||||||
var hasCVE bool
|
|
||||||
|
|
||||||
for tag, descriptor := range repoMeta.Tags {
|
for tag, descriptor := range repoMeta.Tags {
|
||||||
manifestDigestStr := descriptor.Digest
|
|
||||||
|
|
||||||
switch descriptor.MediaType {
|
switch descriptor.MediaType {
|
||||||
case ispec.MediaTypeImageManifest:
|
case ispec.MediaTypeImageManifest:
|
||||||
manifestDigest, err := godigest.Parse(manifestDigestStr)
|
manifestDigestStr := descriptor.Digest
|
||||||
|
|
||||||
|
tagInfo, err := getTagInfoForManifest(tag, manifestDigestStr, cveinfo.RepoDB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cveinfo.Log.Error().Err(err).Str("repository", repo).Str("tag", tag).
|
cveinfo.Log.Error().Err(err).Str("repository", repo).Str("tag", tag).
|
||||||
Str("cve-id", cveID).Str("digest", manifestDigestStr).Msg("unable to parse digest")
|
Str("cve-id", cveID).Msg("unable to retrieve manifest and config")
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
manifestMeta, err := cveinfo.RepoDB.GetManifestMeta(repo, manifestDigest)
|
|
||||||
if err != nil {
|
|
||||||
cveinfo.Log.Error().Err(err).Str("repository", repo).Str("tag", tag).
|
|
||||||
Str("cve-id", cveID).Msg("unable to obtain manifest meta")
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var configContent ispec.Image
|
|
||||||
|
|
||||||
err = json.Unmarshal(manifestMeta.ConfigBlob, &configContent)
|
|
||||||
if err != nil {
|
|
||||||
cveinfo.Log.Error().Err(err).Str("repository", repo).Str("tag", tag).
|
|
||||||
Str("cve-id", cveID).Msg("unable to unmashal manifest blob")
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
tagInfo := cvemodel.TagInfo{
|
|
||||||
Name: tag,
|
|
||||||
Timestamp: common.GetImageLastUpdated(configContent),
|
|
||||||
Descriptor: cvemodel.Descriptor{Digest: manifestDigest, MediaType: descriptor.MediaType},
|
|
||||||
}
|
|
||||||
|
|
||||||
allTags = append(allTags, tagInfo)
|
allTags = append(allTags, tagInfo)
|
||||||
|
|
||||||
image := fmt.Sprintf("%s:%s", repo, tag)
|
if cveinfo.isManifestVulnerable(repo, tag, manifestDigestStr, cveID) {
|
||||||
|
|
||||||
isValidImage, err := cveinfo.Scanner.IsImageFormatScannable(repo, tag)
|
|
||||||
if !isValidImage || err != nil {
|
|
||||||
cveinfo.Log.Debug().Str("image", image).Str("cve-id", cveID).
|
|
||||||
Msg("image media type not supported for scanning, adding as a vulnerable image")
|
|
||||||
|
|
||||||
vulnerableTags = append(vulnerableTags, tagInfo)
|
vulnerableTags = append(vulnerableTags, tagInfo)
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
case ispec.MediaTypeImageIndex:
|
||||||
|
indexDigestStr := descriptor.Digest
|
||||||
|
|
||||||
cveMap, err := cveinfo.Scanner.ScanImage(getImageString(repo, tag))
|
indexContent, err := getIndexContent(cveinfo.RepoDB, indexDigestStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cveinfo.Log.Debug().Str("image", image).Str("cve-id", cveID).
|
continue
|
||||||
Msg("scanning failed, adding as a vulnerable image")
|
}
|
||||||
|
|
||||||
vulnerableTags = append(vulnerableTags, tagInfo)
|
vulnerableManifests := []cvemodel.DescriptorInfo{}
|
||||||
|
allManifests := []cvemodel.DescriptorInfo{}
|
||||||
|
|
||||||
|
for _, manifest := range indexContent.Manifests {
|
||||||
|
tagInfo, err := getTagInfoForManifest(tag, manifest.Digest.String(), cveinfo.RepoDB)
|
||||||
|
if err != nil {
|
||||||
|
cveinfo.Log.Error().Err(err).Str("repository", repo).Str("tag", tag).
|
||||||
|
Str("cve-id", cveID).Msg("unable to retrieve manifest and config")
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
hasCVE = false
|
manifestDescriptorInfo := cvemodel.DescriptorInfo{
|
||||||
|
Descriptor: tagInfo.Descriptor,
|
||||||
|
Timestamp: tagInfo.Timestamp,
|
||||||
|
}
|
||||||
|
|
||||||
for id := range cveMap {
|
allManifests = append(allManifests, manifestDescriptorInfo)
|
||||||
if id == cveID {
|
|
||||||
hasCVE = true
|
|
||||||
|
|
||||||
break
|
if cveinfo.isManifestVulnerable(repo, tag, manifest.Digest.String(), cveID) {
|
||||||
|
vulnerableManifests = append(vulnerableManifests, manifestDescriptorInfo)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if hasCVE {
|
if len(allManifests) > 0 {
|
||||||
vulnerableTags = append(vulnerableTags, tagInfo)
|
allTags = append(allTags, cvemodel.TagInfo{
|
||||||
|
Tag: tag,
|
||||||
|
Descriptor: cvemodel.Descriptor{
|
||||||
|
Digest: godigest.Digest(indexDigestStr),
|
||||||
|
MediaType: ispec.MediaTypeImageIndex,
|
||||||
|
},
|
||||||
|
Manifests: allManifests,
|
||||||
|
Timestamp: mostRecentUpdate(allManifests),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(vulnerableManifests) > 0 {
|
||||||
|
vulnerableTags = append(vulnerableTags, cvemodel.TagInfo{
|
||||||
|
Tag: tag,
|
||||||
|
Descriptor: cvemodel.Descriptor{
|
||||||
|
Digest: godigest.Digest(indexDigestStr),
|
||||||
|
MediaType: ispec.MediaTypeImageIndex,
|
||||||
|
},
|
||||||
|
Manifests: vulnerableManifests,
|
||||||
|
Timestamp: mostRecentUpdate(vulnerableManifests),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
cveinfo.Log.Error().Str("mediaType", descriptor.MediaType).Msg("media type not supported")
|
cveinfo.Log.Error().Str("mediaType", descriptor.MediaType).Msg("media type not supported")
|
||||||
|
|
||||||
return []cvemodel.TagInfo{},
|
|
||||||
fmt.Errorf("media type '%s' is not supported: %w", descriptor.MediaType, errors.ErrNotImplemented)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,6 +210,117 @@ func (cveinfo BaseCveInfo) GetImageListWithCVEFixed(repo, cveID string) ([]cvemo
|
||||||
return fixedTags, nil
|
return fixedTags, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mostRecentUpdate(allManifests []cvemodel.DescriptorInfo) time.Time {
|
||||||
|
if len(allManifests) == 0 {
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
|
||||||
|
timeStamp := allManifests[0].Timestamp
|
||||||
|
|
||||||
|
for i := range allManifests {
|
||||||
|
if timeStamp.Before(allManifests[i].Timestamp) {
|
||||||
|
timeStamp = allManifests[i].Timestamp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return timeStamp
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTagInfoForManifest(tag, manifestDigestStr string, repoDB repodb.RepoDB) (cvemodel.TagInfo, error) {
|
||||||
|
configContent, manifestDigest, err := getConfigAndDigest(repoDB, manifestDigestStr)
|
||||||
|
if err != nil {
|
||||||
|
return cvemodel.TagInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
lastUpdated := zcommon.GetImageLastUpdated(configContent)
|
||||||
|
|
||||||
|
return cvemodel.TagInfo{
|
||||||
|
Tag: tag,
|
||||||
|
Descriptor: cvemodel.Descriptor{Digest: manifestDigest, MediaType: ispec.MediaTypeImageManifest},
|
||||||
|
Manifests: []cvemodel.DescriptorInfo{
|
||||||
|
{
|
||||||
|
Descriptor: cvemodel.Descriptor{Digest: manifestDigest, MediaType: ispec.MediaTypeImageManifest},
|
||||||
|
Timestamp: lastUpdated,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timestamp: lastUpdated,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cveinfo *BaseCveInfo) isManifestVulnerable(repo, tag, manifestDigestStr, cveID string) bool {
|
||||||
|
image := zcommon.GetFullImageName(repo, tag)
|
||||||
|
|
||||||
|
isValidImage, err := cveinfo.Scanner.IsImageMediaScannable(repo, manifestDigestStr, ispec.MediaTypeImageManifest)
|
||||||
|
if !isValidImage || err != nil {
|
||||||
|
cveinfo.Log.Debug().Str("image", image).Str("cve-id", cveID).
|
||||||
|
Msg("image media type not supported for scanning, adding as a vulnerable image")
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
cveMap, err := cveinfo.Scanner.ScanImage(zcommon.GetFullImageName(repo, manifestDigestStr))
|
||||||
|
if err != nil {
|
||||||
|
cveinfo.Log.Debug().Str("image", image).Str("cve-id", cveID).
|
||||||
|
Msg("scanning failed, adding as a vulnerable image")
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
hasCVE := false
|
||||||
|
|
||||||
|
for id := range cveMap {
|
||||||
|
if id == cveID {
|
||||||
|
hasCVE = true
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasCVE
|
||||||
|
}
|
||||||
|
|
||||||
|
func getIndexContent(repoDB repodb.RepoDB, indexDigestStr string) (ispec.Index, error) {
|
||||||
|
indexDigest, err := godigest.Parse(indexDigestStr)
|
||||||
|
if err != nil {
|
||||||
|
return ispec.Index{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
indexData, err := repoDB.GetIndexData(indexDigest)
|
||||||
|
if err != nil {
|
||||||
|
return ispec.Index{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var indexContent ispec.Index
|
||||||
|
|
||||||
|
err = json.Unmarshal(indexData.IndexBlob, &indexContent)
|
||||||
|
if err != nil {
|
||||||
|
return ispec.Index{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return indexContent, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getConfigAndDigest(repoDB repodb.RepoDB, manifestDigestStr string) (ispec.Image, godigest.Digest, error) {
|
||||||
|
manifestDigest, err := godigest.Parse(manifestDigestStr)
|
||||||
|
if err != nil {
|
||||||
|
return ispec.Image{}, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
manifestData, err := repoDB.GetManifestData(manifestDigest)
|
||||||
|
if err != nil {
|
||||||
|
return ispec.Image{}, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var configContent ispec.Image
|
||||||
|
|
||||||
|
err = json.Unmarshal(manifestData.ConfigBlob, &configContent)
|
||||||
|
if err != nil {
|
||||||
|
return ispec.Image{}, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return configContent, manifestDigest, nil
|
||||||
|
}
|
||||||
|
|
||||||
func filterCVEList(cveMap map[string]cvemodel.CVE, searchedCVE string, pageFinder *CvePageFinder) {
|
func filterCVEList(cveMap map[string]cvemodel.CVE, searchedCVE string, pageFinder *CvePageFinder) {
|
||||||
searchedCVE = strings.ToUpper(searchedCVE)
|
searchedCVE = strings.ToUpper(searchedCVE)
|
||||||
|
|
||||||
|
@ -230,26 +332,26 @@ func filterCVEList(cveMap map[string]cvemodel.CVE, searchedCVE string, pageFinde
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cveinfo BaseCveInfo) GetCVEListForImage(repo, tag string, searchedCVE string, pageInput PageInput) (
|
func (cveinfo BaseCveInfo) GetCVEListForImage(repo, ref string, searchedCVE string, pageInput cvemodel.PageInput) (
|
||||||
[]cvemodel.CVE,
|
[]cvemodel.CVE,
|
||||||
common.PageInfo,
|
zcommon.PageInfo,
|
||||||
error,
|
error,
|
||||||
) {
|
) {
|
||||||
isValidImage, err := cveinfo.Scanner.IsImageFormatScannable(repo, tag)
|
isValidImage, err := cveinfo.Scanner.IsImageFormatScannable(repo, ref)
|
||||||
if !isValidImage {
|
if !isValidImage {
|
||||||
return []cvemodel.CVE{}, common.PageInfo{}, err
|
return []cvemodel.CVE{}, zcommon.PageInfo{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
image := getImageString(repo, tag)
|
image := zcommon.GetFullImageName(repo, ref)
|
||||||
|
|
||||||
cveMap, err := cveinfo.Scanner.ScanImage(image)
|
cveMap, err := cveinfo.Scanner.ScanImage(image)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []cvemodel.CVE{}, common.PageInfo{}, err
|
return []cvemodel.CVE{}, zcommon.PageInfo{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
pageFinder, err := NewCvePageFinder(pageInput.Limit, pageInput.Offset, pageInput.SortBy, cveinfo)
|
pageFinder, err := NewCvePageFinder(pageInput.Limit, pageInput.Offset, pageInput.SortBy, cveinfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []cvemodel.CVE{}, common.PageInfo{}, err
|
return []cvemodel.CVE{}, zcommon.PageInfo{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
filterCVEList(cveMap, searchedCVE, pageFinder)
|
filterCVEList(cveMap, searchedCVE, pageFinder)
|
||||||
|
@ -259,23 +361,22 @@ func (cveinfo BaseCveInfo) GetCVEListForImage(repo, tag string, searchedCVE stri
|
||||||
return cveList, pageInfo, nil
|
return cveList, pageInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cveinfo BaseCveInfo) GetCVESummaryForImage(repo, tag string,
|
func (cveinfo BaseCveInfo) GetCVESummaryForImage(repo, ref string) (cvemodel.ImageCVESummary, error) {
|
||||||
) (ImageCVESummary, error) {
|
|
||||||
// There are several cases, expected returned values below:
|
// There are several cases, expected returned values below:
|
||||||
// not scannable / error during scan - max severity "" - cve count 0 - Errors
|
// not scannable / error during scan - max severity "" - cve count 0 - Errors
|
||||||
// scannable no issues found - max severity "NONE" - cve count 0 - no Errors
|
// scannable no issues found - max severity "NONE" - cve count 0 - no Errors
|
||||||
// scannable issues found - max severity from Scanner - cve count >0 - no Errors
|
// scannable issues found - max severity from Scanner - cve count >0 - no Errors
|
||||||
imageCVESummary := ImageCVESummary{
|
imageCVESummary := cvemodel.ImageCVESummary{
|
||||||
Count: 0,
|
Count: 0,
|
||||||
MaxSeverity: "",
|
MaxSeverity: "",
|
||||||
}
|
}
|
||||||
|
|
||||||
isValidImage, err := cveinfo.Scanner.IsImageFormatScannable(repo, tag)
|
isValidImage, err := cveinfo.Scanner.IsImageFormatScannable(repo, ref)
|
||||||
if !isValidImage {
|
if !isValidImage {
|
||||||
return imageCVESummary, err
|
return imageCVESummary, err
|
||||||
}
|
}
|
||||||
|
|
||||||
image := getImageString(repo, tag)
|
image := zcommon.GetFullImageName(repo, ref)
|
||||||
|
|
||||||
cveMap, err := cveinfo.Scanner.ScanImage(image)
|
cveMap, err := cveinfo.Scanner.ScanImage(image)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -300,20 +401,41 @@ func (cveinfo BaseCveInfo) GetCVESummaryForImage(repo, tag string,
|
||||||
return imageCVESummary, nil
|
return imageCVESummary, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func referenceIsDigest(reference string) bool {
|
func (cveinfo BaseCveInfo) GetCVESummaryForImageMedia(repo, digest, mediaType string,
|
||||||
_, err := godigest.Parse(reference)
|
) (cvemodel.ImageCVESummary, error) {
|
||||||
|
imageCVESummary := cvemodel.ImageCVESummary{
|
||||||
return err == nil
|
Count: 0,
|
||||||
|
MaxSeverity: "",
|
||||||
}
|
}
|
||||||
|
|
||||||
func getImageString(repo, reference string) string {
|
isValidImage, err := cveinfo.Scanner.IsImageMediaScannable(repo, digest, mediaType)
|
||||||
image := repo + ":" + reference
|
if !isValidImage {
|
||||||
|
return imageCVESummary, err
|
||||||
if referenceIsDigest(reference) {
|
|
||||||
image = repo + "@" + reference
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return image
|
image := repo + "@" + digest
|
||||||
|
|
||||||
|
cveMap, err := cveinfo.Scanner.ScanImage(image)
|
||||||
|
if err != nil {
|
||||||
|
return imageCVESummary, err
|
||||||
|
}
|
||||||
|
|
||||||
|
imageCVESummary.Count = len(cveMap)
|
||||||
|
|
||||||
|
if imageCVESummary.Count == 0 {
|
||||||
|
imageCVESummary.MaxSeverity = "NONE"
|
||||||
|
|
||||||
|
return imageCVESummary, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
imageCVESummary.MaxSeverity = "UNKNOWN"
|
||||||
|
for _, cve := range cveMap {
|
||||||
|
if cveinfo.Scanner.CompareSeverities(imageCVESummary.MaxSeverity, cve.Severity) > 0 {
|
||||||
|
imageCVESummary.MaxSeverity = cve.Severity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return imageCVESummary, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cveinfo BaseCveInfo) UpdateDB() error {
|
func (cveinfo BaseCveInfo) UpdateDB() error {
|
||||||
|
@ -333,11 +455,22 @@ func GetFixedTags(allTags, vulnerableTags []cvemodel.TagInfo) []cvemodel.TagInfo
|
||||||
vulnerableTagMap := make(map[string]cvemodel.TagInfo, len(vulnerableTags))
|
vulnerableTagMap := make(map[string]cvemodel.TagInfo, len(vulnerableTags))
|
||||||
|
|
||||||
for _, tag := range vulnerableTags {
|
for _, tag := range vulnerableTags {
|
||||||
vulnerableTagMap[tag.Name] = tag
|
vulnerableTagMap[tag.Tag] = tag
|
||||||
|
|
||||||
|
switch tag.Descriptor.MediaType {
|
||||||
|
case ispec.MediaTypeImageManifest:
|
||||||
if tag.Timestamp.Before(earliestVulnerable.Timestamp) {
|
if tag.Timestamp.Before(earliestVulnerable.Timestamp) {
|
||||||
earliestVulnerable = tag
|
earliestVulnerable = tag
|
||||||
}
|
}
|
||||||
|
case ispec.MediaTypeImageIndex:
|
||||||
|
for _, manifestDesc := range tag.Manifests {
|
||||||
|
if manifestDesc.Timestamp.Before(earliestVulnerable.Timestamp) {
|
||||||
|
earliestVulnerable = tag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var fixedTags []cvemodel.TagInfo
|
var fixedTags []cvemodel.TagInfo
|
||||||
|
@ -348,6 +481,8 @@ func GetFixedTags(allTags, vulnerableTags []cvemodel.TagInfo) []cvemodel.TagInfo
|
||||||
// There may be older images which have a fix or
|
// There may be older images which have a fix or
|
||||||
// newer images which don't
|
// newer images which don't
|
||||||
for _, tag := range allTags {
|
for _, tag := range allTags {
|
||||||
|
switch tag.Descriptor.MediaType {
|
||||||
|
case ispec.MediaTypeImageManifest:
|
||||||
if tag.Timestamp.Before(earliestVulnerable.Timestamp) {
|
if tag.Timestamp.Before(earliestVulnerable.Timestamp) {
|
||||||
// The vulnerability did not exist at the time this
|
// The vulnerability did not exist at the time this
|
||||||
// image was built
|
// image was built
|
||||||
|
@ -356,10 +491,52 @@ func GetFixedTags(allTags, vulnerableTags []cvemodel.TagInfo) []cvemodel.TagInfo
|
||||||
// If the image is old enough for the vulnerability to
|
// If the image is old enough for the vulnerability to
|
||||||
// exist, but it was not detected, it means it contains
|
// exist, but it was not detected, it means it contains
|
||||||
// the fix
|
// the fix
|
||||||
if _, ok := vulnerableTagMap[tag.Name]; !ok {
|
if _, ok := vulnerableTagMap[tag.Tag]; !ok {
|
||||||
fixedTags = append(fixedTags, tag)
|
fixedTags = append(fixedTags, tag)
|
||||||
}
|
}
|
||||||
|
case ispec.MediaTypeImageIndex:
|
||||||
|
fixedManifests := []cvemodel.DescriptorInfo{}
|
||||||
|
|
||||||
|
// If the latest update inside the index is before the earliest vulnerability found then
|
||||||
|
// the index can't contain a fix
|
||||||
|
if tag.Timestamp.Before(earliestVulnerable.Timestamp) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
vulnTagInfo, indexHasVulnerableManifest := vulnerableTagMap[tag.Tag]
|
||||||
|
|
||||||
|
for _, manifestDesc := range tag.Manifests {
|
||||||
|
if manifestDesc.Timestamp.Before(earliestVulnerable.Timestamp) {
|
||||||
|
// The vulnerability did not exist at the time this image was built
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the current manifest doesn't have the vulnerability
|
||||||
|
if !indexHasVulnerableManifest || !containsDescriptorInfo(vulnTagInfo.Manifests, manifestDesc) {
|
||||||
|
fixedManifests = append(fixedManifests, manifestDesc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(fixedManifests) > 0 {
|
||||||
|
fixedTag := tag
|
||||||
|
fixedTag.Manifests = fixedManifests
|
||||||
|
|
||||||
|
fixedTags = append(fixedTags, fixedTag)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return fixedTags
|
return fixedTags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func containsDescriptorInfo(slice []cvemodel.DescriptorInfo, descriptorInfo cvemodel.DescriptorInfo) bool {
|
||||||
|
for _, di := range slice {
|
||||||
|
if di.Digest == descriptorInfo.Digest {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
65
pkg/extensions/search/cve/cve_internal_test.go
Normal file
65
pkg/extensions/search/cve/cve_internal_test.go
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
package cveinfo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
|
||||||
|
cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUtils(t *testing.T) {
|
||||||
|
Convey("Utils", t, func() {
|
||||||
|
Convey("mostRecentUpdate", func() {
|
||||||
|
// empty
|
||||||
|
timestamp := mostRecentUpdate([]cvemodel.DescriptorInfo{})
|
||||||
|
So(timestamp, ShouldResemble, time.Time{})
|
||||||
|
|
||||||
|
timestamp = mostRecentUpdate([]cvemodel.DescriptorInfo{
|
||||||
|
{
|
||||||
|
Timestamp: time.Date(2000, 1, 1, 1, 1, 1, 1, time.UTC),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Timestamp: time.Date(2005, 1, 1, 1, 1, 1, 1, time.UTC),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
So(timestamp, ShouldResemble, time.Date(2005, 1, 1, 1, 1, 1, 1, time.UTC))
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("GetFixedTags", func() {
|
||||||
|
tags := GetFixedTags(
|
||||||
|
[]cvemodel.TagInfo{
|
||||||
|
{},
|
||||||
|
},
|
||||||
|
[]cvemodel.TagInfo{
|
||||||
|
{
|
||||||
|
Descriptor: cvemodel.Descriptor{
|
||||||
|
MediaType: ispec.MediaTypeImageManifest,
|
||||||
|
},
|
||||||
|
Timestamp: time.Date(2010, 1, 1, 1, 1, 1, 1, time.UTC),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Descriptor: cvemodel.Descriptor{
|
||||||
|
MediaType: ispec.MediaTypeImageIndex,
|
||||||
|
},
|
||||||
|
Manifests: []cvemodel.DescriptorInfo{
|
||||||
|
{
|
||||||
|
Timestamp: time.Date(2002, 1, 1, 1, 1, 1, 1, time.UTC),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Timestamp: time.Date(2000, 1, 1, 1, 1, 1, 1, time.UTC),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Descriptor: cvemodel.Descriptor{
|
||||||
|
MediaType: "bad Type",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
So(tags, ShouldBeEmpty)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -25,10 +26,12 @@ import (
|
||||||
"zotregistry.io/zot/pkg/api/config"
|
"zotregistry.io/zot/pkg/api/config"
|
||||||
"zotregistry.io/zot/pkg/api/constants"
|
"zotregistry.io/zot/pkg/api/constants"
|
||||||
apiErr "zotregistry.io/zot/pkg/api/errors"
|
apiErr "zotregistry.io/zot/pkg/api/errors"
|
||||||
|
zcommon "zotregistry.io/zot/pkg/common"
|
||||||
extconf "zotregistry.io/zot/pkg/extensions/config"
|
extconf "zotregistry.io/zot/pkg/extensions/config"
|
||||||
"zotregistry.io/zot/pkg/extensions/monitoring"
|
"zotregistry.io/zot/pkg/extensions/monitoring"
|
||||||
cveinfo "zotregistry.io/zot/pkg/extensions/search/cve"
|
cveinfo "zotregistry.io/zot/pkg/extensions/search/cve"
|
||||||
cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model"
|
cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model"
|
||||||
|
"zotregistry.io/zot/pkg/extensions/search/cve/trivy"
|
||||||
"zotregistry.io/zot/pkg/log"
|
"zotregistry.io/zot/pkg/log"
|
||||||
"zotregistry.io/zot/pkg/meta/bolt"
|
"zotregistry.io/zot/pkg/meta/bolt"
|
||||||
"zotregistry.io/zot/pkg/meta/repodb"
|
"zotregistry.io/zot/pkg/meta/repodb"
|
||||||
|
@ -382,10 +385,16 @@ func TestImageFormat(t *testing.T) {
|
||||||
GetRepoMetaFn: func(repo string) (repodb.RepoMetadata, error) {
|
GetRepoMetaFn: func(repo string) (repodb.RepoMetadata, error) {
|
||||||
return repodb.RepoMetadata{
|
return repodb.RepoMetadata{
|
||||||
Tags: map[string]repodb.Descriptor{
|
Tags: map[string]repodb.Descriptor{
|
||||||
"tag": {MediaType: ispec.MediaTypeImageIndex},
|
"tag": {
|
||||||
|
MediaType: ispec.MediaTypeImageIndex,
|
||||||
|
Digest: godigest.FromString("digest").String(),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
|
GetIndexDataFn: func(indexDigest godigest.Digest) (repodb.IndexData, error) {
|
||||||
|
return repodb.IndexData{IndexBlob: []byte(`{}`)}, nil
|
||||||
|
},
|
||||||
}
|
}
|
||||||
storeController := storage.StoreController{
|
storeController := storage.StoreController{
|
||||||
DefaultStore: mocks.MockedImageStore{},
|
DefaultStore: mocks.MockedImageStore{},
|
||||||
|
@ -395,7 +404,7 @@ func TestImageFormat(t *testing.T) {
|
||||||
|
|
||||||
isScanable, err := cveInfo.Scanner.IsImageFormatScannable("repo", "tag")
|
isScanable, err := cveInfo.Scanner.IsImageFormatScannable("repo", "tag")
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(isScanable, ShouldBeFalse)
|
So(isScanable, ShouldBeTrue)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1024,8 +1033,11 @@ func TestCVEStruct(t *testing.T) {
|
||||||
// Setup test CVE data in mock scanner
|
// Setup test CVE data in mock scanner
|
||||||
scanner := mocks.CveScannerMock{
|
scanner := mocks.CveScannerMock{
|
||||||
ScanImageFn: func(image string) (map[string]cvemodel.CVE, error) {
|
ScanImageFn: func(image string) (map[string]cvemodel.CVE, error) {
|
||||||
|
repo1 := "repo1"
|
||||||
|
|
||||||
|
repo, ref, _ := zcommon.GetImageDirAndReference(image)
|
||||||
// Images in chronological order
|
// Images in chronological order
|
||||||
if image == "repo1:0.1.0" {
|
if image == "repo1:0.1.0" || ref == digest11.String() {
|
||||||
return map[string]cvemodel.CVE{
|
return map[string]cvemodel.CVE{
|
||||||
"CVE1": {
|
"CVE1": {
|
||||||
ID: "CVE1",
|
ID: "CVE1",
|
||||||
|
@ -1036,7 +1048,8 @@ func TestCVEStruct(t *testing.T) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if image == "repo1:1.0.0" {
|
if image == "repo1:1.0.0" || (repo == repo1 &&
|
||||||
|
zcommon.Contains([]string{digest12.String(), digest21.String()}, ref)) {
|
||||||
return map[string]cvemodel.CVE{
|
return map[string]cvemodel.CVE{
|
||||||
"CVE1": {
|
"CVE1": {
|
||||||
ID: "CVE1",
|
ID: "CVE1",
|
||||||
|
@ -1059,7 +1072,7 @@ func TestCVEStruct(t *testing.T) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if image == "repo1:1.1.0" {
|
if image == "repo1:1.1.0" || (repo == repo1 && ref == digest13.String()) {
|
||||||
return map[string]cvemodel.CVE{
|
return map[string]cvemodel.CVE{
|
||||||
"CVE3": {
|
"CVE3": {
|
||||||
ID: "CVE3",
|
ID: "CVE3",
|
||||||
|
@ -1072,7 +1085,7 @@ func TestCVEStruct(t *testing.T) {
|
||||||
|
|
||||||
// As a minor release on 1.0.0 banch
|
// As a minor release on 1.0.0 banch
|
||||||
// does not include all fixes published in 1.1.0
|
// does not include all fixes published in 1.1.0
|
||||||
if image == "repo1:1.0.1" {
|
if image == "repo1:1.0.1" || (repo == repo1 && ref == digest14.String()) {
|
||||||
return map[string]cvemodel.CVE{
|
return map[string]cvemodel.CVE{
|
||||||
"CVE1": {
|
"CVE1": {
|
||||||
ID: "CVE1",
|
ID: "CVE1",
|
||||||
|
@ -1089,7 +1102,7 @@ func TestCVEStruct(t *testing.T) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if image == "repoIndex:tagIndex" {
|
if image == "repoIndex:tagIndex" || (repo == "repoIndex" && ref == indexDigest.String()) {
|
||||||
return map[string]cvemodel.CVE{
|
return map[string]cvemodel.CVE{
|
||||||
"CVE1": {
|
"CVE1": {
|
||||||
ID: "CVE1",
|
ID: "CVE1",
|
||||||
|
@ -1119,12 +1132,20 @@ func TestCVEStruct(t *testing.T) {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
manifestDigestStr, ok := repoMeta.Tags[inputTag]
|
manifestDigestStr := reference
|
||||||
|
|
||||||
|
if zcommon.IsTag(reference) {
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
descriptor, ok := repoMeta.Tags[inputTag]
|
||||||
if !ok {
|
if !ok {
|
||||||
return false, zerr.ErrTagMetaNotFound
|
return false, zerr.ErrTagMetaNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
manifestDigest, err := godigest.Parse(manifestDigestStr.Digest)
|
manifestDigestStr = descriptor.Digest
|
||||||
|
}
|
||||||
|
|
||||||
|
manifestDigest, err := godigest.Parse(manifestDigestStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
@ -1154,6 +1175,15 @@ func TestCVEStruct(t *testing.T) {
|
||||||
|
|
||||||
return false, nil
|
return false, nil
|
||||||
},
|
},
|
||||||
|
IsImageMediaScannableFn: func(repo, digest, mediaType string) (bool, error) {
|
||||||
|
if repo == "repo2" {
|
||||||
|
if digest == digest21.String() {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
log := log.NewLogger("debug", "")
|
log := log.NewLogger("debug", "")
|
||||||
|
@ -1213,7 +1243,7 @@ func TestCVEStruct(t *testing.T) {
|
||||||
|
|
||||||
t.Log("Test GetCVEListForImage")
|
t.Log("Test GetCVEListForImage")
|
||||||
|
|
||||||
pageInput := cveinfo.PageInput{
|
pageInput := cvemodel.PageInput{
|
||||||
SortBy: cveinfo.SeverityDsc,
|
SortBy: cveinfo.SeverityDsc,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1289,14 +1319,14 @@ func TestCVEStruct(t *testing.T) {
|
||||||
tagList, err := cveInfo.GetImageListWithCVEFixed("repo1", "CVE1")
|
tagList, err := cveInfo.GetImageListWithCVEFixed("repo1", "CVE1")
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(len(tagList), ShouldEqual, 1)
|
So(len(tagList), ShouldEqual, 1)
|
||||||
So(tagList[0].Name, ShouldEqual, "1.1.0")
|
So(tagList[0].Tag, ShouldEqual, "1.1.0")
|
||||||
|
|
||||||
tagList, err = cveInfo.GetImageListWithCVEFixed("repo1", "CVE2")
|
tagList, err = cveInfo.GetImageListWithCVEFixed("repo1", "CVE2")
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(len(tagList), ShouldEqual, 2)
|
So(len(tagList), ShouldEqual, 2)
|
||||||
expectedTags := []string{"1.0.1", "1.1.0"}
|
expectedTags := []string{"1.0.1", "1.1.0"}
|
||||||
So(expectedTags, ShouldContain, tagList[0].Name)
|
So(expectedTags, ShouldContain, tagList[0].Tag)
|
||||||
So(expectedTags, ShouldContain, tagList[1].Name)
|
So(expectedTags, ShouldContain, tagList[1].Tag)
|
||||||
|
|
||||||
tagList, err = cveInfo.GetImageListWithCVEFixed("repo1", "CVE3")
|
tagList, err = cveInfo.GetImageListWithCVEFixed("repo1", "CVE3")
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
@ -1309,7 +1339,7 @@ func TestCVEStruct(t *testing.T) {
|
||||||
tagList, err = cveInfo.GetImageListWithCVEFixed("repo6", "CVE1")
|
tagList, err = cveInfo.GetImageListWithCVEFixed("repo6", "CVE1")
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(len(tagList), ShouldEqual, 1)
|
So(len(tagList), ShouldEqual, 1)
|
||||||
So(tagList[0].Name, ShouldEqual, "1.0.0")
|
So(tagList[0].Tag, ShouldEqual, "1.0.0")
|
||||||
|
|
||||||
// Image is not scannable
|
// Image is not scannable
|
||||||
tagList, err = cveInfo.GetImageListWithCVEFixed("repo2", "CVE100")
|
tagList, err = cveInfo.GetImageListWithCVEFixed("repo2", "CVE100")
|
||||||
|
@ -1341,22 +1371,22 @@ func TestCVEStruct(t *testing.T) {
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(len(tagList), ShouldEqual, 3)
|
So(len(tagList), ShouldEqual, 3)
|
||||||
expectedTags = []string{"0.1.0", "1.0.0", "1.0.1"}
|
expectedTags = []string{"0.1.0", "1.0.0", "1.0.1"}
|
||||||
So(expectedTags, ShouldContain, tagList[0].Name)
|
So(expectedTags, ShouldContain, tagList[0].Tag)
|
||||||
So(expectedTags, ShouldContain, tagList[1].Name)
|
So(expectedTags, ShouldContain, tagList[1].Tag)
|
||||||
So(expectedTags, ShouldContain, tagList[2].Name)
|
So(expectedTags, ShouldContain, tagList[2].Tag)
|
||||||
|
|
||||||
tagList, err = cveInfo.GetImageListForCVE("repo1", "CVE2")
|
tagList, err = cveInfo.GetImageListForCVE("repo1", "CVE2")
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(len(tagList), ShouldEqual, 1)
|
So(len(tagList), ShouldEqual, 1)
|
||||||
So(tagList[0].Name, ShouldEqual, "1.0.0")
|
So(tagList[0].Tag, ShouldEqual, "1.0.0")
|
||||||
|
|
||||||
tagList, err = cveInfo.GetImageListForCVE("repo1", "CVE3")
|
tagList, err = cveInfo.GetImageListForCVE("repo1", "CVE3")
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(len(tagList), ShouldEqual, 3)
|
So(len(tagList), ShouldEqual, 3)
|
||||||
expectedTags = []string{"1.0.0", "1.0.1", "1.1.0"}
|
expectedTags = []string{"1.0.0", "1.0.1", "1.1.0"}
|
||||||
So(expectedTags, ShouldContain, tagList[0].Name)
|
So(expectedTags, ShouldContain, tagList[0].Tag)
|
||||||
So(expectedTags, ShouldContain, tagList[1].Name)
|
So(expectedTags, ShouldContain, tagList[1].Tag)
|
||||||
So(expectedTags, ShouldContain, tagList[2].Name)
|
So(expectedTags, ShouldContain, tagList[2].Tag)
|
||||||
|
|
||||||
// Image/repo doesn't have the CVE at all
|
// Image/repo doesn't have the CVE at all
|
||||||
tagList, err = cveInfo.GetImageListForCVE("repo6", "CVE1")
|
tagList, err = cveInfo.GetImageListForCVE("repo6", "CVE1")
|
||||||
|
@ -1419,7 +1449,7 @@ func TestCVEStruct(t *testing.T) {
|
||||||
|
|
||||||
tagList, err = cveInfo.GetImageListForCVE("repoIndex", "CVE1")
|
tagList, err = cveInfo.GetImageListForCVE("repoIndex", "CVE1")
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(len(tagList), ShouldEqual, 0)
|
So(len(tagList), ShouldEqual, 1)
|
||||||
|
|
||||||
cveInfo = cveinfo.BaseCveInfo{Log: log, Scanner: mocks.CveScannerMock{
|
cveInfo = cveinfo.BaseCveInfo{Log: log, Scanner: mocks.CveScannerMock{
|
||||||
IsImageFormatScannableFn: func(repo, reference string) (bool, error) {
|
IsImageFormatScannableFn: func(repo, reference string) (bool, error) {
|
||||||
|
@ -1448,7 +1478,7 @@ func getTags() ([]cvemodel.TagInfo, []cvemodel.TagInfo) {
|
||||||
tags := make([]cvemodel.TagInfo, 0)
|
tags := make([]cvemodel.TagInfo, 0)
|
||||||
|
|
||||||
firstTag := cvemodel.TagInfo{
|
firstTag := cvemodel.TagInfo{
|
||||||
Name: "1.0.0",
|
Tag: "1.0.0",
|
||||||
Descriptor: cvemodel.Descriptor{
|
Descriptor: cvemodel.Descriptor{
|
||||||
Digest: "sha256:eca04f027f414362596f2632746d8a178362170b9ac9af772011fedcc3877ebb",
|
Digest: "sha256:eca04f027f414362596f2632746d8a178362170b9ac9af772011fedcc3877ebb",
|
||||||
MediaType: ispec.MediaTypeImageManifest,
|
MediaType: ispec.MediaTypeImageManifest,
|
||||||
|
@ -1456,7 +1486,7 @@ func getTags() ([]cvemodel.TagInfo, []cvemodel.TagInfo) {
|
||||||
Timestamp: time.Now(),
|
Timestamp: time.Now(),
|
||||||
}
|
}
|
||||||
secondTag := cvemodel.TagInfo{
|
secondTag := cvemodel.TagInfo{
|
||||||
Name: "1.0.1",
|
Tag: "1.0.1",
|
||||||
Descriptor: cvemodel.Descriptor{
|
Descriptor: cvemodel.Descriptor{
|
||||||
Digest: "sha256:eca04f027f414362596f2632746d8a179362170b9ac9af772011fedcc3877ebb",
|
Digest: "sha256:eca04f027f414362596f2632746d8a179362170b9ac9af772011fedcc3877ebb",
|
||||||
MediaType: ispec.MediaTypeImageManifest,
|
MediaType: ispec.MediaTypeImageManifest,
|
||||||
|
@ -1464,7 +1494,7 @@ func getTags() ([]cvemodel.TagInfo, []cvemodel.TagInfo) {
|
||||||
Timestamp: time.Now(),
|
Timestamp: time.Now(),
|
||||||
}
|
}
|
||||||
thirdTag := cvemodel.TagInfo{
|
thirdTag := cvemodel.TagInfo{
|
||||||
Name: "1.0.2",
|
Tag: "1.0.2",
|
||||||
Descriptor: cvemodel.Descriptor{
|
Descriptor: cvemodel.Descriptor{
|
||||||
Digest: "sha256:eca04f027f414362596f2632746d8a170362170b9ac9af772011fedcc3877ebb",
|
Digest: "sha256:eca04f027f414362596f2632746d8a170362170b9ac9af772011fedcc3877ebb",
|
||||||
MediaType: ispec.MediaTypeImageManifest,
|
MediaType: ispec.MediaTypeImageManifest,
|
||||||
|
@ -1472,7 +1502,7 @@ func getTags() ([]cvemodel.TagInfo, []cvemodel.TagInfo) {
|
||||||
Timestamp: time.Now(),
|
Timestamp: time.Now(),
|
||||||
}
|
}
|
||||||
fourthTag := cvemodel.TagInfo{
|
fourthTag := cvemodel.TagInfo{
|
||||||
Name: "1.0.3",
|
Tag: "1.0.3",
|
||||||
Descriptor: cvemodel.Descriptor{
|
Descriptor: cvemodel.Descriptor{
|
||||||
Digest: "sha256:eca04f027f414362596f2632746d8a171362170b9ac9af772011fedcc3877ebb",
|
Digest: "sha256:eca04f027f414362596f2632746d8a171362170b9ac9af772011fedcc3877ebb",
|
||||||
MediaType: ispec.MediaTypeImageManifest,
|
MediaType: ispec.MediaTypeImageManifest,
|
||||||
|
@ -1496,10 +1526,298 @@ func TestFixedTags(t *testing.T) {
|
||||||
So(len(fixedTags), ShouldEqual, 2)
|
So(len(fixedTags), ShouldEqual, 2)
|
||||||
|
|
||||||
fixedTags = cveinfo.GetFixedTags(allTags, append(vulnerableTags, cvemodel.TagInfo{
|
fixedTags = cveinfo.GetFixedTags(allTags, append(vulnerableTags, cvemodel.TagInfo{
|
||||||
Name: "taginfo",
|
Tag: "taginfo",
|
||||||
Descriptor: cvemodel.Descriptor{},
|
Descriptor: cvemodel.Descriptor{
|
||||||
|
MediaType: ispec.MediaTypeImageManifest,
|
||||||
|
Digest: "sha256:eca04f027f414362596f2632746d8a179362170b9ac9af772011fedcc3877ebb",
|
||||||
|
},
|
||||||
Timestamp: time.Date(2000, time.July, 20, 10, 10, 10, 10, time.UTC),
|
Timestamp: time.Date(2000, time.July, 20, 10, 10, 10, 10, time.UTC),
|
||||||
}))
|
}))
|
||||||
So(len(fixedTags), ShouldEqual, 3)
|
So(len(fixedTags), ShouldEqual, 3)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFixedTagsWithIndex(t *testing.T) {
|
||||||
|
Convey("Test fixed tags", t, func() {
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
port := GetFreePort()
|
||||||
|
baseURL := GetBaseURL(port)
|
||||||
|
conf := config.New()
|
||||||
|
conf.HTTP.Port = port
|
||||||
|
defaultVal := true
|
||||||
|
conf.Storage.RootDirectory = tempDir
|
||||||
|
conf.Extensions = &extconf.ExtensionConfig{
|
||||||
|
Search: &extconf.SearchConfig{
|
||||||
|
BaseConfig: extconf.BaseConfig{Enable: &defaultVal},
|
||||||
|
CVE: &extconf.CVEConfig{
|
||||||
|
UpdateInterval: 24 * time.Hour,
|
||||||
|
Trivy: &extconf.TrivyConfig{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ctlr := api.NewController(conf)
|
||||||
|
So(ctlr, ShouldNotBeNil)
|
||||||
|
|
||||||
|
cm := NewControllerManager(ctlr)
|
||||||
|
cm.StartAndWait(port)
|
||||||
|
defer cm.StopServer()
|
||||||
|
// push index with 2 manifests: one with vulns and one without
|
||||||
|
vulnManifestCreated := time.Date(2010, 1, 1, 1, 1, 1, 1, time.UTC)
|
||||||
|
vulnManifest, err := GetVulnImageWithConfig("", ispec.Image{
|
||||||
|
Created: &vulnManifestCreated,
|
||||||
|
Platform: ispec.Platform{OS: "linux", Architecture: "amd64"},
|
||||||
|
})
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
vulnDigest, err := vulnManifest.Digest()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
fixedManifestCreated := time.Date(2010, 1, 1, 1, 1, 1, 1, time.UTC)
|
||||||
|
fixedManifest, err := GetImageWithConfig(ispec.Image{
|
||||||
|
Created: &fixedManifestCreated,
|
||||||
|
Platform: ispec.Platform{OS: "windows", Architecture: "amd64"},
|
||||||
|
})
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
fixedDigest, err := fixedManifest.Digest()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
multiArch := GetMultiarchImageForImages("multi-arch-tag", []Image{fixedManifest, vulnManifest})
|
||||||
|
multiArchDigest, err := multiArch.Digest()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
err = UploadMultiarchImage(multiArch, baseURL, "repo")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
// oldest vulnerability
|
||||||
|
simpleVulnCreated := time.Date(2005, 1, 1, 1, 1, 1, 1, time.UTC)
|
||||||
|
simpleVulnImg, err := GetVulnImageWithConfig("vuln-img", ispec.Image{
|
||||||
|
Created: &simpleVulnCreated,
|
||||||
|
Platform: ispec.Platform{OS: "windows", Architecture: "amd64"},
|
||||||
|
})
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
err = UploadImage(simpleVulnImg, baseURL, "repo")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
scanner := trivy.NewScanner(ctlr.StoreController, ctlr.RepoDB, "ghcr.io/project-zot/trivy-db", "", ctlr.Log)
|
||||||
|
|
||||||
|
err = scanner.UpdateDB()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
cveInfo := cveinfo.NewCVEInfo(ctlr.StoreController, ctlr.RepoDB, "ghcr.io/project-zot/trivy-db", "", ctlr.Log)
|
||||||
|
|
||||||
|
tagsInfo, err := cveInfo.GetImageListWithCVEFixed("repo", Vulnerability1ID)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(len(tagsInfo), ShouldEqual, 1)
|
||||||
|
So(len(tagsInfo[0].Manifests), ShouldEqual, 1)
|
||||||
|
So(tagsInfo[0].Manifests[0].Digest, ShouldResemble, fixedDigest)
|
||||||
|
_ = tagsInfo
|
||||||
|
_ = vulnDigest
|
||||||
|
_ = multiArchDigest
|
||||||
|
|
||||||
|
const query = `
|
||||||
|
{
|
||||||
|
ImageListWithCVEFixed(id:"%s",image:"%s"){
|
||||||
|
Results{
|
||||||
|
RepoName
|
||||||
|
Manifests {Digest}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
resp, _ := resty.R().Get(baseURL + constants.FullSearchPrefix + "?query=" +
|
||||||
|
url.QueryEscape(fmt.Sprintf(query, Vulnerability1ID, "repo")))
|
||||||
|
So(resp, ShouldNotBeNil)
|
||||||
|
So(resp.StatusCode(), ShouldEqual, 200)
|
||||||
|
|
||||||
|
responseStruct := &zcommon.ImageListWithCVEFixedResponse{}
|
||||||
|
err = json.Unmarshal(resp.Body(), &responseStruct)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(len(responseStruct.Results), ShouldEqual, 1)
|
||||||
|
So(len(responseStruct.Results[0].Manifests), ShouldEqual, 1)
|
||||||
|
fixedManifestResp := responseStruct.Results[0].Manifests[0]
|
||||||
|
So(fixedManifestResp.Digest, ShouldResemble, fixedDigest.String())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestImageListWithCVEFixedErrors(t *testing.T) {
|
||||||
|
indexDigest := godigest.FromString("index")
|
||||||
|
manifestDigest := "sha256:1111111111111111111111111111111111111111111111111111111111111111"
|
||||||
|
|
||||||
|
Convey("Errors", t, func() {
|
||||||
|
storeController := storage.StoreController{}
|
||||||
|
storeController.DefaultStore = mocks.MockedImageStore{}
|
||||||
|
|
||||||
|
repoDB := mocks.RepoDBMock{}
|
||||||
|
log := log.NewLogger("debug", "")
|
||||||
|
|
||||||
|
Convey("getIndexContent errors", func() {
|
||||||
|
repoDB.GetRepoMetaFn = func(repo string) (repodb.RepoMetadata, error) {
|
||||||
|
return repodb.RepoMetadata{
|
||||||
|
Tags: map[string]repodb.Descriptor{
|
||||||
|
"tag": {
|
||||||
|
Digest: indexDigest.String(),
|
||||||
|
MediaType: ispec.MediaTypeImageIndex,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
repoDB.GetIndexDataFn = func(indexDigest godigest.Digest) (repodb.IndexData, error) {
|
||||||
|
return repodb.IndexData{}, zerr.ErrIndexDataNotFount
|
||||||
|
}
|
||||||
|
|
||||||
|
cveInfo := cveinfo.NewCVEInfo(storeController, repoDB, "", "", log)
|
||||||
|
|
||||||
|
_, err := cveInfo.GetImageListWithCVEFixed("repo", Vulnerability1ID)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("getIndexContent bad indexDigest", func() {
|
||||||
|
repoDB.GetRepoMetaFn = func(repo string) (repodb.RepoMetadata, error) {
|
||||||
|
return repodb.RepoMetadata{
|
||||||
|
Tags: map[string]repodb.Descriptor{
|
||||||
|
"tag": {
|
||||||
|
Digest: "bad digest",
|
||||||
|
MediaType: ispec.MediaTypeImageIndex,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
repoDB.GetIndexDataFn = func(indexDigest godigest.Digest) (repodb.IndexData, error) {
|
||||||
|
return repodb.IndexData{}, zerr.ErrIndexDataNotFount
|
||||||
|
}
|
||||||
|
|
||||||
|
cveInfo := cveinfo.NewCVEInfo(storeController, repoDB, "", "", log)
|
||||||
|
|
||||||
|
_, err := cveInfo.GetImageListWithCVEFixed("repo", Vulnerability1ID)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("getIndexContent bad index content", func() {
|
||||||
|
repoDB.GetRepoMetaFn = func(repo string) (repodb.RepoMetadata, error) {
|
||||||
|
return repodb.RepoMetadata{
|
||||||
|
Tags: map[string]repodb.Descriptor{
|
||||||
|
"tag": {
|
||||||
|
Digest: indexDigest.String(),
|
||||||
|
MediaType: ispec.MediaTypeImageIndex,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
repoDB.GetIndexDataFn = func(indexDigest godigest.Digest) (repodb.IndexData, error) {
|
||||||
|
return repodb.IndexData{IndexBlob: []byte(`bad index`)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cveInfo := cveinfo.NewCVEInfo(storeController, repoDB, "", "", log)
|
||||||
|
|
||||||
|
_, err := cveInfo.GetImageListWithCVEFixed("repo", Vulnerability1ID)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("getTagInfoForManifest bad manifest digest", func() {
|
||||||
|
repoDB.GetRepoMetaFn = func(repo string) (repodb.RepoMetadata, error) {
|
||||||
|
return repodb.RepoMetadata{
|
||||||
|
Tags: map[string]repodb.Descriptor{
|
||||||
|
"tag": {
|
||||||
|
Digest: "bad digest",
|
||||||
|
MediaType: ispec.MediaTypeImageManifest,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cveInfo := cveinfo.NewCVEInfo(storeController, repoDB, "", "", log)
|
||||||
|
|
||||||
|
_, err := cveInfo.GetImageListWithCVEFixed("repo", Vulnerability1ID)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("getTagInfoForManifest fails for index", func() {
|
||||||
|
repoDB.GetRepoMetaFn = func(repo string) (repodb.RepoMetadata, error) {
|
||||||
|
return repodb.RepoMetadata{
|
||||||
|
Tags: map[string]repodb.Descriptor{
|
||||||
|
"tag": {
|
||||||
|
Digest: indexDigest.String(),
|
||||||
|
MediaType: ispec.MediaTypeImageIndex,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
repoDB.GetIndexDataFn = func(indexDigest godigest.Digest) (repodb.IndexData, error) {
|
||||||
|
return repodb.IndexData{
|
||||||
|
IndexBlob: []byte(fmt.Sprintf(`{
|
||||||
|
"manifests": [
|
||||||
|
{
|
||||||
|
"digest": "%s",
|
||||||
|
"mediaType": "application/vnd.oci.image.manifest.v1+json"
|
||||||
|
}
|
||||||
|
]}`, manifestDigest)),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
repoDB.GetManifestDataFn = func(manifestDigest godigest.Digest) (repodb.ManifestData, error) {
|
||||||
|
return repodb.ManifestData{}, zerr.ErrManifestDataNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
cveInfo := cveinfo.NewCVEInfo(storeController, repoDB, "", "", log)
|
||||||
|
|
||||||
|
tagsInfo, err := cveInfo.GetImageListWithCVEFixed("repo", Vulnerability1ID)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(tagsInfo, ShouldBeEmpty)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("media type not supported", func() {
|
||||||
|
repoDB.GetRepoMetaFn = func(repo string) (repodb.RepoMetadata, error) {
|
||||||
|
return repodb.RepoMetadata{
|
||||||
|
Tags: map[string]repodb.Descriptor{
|
||||||
|
"tag": {
|
||||||
|
Digest: godigest.FromString("media type").String(),
|
||||||
|
MediaType: "bad media type",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cveInfo := cveinfo.NewCVEInfo(storeController, repoDB, "", "", log)
|
||||||
|
|
||||||
|
tagsInfo, err := cveInfo.GetImageListWithCVEFixed("repo", Vulnerability1ID)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(tagsInfo, ShouldBeEmpty)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetCVESummaryForImageMediaErrors(t *testing.T) {
|
||||||
|
Convey("Errors", t, func() {
|
||||||
|
storeController := storage.StoreController{}
|
||||||
|
storeController.DefaultStore = mocks.MockedImageStore{}
|
||||||
|
|
||||||
|
repoDB := mocks.RepoDBMock{}
|
||||||
|
log := log.NewLogger("debug", "")
|
||||||
|
|
||||||
|
Convey("IsImageMediaScannable returns false", func() {
|
||||||
|
cveInfo := cveinfo.NewCVEInfo(storeController, repoDB, "", "", log)
|
||||||
|
cveInfo.Scanner = mocks.CveScannerMock{
|
||||||
|
IsImageMediaScannableFn: func(repo, digest, mediaType string) (bool, error) {
|
||||||
|
return false, zerr.ErrScanNotSupported
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := cveInfo.GetCVESummaryForImageMedia("repo", "digest", ispec.MediaTypeImageManifest)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Scan fails", func() {
|
||||||
|
cveInfo := cveinfo.NewCVEInfo(storeController, repoDB, "", "", log)
|
||||||
|
cveInfo.Scanner = mocks.CveScannerMock{
|
||||||
|
IsImageMediaScannableFn: func(repo, digest, mediaType string) (bool, error) {
|
||||||
|
return true, nil
|
||||||
|
},
|
||||||
|
ScanImageFn: func(image string) (map[string]cvemodel.CVE, error) {
|
||||||
|
return nil, zerr.ErrScanNotSupported
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := cveInfo.GetCVESummaryForImageMedia("repo", "digest", ispec.MediaTypeImageManifest)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -6,6 +6,11 @@ import (
|
||||||
godigest "github.com/opencontainers/go-digest"
|
godigest "github.com/opencontainers/go-digest"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ImageCVESummary struct {
|
||||||
|
Count int
|
||||||
|
MaxSeverity string
|
||||||
|
}
|
||||||
|
|
||||||
//nolint:tagliatelle // graphQL schema
|
//nolint:tagliatelle // graphQL schema
|
||||||
type CVE struct {
|
type CVE struct {
|
||||||
ID string `json:"Id"`
|
ID string `json:"Id"`
|
||||||
|
@ -47,8 +52,15 @@ type Descriptor struct {
|
||||||
MediaType string
|
MediaType string
|
||||||
}
|
}
|
||||||
|
|
||||||
type TagInfo struct {
|
type DescriptorInfo struct {
|
||||||
Name string
|
Descriptor
|
||||||
Descriptor Descriptor
|
|
||||||
|
Timestamp time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type TagInfo struct {
|
||||||
|
Tag string
|
||||||
|
Descriptor Descriptor
|
||||||
|
Manifests []DescriptorInfo
|
||||||
Timestamp time.Time
|
Timestamp time.Time
|
||||||
}
|
}
|
||||||
|
|
9
pkg/extensions/search/cve/model/pagination.go
Normal file
9
pkg/extensions/search/cve/model/pagination.go
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
type SortCriteria string
|
||||||
|
|
||||||
|
type PageInput struct {
|
||||||
|
Limit int
|
||||||
|
Offset int
|
||||||
|
SortBy SortCriteria
|
||||||
|
}
|
|
@ -9,16 +9,14 @@ import (
|
||||||
cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model"
|
cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SortCriteria string
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
AlphabeticAsc = SortCriteria("ALPHABETIC_ASC")
|
AlphabeticAsc = cvemodel.SortCriteria("ALPHABETIC_ASC")
|
||||||
AlphabeticDsc = SortCriteria("ALPHABETIC_DSC")
|
AlphabeticDsc = cvemodel.SortCriteria("ALPHABETIC_DSC")
|
||||||
SeverityDsc = SortCriteria("SEVERITY")
|
SeverityDsc = cvemodel.SortCriteria("SEVERITY")
|
||||||
)
|
)
|
||||||
|
|
||||||
func SortFunctions() map[SortCriteria]func(pageBuffer []cvemodel.CVE, cveInfo CveInfo) func(i, j int) bool {
|
func SortFunctions() map[cvemodel.SortCriteria]func(pageBuffer []cvemodel.CVE, cveInfo CveInfo) func(i, j int) bool {
|
||||||
return map[SortCriteria]func(pageBuffer []cvemodel.CVE, cveInfo CveInfo) func(i, j int) bool{
|
return map[cvemodel.SortCriteria]func(pageBuffer []cvemodel.CVE, cveInfo CveInfo) func(i, j int) bool{
|
||||||
AlphabeticAsc: SortByAlphabeticAsc,
|
AlphabeticAsc: SortByAlphabeticAsc,
|
||||||
AlphabeticDsc: SortByAlphabeticDsc,
|
AlphabeticDsc: SortByAlphabeticDsc,
|
||||||
SeverityDsc: SortBySeverity,
|
SeverityDsc: SortBySeverity,
|
||||||
|
@ -56,12 +54,12 @@ type PageFinder interface {
|
||||||
type CvePageFinder struct {
|
type CvePageFinder struct {
|
||||||
limit int
|
limit int
|
||||||
offset int
|
offset int
|
||||||
sortBy SortCriteria
|
sortBy cvemodel.SortCriteria
|
||||||
pageBuffer []cvemodel.CVE
|
pageBuffer []cvemodel.CVE
|
||||||
cveInfo CveInfo
|
cveInfo CveInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCvePageFinder(limit, offset int, sortBy SortCriteria, cveInfo CveInfo) (*CvePageFinder, error) {
|
func NewCvePageFinder(limit, offset int, sortBy cvemodel.SortCriteria, cveInfo CveInfo) (*CvePageFinder, error) {
|
||||||
if sortBy == "" {
|
if sortBy == "" {
|
||||||
sortBy = SeverityDsc
|
sortBy = SeverityDsc
|
||||||
}
|
}
|
||||||
|
@ -131,9 +129,3 @@ func (bpt *CvePageFinder) Page() ([]cvemodel.CVE, common.PageInfo) {
|
||||||
|
|
||||||
return cves, *pageInfo
|
return cves, *pageInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
type PageInput struct {
|
|
||||||
Limit int
|
|
||||||
Offset int
|
|
||||||
SortBy SortCriteria
|
|
||||||
}
|
|
||||||
|
|
|
@ -187,7 +187,7 @@ func TestCVEPagination(t *testing.T) {
|
||||||
Convey("Page", func() {
|
Convey("Page", func() {
|
||||||
Convey("defaults", func() {
|
Convey("defaults", func() {
|
||||||
// By default expect unlimitted results sorted by severity
|
// By default expect unlimitted results sorted by severity
|
||||||
cves, pageInfo, err := cveInfo.GetCVEListForImage("repo1", "0.1.0", "", cveinfo.PageInput{})
|
cves, pageInfo, err := cveInfo.GetCVEListForImage("repo1", "0.1.0", "", cvemodel.PageInput{})
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(len(cves), ShouldEqual, 5)
|
So(len(cves), ShouldEqual, 5)
|
||||||
So(pageInfo.ItemCount, ShouldEqual, 5)
|
So(pageInfo.ItemCount, ShouldEqual, 5)
|
||||||
|
@ -198,7 +198,7 @@ func TestCVEPagination(t *testing.T) {
|
||||||
previousSeverity = severityToInt[cve.Severity]
|
previousSeverity = severityToInt[cve.Severity]
|
||||||
}
|
}
|
||||||
|
|
||||||
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "1.0.0", "", cveinfo.PageInput{})
|
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "1.0.0", "", cvemodel.PageInput{})
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(len(cves), ShouldEqual, 30)
|
So(len(cves), ShouldEqual, 30)
|
||||||
So(pageInfo.ItemCount, ShouldEqual, 30)
|
So(pageInfo.ItemCount, ShouldEqual, 30)
|
||||||
|
@ -217,7 +217,7 @@ func TestCVEPagination(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
cves, pageInfo, err := cveInfo.GetCVEListForImage("repo1", "0.1.0", "",
|
cves, pageInfo, err := cveInfo.GetCVEListForImage("repo1", "0.1.0", "",
|
||||||
cveinfo.PageInput{SortBy: cveinfo.AlphabeticAsc})
|
cvemodel.PageInput{SortBy: cveinfo.AlphabeticAsc})
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(len(cves), ShouldEqual, 5)
|
So(len(cves), ShouldEqual, 5)
|
||||||
So(pageInfo.ItemCount, ShouldEqual, 5)
|
So(pageInfo.ItemCount, ShouldEqual, 5)
|
||||||
|
@ -228,7 +228,7 @@ func TestCVEPagination(t *testing.T) {
|
||||||
|
|
||||||
sort.Strings(cveIds)
|
sort.Strings(cveIds)
|
||||||
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "1.0.0", "",
|
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "1.0.0", "",
|
||||||
cveinfo.PageInput{SortBy: cveinfo.AlphabeticAsc})
|
cvemodel.PageInput{SortBy: cveinfo.AlphabeticAsc})
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(len(cves), ShouldEqual, 30)
|
So(len(cves), ShouldEqual, 30)
|
||||||
So(pageInfo.ItemCount, ShouldEqual, 30)
|
So(pageInfo.ItemCount, ShouldEqual, 30)
|
||||||
|
@ -239,7 +239,7 @@ func TestCVEPagination(t *testing.T) {
|
||||||
|
|
||||||
sort.Sort(sort.Reverse(sort.StringSlice(cveIds)))
|
sort.Sort(sort.Reverse(sort.StringSlice(cveIds)))
|
||||||
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "1.0.0", "",
|
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "1.0.0", "",
|
||||||
cveinfo.PageInput{SortBy: cveinfo.AlphabeticDsc})
|
cvemodel.PageInput{SortBy: cveinfo.AlphabeticDsc})
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(len(cves), ShouldEqual, 30)
|
So(len(cves), ShouldEqual, 30)
|
||||||
So(pageInfo.ItemCount, ShouldEqual, 30)
|
So(pageInfo.ItemCount, ShouldEqual, 30)
|
||||||
|
@ -249,7 +249,7 @@ func TestCVEPagination(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "1.0.0", "",
|
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "1.0.0", "",
|
||||||
cveinfo.PageInput{SortBy: cveinfo.SeverityDsc})
|
cvemodel.PageInput{SortBy: cveinfo.SeverityDsc})
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(len(cves), ShouldEqual, 30)
|
So(len(cves), ShouldEqual, 30)
|
||||||
So(pageInfo.ItemCount, ShouldEqual, 30)
|
So(pageInfo.ItemCount, ShouldEqual, 30)
|
||||||
|
@ -267,7 +267,7 @@ func TestCVEPagination(t *testing.T) {
|
||||||
cveIds = append(cveIds, fmt.Sprintf("CVE%d", i))
|
cveIds = append(cveIds, fmt.Sprintf("CVE%d", i))
|
||||||
}
|
}
|
||||||
|
|
||||||
cves, pageInfo, err := cveInfo.GetCVEListForImage("repo1", "0.1.0", "", cveinfo.PageInput{
|
cves, pageInfo, err := cveInfo.GetCVEListForImage("repo1", "0.1.0", "", cvemodel.PageInput{
|
||||||
Limit: 3,
|
Limit: 3,
|
||||||
Offset: 1,
|
Offset: 1,
|
||||||
SortBy: cveinfo.AlphabeticAsc,
|
SortBy: cveinfo.AlphabeticAsc,
|
||||||
|
@ -281,7 +281,7 @@ func TestCVEPagination(t *testing.T) {
|
||||||
So(cves[1].ID, ShouldEqual, "CVE2")
|
So(cves[1].ID, ShouldEqual, "CVE2")
|
||||||
So(cves[2].ID, ShouldEqual, "CVE3")
|
So(cves[2].ID, ShouldEqual, "CVE3")
|
||||||
|
|
||||||
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "0.1.0", "", cveinfo.PageInput{
|
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "0.1.0", "", cvemodel.PageInput{
|
||||||
Limit: 2,
|
Limit: 2,
|
||||||
Offset: 1,
|
Offset: 1,
|
||||||
SortBy: cveinfo.AlphabeticDsc,
|
SortBy: cveinfo.AlphabeticDsc,
|
||||||
|
@ -294,7 +294,7 @@ func TestCVEPagination(t *testing.T) {
|
||||||
So(cves[0].ID, ShouldEqual, "CVE3")
|
So(cves[0].ID, ShouldEqual, "CVE3")
|
||||||
So(cves[1].ID, ShouldEqual, "CVE2")
|
So(cves[1].ID, ShouldEqual, "CVE2")
|
||||||
|
|
||||||
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "0.1.0", "", cveinfo.PageInput{
|
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "0.1.0", "", cvemodel.PageInput{
|
||||||
Limit: 3,
|
Limit: 3,
|
||||||
Offset: 1,
|
Offset: 1,
|
||||||
SortBy: cveinfo.SeverityDsc,
|
SortBy: cveinfo.SeverityDsc,
|
||||||
|
@ -311,7 +311,7 @@ func TestCVEPagination(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Strings(cveIds)
|
sort.Strings(cveIds)
|
||||||
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "1.0.0", "", cveinfo.PageInput{
|
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "1.0.0", "", cvemodel.PageInput{
|
||||||
Limit: 5,
|
Limit: 5,
|
||||||
Offset: 20,
|
Offset: 20,
|
||||||
SortBy: cveinfo.AlphabeticAsc,
|
SortBy: cveinfo.AlphabeticAsc,
|
||||||
|
@ -327,7 +327,7 @@ func TestCVEPagination(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("limit > len(cves)", func() {
|
Convey("limit > len(cves)", func() {
|
||||||
cves, pageInfo, err := cveInfo.GetCVEListForImage("repo1", "0.1.0", "", cveinfo.PageInput{
|
cves, pageInfo, err := cveInfo.GetCVEListForImage("repo1", "0.1.0", "", cvemodel.PageInput{
|
||||||
Limit: 6,
|
Limit: 6,
|
||||||
Offset: 3,
|
Offset: 3,
|
||||||
SortBy: cveinfo.AlphabeticAsc,
|
SortBy: cveinfo.AlphabeticAsc,
|
||||||
|
@ -340,7 +340,7 @@ func TestCVEPagination(t *testing.T) {
|
||||||
So(cves[0].ID, ShouldEqual, "CVE3")
|
So(cves[0].ID, ShouldEqual, "CVE3")
|
||||||
So(cves[1].ID, ShouldEqual, "CVE4")
|
So(cves[1].ID, ShouldEqual, "CVE4")
|
||||||
|
|
||||||
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "0.1.0", "", cveinfo.PageInput{
|
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "0.1.0", "", cvemodel.PageInput{
|
||||||
Limit: 6,
|
Limit: 6,
|
||||||
Offset: 3,
|
Offset: 3,
|
||||||
SortBy: cveinfo.AlphabeticDsc,
|
SortBy: cveinfo.AlphabeticDsc,
|
||||||
|
@ -353,7 +353,7 @@ func TestCVEPagination(t *testing.T) {
|
||||||
So(cves[0].ID, ShouldEqual, "CVE1")
|
So(cves[0].ID, ShouldEqual, "CVE1")
|
||||||
So(cves[1].ID, ShouldEqual, "CVE0")
|
So(cves[1].ID, ShouldEqual, "CVE0")
|
||||||
|
|
||||||
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "0.1.0", "", cveinfo.PageInput{
|
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "0.1.0", "", cvemodel.PageInput{
|
||||||
Limit: 6,
|
Limit: 6,
|
||||||
Offset: 3,
|
Offset: 3,
|
||||||
SortBy: cveinfo.SeverityDsc,
|
SortBy: cveinfo.SeverityDsc,
|
||||||
|
|
|
@ -22,6 +22,7 @@ import (
|
||||||
_ "modernc.org/sqlite"
|
_ "modernc.org/sqlite"
|
||||||
|
|
||||||
zerr "zotregistry.io/zot/errors"
|
zerr "zotregistry.io/zot/errors"
|
||||||
|
zcommon "zotregistry.io/zot/pkg/common"
|
||||||
cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model"
|
cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model"
|
||||||
"zotregistry.io/zot/pkg/log"
|
"zotregistry.io/zot/pkg/log"
|
||||||
"zotregistry.io/zot/pkg/meta/repodb"
|
"zotregistry.io/zot/pkg/meta/repodb"
|
||||||
|
@ -181,54 +182,61 @@ func (scanner Scanner) runTrivy(opts flag.Options) (types.Report, error) {
|
||||||
return report, nil
|
return report, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (scanner Scanner) IsImageFormatScannable(repo, tag string) (bool, error) {
|
func (scanner Scanner) IsImageFormatScannable(repo, ref string) (bool, error) {
|
||||||
image := repo + ":" + tag
|
var (
|
||||||
|
digestStr = ref
|
||||||
|
mediaType string
|
||||||
|
)
|
||||||
|
|
||||||
if scanner.cache.Get(image) != nil {
|
if zcommon.IsTag(ref) {
|
||||||
return true, nil
|
imgDescriptor, err := repodb.GetImageDescriptor(scanner.repoDB, repo, ref)
|
||||||
}
|
|
||||||
|
|
||||||
repoMeta, err := scanner.repoDB.GetRepoMeta(repo)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var ok bool
|
digestStr = imgDescriptor.Digest
|
||||||
|
mediaType = imgDescriptor.MediaType
|
||||||
|
} else {
|
||||||
|
var found bool
|
||||||
|
|
||||||
imageDescriptor, ok := repoMeta.Tags[tag]
|
found, mediaType = repodb.FindMediaTypeForDigest(scanner.repoDB, godigest.Digest(ref))
|
||||||
if !ok {
|
if !found {
|
||||||
return false, zerr.ErrTagMetaNotFound
|
return false, zerr.ErrManifestNotFound
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch imageDescriptor.MediaType {
|
return scanner.IsImageMediaScannable(repo, digestStr, mediaType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (scanner Scanner) IsImageMediaScannable(repo, digestStr, mediaType string) (bool, error) {
|
||||||
|
image := repo + "@" + digestStr
|
||||||
|
|
||||||
|
switch mediaType {
|
||||||
case ispec.MediaTypeImageManifest:
|
case ispec.MediaTypeImageManifest:
|
||||||
ok, err := scanner.isManifestScanable(imageDescriptor)
|
ok, err := scanner.isManifestScanable(digestStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ok, fmt.Errorf("image '%s' %w", image, err)
|
return ok, fmt.Errorf("image '%s' %w", image, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ok, nil
|
return ok, nil
|
||||||
case ispec.MediaTypeImageIndex:
|
case ispec.MediaTypeImageIndex:
|
||||||
ok, err := scanner.isIndexScanable(imageDescriptor)
|
ok, err := scanner.isIndexScanable(digestStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ok, fmt.Errorf("image '%s' %w", image, err)
|
return ok, fmt.Errorf("image '%s' %w", image, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ok, nil
|
return ok, nil
|
||||||
}
|
default:
|
||||||
|
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (scanner Scanner) isManifestScanable(descriptor repodb.Descriptor) (bool, error) {
|
|
||||||
manifestDigestStr := descriptor.Digest
|
|
||||||
|
|
||||||
manifestDigest, err := godigest.Parse(manifestDigestStr)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
manifestData, err := scanner.repoDB.GetManifestData(manifestDigest)
|
func (scanner Scanner) isManifestScanable(digestStr string) (bool, error) {
|
||||||
|
if scanner.cache.Get(digestStr) != nil {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
manifestData, err := scanner.repoDB.GetManifestData(godigest.Digest(digestStr))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
@ -257,18 +265,98 @@ func (scanner Scanner) isManifestScanable(descriptor repodb.Descriptor) (bool, e
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (scanner Scanner) isIndexScanable(descriptor repodb.Descriptor) (bool, error) {
|
func (scanner Scanner) isIndexScanable(digestStr string) (bool, error) {
|
||||||
|
if scanner.cache.Get(digestStr) != nil {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
indexData, err := scanner.repoDB.GetIndexData(godigest.Digest(digestStr))
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var indexContent ispec.Index
|
||||||
|
|
||||||
|
err = json.Unmarshal(indexData.IndexBlob, &indexContent)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(indexContent.Manifests) == 0 {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, manifest := range indexContent.Manifests {
|
||||||
|
isScannable, err := scanner.isManifestScanable(manifest.Digest.String())
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// if at least 1 manifest is scanable, the whole index is scanable
|
||||||
|
if isScannable {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (scanner Scanner) ScanImage(image string) (map[string]cvemodel.CVE, error) {
|
func (scanner Scanner) ScanImage(image string) (map[string]cvemodel.CVE, error) {
|
||||||
if scanner.cache.Get(image) != nil {
|
var (
|
||||||
return scanner.cache.Get(image), nil
|
originalImageInput = image
|
||||||
|
digest string
|
||||||
|
mediaType string
|
||||||
|
)
|
||||||
|
|
||||||
|
repo, ref, isTag := zcommon.GetImageDirAndReference(image)
|
||||||
|
|
||||||
|
digest = ref
|
||||||
|
|
||||||
|
if isTag {
|
||||||
|
imgDescriptor, err := repodb.GetImageDescriptor(scanner.repoDB, repo, ref)
|
||||||
|
if err != nil {
|
||||||
|
return map[string]cvemodel.CVE{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cveidMap := make(map[string]cvemodel.CVE)
|
digest = imgDescriptor.Digest
|
||||||
|
mediaType = imgDescriptor.MediaType
|
||||||
|
} else {
|
||||||
|
var found bool
|
||||||
|
|
||||||
scanner.log.Debug().Str("image", image).Msg("scanning image")
|
found, mediaType = repodb.FindMediaTypeForDigest(scanner.repoDB, godigest.Digest(ref))
|
||||||
|
if !found {
|
||||||
|
return map[string]cvemodel.CVE{}, zerr.ErrManifestNotFound
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
cveIDMap map[string]cvemodel.CVE
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
switch mediaType {
|
||||||
|
case ispec.MediaTypeImageIndex:
|
||||||
|
cveIDMap, err = scanner.scanIndex(repo, digest)
|
||||||
|
default:
|
||||||
|
cveIDMap, err = scanner.scanManifest(repo, digest)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
scanner.log.Error().Err(err).Str("image", originalImageInput).Msg("unable to scan image")
|
||||||
|
|
||||||
|
return map[string]cvemodel.CVE{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cveIDMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (scanner Scanner) scanManifest(repo, digest string) (map[string]cvemodel.CVE, error) {
|
||||||
|
if cachedMap := scanner.cache.Get(digest); cachedMap != nil {
|
||||||
|
return cachedMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cveidMap := map[string]cvemodel.CVE{}
|
||||||
|
image := repo + "@" + digest
|
||||||
|
|
||||||
scanner.dbLock.Lock()
|
scanner.dbLock.Lock()
|
||||||
opts := scanner.getTrivyOptions(image)
|
opts := scanner.getTrivyOptions(image)
|
||||||
|
@ -276,8 +364,6 @@ func (scanner Scanner) ScanImage(image string) (map[string]cvemodel.CVE, error)
|
||||||
scanner.dbLock.Unlock()
|
scanner.dbLock.Unlock()
|
||||||
|
|
||||||
if err != nil { //nolint: wsl
|
if err != nil { //nolint: wsl
|
||||||
scanner.log.Error().Err(err).Str("image", image).Msg("unable to scan image")
|
|
||||||
|
|
||||||
return cveidMap, err
|
return cveidMap, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -335,11 +421,42 @@ func (scanner Scanner) ScanImage(image string) (map[string]cvemodel.CVE, error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
scanner.cache.Add(image, cveidMap)
|
scanner.cache.Add(digest, cveidMap)
|
||||||
|
|
||||||
return cveidMap, nil
|
return cveidMap, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (scanner Scanner) scanIndex(repo, digest string) (map[string]cvemodel.CVE, error) {
|
||||||
|
indexData, err := scanner.repoDB.GetIndexData(godigest.Digest(digest))
|
||||||
|
if err != nil {
|
||||||
|
return map[string]cvemodel.CVE{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var indexContent ispec.Index
|
||||||
|
|
||||||
|
err = json.Unmarshal(indexData.IndexBlob, &indexContent)
|
||||||
|
if err != nil {
|
||||||
|
return map[string]cvemodel.CVE{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
indexCveIDMap := map[string]cvemodel.CVE{}
|
||||||
|
|
||||||
|
for _, manifest := range indexContent.Manifests {
|
||||||
|
if isScannable, err := scanner.isManifestScanable(manifest.Digest.String()); isScannable && err == nil {
|
||||||
|
manifestCveIDMap, err := scanner.scanManifest(repo, manifest.Digest.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for vulnerabilityID, CVE := range manifestCveIDMap {
|
||||||
|
indexCveIDMap[vulnerabilityID] = CVE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return indexCveIDMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateDB downloads the Trivy DB / Cache under the store root directory.
|
// UpdateDB downloads the Trivy DB / Cache under the store root directory.
|
||||||
func (scanner Scanner) UpdateDB() error {
|
func (scanner Scanner) UpdateDB() error {
|
||||||
// We need a lock as using multiple substores each with it's own DB
|
// We need a lock as using multiple substores each with it's own DB
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
zerr "zotregistry.io/zot/errors"
|
zerr "zotregistry.io/zot/errors"
|
||||||
"zotregistry.io/zot/pkg/common"
|
"zotregistry.io/zot/pkg/common"
|
||||||
"zotregistry.io/zot/pkg/extensions/monitoring"
|
"zotregistry.io/zot/pkg/extensions/monitoring"
|
||||||
|
"zotregistry.io/zot/pkg/extensions/search/cve/model"
|
||||||
"zotregistry.io/zot/pkg/log"
|
"zotregistry.io/zot/pkg/log"
|
||||||
"zotregistry.io/zot/pkg/meta/bolt"
|
"zotregistry.io/zot/pkg/meta/bolt"
|
||||||
"zotregistry.io/zot/pkg/meta/repodb"
|
"zotregistry.io/zot/pkg/meta/repodb"
|
||||||
|
@ -27,6 +28,7 @@ import (
|
||||||
"zotregistry.io/zot/pkg/storage/local"
|
"zotregistry.io/zot/pkg/storage/local"
|
||||||
storageTypes "zotregistry.io/zot/pkg/storage/types"
|
storageTypes "zotregistry.io/zot/pkg/storage/types"
|
||||||
"zotregistry.io/zot/pkg/test"
|
"zotregistry.io/zot/pkg/test"
|
||||||
|
"zotregistry.io/zot/pkg/test/mocks"
|
||||||
)
|
)
|
||||||
|
|
||||||
func generateTestImage(storeController storage.StoreController, image string) {
|
func generateTestImage(storeController storage.StoreController, image string) {
|
||||||
|
@ -100,9 +102,6 @@ func TestMultipleStoragePath(t *testing.T) {
|
||||||
repoDB, err := boltdb_wrapper.NewBoltDBWrapper(boltDriver, log)
|
repoDB, err := boltdb_wrapper.NewBoltDBWrapper(boltDriver, log)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
err = repodb.ParseStorage(repoDB, storeController, log)
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
|
|
||||||
scanner := NewScanner(storeController, repoDB, "ghcr.io/project-zot/trivy-db", "", log)
|
scanner := NewScanner(storeController, repoDB, "ghcr.io/project-zot/trivy-db", "", log)
|
||||||
|
|
||||||
So(scanner.storeController.DefaultStore, ShouldNotBeNil)
|
So(scanner.storeController.DefaultStore, ShouldNotBeNil)
|
||||||
|
@ -125,6 +124,9 @@ func TestMultipleStoragePath(t *testing.T) {
|
||||||
generateTestImage(storeController, img1)
|
generateTestImage(storeController, img1)
|
||||||
generateTestImage(storeController, img2)
|
generateTestImage(storeController, img2)
|
||||||
|
|
||||||
|
err = repodb.ParseStorage(repoDB, storeController, log)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
// Try to scan without the DB being downloaded
|
// Try to scan without the DB being downloaded
|
||||||
_, err = scanner.ScanImage(img0)
|
_, err = scanner.ScanImage(img0)
|
||||||
So(err, ShouldNotBeNil)
|
So(err, ShouldNotBeNil)
|
||||||
|
@ -508,3 +510,138 @@ func TestDefaultTrivyDBUrl(t *testing.T) {
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIsIndexScanable(t *testing.T) {
|
||||||
|
Convey("IsIndexScanable", t, func() {
|
||||||
|
storeController := storage.StoreController{}
|
||||||
|
storeController.DefaultStore = &local.ImageStoreLocal{}
|
||||||
|
|
||||||
|
repoDB := &boltdb_wrapper.DBWrapper{}
|
||||||
|
log := log.NewLogger("debug", "")
|
||||||
|
|
||||||
|
Convey("Find index in cache", func() {
|
||||||
|
scanner := NewScanner(storeController, repoDB, "", "", log)
|
||||||
|
|
||||||
|
scanner.cache.Add("digest", make(map[string]model.CVE))
|
||||||
|
|
||||||
|
found, err := scanner.isIndexScanable("digest")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(found, ShouldBeTrue)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestScanIndexErrors(t *testing.T) {
|
||||||
|
Convey("Errors", t, func() {
|
||||||
|
storeController := storage.StoreController{}
|
||||||
|
storeController.DefaultStore = mocks.MockedImageStore{}
|
||||||
|
|
||||||
|
repoDB := mocks.RepoDBMock{}
|
||||||
|
log := log.NewLogger("debug", "")
|
||||||
|
|
||||||
|
Convey("GetIndexData fails", func() {
|
||||||
|
repoDB.GetIndexDataFn = func(indexDigest godigest.Digest) (repodb.IndexData, error) {
|
||||||
|
return repodb.IndexData{}, godigest.ErrDigestUnsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
scanner := NewScanner(storeController, repoDB, "", "", log)
|
||||||
|
|
||||||
|
_, err := scanner.scanIndex("repo", "digest")
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Bad Index Blob, Unamrshal fails", func() {
|
||||||
|
repoDB.GetIndexDataFn = func(indexDigest godigest.Digest) (repodb.IndexData, error) {
|
||||||
|
return repodb.IndexData{
|
||||||
|
IndexBlob: []byte(`bad-blob`),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
scanner := NewScanner(storeController, repoDB, "", "", log)
|
||||||
|
|
||||||
|
_, err := scanner.scanIndex("repo", "digest")
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsIndexScanableErrors(t *testing.T) {
|
||||||
|
Convey("Errors", t, func() {
|
||||||
|
storeController := storage.StoreController{}
|
||||||
|
storeController.DefaultStore = mocks.MockedImageStore{}
|
||||||
|
|
||||||
|
repoDB := mocks.RepoDBMock{}
|
||||||
|
log := log.NewLogger("debug", "")
|
||||||
|
|
||||||
|
Convey("GetIndexData errors", func() {
|
||||||
|
repoDB.GetIndexDataFn = func(indexDigest godigest.Digest) (repodb.IndexData, error) {
|
||||||
|
return repodb.IndexData{}, zerr.ErrManifestDataNotFound
|
||||||
|
}
|
||||||
|
scanner := NewScanner(storeController, repoDB, "", "", log)
|
||||||
|
|
||||||
|
_, err := scanner.isIndexScanable("digest")
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("bad index data, can't unmarshal", func() {
|
||||||
|
repoDB.GetIndexDataFn = func(indexDigest godigest.Digest) (repodb.IndexData, error) {
|
||||||
|
return repodb.IndexData{IndexBlob: []byte(`bad`)}, nil
|
||||||
|
}
|
||||||
|
scanner := NewScanner(storeController, repoDB, "", "", log)
|
||||||
|
|
||||||
|
ok, err := scanner.isIndexScanable("digest")
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
So(ok, ShouldBeFalse)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("is Manifest Scanable errors", func() {
|
||||||
|
repoDB.GetIndexDataFn = func(indexDigest godigest.Digest) (repodb.IndexData, error) {
|
||||||
|
return repodb.IndexData{IndexBlob: []byte(`{
|
||||||
|
"manifests": [{
|
||||||
|
"digest": "digest2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"digest": "digest1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`)}, nil
|
||||||
|
}
|
||||||
|
repoDB.GetManifestDataFn = func(manifestDigest godigest.Digest) (repodb.ManifestData, error) {
|
||||||
|
switch manifestDigest {
|
||||||
|
case "digest1":
|
||||||
|
return repodb.ManifestData{
|
||||||
|
ManifestBlob: []byte("{}"),
|
||||||
|
}, nil
|
||||||
|
case "digest2":
|
||||||
|
return repodb.ManifestData{}, zerr.ErrBadBlob
|
||||||
|
}
|
||||||
|
|
||||||
|
return repodb.ManifestData{}, nil
|
||||||
|
}
|
||||||
|
scanner := NewScanner(storeController, repoDB, "", "", log)
|
||||||
|
|
||||||
|
ok, err := scanner.isIndexScanable("digest")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(ok, ShouldBeTrue)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("is Manifest Scanable returns false because no manifest is scanable", func() {
|
||||||
|
repoDB.GetIndexDataFn = func(indexDigest godigest.Digest) (repodb.IndexData, error) {
|
||||||
|
return repodb.IndexData{IndexBlob: []byte(`{
|
||||||
|
"manifests": [{
|
||||||
|
"digest": "digest2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`)}, nil
|
||||||
|
}
|
||||||
|
repoDB.GetManifestDataFn = func(manifestDigest godigest.Digest) (repodb.ManifestData, error) {
|
||||||
|
return repodb.ManifestData{}, zerr.ErrBadBlob
|
||||||
|
}
|
||||||
|
scanner := NewScanner(storeController, repoDB, "", "", log)
|
||||||
|
|
||||||
|
ok, err := scanner.isIndexScanable("digest")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(ok, ShouldBeFalse)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
186
pkg/extensions/search/cve/trivy/scanner_test.go
Normal file
186
pkg/extensions/search/cve/trivy/scanner_test.go
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
package trivy_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
godigest "github.com/opencontainers/go-digest"
|
||||||
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
|
||||||
|
zerr "zotregistry.io/zot/errors"
|
||||||
|
"zotregistry.io/zot/pkg/api"
|
||||||
|
"zotregistry.io/zot/pkg/api/config"
|
||||||
|
extconf "zotregistry.io/zot/pkg/extensions/config"
|
||||||
|
"zotregistry.io/zot/pkg/extensions/monitoring"
|
||||||
|
"zotregistry.io/zot/pkg/extensions/search/cve/trivy"
|
||||||
|
"zotregistry.io/zot/pkg/log"
|
||||||
|
"zotregistry.io/zot/pkg/meta/bolt"
|
||||||
|
"zotregistry.io/zot/pkg/meta/repodb"
|
||||||
|
boltdb_wrapper "zotregistry.io/zot/pkg/meta/repodb/boltdb-wrapper"
|
||||||
|
"zotregistry.io/zot/pkg/storage"
|
||||||
|
"zotregistry.io/zot/pkg/storage/local"
|
||||||
|
"zotregistry.io/zot/pkg/test"
|
||||||
|
"zotregistry.io/zot/pkg/test/mocks"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestScanningByDigest(t *testing.T) {
|
||||||
|
Convey("Scan the individual manifests inside an index", t, func() {
|
||||||
|
// start server
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
port := test.GetFreePort()
|
||||||
|
baseURL := test.GetBaseURL(port)
|
||||||
|
conf := config.New()
|
||||||
|
conf.HTTP.Port = port
|
||||||
|
defaultVal := true
|
||||||
|
conf.Storage.RootDirectory = tempDir
|
||||||
|
conf.Extensions = &extconf.ExtensionConfig{
|
||||||
|
Search: &extconf.SearchConfig{
|
||||||
|
BaseConfig: extconf.BaseConfig{Enable: &defaultVal},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ctlr := api.NewController(conf)
|
||||||
|
So(ctlr, ShouldNotBeNil)
|
||||||
|
|
||||||
|
cm := test.NewControllerManager(ctlr)
|
||||||
|
cm.StartAndWait(port)
|
||||||
|
defer cm.StopServer()
|
||||||
|
// push index with 2 manifests: one with vulns and one without
|
||||||
|
vulnImage, err := test.GetVulnImage("")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
vulnDigest, err := vulnImage.Digest()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
simpleImage, err := test.GetRandomImage("")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
simpleDigest, err := simpleImage.Digest()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
multiArch := test.GetMultiarchImageForImages("multi-arch-tag", []test.Image{simpleImage, vulnImage})
|
||||||
|
multiArchDigest, err := multiArch.Digest()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
err = test.UploadMultiarchImage(multiArch, baseURL, "multi-arch")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
// scan
|
||||||
|
scanner := trivy.NewScanner(ctlr.StoreController, ctlr.RepoDB, "ghcr.io/project-zot/trivy-db", "", ctlr.Log)
|
||||||
|
|
||||||
|
err = scanner.UpdateDB()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
cveMap, err := scanner.ScanImage("multi-arch@" + vulnDigest.String())
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(cveMap, ShouldContainKey, test.Vulnerability1ID)
|
||||||
|
So(cveMap, ShouldContainKey, test.Vulnerability2ID)
|
||||||
|
|
||||||
|
cveMap, err = scanner.ScanImage("multi-arch@" + simpleDigest.String())
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(cveMap, ShouldBeEmpty)
|
||||||
|
|
||||||
|
cveMap, err = scanner.ScanImage("multi-arch@" + multiArchDigest.String())
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(cveMap, ShouldContainKey, test.Vulnerability1ID)
|
||||||
|
So(cveMap, ShouldContainKey, test.Vulnerability2ID)
|
||||||
|
|
||||||
|
cveMap, err = scanner.ScanImage("multi-arch:multi-arch-tag")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(cveMap, ShouldContainKey, test.Vulnerability1ID)
|
||||||
|
So(cveMap, ShouldContainKey, test.Vulnerability2ID)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestScannerErrors(t *testing.T) {
|
||||||
|
digest := godigest.FromString("dig")
|
||||||
|
|
||||||
|
Convey("Errors", t, func() {
|
||||||
|
storeController := storage.StoreController{}
|
||||||
|
storeController.DefaultStore = mocks.MockedImageStore{}
|
||||||
|
|
||||||
|
repoDB := mocks.RepoDBMock{}
|
||||||
|
log := log.NewLogger("debug", "")
|
||||||
|
|
||||||
|
Convey("IsImageFormatSanable", func() {
|
||||||
|
repoDB.GetManifestDataFn = func(manifestDigest godigest.Digest) (repodb.ManifestData, error) {
|
||||||
|
return repodb.ManifestData{}, zerr.ErrManifestDataNotFound
|
||||||
|
}
|
||||||
|
repoDB.GetIndexDataFn = func(indexDigest godigest.Digest) (repodb.IndexData, error) {
|
||||||
|
return repodb.IndexData{}, zerr.ErrManifestDataNotFound
|
||||||
|
}
|
||||||
|
scanner := trivy.NewScanner(storeController, repoDB, "", "", log)
|
||||||
|
|
||||||
|
_, err := scanner.ScanImage("repo@" + digest.String())
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVulnerableLayer(t *testing.T) {
|
||||||
|
Convey("Vulnerable layer", t, func() {
|
||||||
|
vulnerableLayer, err := test.GetLayerWithVulnerability(1)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
created, err := time.Parse(time.RFC3339, "2023-03-29T18:19:24Z")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
config := ispec.Image{
|
||||||
|
Created: &created,
|
||||||
|
Platform: ispec.Platform{
|
||||||
|
Architecture: "amd64",
|
||||||
|
OS: "linux",
|
||||||
|
},
|
||||||
|
Config: ispec.ImageConfig{
|
||||||
|
Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"},
|
||||||
|
Cmd: []string{"/bin/sh"},
|
||||||
|
},
|
||||||
|
RootFS: ispec.RootFS{
|
||||||
|
Type: "layers",
|
||||||
|
DiffIDs: []godigest.Digest{"sha256:f1417ff83b319fbdae6dd9cd6d8c9c88002dcd75ecf6ec201c8c6894681cf2b5"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
img, err := test.GetImageWithComponents(
|
||||||
|
config,
|
||||||
|
[][]byte{
|
||||||
|
vulnerableLayer,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
imgDigest, err := img.Digest()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
|
||||||
|
log := log.NewLogger("debug", "")
|
||||||
|
imageStore := local.NewImageStore(tempDir, false, 0, false, false,
|
||||||
|
log, monitoring.NewMetricsServer(false, log), nil, nil)
|
||||||
|
|
||||||
|
storeController := storage.StoreController{
|
||||||
|
DefaultStore: imageStore,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = test.WriteImageToFileSystem(img, "repo", storeController)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
params := bolt.DBParameters{
|
||||||
|
RootDir: tempDir,
|
||||||
|
}
|
||||||
|
boltDriver, err := bolt.GetBoltDriver(params)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
repoDB, err := boltdb_wrapper.NewBoltDBWrapper(boltDriver, log)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
err = repodb.ParseStorage(repoDB, storeController, log)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
scanner := trivy.NewScanner(storeController, repoDB, "ghcr.io/project-zot/trivy-db", "", log)
|
||||||
|
|
||||||
|
err = scanner.UpdateDB()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
cveMap, err := scanner.ScanImage("repo@" + imgDigest.String())
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(len(cveMap), ShouldEqual, 2)
|
||||||
|
})
|
||||||
|
}
|
|
@ -1679,7 +1679,7 @@ type Query {
|
||||||
Returns a CVE list for the image specified in the argument
|
Returns a CVE list for the image specified in the argument
|
||||||
"""
|
"""
|
||||||
CVEListForImage(
|
CVEListForImage(
|
||||||
"Image name in format ` + "`" + `repository:tag` + "`" + `"
|
"Image name in format ` + "`" + `repository:tag` + "`" + ` or ` + "`" + `repository@digest` + "`" + `"
|
||||||
image: String!,
|
image: String!,
|
||||||
"Sets the parameters of the requested page"
|
"Sets the parameters of the requested page"
|
||||||
requestedPage: PageInput
|
requestedPage: PageInput
|
||||||
|
|
|
@ -125,10 +125,10 @@ func getImageListForDigest(ctx context.Context, digest string, repoDB repodb.Rep
|
||||||
}
|
}
|
||||||
|
|
||||||
pageInput := repodb.PageInput{
|
pageInput := repodb.PageInput{
|
||||||
Limit: safeDerefferencing(requestedPage.Limit, 0),
|
Limit: safeDereferencing(requestedPage.Limit, 0),
|
||||||
Offset: safeDerefferencing(requestedPage.Offset, 0),
|
Offset: safeDereferencing(requestedPage.Offset, 0),
|
||||||
SortBy: repodb.SortCriteria(
|
SortBy: repodb.SortCriteria(
|
||||||
safeDerefferencing(requestedPage.SortBy, gql_generated.SortCriteriaRelevance),
|
safeDereferencing(requestedPage.SortBy, gql_generated.SortCriteriaRelevance),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -294,24 +294,20 @@ func getCVEListForImage(
|
||||||
requestedPage = &gql_generated.PageInput{}
|
requestedPage = &gql_generated.PageInput{}
|
||||||
}
|
}
|
||||||
|
|
||||||
pageInput := cveinfo.PageInput{
|
pageInput := cvemodel.PageInput{
|
||||||
Limit: safeDerefferencing(requestedPage.Limit, 0),
|
Limit: safeDereferencing(requestedPage.Limit, 0),
|
||||||
Offset: safeDerefferencing(requestedPage.Offset, 0),
|
Offset: safeDereferencing(requestedPage.Offset, 0),
|
||||||
SortBy: cveinfo.SortCriteria(
|
SortBy: cvemodel.SortCriteria(
|
||||||
safeDerefferencing(requestedPage.SortBy, gql_generated.SortCriteriaSeverity),
|
safeDereferencing(requestedPage.SortBy, gql_generated.SortCriteriaSeverity),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
repo, ref, isTag := zcommon.GetImageDirAndReference(image)
|
repo, ref, _ := zcommon.GetImageDirAndReference(image)
|
||||||
|
|
||||||
if ref == "" {
|
if ref == "" {
|
||||||
return &gql_generated.CVEResultForImage{}, gqlerror.Errorf("no reference provided")
|
return &gql_generated.CVEResultForImage{}, gqlerror.Errorf("no reference provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isTag {
|
|
||||||
return &gql_generated.CVEResultForImage{}, gqlerror.Errorf("reference by digest not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
cveList, pageInfo, err := cveInfo.GetCVEListForImage(repo, ref, searchedCVE, pageInput)
|
cveList, pageInfo, err := cveInfo.GetCVEListForImage(repo, ref, searchedCVE, pageInput)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &gql_generated.CVEResultForImage{}, err
|
return &gql_generated.CVEResultForImage{}, err
|
||||||
|
@ -365,9 +361,18 @@ func FilterByTagInfo(tagsInfo []cvemodel.TagInfo) repodb.FilterFunc {
|
||||||
manifestDigest := godigest.FromBytes(manifestMeta.ManifestBlob).String()
|
manifestDigest := godigest.FromBytes(manifestMeta.ManifestBlob).String()
|
||||||
|
|
||||||
for _, tagInfo := range tagsInfo {
|
for _, tagInfo := range tagsInfo {
|
||||||
|
switch tagInfo.Descriptor.MediaType {
|
||||||
|
case ispec.MediaTypeImageManifest:
|
||||||
if tagInfo.Descriptor.Digest.String() == manifestDigest {
|
if tagInfo.Descriptor.Digest.String() == manifestDigest {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
case ispec.MediaTypeImageIndex:
|
||||||
|
for _, manifestDesc := range tagInfo.Manifests {
|
||||||
|
if manifestDesc.Digest.String() == manifestDigest {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
|
@ -423,10 +428,10 @@ func getImageListForCVE(
|
||||||
|
|
||||||
// Actual page requested by user
|
// Actual page requested by user
|
||||||
pageInput := repodb.PageInput{
|
pageInput := repodb.PageInput{
|
||||||
Limit: safeDerefferencing(requestedPage.Limit, 0),
|
Limit: safeDereferencing(requestedPage.Limit, 0),
|
||||||
Offset: safeDerefferencing(requestedPage.Offset, 0),
|
Offset: safeDereferencing(requestedPage.Offset, 0),
|
||||||
SortBy: repodb.SortCriteria(
|
SortBy: repodb.SortCriteria(
|
||||||
safeDerefferencing(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime),
|
safeDereferencing(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -485,10 +490,10 @@ func getImageListWithCVEFixed(
|
||||||
|
|
||||||
// Actual page requested by user
|
// Actual page requested by user
|
||||||
pageInput := repodb.PageInput{
|
pageInput := repodb.PageInput{
|
||||||
Limit: safeDerefferencing(requestedPage.Limit, 0),
|
Limit: safeDereferencing(requestedPage.Limit, 0),
|
||||||
Offset: safeDerefferencing(requestedPage.Offset, 0),
|
Offset: safeDereferencing(requestedPage.Offset, 0),
|
||||||
SortBy: repodb.SortCriteria(
|
SortBy: repodb.SortCriteria(
|
||||||
safeDerefferencing(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime),
|
safeDereferencing(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -535,10 +540,10 @@ func repoListWithNewestImage(
|
||||||
}
|
}
|
||||||
|
|
||||||
pageInput := repodb.PageInput{
|
pageInput := repodb.PageInput{
|
||||||
Limit: safeDerefferencing(requestedPage.Limit, 0),
|
Limit: safeDereferencing(requestedPage.Limit, 0),
|
||||||
Offset: safeDerefferencing(requestedPage.Offset, 0),
|
Offset: safeDereferencing(requestedPage.Offset, 0),
|
||||||
SortBy: repodb.SortCriteria(
|
SortBy: repodb.SortCriteria(
|
||||||
safeDerefferencing(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime),
|
safeDereferencing(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -620,10 +625,10 @@ func getFilteredPaginatedRepos(
|
||||||
}
|
}
|
||||||
|
|
||||||
pageInput := repodb.PageInput{
|
pageInput := repodb.PageInput{
|
||||||
Limit: safeDerefferencing(requestedPage.Limit, 0),
|
Limit: safeDereferencing(requestedPage.Limit, 0),
|
||||||
Offset: safeDerefferencing(requestedPage.Offset, 0),
|
Offset: safeDereferencing(requestedPage.Offset, 0),
|
||||||
SortBy: repodb.SortCriteria(
|
SortBy: repodb.SortCriteria(
|
||||||
safeDerefferencing(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime),
|
safeDereferencing(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -678,10 +683,10 @@ func globalSearch(ctx context.Context, query string, repoDB repodb.RepoDB, filte
|
||||||
}
|
}
|
||||||
|
|
||||||
pageInput := repodb.PageInput{
|
pageInput := repodb.PageInput{
|
||||||
Limit: safeDerefferencing(requestedPage.Limit, 0),
|
Limit: safeDereferencing(requestedPage.Limit, 0),
|
||||||
Offset: safeDerefferencing(requestedPage.Offset, 0),
|
Offset: safeDereferencing(requestedPage.Offset, 0),
|
||||||
SortBy: repodb.SortCriteria(
|
SortBy: repodb.SortCriteria(
|
||||||
safeDerefferencing(requestedPage.SortBy, gql_generated.SortCriteriaRelevance),
|
safeDereferencing(requestedPage.SortBy, gql_generated.SortCriteriaRelevance),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -709,10 +714,10 @@ func globalSearch(ctx context.Context, query string, repoDB repodb.RepoDB, filte
|
||||||
}
|
}
|
||||||
|
|
||||||
pageInput := repodb.PageInput{
|
pageInput := repodb.PageInput{
|
||||||
Limit: safeDerefferencing(requestedPage.Limit, 0),
|
Limit: safeDereferencing(requestedPage.Limit, 0),
|
||||||
Offset: safeDerefferencing(requestedPage.Offset, 0),
|
Offset: safeDereferencing(requestedPage.Offset, 0),
|
||||||
SortBy: repodb.SortCriteria(
|
SortBy: repodb.SortCriteria(
|
||||||
safeDerefferencing(requestedPage.SortBy, gql_generated.SortCriteriaRelevance),
|
safeDereferencing(requestedPage.SortBy, gql_generated.SortCriteriaRelevance),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -753,10 +758,10 @@ func derivedImageList(ctx context.Context, image string, digest *string, repoDB
|
||||||
}
|
}
|
||||||
|
|
||||||
pageInput := repodb.PageInput{
|
pageInput := repodb.PageInput{
|
||||||
Limit: safeDerefferencing(requestedPage.Limit, 0),
|
Limit: safeDereferencing(requestedPage.Limit, 0),
|
||||||
Offset: safeDerefferencing(requestedPage.Offset, 0),
|
Offset: safeDereferencing(requestedPage.Offset, 0),
|
||||||
SortBy: repodb.SortCriteria(
|
SortBy: repodb.SortCriteria(
|
||||||
safeDerefferencing(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime),
|
safeDereferencing(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -866,10 +871,10 @@ func baseImageList(ctx context.Context, image string, digest *string, repoDB rep
|
||||||
}
|
}
|
||||||
|
|
||||||
pageInput := repodb.PageInput{
|
pageInput := repodb.PageInput{
|
||||||
Limit: safeDerefferencing(requestedPage.Limit, 0),
|
Limit: safeDereferencing(requestedPage.Limit, 0),
|
||||||
Offset: safeDerefferencing(requestedPage.Offset, 0),
|
Offset: safeDereferencing(requestedPage.Offset, 0),
|
||||||
SortBy: repodb.SortCriteria(
|
SortBy: repodb.SortCriteria(
|
||||||
safeDerefferencing(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime),
|
safeDereferencing(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1117,7 +1122,7 @@ func expandedRepoInfo(ctx context.Context, repo string, repoDB repodb.RepoDB, cv
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
manifestMeta, err := repoDB.GetManifestMeta(repo, godigest.Digest(digest))
|
manifestData, err := repoDB.GetManifestData(godigest.Digest(digest))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
graphql.AddError(ctx, fmt.Errorf("resolver: failed to get manifest meta for image %s:%s with manifest digest %s %w",
|
graphql.AddError(ctx, fmt.Errorf("resolver: failed to get manifest meta for image %s:%s with manifest digest %s %w",
|
||||||
repo, tag, digest, err))
|
repo, tag, digest, err))
|
||||||
|
@ -1125,7 +1130,10 @@ func expandedRepoInfo(ctx context.Context, repo string, repoDB repodb.RepoDB, cv
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
manifestMetaMap[digest] = manifestMeta
|
manifestMetaMap[digest] = repodb.ManifestMetadata{
|
||||||
|
ManifestBlob: manifestData.ManifestBlob,
|
||||||
|
ConfigBlob: manifestData.ConfigBlob,
|
||||||
|
}
|
||||||
case ispec.MediaTypeImageIndex:
|
case ispec.MediaTypeImageIndex:
|
||||||
digest := descriptor.Digest
|
digest := descriptor.Digest
|
||||||
|
|
||||||
|
@ -1154,7 +1162,7 @@ func expandedRepoInfo(ctx context.Context, repo string, repoDB repodb.RepoDB, cv
|
||||||
var errorOccured bool
|
var errorOccured bool
|
||||||
|
|
||||||
for _, descriptor := range indexContent.Manifests {
|
for _, descriptor := range indexContent.Manifests {
|
||||||
manifestMeta, err := repoDB.GetManifestMeta(repo, descriptor.Digest)
|
manifestData, err := repoDB.GetManifestData(descriptor.Digest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
graphql.AddError(ctx,
|
graphql.AddError(ctx,
|
||||||
fmt.Errorf("resolver: failed to get manifest meta with digest '%s' for multiarch image %s:%s %w",
|
fmt.Errorf("resolver: failed to get manifest meta with digest '%s' for multiarch image %s:%s %w",
|
||||||
|
@ -1166,7 +1174,10 @@ func expandedRepoInfo(ctx context.Context, repo string, repoDB repodb.RepoDB, cv
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
manifestMetaMap[descriptor.Digest.String()] = manifestMeta
|
manifestMetaMap[descriptor.Digest.String()] = repodb.ManifestMetadata{
|
||||||
|
ManifestBlob: manifestData.ManifestBlob,
|
||||||
|
ConfigBlob: manifestData.ConfigBlob,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if errorOccured {
|
if errorOccured {
|
||||||
|
@ -1210,7 +1221,7 @@ func (p timeSlice) Swap(i, j int) {
|
||||||
p[i], p[j] = p[j], p[i]
|
p[i], p[j] = p[j], p[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
func safeDerefferencing[T any](pointer *T, defaultVal T) T {
|
func safeDereferencing[T any](pointer *T, defaultVal T) T {
|
||||||
if pointer != nil {
|
if pointer != nil {
|
||||||
return *pointer
|
return *pointer
|
||||||
}
|
}
|
||||||
|
@ -1236,10 +1247,10 @@ func getImageList(ctx context.Context, repo string, repoDB repodb.RepoDB, cveInf
|
||||||
}
|
}
|
||||||
|
|
||||||
pageInput := repodb.PageInput{
|
pageInput := repodb.PageInput{
|
||||||
Limit: safeDerefferencing(requestedPage.Limit, 0),
|
Limit: safeDereferencing(requestedPage.Limit, 0),
|
||||||
Offset: safeDerefferencing(requestedPage.Offset, 0),
|
Offset: safeDereferencing(requestedPage.Offset, 0),
|
||||||
SortBy: repodb.SortCriteria(
|
SortBy: repodb.SortCriteria(
|
||||||
safeDerefferencing(requestedPage.SortBy, gql_generated.SortCriteriaRelevance),
|
safeDereferencing(requestedPage.SortBy, gql_generated.SortCriteriaRelevance),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -1987,9 +1988,14 @@ func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
|
||||||
ScanImageFn: func(image string) (map[string]cvemodel.CVE, error) {
|
ScanImageFn: func(image string) (map[string]cvemodel.CVE, error) {
|
||||||
digest, ok := tagsMap[image]
|
digest, ok := tagsMap[image]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
if !strings.Contains(image, "@") {
|
||||||
return map[string]cvemodel.CVE{}, nil
|
return map[string]cvemodel.CVE{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, digestStr := common.GetImageDirAndDigest(image)
|
||||||
|
digest = godigest.Digest(digestStr)
|
||||||
|
}
|
||||||
|
|
||||||
if digest.String() == digest1.String() {
|
if digest.String() == digest1.String() {
|
||||||
return map[string]cvemodel.CVE{
|
return map[string]cvemodel.CVE{
|
||||||
"CVE1": {
|
"CVE1": {
|
||||||
|
@ -2075,7 +2081,7 @@ func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
|
||||||
repoWithDigestRef := fmt.Sprintf("repo@%s", dig)
|
repoWithDigestRef := fmt.Sprintf("repo@%s", dig)
|
||||||
|
|
||||||
_, err := getCVEListForImage(responseContext, repoWithDigestRef, cveInfo, pageInput, "", log)
|
_, err := getCVEListForImage(responseContext, repoWithDigestRef, cveInfo, pageInput, "", log)
|
||||||
So(err.Error(), ShouldContainSubstring, "reference by digest not supported")
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
cveResult, err := getCVEListForImage(responseContext, "repo1:1.0.0", cveInfo, pageInput, "", log)
|
cveResult, err := getCVEListForImage(responseContext, "repo1:1.0.0", cveInfo, pageInput, "", log)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
@ -3304,12 +3310,12 @@ func TestExpandedRepoInfo(t *testing.T) {
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
GetManifestMetaFn: func(repo string, manifestDigest godigest.Digest) (repodb.ManifestMetadata, error) {
|
GetManifestDataFn: func(manifestDigest godigest.Digest) (repodb.ManifestData, error) {
|
||||||
switch manifestDigest {
|
switch manifestDigest {
|
||||||
case "errorDigest":
|
case "errorDigest":
|
||||||
return repodb.ManifestMetadata{}, ErrTestError
|
return repodb.ManifestData{}, ErrTestError
|
||||||
default:
|
default:
|
||||||
return repodb.ManifestMetadata{
|
return repodb.ManifestData{
|
||||||
ManifestBlob: []byte("{}"),
|
ManifestBlob: []byte("{}"),
|
||||||
ConfigBlob: []byte("{}"),
|
ConfigBlob: []byte("{}"),
|
||||||
}, nil
|
}, nil
|
||||||
|
|
|
@ -592,7 +592,7 @@ type Query {
|
||||||
Returns a CVE list for the image specified in the argument
|
Returns a CVE list for the image specified in the argument
|
||||||
"""
|
"""
|
||||||
CVEListForImage(
|
CVEListForImage(
|
||||||
"Image name in format `repository:tag`"
|
"Image name in format `repository:tag` or `repository@digest`"
|
||||||
image: String!,
|
image: String!,
|
||||||
"Sets the parameters of the requested page"
|
"Sets the parameters of the requested page"
|
||||||
requestedPage: PageInput
|
requestedPage: PageInput
|
||||||
|
|
|
@ -235,7 +235,9 @@ func getMockCveInfo(repoDB repodb.RepoDB, log log.Logger) cveinfo.CveInfo {
|
||||||
// Setup test CVE data in mock scanner
|
// Setup test CVE data in mock scanner
|
||||||
scanner := mocks.CveScannerMock{
|
scanner := mocks.CveScannerMock{
|
||||||
ScanImageFn: func(image string) (map[string]cvemodel.CVE, error) {
|
ScanImageFn: func(image string) (map[string]cvemodel.CVE, error) {
|
||||||
if image == "zot-cve-test:0.0.1" || image == "a/zot-cve-test:0.0.1" {
|
if image == "zot-cve-test:0.0.1" || image == "a/zot-cve-test:0.0.1" ||
|
||||||
|
strings.Contains(image, "zot-cve-test@sha256:40d1f74918aefed733c590f798d7eafde8fc0a7ec63bb8bc52eaae133cf92495") ||
|
||||||
|
strings.Contains(image, "a/zot-cve-test@sha256:40d1f74918aefed733c590f798d7eafde8fc0a7ec63bb8bc52eaae133cf92495") {
|
||||||
return map[string]cvemodel.CVE{
|
return map[string]cvemodel.CVE{
|
||||||
"CVE1": {
|
"CVE1": {
|
||||||
ID: "CVE1",
|
ID: "CVE1",
|
||||||
|
@ -258,7 +260,9 @@ func getMockCveInfo(repoDB repodb.RepoDB, log log.Logger) cveinfo.CveInfo {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if image == "zot-test:0.0.1" || image == "a/zot-test:0.0.1" {
|
if image == "zot-test:0.0.1" || image == "a/zot-test:0.0.1" ||
|
||||||
|
strings.Contains(image, "a/zot-test@sha256:40d1f74918aefed733c590f798d7eafde8fc0a7ec63bb8bc52eaae133cf92495") ||
|
||||||
|
strings.Contains(image, "zot-test@sha256:40d1f74918aefed733c590f798d7eafde8fc0a7ec63bb8bc52eaae133cf92495") {
|
||||||
return map[string]cvemodel.CVE{
|
return map[string]cvemodel.CVE{
|
||||||
"CVE3": {
|
"CVE3": {
|
||||||
ID: "CVE3",
|
ID: "CVE3",
|
||||||
|
@ -275,7 +279,8 @@ func getMockCveInfo(repoDB repodb.RepoDB, log log.Logger) cveinfo.CveInfo {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if image == "test-repo:latest" {
|
if image == "test-repo:latest" ||
|
||||||
|
image == "test-repo@sha256:9f8e1a125c4fb03a0f157d75999b73284ccc5cba18eb772e4643e3499343607e" {
|
||||||
return map[string]cvemodel.CVE{
|
return map[string]cvemodel.CVE{
|
||||||
"CVE1": {
|
"CVE1": {
|
||||||
ID: "CVE1",
|
ID: "CVE1",
|
||||||
|
@ -320,12 +325,20 @@ func getMockCveInfo(repoDB repodb.RepoDB, log log.Logger) cveinfo.CveInfo {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
manifestDigestStr, ok := repoMeta.Tags[inputTag]
|
manifestDigestStr := reference
|
||||||
|
|
||||||
|
if zcommon.IsTag(reference) {
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
descriptor, ok := repoMeta.Tags[inputTag]
|
||||||
if !ok {
|
if !ok {
|
||||||
return false, zerr.ErrTagMetaNotFound
|
return false, zerr.ErrTagMetaNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
manifestDigest, err := godigest.Parse(manifestDigestStr.Digest)
|
manifestDigestStr = descriptor.Digest
|
||||||
|
}
|
||||||
|
|
||||||
|
manifestDigest, err := godigest.Parse(manifestDigestStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
@ -758,6 +771,7 @@ func TestRepoListWithNewestImage(t *testing.T) {
|
||||||
Name
|
Name
|
||||||
NewestImage{
|
NewestImage{
|
||||||
Tag
|
Tag
|
||||||
|
Digest
|
||||||
Vulnerabilities{
|
Vulnerabilities{
|
||||||
MaxSeverity
|
MaxSeverity
|
||||||
Count
|
Count
|
||||||
|
|
|
@ -176,7 +176,7 @@ func GetRepoTag(searchText string) (string, string, error) {
|
||||||
splitSlice := strings.Split(searchText, ":")
|
splitSlice := strings.Split(searchText, ":")
|
||||||
|
|
||||||
if len(splitSlice) != repoTagCount {
|
if len(splitSlice) != repoTagCount {
|
||||||
return "", "", zerr.ErrInvalidRepoTagFormat
|
return "", "", zerr.ErrInvalidRepoRefFormat
|
||||||
}
|
}
|
||||||
|
|
||||||
repo := strings.TrimSpace(splitSlice[0])
|
repo := strings.TrimSpace(splitSlice[0])
|
||||||
|
@ -329,6 +329,7 @@ func FilterDataByRepo(foundRepos []repodb.RepoMetadata, manifestMetadataMap map[
|
||||||
|
|
||||||
foundindexDataMap[descriptor.Digest] = indexData
|
foundindexDataMap[descriptor.Digest] = indexData
|
||||||
default:
|
default:
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1339,7 +1339,6 @@ func (bdw *DBWrapper) FilterTags(ctx context.Context, filter repodb.FilterFunc,
|
||||||
matchedTags := make(map[string]repodb.Descriptor)
|
matchedTags := make(map[string]repodb.Descriptor)
|
||||||
// take all manifestMetas
|
// take all manifestMetas
|
||||||
for tag, descriptor := range repoMeta.Tags {
|
for tag, descriptor := range repoMeta.Tags {
|
||||||
matchedTags[tag] = descriptor
|
|
||||||
switch descriptor.MediaType {
|
switch descriptor.MediaType {
|
||||||
case ispec.MediaTypeImageManifest:
|
case ispec.MediaTypeImageManifest:
|
||||||
manifestDigest := descriptor.Digest
|
manifestDigest := descriptor.Digest
|
||||||
|
@ -1349,13 +1348,10 @@ func (bdw *DBWrapper) FilterTags(ctx context.Context, filter repodb.FilterFunc,
|
||||||
return fmt.Errorf("repodb: error while unmashaling manifest metadata for digest %s %w", manifestDigest, err)
|
return fmt.Errorf("repodb: error while unmashaling manifest metadata for digest %s %w", manifestDigest, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !filter(repoMeta, manifestMeta) {
|
if filter(repoMeta, manifestMeta) {
|
||||||
delete(matchedTags, tag)
|
matchedTags[tag] = descriptor
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
manifestMetadataMap[manifestDigest] = manifestMeta
|
manifestMetadataMap[manifestDigest] = manifestMeta
|
||||||
|
}
|
||||||
case ispec.MediaTypeImageIndex:
|
case ispec.MediaTypeImageIndex:
|
||||||
indexDigest := descriptor.Digest
|
indexDigest := descriptor.Digest
|
||||||
|
|
||||||
|
@ -1371,7 +1367,7 @@ func (bdw *DBWrapper) FilterTags(ctx context.Context, filter repodb.FilterFunc,
|
||||||
return fmt.Errorf("repodb: error while unmashaling index content for digest %s %w", indexDigest, err)
|
return fmt.Errorf("repodb: error while unmashaling index content for digest %s %w", indexDigest, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
manifestHasBeenMatched := false
|
matchedManifests := []ispec.Descriptor{}
|
||||||
|
|
||||||
for _, manifest := range indexContent.Manifests {
|
for _, manifest := range indexContent.Manifests {
|
||||||
manifestDigest := manifest.Digest.String()
|
manifestDigest := manifest.Digest.String()
|
||||||
|
@ -1381,24 +1377,25 @@ func (bdw *DBWrapper) FilterTags(ctx context.Context, filter repodb.FilterFunc,
|
||||||
return fmt.Errorf("repodb: error while getting manifest data for digest %s %w", manifestDigest, err)
|
return fmt.Errorf("repodb: error while getting manifest data for digest %s %w", manifestDigest, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
manifestMetadataMap[manifestDigest] = manifestMeta
|
|
||||||
|
|
||||||
if filter(repoMeta, manifestMeta) {
|
if filter(repoMeta, manifestMeta) {
|
||||||
manifestHasBeenMatched = true
|
matchedManifests = append(matchedManifests, manifest)
|
||||||
|
manifestMetadataMap[manifestDigest] = manifestMeta
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !manifestHasBeenMatched {
|
if len(matchedManifests) > 0 {
|
||||||
delete(matchedTags, tag)
|
indexContent.Manifests = matchedManifests
|
||||||
|
|
||||||
for _, manifest := range indexContent.Manifests {
|
indexBlob, err := json.Marshal(indexContent)
|
||||||
delete(manifestMetadataMap, manifest.Digest.String())
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
continue
|
indexData.IndexBlob = indexBlob
|
||||||
}
|
|
||||||
|
|
||||||
indexDataMap[indexDigest] = indexData
|
indexDataMap[indexDigest] = indexData
|
||||||
|
matchedTags[tag] = descriptor
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
bdw.Log.Error().Str("mediaType", descriptor.MediaType).Msg("Unsupported media type")
|
bdw.Log.Error().Str("mediaType", descriptor.MediaType).Msg("Unsupported media type")
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,11 @@ package repodb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
godigest "github.com/opencontainers/go-digest"
|
||||||
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
|
||||||
|
zerr "zotregistry.io/zot/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DetailedRepoMeta is a auxiliary structure used for sorting RepoMeta arrays by information
|
// DetailedRepoMeta is a auxiliary structure used for sorting RepoMeta arrays by information
|
||||||
|
@ -55,3 +60,33 @@ func SortByDownloads(pageBuffer []DetailedRepoMeta) func(i, j int) bool {
|
||||||
return pageBuffer[i].Downloads > pageBuffer[j].Downloads
|
return pageBuffer[i].Downloads > pageBuffer[j].Downloads
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FindMediaTypeForDigest will look into the buckets for a certain digest. Depending on which bucket that
|
||||||
|
// digest is found the corresponding mediatype is returned.
|
||||||
|
func FindMediaTypeForDigest(repoDB RepoDB, digest godigest.Digest) (bool, string) {
|
||||||
|
_, err := repoDB.GetManifestData(digest)
|
||||||
|
if err == nil {
|
||||||
|
return true, ispec.MediaTypeImageManifest
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = repoDB.GetIndexData(digest)
|
||||||
|
if err == nil {
|
||||||
|
return true, ispec.MediaTypeImageIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetImageDescriptor(repoDB RepoDB, repo, tag string) (Descriptor, error) {
|
||||||
|
repoMeta, err := repoDB.GetRepoMeta(repo)
|
||||||
|
if err != nil {
|
||||||
|
return Descriptor{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
imageDescriptor, ok := repoMeta.Tags[tag]
|
||||||
|
if !ok {
|
||||||
|
return Descriptor{}, zerr.ErrTagMetaNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return imageDescriptor, nil
|
||||||
|
}
|
||||||
|
|
|
@ -1122,9 +1122,8 @@ func (dwr *DBWrapper) FilterTags(ctx context.Context, filter repodb.FilterFunc,
|
||||||
repoMeta.IsStarred = zcommon.Contains(userStars, repoMeta.Name)
|
repoMeta.IsStarred = zcommon.Contains(userStars, repoMeta.Name)
|
||||||
|
|
||||||
matchedTags := make(map[string]repodb.Descriptor)
|
matchedTags := make(map[string]repodb.Descriptor)
|
||||||
for tag, descriptor := range repoMeta.Tags {
|
|
||||||
matchedTags[tag] = descriptor
|
|
||||||
|
|
||||||
|
for tag, descriptor := range repoMeta.Tags {
|
||||||
switch descriptor.MediaType {
|
switch descriptor.MediaType {
|
||||||
case ispec.MediaTypeImageManifest:
|
case ispec.MediaTypeImageManifest:
|
||||||
manifestDigest := descriptor.Digest
|
manifestDigest := descriptor.Digest
|
||||||
|
@ -1137,13 +1136,10 @@ func (dwr *DBWrapper) FilterTags(ctx context.Context, filter repodb.FilterFunc,
|
||||||
fmt.Errorf("repodb: error while unmashaling manifest metadata for digest %s \n%w", manifestDigest, err)
|
fmt.Errorf("repodb: error while unmashaling manifest metadata for digest %s \n%w", manifestDigest, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !filter(repoMeta, manifestMeta) {
|
if filter(repoMeta, manifestMeta) {
|
||||||
delete(matchedTags, tag)
|
matchedTags[tag] = descriptor
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
manifestMetadataMap[manifestDigest] = manifestMeta
|
manifestMetadataMap[manifestDigest] = manifestMeta
|
||||||
|
}
|
||||||
case ispec.MediaTypeImageIndex:
|
case ispec.MediaTypeImageIndex:
|
||||||
indexDigest := descriptor.Digest
|
indexDigest := descriptor.Digest
|
||||||
|
|
||||||
|
@ -1163,7 +1159,7 @@ func (dwr *DBWrapper) FilterTags(ctx context.Context, filter repodb.FilterFunc,
|
||||||
fmt.Errorf("repodb: error while unmashaling index content for digest %s %w", indexDigest, err)
|
fmt.Errorf("repodb: error while unmashaling index content for digest %s %w", indexDigest, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
manifestHasBeenMatched := false
|
matchedManifests := []ispec.Descriptor{}
|
||||||
|
|
||||||
for _, manifest := range indexContent.Manifests {
|
for _, manifest := range indexContent.Manifests {
|
||||||
manifestDigest := manifest.Digest.String()
|
manifestDigest := manifest.Digest.String()
|
||||||
|
@ -1176,24 +1172,26 @@ func (dwr *DBWrapper) FilterTags(ctx context.Context, filter repodb.FilterFunc,
|
||||||
fmt.Errorf("%w repodb: error while getting manifest data for digest %s", err, manifestDigest)
|
fmt.Errorf("%w repodb: error while getting manifest data for digest %s", err, manifestDigest)
|
||||||
}
|
}
|
||||||
|
|
||||||
manifestMetadataMap[manifestDigest] = manifestMeta
|
|
||||||
|
|
||||||
if filter(repoMeta, manifestMeta) {
|
if filter(repoMeta, manifestMeta) {
|
||||||
manifestHasBeenMatched = true
|
matchedManifests = append(matchedManifests, manifest)
|
||||||
|
manifestMetadataMap[manifestDigest] = manifestMeta
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !manifestHasBeenMatched {
|
if len(matchedManifests) > 0 {
|
||||||
delete(matchedTags, tag)
|
indexContent.Manifests = matchedManifests
|
||||||
|
|
||||||
for _, manifest := range indexContent.Manifests {
|
indexBlob, err := json.Marshal(indexContent)
|
||||||
delete(manifestMetadataMap, manifest.Digest.String())
|
if err != nil {
|
||||||
|
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, map[string]repodb.IndexData{},
|
||||||
|
pageInfo, err
|
||||||
}
|
}
|
||||||
|
|
||||||
continue
|
indexData.IndexBlob = indexBlob
|
||||||
}
|
|
||||||
|
|
||||||
indexDataMap[indexDigest] = indexData
|
indexDataMap[indexDigest] = indexData
|
||||||
|
matchedTags[tag] = descriptor
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
dwr.Log.Error().Str("mediaType", descriptor.MediaType).Msg("Unsupported media type")
|
dwr.Log.Error().Str("mediaType", descriptor.MediaType).Msg("Unsupported media type")
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
@ -45,6 +46,7 @@ import (
|
||||||
"oras.land/oras-go/v2/registry/remote"
|
"oras.land/oras-go/v2/registry/remote"
|
||||||
"oras.land/oras-go/v2/registry/remote/auth"
|
"oras.land/oras-go/v2/registry/remote/auth"
|
||||||
|
|
||||||
|
zerr "zotregistry.io/zot/errors"
|
||||||
"zotregistry.io/zot/pkg/meta/repodb"
|
"zotregistry.io/zot/pkg/meta/repodb"
|
||||||
"zotregistry.io/zot/pkg/storage"
|
"zotregistry.io/zot/pkg/storage"
|
||||||
"zotregistry.io/zot/pkg/test/inject"
|
"zotregistry.io/zot/pkg/test/inject"
|
||||||
|
@ -56,6 +58,8 @@ const (
|
||||||
SleepTime = 100 * time.Millisecond
|
SleepTime = 100 * time.Millisecond
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var vulnerableLayer []byte //nolint: gochecknoglobals
|
||||||
|
|
||||||
var NotationPathLock = new(sync.Mutex) //nolint: gochecknoglobals
|
var NotationPathLock = new(sync.Mutex) //nolint: gochecknoglobals
|
||||||
|
|
||||||
// which: manifest, config, layer
|
// which: manifest, config, layer
|
||||||
|
@ -604,15 +608,8 @@ func GetRandomImageComponents(layerSize int) (ispec.Image, [][]byte, ispec.Manif
|
||||||
|
|
||||||
configDigest := godigest.FromBytes(configBlob)
|
configDigest := godigest.FromBytes(configBlob)
|
||||||
|
|
||||||
layer := make([]byte, layerSize)
|
|
||||||
|
|
||||||
_, err = rand.Read(layer)
|
|
||||||
if err != nil {
|
|
||||||
return ispec.Image{}, [][]byte{}, ispec.Manifest{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
layers := [][]byte{
|
layers := [][]byte{
|
||||||
layer,
|
GetRandomLayer(layerSize),
|
||||||
}
|
}
|
||||||
|
|
||||||
schemaVersion := 2
|
schemaVersion := 2
|
||||||
|
@ -639,6 +636,138 @@ func GetRandomImageComponents(layerSize int) (ispec.Image, [][]byte, ispec.Manif
|
||||||
return config, layers, manifest, nil
|
return config, layers, manifest, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// These are the 2 vulnerabilities found for the returned image by the GetVulnImage function.
|
||||||
|
const (
|
||||||
|
Vulnerability1ID = "CVE-2023-2650"
|
||||||
|
Vulnerability2ID = "CVE-2023-1255"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetVulnImage(ref string) (Image, error) {
|
||||||
|
const skipStackFrame = 2
|
||||||
|
|
||||||
|
vulnerableLayer, err := GetLayerWithVulnerability(skipStackFrame)
|
||||||
|
if err != nil {
|
||||||
|
return Image{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
vulnerableConfig := ispec.Image{
|
||||||
|
Platform: ispec.Platform{
|
||||||
|
Architecture: "amd64",
|
||||||
|
OS: "linux",
|
||||||
|
},
|
||||||
|
Config: ispec.ImageConfig{
|
||||||
|
Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"},
|
||||||
|
Cmd: []string{"/bin/sh"},
|
||||||
|
},
|
||||||
|
RootFS: ispec.RootFS{
|
||||||
|
Type: "layers",
|
||||||
|
DiffIDs: []godigest.Digest{"sha256:f1417ff83b319fbdae6dd9cd6d8c9c88002dcd75ecf6ec201c8c6894681cf2b5"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
img, err := GetImageWithComponents(
|
||||||
|
vulnerableConfig,
|
||||||
|
[][]byte{
|
||||||
|
vulnerableLayer,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return Image{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
img.Reference = ref
|
||||||
|
|
||||||
|
return img, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetVulnImageWithConfig(ref string, config ispec.Image) (Image, error) {
|
||||||
|
const skipStackFrame = 2
|
||||||
|
|
||||||
|
vulnerableLayer, err := GetLayerWithVulnerability(skipStackFrame)
|
||||||
|
if err != nil {
|
||||||
|
return Image{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
vulnerableConfig := ispec.Image{
|
||||||
|
Platform: config.Platform,
|
||||||
|
Config: config.Config,
|
||||||
|
RootFS: ispec.RootFS{
|
||||||
|
Type: "layers",
|
||||||
|
DiffIDs: []godigest.Digest{"sha256:f1417ff83b319fbdae6dd9cd6d8c9c88002dcd75ecf6ec201c8c6894681cf2b5"},
|
||||||
|
},
|
||||||
|
Created: config.Created,
|
||||||
|
History: config.History,
|
||||||
|
}
|
||||||
|
|
||||||
|
img, err := GetImageWithComponents(
|
||||||
|
vulnerableConfig,
|
||||||
|
[][]byte{
|
||||||
|
vulnerableLayer,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return Image{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
img.Reference = ref
|
||||||
|
|
||||||
|
return img, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetLayerWithVulnerability(skip int) ([]byte, error) {
|
||||||
|
if vulnerableLayer != nil {
|
||||||
|
return vulnerableLayer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, b, _, ok := runtime.Caller(skip)
|
||||||
|
if !ok {
|
||||||
|
return []byte{}, zerr.ErrCallerInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
absoluteCallerpath := filepath.Dir(b)
|
||||||
|
fmt.Println(absoluteCallerpath)
|
||||||
|
|
||||||
|
// we know pkg folder inside zot must exist, and since all tests are called from within pkg we'll use it as reference
|
||||||
|
relCallerPath := absoluteCallerpath[strings.LastIndex(absoluteCallerpath, "pkg"):]
|
||||||
|
|
||||||
|
relCallerSlice := strings.Split(relCallerPath, string(os.PathSeparator))
|
||||||
|
fmt.Println(relCallerPath, relCallerSlice)
|
||||||
|
|
||||||
|
// we'll calculate how many folder we should go back to reach the root of the zot folder relative
|
||||||
|
// to the callers position
|
||||||
|
backPathSlice := make([]string, len(relCallerSlice))
|
||||||
|
|
||||||
|
for i := 0; i < len(backPathSlice); i++ {
|
||||||
|
backPathSlice[i] = ".."
|
||||||
|
}
|
||||||
|
|
||||||
|
backPath := filepath.Join(backPathSlice...)
|
||||||
|
|
||||||
|
// this is the path of the blob relative to the root of the zot folder
|
||||||
|
vulnBlobPath := "test/data/alpine/blobs/sha256/f56be85fc22e46face30e2c3de3f7fe7c15f8fd7c4e5add29d7f64b87abdaa09"
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
x, _ := filepath.Abs(filepath.Join(backPath, vulnBlobPath))
|
||||||
|
_ = x
|
||||||
|
|
||||||
|
vulnerableLayer, err = os.ReadFile(filepath.Join(backPath, vulnBlobPath)) //nolint: lll
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return vulnerableLayer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetRandomLayer(size int) []byte {
|
||||||
|
layer := make([]byte, size)
|
||||||
|
|
||||||
|
_, err := rand.Read(layer)
|
||||||
|
if err != nil {
|
||||||
|
return layer
|
||||||
|
}
|
||||||
|
|
||||||
|
return layer
|
||||||
|
}
|
||||||
|
|
||||||
func GetRandomImage(reference string) (Image, error) {
|
func GetRandomImage(reference string) (Image, error) {
|
||||||
const layerSize = 20
|
const layerSize = 20
|
||||||
|
|
||||||
|
|
|
@ -2,17 +2,18 @@ package mocks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"zotregistry.io/zot/pkg/common"
|
"zotregistry.io/zot/pkg/common"
|
||||||
cveinfo "zotregistry.io/zot/pkg/extensions/search/cve"
|
|
||||||
cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model"
|
cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CveInfoMock struct {
|
type CveInfoMock struct {
|
||||||
GetImageListForCVEFn func(repo, cveID string) ([]cvemodel.TagInfo, error)
|
GetImageListForCVEFn func(repo, cveID string) ([]cvemodel.TagInfo, error)
|
||||||
GetImageListWithCVEFixedFn func(repo, cveID string) ([]cvemodel.TagInfo, error)
|
GetImageListWithCVEFixedFn func(repo, cveID string) ([]cvemodel.TagInfo, error)
|
||||||
GetCVEListForImageFn func(repo string, reference string, searchedCVE string, pageInput cveinfo.PageInput,
|
GetCVEListForImageFn func(repo string, reference string, searchedCVE string, pageInput cvemodel.PageInput,
|
||||||
) ([]cvemodel.CVE, common.PageInfo, error)
|
) ([]cvemodel.CVE, common.PageInfo, error)
|
||||||
GetCVESummaryForImageFn func(repo string, reference string,
|
GetCVESummaryForImageFn func(repo string, reference string,
|
||||||
) (cveinfo.ImageCVESummary, error)
|
) (cvemodel.ImageCVESummary, error)
|
||||||
|
GetCVESummaryForImageMediaFn func(repo string, digest, mediaType string,
|
||||||
|
) (cvemodel.ImageCVESummary, error)
|
||||||
CompareSeveritiesFn func(severity1, severity2 string) int
|
CompareSeveritiesFn func(severity1, severity2 string) int
|
||||||
UpdateDBFn func() error
|
UpdateDBFn func() error
|
||||||
}
|
}
|
||||||
|
@ -34,7 +35,7 @@ func (cveInfo CveInfoMock) GetImageListWithCVEFixed(repo, cveID string) ([]cvemo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cveInfo CveInfoMock) GetCVEListForImage(repo string, reference string,
|
func (cveInfo CveInfoMock) GetCVEListForImage(repo string, reference string,
|
||||||
searchedCVE string, pageInput cveinfo.PageInput,
|
searchedCVE string, pageInput cvemodel.PageInput,
|
||||||
) (
|
) (
|
||||||
[]cvemodel.CVE,
|
[]cvemodel.CVE,
|
||||||
common.PageInfo,
|
common.PageInfo,
|
||||||
|
@ -48,12 +49,21 @@ func (cveInfo CveInfoMock) GetCVEListForImage(repo string, reference string,
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cveInfo CveInfoMock) GetCVESummaryForImage(repo string, reference string,
|
func (cveInfo CveInfoMock) GetCVESummaryForImage(repo string, reference string,
|
||||||
) (cveinfo.ImageCVESummary, error) {
|
) (cvemodel.ImageCVESummary, error) {
|
||||||
if cveInfo.GetCVESummaryForImageFn != nil {
|
if cveInfo.GetCVESummaryForImageFn != nil {
|
||||||
return cveInfo.GetCVESummaryForImageFn(repo, reference)
|
return cveInfo.GetCVESummaryForImageFn(repo, reference)
|
||||||
}
|
}
|
||||||
|
|
||||||
return cveinfo.ImageCVESummary{}, nil
|
return cvemodel.ImageCVESummary{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cveInfo CveInfoMock) GetCVESummaryForImageMedia(repo, digest, mediaType string,
|
||||||
|
) (cvemodel.ImageCVESummary, error) {
|
||||||
|
if cveInfo.GetCVESummaryForImageMediaFn != nil {
|
||||||
|
return cveInfo.GetCVESummaryForImageMediaFn(repo, digest, mediaType)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cvemodel.ImageCVESummary{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cveInfo CveInfoMock) CompareSeverities(severity1, severity2 string) int {
|
func (cveInfo CveInfoMock) CompareSeverities(severity1, severity2 string) int {
|
||||||
|
@ -74,6 +84,7 @@ func (cveInfo CveInfoMock) UpdateDB() error {
|
||||||
|
|
||||||
type CveScannerMock struct {
|
type CveScannerMock struct {
|
||||||
IsImageFormatScannableFn func(repo string, reference string) (bool, error)
|
IsImageFormatScannableFn func(repo string, reference string) (bool, error)
|
||||||
|
IsImageMediaScannableFn func(repo string, digest, mediaType string) (bool, error)
|
||||||
ScanImageFn func(image string) (map[string]cvemodel.CVE, error)
|
ScanImageFn func(image string) (map[string]cvemodel.CVE, error)
|
||||||
CompareSeveritiesFn func(severity1, severity2 string) int
|
CompareSeveritiesFn func(severity1, severity2 string) int
|
||||||
UpdateDBFn func() error
|
UpdateDBFn func() error
|
||||||
|
@ -87,6 +98,14 @@ func (scanner CveScannerMock) IsImageFormatScannable(repo string, reference stri
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (scanner CveScannerMock) IsImageMediaScannable(repo string, digest, mediaType string) (bool, error) {
|
||||||
|
if scanner.IsImageMediaScannableFn != nil {
|
||||||
|
return scanner.IsImageMediaScannableFn(repo, digest, mediaType)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (scanner CveScannerMock) ScanImage(image string) (map[string]cvemodel.CVE, error) {
|
func (scanner CveScannerMock) ScanImage(image string) (map[string]cvemodel.CVE, error) {
|
||||||
if scanner.ScanImageFn != nil {
|
if scanner.ScanImageFn != nil {
|
||||||
return scanner.ScanImageFn(image)
|
return scanner.ScanImageFn(image)
|
||||||
|
|
|
@ -216,7 +216,7 @@ func (olu BaseOciLayoutUtils) GetImageTagsWithTimestamp(repo string) ([]cvemodel
|
||||||
|
|
||||||
tagsInfo = append(tagsInfo,
|
tagsInfo = append(tagsInfo,
|
||||||
cvemodel.TagInfo{
|
cvemodel.TagInfo{
|
||||||
Name: val,
|
Tag: val,
|
||||||
Timestamp: timeStamp,
|
Timestamp: timeStamp,
|
||||||
Descriptor: cvemodel.Descriptor{
|
Descriptor: cvemodel.Descriptor{
|
||||||
Digest: digest,
|
Digest: digest,
|
||||||
|
|
|
@ -450,7 +450,7 @@ func TestTagsInfo(t *testing.T) {
|
||||||
allTags := make([]cvemodel.TagInfo, 0)
|
allTags := make([]cvemodel.TagInfo, 0)
|
||||||
|
|
||||||
firstTag := cvemodel.TagInfo{
|
firstTag := cvemodel.TagInfo{
|
||||||
Name: "1.0.0",
|
Tag: "1.0.0",
|
||||||
Descriptor: cvemodel.Descriptor{
|
Descriptor: cvemodel.Descriptor{
|
||||||
Digest: "sha256:eca04f027f414362596f2632746d8a178362170b9ac9af772011fedcc3877ebb",
|
Digest: "sha256:eca04f027f414362596f2632746d8a178362170b9ac9af772011fedcc3877ebb",
|
||||||
MediaType: ispec.MediaTypeImageManifest,
|
MediaType: ispec.MediaTypeImageManifest,
|
||||||
|
@ -458,7 +458,7 @@ func TestTagsInfo(t *testing.T) {
|
||||||
Timestamp: time.Now(),
|
Timestamp: time.Now(),
|
||||||
}
|
}
|
||||||
secondTag := cvemodel.TagInfo{
|
secondTag := cvemodel.TagInfo{
|
||||||
Name: "1.0.1",
|
Tag: "1.0.1",
|
||||||
Descriptor: cvemodel.Descriptor{
|
Descriptor: cvemodel.Descriptor{
|
||||||
Digest: "sha256:eca04f027f414362596f2632746d8a179362170b9ac9af772011fedcc3877ebb",
|
Digest: "sha256:eca04f027f414362596f2632746d8a179362170b9ac9af772011fedcc3877ebb",
|
||||||
MediaType: ispec.MediaTypeImageManifest,
|
MediaType: ispec.MediaTypeImageManifest,
|
||||||
|
@ -466,7 +466,7 @@ func TestTagsInfo(t *testing.T) {
|
||||||
Timestamp: time.Now(),
|
Timestamp: time.Now(),
|
||||||
}
|
}
|
||||||
thirdTag := cvemodel.TagInfo{
|
thirdTag := cvemodel.TagInfo{
|
||||||
Name: "1.0.2",
|
Tag: "1.0.2",
|
||||||
Descriptor: cvemodel.Descriptor{
|
Descriptor: cvemodel.Descriptor{
|
||||||
Digest: "sha256:eca04f027f414362596f2632746d8a170362170b9ac9af772011fedcc3877ebb",
|
Digest: "sha256:eca04f027f414362596f2632746d8a170362170b9ac9af772011fedcc3877ebb",
|
||||||
MediaType: ispec.MediaTypeImageManifest,
|
MediaType: ispec.MediaTypeImageManifest,
|
||||||
|
@ -474,7 +474,7 @@ func TestTagsInfo(t *testing.T) {
|
||||||
Timestamp: time.Now(),
|
Timestamp: time.Now(),
|
||||||
}
|
}
|
||||||
fourthTag := cvemodel.TagInfo{
|
fourthTag := cvemodel.TagInfo{
|
||||||
Name: "1.0.3",
|
Tag: "1.0.3",
|
||||||
Descriptor: cvemodel.Descriptor{
|
Descriptor: cvemodel.Descriptor{
|
||||||
Digest: "sha256:eca04f027f414362596f2632746d8a171362170b9ac9af772011fedcc3877ebb",
|
Digest: "sha256:eca04f027f414362596f2632746d8a171362170b9ac9af772011fedcc3877ebb",
|
||||||
MediaType: ispec.MediaTypeImageManifest,
|
MediaType: ispec.MediaTypeImageManifest,
|
||||||
|
@ -485,6 +485,6 @@ func TestTagsInfo(t *testing.T) {
|
||||||
allTags = append(allTags, firstTag, secondTag, thirdTag, fourthTag)
|
allTags = append(allTags, firstTag, secondTag, thirdTag, fourthTag)
|
||||||
|
|
||||||
latestTag := ocilayout.GetLatestTag(allTags)
|
latestTag := ocilayout.GetLatestTag(allTags)
|
||||||
So(latestTag.Name, ShouldEqual, "1.0.3")
|
So(latestTag.Tag, ShouldEqual, "1.0.3")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue