mirror of
https://github.com/project-zot/zot.git
synced 2024-12-16 21:56:37 -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: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/alpine:3.17.3 oci:${TESTDATA}/alpine:3.17.3; \
|
||||
chmod -R a=rwx ${TESTDATA}
|
||||
|
||||
.PHONY: run-bench
|
||||
|
|
|
@ -73,7 +73,7 @@ var (
|
|||
ErrEmptyRepoName = errors.New("repodb: repo name 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")
|
||||
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")
|
||||
ErrOffsetIsNegative = errors.New("pageturner: offset has negative value")
|
||||
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")
|
||||
ErrSyncImageNotSigned = errors.New("sync: image is not signed")
|
||||
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"
|
||||
"zotregistry.io/zot/pkg/api"
|
||||
"zotregistry.io/zot/pkg/api/config"
|
||||
zcommon "zotregistry.io/zot/pkg/common"
|
||||
extconf "zotregistry.io/zot/pkg/extensions/config"
|
||||
"zotregistry.io/zot/pkg/extensions/monitoring"
|
||||
cveinfo "zotregistry.io/zot/pkg/extensions/search/cve"
|
||||
|
@ -1035,7 +1036,7 @@ func TestServerCVEResponse(t *testing.T) {
|
|||
space := regexp.MustCompile(`\s+`)
|
||||
str := space.ReplaceAllString(buff.String(), " ")
|
||||
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")
|
||||
})
|
||||
|
||||
|
@ -1172,7 +1173,8 @@ func getMockCveInfo(repoDB repodb.RepoDB, log log.Logger) cveinfo.CveInfo {
|
|||
// Setup test CVE data in mock scanner
|
||||
scanner := mocks.CveScannerMock{
|
||||
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{
|
||||
"CVE-1": {
|
||||
ID: "CVE-1",
|
||||
|
@ -1223,12 +1225,20 @@ func getMockCveInfo(repoDB repodb.RepoDB, log log.Logger) cveinfo.CveInfo {
|
|||
return false, err
|
||||
}
|
||||
|
||||
manifestDigestStr, ok := repoMeta.Tags[inputTag]
|
||||
if !ok {
|
||||
return false, zotErrors.ErrTagMetaNotFound
|
||||
manifestDigestStr := reference
|
||||
|
||||
if zcommon.IsTag(reference) {
|
||||
var ok bool
|
||||
|
||||
descriptor, ok := repoMeta.Tags[inputTag]
|
||||
if !ok {
|
||||
return false, zotErrors.ErrTagMetaNotFound
|
||||
}
|
||||
|
||||
manifestDigestStr = descriptor.Digest
|
||||
}
|
||||
|
||||
manifestDigest, err := godigest.Parse(manifestDigestStr.Digest)
|
||||
manifestDigest, err := godigest.Parse(manifestDigestStr)
|
||||
if err != nil {
|
||||
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,
|
||||
imageName, cveID string,
|
||||
) (*common.FixedTags, error) {
|
||||
fixedTags := &common.FixedTags{
|
||||
) (*common.ImageListWithCVEFixedResponse, error) {
|
||||
fixedTags := &common.ImageListWithCVEFixedResponse{
|
||||
Errors: nil,
|
||||
ImageListWithCVEFixed: struct {
|
||||
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,
|
||||
cveID string) (*common.ImagesForCve, error)
|
||||
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,
|
||||
derivedImage string) (*common.DerivedImageListResponse, error)
|
||||
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,
|
||||
username, password, imageName, cveID string,
|
||||
) (*common.FixedTags, error) {
|
||||
) (*common.ImageListWithCVEFixedResponse, error) {
|
||||
query := fmt.Sprintf(`
|
||||
{
|
||||
ImageListWithCVEFixed(id: "%s", image: "%s") {
|
||||
|
@ -398,7 +398,7 @@ func (service searchService) getFixedTagsForCVEGQL(ctx context.Context, config s
|
|||
}`,
|
||||
cveID, imageName)
|
||||
|
||||
result := &common.FixedTags{}
|
||||
result := &common.ImageListWithCVEFixedResponse{}
|
||||
|
||||
err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
|
||||
|
||||
|
@ -847,7 +847,7 @@ func (service searchService) getFixedTagsForCVE(ctx context.Context, config sear
|
|||
}
|
||||
}`, cvid, imageName)
|
||||
|
||||
result := &common.FixedTags{}
|
||||
result := &common.ImageListWithCVEFixedResponse{}
|
||||
|
||||
err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
|
||||
if err != nil {
|
||||
|
|
|
@ -112,7 +112,7 @@ type Annotation struct {
|
|||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type FixedTags struct {
|
||||
type ImageListWithCVEFixedResponse struct {
|
||||
Errors []ErrorGQL `json:"errors"`
|
||||
ImageListWithCVEFixed `json:"data"`
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/opencontainers/go-digest"
|
||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
|
@ -101,7 +102,7 @@ func GetRepoRefference(repo string) (string, string, bool, error) {
|
|||
repoName, tag, found := strings.Cut(repo, ":")
|
||||
|
||||
if !found {
|
||||
return "", "", false, zerr.ErrInvalidRepoTagFormat
|
||||
return "", "", false, zerr.ErrInvalidRepoRefFormat
|
||||
}
|
||||
|
||||
return repoName, tag, true, nil
|
||||
|
@ -109,3 +110,22 @@ func GetRepoRefference(repo string) (string, string, bool, error) {
|
|||
|
||||
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"
|
||||
|
||||
"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/log"
|
||||
"zotregistry.io/zot/pkg/meta/bolt"
|
||||
|
@ -74,9 +74,9 @@ func TestConvertErrors(t *testing.T) {
|
|||
map[string]repodb.IndexData{},
|
||||
convert.SkipQGLField{},
|
||||
mocks.CveInfoMock{
|
||||
GetCVESummaryForImageFn: func(repo string, reference string,
|
||||
) (cveinfo.ImageCVESummary, error) {
|
||||
return cveinfo.ImageCVESummary{}, ErrTestError
|
||||
GetCVESummaryForImageMediaFn: func(repo string, digest, mediaType string,
|
||||
) (cvemodel.ImageCVESummary, error) {
|
||||
return cvemodel.ImageCVESummary{}, ErrTestError
|
||||
},
|
||||
},
|
||||
)
|
||||
|
@ -120,9 +120,8 @@ func TestConvertErrors(t *testing.T) {
|
|||
},
|
||||
map[string]repodb.ManifestMetadata{},
|
||||
mocks.CveInfoMock{
|
||||
GetCVESummaryForImageFn: func(repo, reference string,
|
||||
) (cveinfo.ImageCVESummary, error) {
|
||||
return cveinfo.ImageCVESummary{}, ErrTestError
|
||||
GetCVESummaryForImageMediaFn: func(repo, digest, mediaType string) (cvemodel.ImageCVESummary, error) {
|
||||
return cvemodel.ImageCVESummary{}, ErrTestError
|
||||
},
|
||||
},
|
||||
)
|
||||
|
@ -153,9 +152,8 @@ func TestConvertErrors(t *testing.T) {
|
|||
ConfigBlob: configBlob,
|
||||
},
|
||||
mocks.CveInfoMock{
|
||||
GetCVESummaryForImageFn: func(repo, reference string,
|
||||
) (cveinfo.ImageCVESummary, error) {
|
||||
return cveinfo.ImageCVESummary{}, ErrTestError
|
||||
GetCVESummaryForImageMediaFn: func(repo, digest, mediaType string) (cvemodel.ImageCVESummary, error) {
|
||||
return cvemodel.ImageCVESummary{}, ErrTestError
|
||||
},
|
||||
},
|
||||
)
|
||||
|
@ -187,12 +185,7 @@ func TestConvertErrors(t *testing.T) {
|
|||
ConfigBlob: []byte("bad json"),
|
||||
},
|
||||
nil,
|
||||
mocks.CveInfoMock{
|
||||
GetCVESummaryForImageFn: func(repo, reference string,
|
||||
) (cveinfo.ImageCVESummary, error) {
|
||||
return cveinfo.ImageCVESummary{}, ErrTestError
|
||||
},
|
||||
},
|
||||
mocks.CveInfoMock{},
|
||||
)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
|
@ -227,9 +220,8 @@ func TestConvertErrors(t *testing.T) {
|
|||
},
|
||||
nil,
|
||||
mocks.CveInfoMock{
|
||||
GetCVESummaryForImageFn: func(repo, reference string,
|
||||
) (cveinfo.ImageCVESummary, error) {
|
||||
return cveinfo.ImageCVESummary{}, ErrTestError
|
||||
GetCVESummaryForImageMediaFn: func(repo, digest, mediaType string) (cvemodel.ImageCVESummary, error) {
|
||||
return cvemodel.ImageCVESummary{}, ErrTestError
|
||||
},
|
||||
},
|
||||
)
|
||||
|
@ -259,9 +251,8 @@ func TestConvertErrors(t *testing.T) {
|
|||
Vulnerabilities: false,
|
||||
},
|
||||
mocks.CveInfoMock{
|
||||
GetCVESummaryForImageFn: func(repo, reference string,
|
||||
) (cveinfo.ImageCVESummary, error) {
|
||||
return cveinfo.ImageCVESummary{}, ErrTestError
|
||||
GetCVESummaryForImageMediaFn: func(repo, digest, mediaType string) (cvemodel.ImageCVESummary, error) {
|
||||
return cvemodel.ImageCVESummary{}, ErrTestError
|
||||
},
|
||||
}, log.NewLogger("debug", ""),
|
||||
)
|
||||
|
@ -286,9 +277,8 @@ func TestConvertErrors(t *testing.T) {
|
|||
Vulnerabilities: false,
|
||||
},
|
||||
mocks.CveInfoMock{
|
||||
GetCVESummaryForImageFn: func(repo, reference string,
|
||||
) (cveinfo.ImageCVESummary, error) {
|
||||
return cveinfo.ImageCVESummary{}, ErrTestError
|
||||
GetCVESummaryForImageMediaFn: func(repo, digest, mediaType string) (cvemodel.ImageCVESummary, error) {
|
||||
return cvemodel.ImageCVESummary{}, ErrTestError
|
||||
},
|
||||
}, 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
|
||||
// Check if vulnerability scanning is disabled
|
||||
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 {
|
||||
// Log the error, but we should still include the image in results
|
||||
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 {
|
||||
imageCveSummary, err = cveInfo.GetCVESummaryForImage(repo, tag)
|
||||
imageCveSummary, err = cveInfo.GetCVESummaryForImageMedia(repo, indexDigestStr, ispec.MediaTypeImageIndex)
|
||||
|
||||
if err != nil {
|
||||
// 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()))
|
||||
}
|
||||
|
||||
imageCveSummary := cveinfo.ImageCVESummary{}
|
||||
imageCveSummary := cvemodel.ImageCVESummary{}
|
||||
|
||||
if cveInfo != nil && !skipCVE {
|
||||
imageCveSummary, err = cveInfo.GetCVESummaryForImage(repo, tag)
|
||||
imageCveSummary, err = cveInfo.GetCVESummaryForImageMedia(repo, manifestDigest, ispec.MediaTypeImageManifest)
|
||||
|
||||
if err != nil {
|
||||
// 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()))
|
||||
}
|
||||
|
||||
imageCveSummary := cveinfo.ImageCVESummary{}
|
||||
imageCveSummary := cvemodel.ImageCVESummary{}
|
||||
|
||||
if cveInfo != nil && !skipCVE {
|
||||
imageCveSummary, err = cveInfo.GetCVESummaryForImage(repo, tag)
|
||||
imageCveSummary, err = cveInfo.GetCVESummaryForImageMedia(repo, manifestDigestStr, ispec.MediaTypeImageManifest)
|
||||
|
||||
if err != nil {
|
||||
// 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
|
||||
// Check if vulnerability scanning is disabled
|
||||
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 {
|
||||
// Log the error, but we should still include the image in results
|
||||
graphql.AddError(
|
||||
|
|
|
@ -2,15 +2,14 @@ package cveinfo
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
godigest "github.com/opencontainers/go-digest"
|
||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
|
||||
"zotregistry.io/zot/errors"
|
||||
"zotregistry.io/zot/pkg/common"
|
||||
zcommon "zotregistry.io/zot/pkg/common"
|
||||
cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model"
|
||||
"zotregistry.io/zot/pkg/extensions/search/cve/trivy"
|
||||
"zotregistry.io/zot/pkg/log"
|
||||
|
@ -21,24 +20,22 @@ import (
|
|||
type CveInfo interface {
|
||||
GetImageListForCVE(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)
|
||||
GetCVESummaryForImage(repo, tag string) (ImageCVESummary, error)
|
||||
GetCVEListForImage(repo, tag string, searchedCVE string, pageinput cvemodel.PageInput,
|
||||
) ([]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
|
||||
UpdateDB() error
|
||||
}
|
||||
|
||||
type Scanner interface {
|
||||
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
|
||||
UpdateDB() error
|
||||
}
|
||||
|
||||
type ImageCVESummary struct {
|
||||
Count int
|
||||
MaxSeverity string
|
||||
}
|
||||
|
||||
type BaseCveInfo struct {
|
||||
Log log.Logger
|
||||
Scanner Scanner
|
||||
|
@ -70,19 +67,19 @@ func (cveinfo BaseCveInfo) GetImageListForCVE(repo, cveID string) ([]cvemodel.Ta
|
|||
|
||||
for tag, descriptor := range repoMeta.Tags {
|
||||
switch descriptor.MediaType {
|
||||
case ispec.MediaTypeImageManifest:
|
||||
case ispec.MediaTypeImageManifest, ispec.MediaTypeImageIndex:
|
||||
manifestDigestStr := descriptor.Digest
|
||||
|
||||
manifestDigest := godigest.Digest(manifestDigestStr)
|
||||
|
||||
isScanableImage, err := cveinfo.Scanner.IsImageFormatScannable(repo, tag)
|
||||
isScanableImage, err := cveinfo.Scanner.IsImageFormatScannable(repo, manifestDigestStr)
|
||||
if !isScanableImage || err != nil {
|
||||
cveinfo.Log.Info().Str("image", repo+":"+tag).Err(err).Msg("image is not scanable")
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
cveMap, err := cveinfo.Scanner.ScanImage(getImageString(repo, tag))
|
||||
cveMap, err := cveinfo.Scanner.ScanImage(zcommon.GetFullImageName(repo, tag))
|
||||
if err != nil {
|
||||
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 {
|
||||
imgList = append(imgList, cvemodel.TagInfo{
|
||||
Name: tag,
|
||||
Tag: tag,
|
||||
Descriptor: cvemodel.Descriptor{
|
||||
Digest: manifestDigest,
|
||||
MediaType: descriptor.MediaType,
|
||||
|
@ -118,87 +115,81 @@ func (cveinfo BaseCveInfo) GetImageListWithCVEFixed(repo, cveID string) ([]cvemo
|
|||
vulnerableTags := make([]cvemodel.TagInfo, 0)
|
||||
allTags := make([]cvemodel.TagInfo, 0)
|
||||
|
||||
var hasCVE bool
|
||||
|
||||
for tag, descriptor := range repoMeta.Tags {
|
||||
manifestDigestStr := descriptor.Digest
|
||||
|
||||
switch descriptor.MediaType {
|
||||
case ispec.MediaTypeImageManifest:
|
||||
manifestDigest, err := godigest.Parse(manifestDigestStr)
|
||||
manifestDigestStr := descriptor.Digest
|
||||
|
||||
tagInfo, err := getTagInfoForManifest(tag, manifestDigestStr, cveinfo.RepoDB)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
image := fmt.Sprintf("%s:%s", repo, tag)
|
||||
|
||||
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")
|
||||
|
||||
if cveinfo.isManifestVulnerable(repo, tag, manifestDigestStr, cveID) {
|
||||
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 {
|
||||
cveinfo.Log.Debug().Str("image", image).Str("cve-id", cveID).
|
||||
Msg("scanning failed, adding as a vulnerable image")
|
||||
|
||||
vulnerableTags = append(vulnerableTags, tagInfo)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
hasCVE = false
|
||||
vulnerableManifests := []cvemodel.DescriptorInfo{}
|
||||
allManifests := []cvemodel.DescriptorInfo{}
|
||||
|
||||
for id := range cveMap {
|
||||
if id == cveID {
|
||||
hasCVE = true
|
||||
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")
|
||||
|
||||
break
|
||||
continue
|
||||
}
|
||||
|
||||
manifestDescriptorInfo := cvemodel.DescriptorInfo{
|
||||
Descriptor: tagInfo.Descriptor,
|
||||
Timestamp: tagInfo.Timestamp,
|
||||
}
|
||||
|
||||
allManifests = append(allManifests, manifestDescriptorInfo)
|
||||
|
||||
if cveinfo.isManifestVulnerable(repo, tag, manifest.Digest.String(), cveID) {
|
||||
vulnerableManifests = append(vulnerableManifests, manifestDescriptorInfo)
|
||||
}
|
||||
}
|
||||
|
||||
if hasCVE {
|
||||
vulnerableTags = append(vulnerableTags, tagInfo)
|
||||
if len(allManifests) > 0 {
|
||||
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:
|
||||
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
|
||||
}
|
||||
|
||||
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) {
|
||||
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,
|
||||
common.PageInfo,
|
||||
zcommon.PageInfo,
|
||||
error,
|
||||
) {
|
||||
isValidImage, err := cveinfo.Scanner.IsImageFormatScannable(repo, tag)
|
||||
isValidImage, err := cveinfo.Scanner.IsImageFormatScannable(repo, ref)
|
||||
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)
|
||||
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)
|
||||
if err != nil {
|
||||
return []cvemodel.CVE{}, common.PageInfo{}, err
|
||||
return []cvemodel.CVE{}, zcommon.PageInfo{}, err
|
||||
}
|
||||
|
||||
filterCVEList(cveMap, searchedCVE, pageFinder)
|
||||
|
@ -259,23 +361,22 @@ func (cveinfo BaseCveInfo) GetCVEListForImage(repo, tag string, searchedCVE stri
|
|||
return cveList, pageInfo, nil
|
||||
}
|
||||
|
||||
func (cveinfo BaseCveInfo) GetCVESummaryForImage(repo, tag string,
|
||||
) (ImageCVESummary, error) {
|
||||
func (cveinfo BaseCveInfo) GetCVESummaryForImage(repo, ref string) (cvemodel.ImageCVESummary, error) {
|
||||
// There are several cases, expected returned values below:
|
||||
// not scannable / error during scan - max severity "" - cve count 0 - 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
|
||||
imageCVESummary := ImageCVESummary{
|
||||
imageCVESummary := cvemodel.ImageCVESummary{
|
||||
Count: 0,
|
||||
MaxSeverity: "",
|
||||
}
|
||||
|
||||
isValidImage, err := cveinfo.Scanner.IsImageFormatScannable(repo, tag)
|
||||
isValidImage, err := cveinfo.Scanner.IsImageFormatScannable(repo, ref)
|
||||
if !isValidImage {
|
||||
return imageCVESummary, err
|
||||
}
|
||||
|
||||
image := getImageString(repo, tag)
|
||||
image := zcommon.GetFullImageName(repo, ref)
|
||||
|
||||
cveMap, err := cveinfo.Scanner.ScanImage(image)
|
||||
if err != nil {
|
||||
|
@ -300,20 +401,41 @@ func (cveinfo BaseCveInfo) GetCVESummaryForImage(repo, tag string,
|
|||
return imageCVESummary, nil
|
||||
}
|
||||
|
||||
func referenceIsDigest(reference string) bool {
|
||||
_, err := godigest.Parse(reference)
|
||||
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func getImageString(repo, reference string) string {
|
||||
image := repo + ":" + reference
|
||||
|
||||
if referenceIsDigest(reference) {
|
||||
image = repo + "@" + reference
|
||||
func (cveinfo BaseCveInfo) GetCVESummaryForImageMedia(repo, digest, mediaType string,
|
||||
) (cvemodel.ImageCVESummary, error) {
|
||||
imageCVESummary := cvemodel.ImageCVESummary{
|
||||
Count: 0,
|
||||
MaxSeverity: "",
|
||||
}
|
||||
|
||||
return image
|
||||
isValidImage, err := cveinfo.Scanner.IsImageMediaScannable(repo, digest, mediaType)
|
||||
if !isValidImage {
|
||||
return imageCVESummary, err
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -333,10 +455,21 @@ func GetFixedTags(allTags, vulnerableTags []cvemodel.TagInfo) []cvemodel.TagInfo
|
|||
vulnerableTagMap := make(map[string]cvemodel.TagInfo, len(vulnerableTags))
|
||||
|
||||
for _, tag := range vulnerableTags {
|
||||
vulnerableTagMap[tag.Name] = tag
|
||||
vulnerableTagMap[tag.Tag] = tag
|
||||
|
||||
if tag.Timestamp.Before(earliestVulnerable.Timestamp) {
|
||||
earliestVulnerable = tag
|
||||
switch tag.Descriptor.MediaType {
|
||||
case ispec.MediaTypeImageManifest:
|
||||
if tag.Timestamp.Before(earliestVulnerable.Timestamp) {
|
||||
earliestVulnerable = tag
|
||||
}
|
||||
case ispec.MediaTypeImageIndex:
|
||||
for _, manifestDesc := range tag.Manifests {
|
||||
if manifestDesc.Timestamp.Before(earliestVulnerable.Timestamp) {
|
||||
earliestVulnerable = tag
|
||||
}
|
||||
}
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -348,18 +481,62 @@ func GetFixedTags(allTags, vulnerableTags []cvemodel.TagInfo) []cvemodel.TagInfo
|
|||
// There may be older images which have a fix or
|
||||
// newer images which don't
|
||||
for _, tag := range allTags {
|
||||
if tag.Timestamp.Before(earliestVulnerable.Timestamp) {
|
||||
// The vulnerability did not exist at the time this
|
||||
// image was built
|
||||
switch tag.Descriptor.MediaType {
|
||||
case ispec.MediaTypeImageManifest:
|
||||
if tag.Timestamp.Before(earliestVulnerable.Timestamp) {
|
||||
// The vulnerability did not exist at the time this
|
||||
// image was built
|
||||
continue
|
||||
}
|
||||
// If the image is old enough for the vulnerability to
|
||||
// exist, but it was not detected, it means it contains
|
||||
// the fix
|
||||
if _, ok := vulnerableTagMap[tag.Tag]; !ok {
|
||||
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
|
||||
}
|
||||
// If the image is old enough for the vulnerability to
|
||||
// exist, but it was not detected, it means it contains
|
||||
// the fix
|
||||
if _, ok := vulnerableTagMap[tag.Name]; !ok {
|
||||
fixedTags = append(fixedTags, tag)
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
@ -25,10 +26,12 @@ import (
|
|||
"zotregistry.io/zot/pkg/api/config"
|
||||
"zotregistry.io/zot/pkg/api/constants"
|
||||
apiErr "zotregistry.io/zot/pkg/api/errors"
|
||||
zcommon "zotregistry.io/zot/pkg/common"
|
||||
extconf "zotregistry.io/zot/pkg/extensions/config"
|
||||
"zotregistry.io/zot/pkg/extensions/monitoring"
|
||||
cveinfo "zotregistry.io/zot/pkg/extensions/search/cve"
|
||||
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/meta/bolt"
|
||||
"zotregistry.io/zot/pkg/meta/repodb"
|
||||
|
@ -382,10 +385,16 @@ func TestImageFormat(t *testing.T) {
|
|||
GetRepoMetaFn: func(repo string) (repodb.RepoMetadata, error) {
|
||||
return repodb.RepoMetadata{
|
||||
Tags: map[string]repodb.Descriptor{
|
||||
"tag": {MediaType: ispec.MediaTypeImageIndex},
|
||||
"tag": {
|
||||
MediaType: ispec.MediaTypeImageIndex,
|
||||
Digest: godigest.FromString("digest").String(),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
GetIndexDataFn: func(indexDigest godigest.Digest) (repodb.IndexData, error) {
|
||||
return repodb.IndexData{IndexBlob: []byte(`{}`)}, nil
|
||||
},
|
||||
}
|
||||
storeController := storage.StoreController{
|
||||
DefaultStore: mocks.MockedImageStore{},
|
||||
|
@ -395,7 +404,7 @@ func TestImageFormat(t *testing.T) {
|
|||
|
||||
isScanable, err := cveInfo.Scanner.IsImageFormatScannable("repo", "tag")
|
||||
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
|
||||
scanner := mocks.CveScannerMock{
|
||||
ScanImageFn: func(image string) (map[string]cvemodel.CVE, error) {
|
||||
repo1 := "repo1"
|
||||
|
||||
repo, ref, _ := zcommon.GetImageDirAndReference(image)
|
||||
// Images in chronological order
|
||||
if image == "repo1:0.1.0" {
|
||||
if image == "repo1:0.1.0" || ref == digest11.String() {
|
||||
return map[string]cvemodel.CVE{
|
||||
"CVE1": {
|
||||
ID: "CVE1",
|
||||
|
@ -1036,7 +1048,8 @@ func TestCVEStruct(t *testing.T) {
|
|||
}, 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{
|
||||
"CVE1": {
|
||||
ID: "CVE1",
|
||||
|
@ -1059,7 +1072,7 @@ func TestCVEStruct(t *testing.T) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
if image == "repo1:1.1.0" {
|
||||
if image == "repo1:1.1.0" || (repo == repo1 && ref == digest13.String()) {
|
||||
return map[string]cvemodel.CVE{
|
||||
"CVE3": {
|
||||
ID: "CVE3",
|
||||
|
@ -1072,7 +1085,7 @@ func TestCVEStruct(t *testing.T) {
|
|||
|
||||
// As a minor release on 1.0.0 banch
|
||||
// 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{
|
||||
"CVE1": {
|
||||
ID: "CVE1",
|
||||
|
@ -1089,7 +1102,7 @@ func TestCVEStruct(t *testing.T) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
if image == "repoIndex:tagIndex" {
|
||||
if image == "repoIndex:tagIndex" || (repo == "repoIndex" && ref == indexDigest.String()) {
|
||||
return map[string]cvemodel.CVE{
|
||||
"CVE1": {
|
||||
ID: "CVE1",
|
||||
|
@ -1119,12 +1132,20 @@ func TestCVEStruct(t *testing.T) {
|
|||
return false, err
|
||||
}
|
||||
|
||||
manifestDigestStr, ok := repoMeta.Tags[inputTag]
|
||||
if !ok {
|
||||
return false, zerr.ErrTagMetaNotFound
|
||||
manifestDigestStr := reference
|
||||
|
||||
if zcommon.IsTag(reference) {
|
||||
var ok bool
|
||||
|
||||
descriptor, ok := repoMeta.Tags[inputTag]
|
||||
if !ok {
|
||||
return false, zerr.ErrTagMetaNotFound
|
||||
}
|
||||
|
||||
manifestDigestStr = descriptor.Digest
|
||||
}
|
||||
|
||||
manifestDigest, err := godigest.Parse(manifestDigestStr.Digest)
|
||||
manifestDigest, err := godigest.Parse(manifestDigestStr)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
@ -1154,6 +1175,15 @@ func TestCVEStruct(t *testing.T) {
|
|||
|
||||
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", "")
|
||||
|
@ -1213,7 +1243,7 @@ func TestCVEStruct(t *testing.T) {
|
|||
|
||||
t.Log("Test GetCVEListForImage")
|
||||
|
||||
pageInput := cveinfo.PageInput{
|
||||
pageInput := cvemodel.PageInput{
|
||||
SortBy: cveinfo.SeverityDsc,
|
||||
}
|
||||
|
||||
|
@ -1289,14 +1319,14 @@ func TestCVEStruct(t *testing.T) {
|
|||
tagList, err := cveInfo.GetImageListWithCVEFixed("repo1", "CVE1")
|
||||
So(err, ShouldBeNil)
|
||||
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")
|
||||
So(err, ShouldBeNil)
|
||||
So(len(tagList), ShouldEqual, 2)
|
||||
expectedTags := []string{"1.0.1", "1.1.0"}
|
||||
So(expectedTags, ShouldContain, tagList[0].Name)
|
||||
So(expectedTags, ShouldContain, tagList[1].Name)
|
||||
So(expectedTags, ShouldContain, tagList[0].Tag)
|
||||
So(expectedTags, ShouldContain, tagList[1].Tag)
|
||||
|
||||
tagList, err = cveInfo.GetImageListWithCVEFixed("repo1", "CVE3")
|
||||
So(err, ShouldBeNil)
|
||||
|
@ -1309,7 +1339,7 @@ func TestCVEStruct(t *testing.T) {
|
|||
tagList, err = cveInfo.GetImageListWithCVEFixed("repo6", "CVE1")
|
||||
So(err, ShouldBeNil)
|
||||
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
|
||||
tagList, err = cveInfo.GetImageListWithCVEFixed("repo2", "CVE100")
|
||||
|
@ -1341,22 +1371,22 @@ func TestCVEStruct(t *testing.T) {
|
|||
So(err, ShouldBeNil)
|
||||
So(len(tagList), ShouldEqual, 3)
|
||||
expectedTags = []string{"0.1.0", "1.0.0", "1.0.1"}
|
||||
So(expectedTags, ShouldContain, tagList[0].Name)
|
||||
So(expectedTags, ShouldContain, tagList[1].Name)
|
||||
So(expectedTags, ShouldContain, tagList[2].Name)
|
||||
So(expectedTags, ShouldContain, tagList[0].Tag)
|
||||
So(expectedTags, ShouldContain, tagList[1].Tag)
|
||||
So(expectedTags, ShouldContain, tagList[2].Tag)
|
||||
|
||||
tagList, err = cveInfo.GetImageListForCVE("repo1", "CVE2")
|
||||
So(err, ShouldBeNil)
|
||||
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")
|
||||
So(err, ShouldBeNil)
|
||||
So(len(tagList), ShouldEqual, 3)
|
||||
expectedTags = []string{"1.0.0", "1.0.1", "1.1.0"}
|
||||
So(expectedTags, ShouldContain, tagList[0].Name)
|
||||
So(expectedTags, ShouldContain, tagList[1].Name)
|
||||
So(expectedTags, ShouldContain, tagList[2].Name)
|
||||
So(expectedTags, ShouldContain, tagList[0].Tag)
|
||||
So(expectedTags, ShouldContain, tagList[1].Tag)
|
||||
So(expectedTags, ShouldContain, tagList[2].Tag)
|
||||
|
||||
// Image/repo doesn't have the CVE at all
|
||||
tagList, err = cveInfo.GetImageListForCVE("repo6", "CVE1")
|
||||
|
@ -1419,7 +1449,7 @@ func TestCVEStruct(t *testing.T) {
|
|||
|
||||
tagList, err = cveInfo.GetImageListForCVE("repoIndex", "CVE1")
|
||||
So(err, ShouldBeNil)
|
||||
So(len(tagList), ShouldEqual, 0)
|
||||
So(len(tagList), ShouldEqual, 1)
|
||||
|
||||
cveInfo = cveinfo.BaseCveInfo{Log: log, Scanner: mocks.CveScannerMock{
|
||||
IsImageFormatScannableFn: func(repo, reference string) (bool, error) {
|
||||
|
@ -1448,7 +1478,7 @@ func getTags() ([]cvemodel.TagInfo, []cvemodel.TagInfo) {
|
|||
tags := make([]cvemodel.TagInfo, 0)
|
||||
|
||||
firstTag := cvemodel.TagInfo{
|
||||
Name: "1.0.0",
|
||||
Tag: "1.0.0",
|
||||
Descriptor: cvemodel.Descriptor{
|
||||
Digest: "sha256:eca04f027f414362596f2632746d8a178362170b9ac9af772011fedcc3877ebb",
|
||||
MediaType: ispec.MediaTypeImageManifest,
|
||||
|
@ -1456,7 +1486,7 @@ func getTags() ([]cvemodel.TagInfo, []cvemodel.TagInfo) {
|
|||
Timestamp: time.Now(),
|
||||
}
|
||||
secondTag := cvemodel.TagInfo{
|
||||
Name: "1.0.1",
|
||||
Tag: "1.0.1",
|
||||
Descriptor: cvemodel.Descriptor{
|
||||
Digest: "sha256:eca04f027f414362596f2632746d8a179362170b9ac9af772011fedcc3877ebb",
|
||||
MediaType: ispec.MediaTypeImageManifest,
|
||||
|
@ -1464,7 +1494,7 @@ func getTags() ([]cvemodel.TagInfo, []cvemodel.TagInfo) {
|
|||
Timestamp: time.Now(),
|
||||
}
|
||||
thirdTag := cvemodel.TagInfo{
|
||||
Name: "1.0.2",
|
||||
Tag: "1.0.2",
|
||||
Descriptor: cvemodel.Descriptor{
|
||||
Digest: "sha256:eca04f027f414362596f2632746d8a170362170b9ac9af772011fedcc3877ebb",
|
||||
MediaType: ispec.MediaTypeImageManifest,
|
||||
|
@ -1472,7 +1502,7 @@ func getTags() ([]cvemodel.TagInfo, []cvemodel.TagInfo) {
|
|||
Timestamp: time.Now(),
|
||||
}
|
||||
fourthTag := cvemodel.TagInfo{
|
||||
Name: "1.0.3",
|
||||
Tag: "1.0.3",
|
||||
Descriptor: cvemodel.Descriptor{
|
||||
Digest: "sha256:eca04f027f414362596f2632746d8a171362170b9ac9af772011fedcc3877ebb",
|
||||
MediaType: ispec.MediaTypeImageManifest,
|
||||
|
@ -1496,10 +1526,298 @@ func TestFixedTags(t *testing.T) {
|
|||
So(len(fixedTags), ShouldEqual, 2)
|
||||
|
||||
fixedTags = cveinfo.GetFixedTags(allTags, append(vulnerableTags, cvemodel.TagInfo{
|
||||
Name: "taginfo",
|
||||
Descriptor: cvemodel.Descriptor{},
|
||||
Timestamp: time.Date(2000, time.July, 20, 10, 10, 10, 10, time.UTC),
|
||||
Tag: "taginfo",
|
||||
Descriptor: cvemodel.Descriptor{
|
||||
MediaType: ispec.MediaTypeImageManifest,
|
||||
Digest: "sha256:eca04f027f414362596f2632746d8a179362170b9ac9af772011fedcc3877ebb",
|
||||
},
|
||||
Timestamp: time.Date(2000, time.July, 20, 10, 10, 10, 10, time.UTC),
|
||||
}))
|
||||
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"
|
||||
)
|
||||
|
||||
type ImageCVESummary struct {
|
||||
Count int
|
||||
MaxSeverity string
|
||||
}
|
||||
|
||||
//nolint:tagliatelle // graphQL schema
|
||||
type CVE struct {
|
||||
ID string `json:"Id"`
|
||||
|
@ -47,8 +52,15 @@ type Descriptor struct {
|
|||
MediaType string
|
||||
}
|
||||
|
||||
type DescriptorInfo struct {
|
||||
Descriptor
|
||||
|
||||
Timestamp time.Time
|
||||
}
|
||||
|
||||
type TagInfo struct {
|
||||
Name string
|
||||
Tag string
|
||||
Descriptor Descriptor
|
||||
Manifests []DescriptorInfo
|
||||
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"
|
||||
)
|
||||
|
||||
type SortCriteria string
|
||||
|
||||
const (
|
||||
AlphabeticAsc = SortCriteria("ALPHABETIC_ASC")
|
||||
AlphabeticDsc = SortCriteria("ALPHABETIC_DSC")
|
||||
SeverityDsc = SortCriteria("SEVERITY")
|
||||
AlphabeticAsc = cvemodel.SortCriteria("ALPHABETIC_ASC")
|
||||
AlphabeticDsc = cvemodel.SortCriteria("ALPHABETIC_DSC")
|
||||
SeverityDsc = cvemodel.SortCriteria("SEVERITY")
|
||||
)
|
||||
|
||||
func SortFunctions() map[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{
|
||||
func SortFunctions() map[cvemodel.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,
|
||||
AlphabeticDsc: SortByAlphabeticDsc,
|
||||
SeverityDsc: SortBySeverity,
|
||||
|
@ -56,12 +54,12 @@ type PageFinder interface {
|
|||
type CvePageFinder struct {
|
||||
limit int
|
||||
offset int
|
||||
sortBy SortCriteria
|
||||
sortBy cvemodel.SortCriteria
|
||||
pageBuffer []cvemodel.CVE
|
||||
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 == "" {
|
||||
sortBy = SeverityDsc
|
||||
}
|
||||
|
@ -131,9 +129,3 @@ func (bpt *CvePageFinder) Page() ([]cvemodel.CVE, common.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("defaults", func() {
|
||||
// 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(len(cves), ShouldEqual, 5)
|
||||
So(pageInfo.ItemCount, ShouldEqual, 5)
|
||||
|
@ -198,7 +198,7 @@ func TestCVEPagination(t *testing.T) {
|
|||
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(len(cves), 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", "",
|
||||
cveinfo.PageInput{SortBy: cveinfo.AlphabeticAsc})
|
||||
cvemodel.PageInput{SortBy: cveinfo.AlphabeticAsc})
|
||||
So(err, ShouldBeNil)
|
||||
So(len(cves), ShouldEqual, 5)
|
||||
So(pageInfo.ItemCount, ShouldEqual, 5)
|
||||
|
@ -228,7 +228,7 @@ func TestCVEPagination(t *testing.T) {
|
|||
|
||||
sort.Strings(cveIds)
|
||||
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "1.0.0", "",
|
||||
cveinfo.PageInput{SortBy: cveinfo.AlphabeticAsc})
|
||||
cvemodel.PageInput{SortBy: cveinfo.AlphabeticAsc})
|
||||
So(err, ShouldBeNil)
|
||||
So(len(cves), ShouldEqual, 30)
|
||||
So(pageInfo.ItemCount, ShouldEqual, 30)
|
||||
|
@ -239,7 +239,7 @@ func TestCVEPagination(t *testing.T) {
|
|||
|
||||
sort.Sort(sort.Reverse(sort.StringSlice(cveIds)))
|
||||
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "1.0.0", "",
|
||||
cveinfo.PageInput{SortBy: cveinfo.AlphabeticDsc})
|
||||
cvemodel.PageInput{SortBy: cveinfo.AlphabeticDsc})
|
||||
So(err, ShouldBeNil)
|
||||
So(len(cves), 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", "",
|
||||
cveinfo.PageInput{SortBy: cveinfo.SeverityDsc})
|
||||
cvemodel.PageInput{SortBy: cveinfo.SeverityDsc})
|
||||
So(err, ShouldBeNil)
|
||||
So(len(cves), ShouldEqual, 30)
|
||||
So(pageInfo.ItemCount, ShouldEqual, 30)
|
||||
|
@ -267,7 +267,7 @@ func TestCVEPagination(t *testing.T) {
|
|||
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,
|
||||
Offset: 1,
|
||||
SortBy: cveinfo.AlphabeticAsc,
|
||||
|
@ -281,7 +281,7 @@ func TestCVEPagination(t *testing.T) {
|
|||
So(cves[1].ID, ShouldEqual, "CVE2")
|
||||
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,
|
||||
Offset: 1,
|
||||
SortBy: cveinfo.AlphabeticDsc,
|
||||
|
@ -294,7 +294,7 @@ func TestCVEPagination(t *testing.T) {
|
|||
So(cves[0].ID, ShouldEqual, "CVE3")
|
||||
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,
|
||||
Offset: 1,
|
||||
SortBy: cveinfo.SeverityDsc,
|
||||
|
@ -311,7 +311,7 @@ func TestCVEPagination(t *testing.T) {
|
|||
}
|
||||
|
||||
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,
|
||||
Offset: 20,
|
||||
SortBy: cveinfo.AlphabeticAsc,
|
||||
|
@ -327,7 +327,7 @@ func TestCVEPagination(t *testing.T) {
|
|||
})
|
||||
|
||||
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,
|
||||
Offset: 3,
|
||||
SortBy: cveinfo.AlphabeticAsc,
|
||||
|
@ -340,7 +340,7 @@ func TestCVEPagination(t *testing.T) {
|
|||
So(cves[0].ID, ShouldEqual, "CVE3")
|
||||
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,
|
||||
Offset: 3,
|
||||
SortBy: cveinfo.AlphabeticDsc,
|
||||
|
@ -353,7 +353,7 @@ func TestCVEPagination(t *testing.T) {
|
|||
So(cves[0].ID, ShouldEqual, "CVE1")
|
||||
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,
|
||||
Offset: 3,
|
||||
SortBy: cveinfo.SeverityDsc,
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
_ "modernc.org/sqlite"
|
||||
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
zcommon "zotregistry.io/zot/pkg/common"
|
||||
cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model"
|
||||
"zotregistry.io/zot/pkg/log"
|
||||
"zotregistry.io/zot/pkg/meta/repodb"
|
||||
|
@ -181,54 +182,61 @@ func (scanner Scanner) runTrivy(opts flag.Options) (types.Report, error) {
|
|||
return report, nil
|
||||
}
|
||||
|
||||
func (scanner Scanner) IsImageFormatScannable(repo, tag string) (bool, error) {
|
||||
image := repo + ":" + tag
|
||||
func (scanner Scanner) IsImageFormatScannable(repo, ref string) (bool, error) {
|
||||
var (
|
||||
digestStr = ref
|
||||
mediaType string
|
||||
)
|
||||
|
||||
if scanner.cache.Get(image) != nil {
|
||||
return true, nil
|
||||
if zcommon.IsTag(ref) {
|
||||
imgDescriptor, err := repodb.GetImageDescriptor(scanner.repoDB, repo, ref)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
digestStr = imgDescriptor.Digest
|
||||
mediaType = imgDescriptor.MediaType
|
||||
} else {
|
||||
var found bool
|
||||
|
||||
found, mediaType = repodb.FindMediaTypeForDigest(scanner.repoDB, godigest.Digest(ref))
|
||||
if !found {
|
||||
return false, zerr.ErrManifestNotFound
|
||||
}
|
||||
}
|
||||
|
||||
repoMeta, err := scanner.repoDB.GetRepoMeta(repo)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return scanner.IsImageMediaScannable(repo, digestStr, mediaType)
|
||||
}
|
||||
|
||||
var ok bool
|
||||
func (scanner Scanner) IsImageMediaScannable(repo, digestStr, mediaType string) (bool, error) {
|
||||
image := repo + "@" + digestStr
|
||||
|
||||
imageDescriptor, ok := repoMeta.Tags[tag]
|
||||
if !ok {
|
||||
return false, zerr.ErrTagMetaNotFound
|
||||
}
|
||||
|
||||
switch imageDescriptor.MediaType {
|
||||
switch mediaType {
|
||||
case ispec.MediaTypeImageManifest:
|
||||
ok, err := scanner.isManifestScanable(imageDescriptor)
|
||||
ok, err := scanner.isManifestScanable(digestStr)
|
||||
if err != nil {
|
||||
return ok, fmt.Errorf("image '%s' %w", image, err)
|
||||
}
|
||||
|
||||
return ok, nil
|
||||
case ispec.MediaTypeImageIndex:
|
||||
ok, err := scanner.isIndexScanable(imageDescriptor)
|
||||
ok, err := scanner.isIndexScanable(digestStr)
|
||||
if err != nil {
|
||||
return ok, fmt.Errorf("image '%s' %w", image, err)
|
||||
}
|
||||
|
||||
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
|
||||
func (scanner Scanner) isManifestScanable(digestStr string) (bool, error) {
|
||||
if scanner.cache.Get(digestStr) != nil {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
manifestData, err := scanner.repoDB.GetManifestData(manifestDigest)
|
||||
manifestData, err := scanner.repoDB.GetManifestData(godigest.Digest(digestStr))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
@ -257,18 +265,98 @@ func (scanner Scanner) isManifestScanable(descriptor repodb.Descriptor) (bool, e
|
|||
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
|
||||
}
|
||||
|
||||
func (scanner Scanner) ScanImage(image string) (map[string]cvemodel.CVE, error) {
|
||||
if scanner.cache.Get(image) != nil {
|
||||
return scanner.cache.Get(image), nil
|
||||
var (
|
||||
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
|
||||
}
|
||||
|
||||
digest = imgDescriptor.Digest
|
||||
mediaType = imgDescriptor.MediaType
|
||||
} else {
|
||||
var found bool
|
||||
|
||||
found, mediaType = repodb.FindMediaTypeForDigest(scanner.repoDB, godigest.Digest(ref))
|
||||
if !found {
|
||||
return map[string]cvemodel.CVE{}, zerr.ErrManifestNotFound
|
||||
}
|
||||
}
|
||||
|
||||
cveidMap := make(map[string]cvemodel.CVE)
|
||||
var (
|
||||
cveIDMap map[string]cvemodel.CVE
|
||||
err error
|
||||
)
|
||||
|
||||
scanner.log.Debug().Str("image", image).Msg("scanning image")
|
||||
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()
|
||||
opts := scanner.getTrivyOptions(image)
|
||||
|
@ -276,8 +364,6 @@ func (scanner Scanner) ScanImage(image string) (map[string]cvemodel.CVE, error)
|
|||
scanner.dbLock.Unlock()
|
||||
|
||||
if err != nil { //nolint: wsl
|
||||
scanner.log.Error().Err(err).Str("image", image).Msg("unable to scan image")
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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.
|
||||
func (scanner Scanner) UpdateDB() error {
|
||||
// We need a lock as using multiple substores each with it's own DB
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
zerr "zotregistry.io/zot/errors"
|
||||
"zotregistry.io/zot/pkg/common"
|
||||
"zotregistry.io/zot/pkg/extensions/monitoring"
|
||||
"zotregistry.io/zot/pkg/extensions/search/cve/model"
|
||||
"zotregistry.io/zot/pkg/log"
|
||||
"zotregistry.io/zot/pkg/meta/bolt"
|
||||
"zotregistry.io/zot/pkg/meta/repodb"
|
||||
|
@ -27,6 +28,7 @@ import (
|
|||
"zotregistry.io/zot/pkg/storage/local"
|
||||
storageTypes "zotregistry.io/zot/pkg/storage/types"
|
||||
"zotregistry.io/zot/pkg/test"
|
||||
"zotregistry.io/zot/pkg/test/mocks"
|
||||
)
|
||||
|
||||
func generateTestImage(storeController storage.StoreController, image string) {
|
||||
|
@ -100,9 +102,6 @@ func TestMultipleStoragePath(t *testing.T) {
|
|||
repoDB, err := boltdb_wrapper.NewBoltDBWrapper(boltDriver, log)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = repodb.ParseStorage(repoDB, storeController, log)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
scanner := NewScanner(storeController, repoDB, "ghcr.io/project-zot/trivy-db", "", log)
|
||||
|
||||
So(scanner.storeController.DefaultStore, ShouldNotBeNil)
|
||||
|
@ -125,6 +124,9 @@ func TestMultipleStoragePath(t *testing.T) {
|
|||
generateTestImage(storeController, img1)
|
||||
generateTestImage(storeController, img2)
|
||||
|
||||
err = repodb.ParseStorage(repoDB, storeController, log)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
// Try to scan without the DB being downloaded
|
||||
_, err = scanner.ScanImage(img0)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
@ -508,3 +510,138 @@ func TestDefaultTrivyDBUrl(t *testing.T) {
|
|||
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
|
||||
"""
|
||||
CVEListForImage(
|
||||
"Image name in format ` + "`" + `repository:tag` + "`" + `"
|
||||
"Image name in format ` + "`" + `repository:tag` + "`" + ` or ` + "`" + `repository@digest` + "`" + `"
|
||||
image: String!,
|
||||
"Sets the parameters of the requested page"
|
||||
requestedPage: PageInput
|
||||
|
|
|
@ -125,10 +125,10 @@ func getImageListForDigest(ctx context.Context, digest string, repoDB repodb.Rep
|
|||
}
|
||||
|
||||
pageInput := repodb.PageInput{
|
||||
Limit: safeDerefferencing(requestedPage.Limit, 0),
|
||||
Offset: safeDerefferencing(requestedPage.Offset, 0),
|
||||
Limit: safeDereferencing(requestedPage.Limit, 0),
|
||||
Offset: safeDereferencing(requestedPage.Offset, 0),
|
||||
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{}
|
||||
}
|
||||
|
||||
pageInput := cveinfo.PageInput{
|
||||
Limit: safeDerefferencing(requestedPage.Limit, 0),
|
||||
Offset: safeDerefferencing(requestedPage.Offset, 0),
|
||||
SortBy: cveinfo.SortCriteria(
|
||||
safeDerefferencing(requestedPage.SortBy, gql_generated.SortCriteriaSeverity),
|
||||
pageInput := cvemodel.PageInput{
|
||||
Limit: safeDereferencing(requestedPage.Limit, 0),
|
||||
Offset: safeDereferencing(requestedPage.Offset, 0),
|
||||
SortBy: cvemodel.SortCriteria(
|
||||
safeDereferencing(requestedPage.SortBy, gql_generated.SortCriteriaSeverity),
|
||||
),
|
||||
}
|
||||
|
||||
repo, ref, isTag := zcommon.GetImageDirAndReference(image)
|
||||
repo, ref, _ := zcommon.GetImageDirAndReference(image)
|
||||
|
||||
if ref == "" {
|
||||
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)
|
||||
if err != nil {
|
||||
return &gql_generated.CVEResultForImage{}, err
|
||||
|
@ -365,8 +361,17 @@ func FilterByTagInfo(tagsInfo []cvemodel.TagInfo) repodb.FilterFunc {
|
|||
manifestDigest := godigest.FromBytes(manifestMeta.ManifestBlob).String()
|
||||
|
||||
for _, tagInfo := range tagsInfo {
|
||||
if tagInfo.Descriptor.Digest.String() == manifestDigest {
|
||||
return true
|
||||
switch tagInfo.Descriptor.MediaType {
|
||||
case ispec.MediaTypeImageManifest:
|
||||
if tagInfo.Descriptor.Digest.String() == manifestDigest {
|
||||
return true
|
||||
}
|
||||
case ispec.MediaTypeImageIndex:
|
||||
for _, manifestDesc := range tagInfo.Manifests {
|
||||
if manifestDesc.Digest.String() == manifestDigest {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -423,10 +428,10 @@ func getImageListForCVE(
|
|||
|
||||
// Actual page requested by user
|
||||
pageInput := repodb.PageInput{
|
||||
Limit: safeDerefferencing(requestedPage.Limit, 0),
|
||||
Offset: safeDerefferencing(requestedPage.Offset, 0),
|
||||
Limit: safeDereferencing(requestedPage.Limit, 0),
|
||||
Offset: safeDereferencing(requestedPage.Offset, 0),
|
||||
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
|
||||
pageInput := repodb.PageInput{
|
||||
Limit: safeDerefferencing(requestedPage.Limit, 0),
|
||||
Offset: safeDerefferencing(requestedPage.Offset, 0),
|
||||
Limit: safeDereferencing(requestedPage.Limit, 0),
|
||||
Offset: safeDereferencing(requestedPage.Offset, 0),
|
||||
SortBy: repodb.SortCriteria(
|
||||
safeDerefferencing(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime),
|
||||
safeDereferencing(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime),
|
||||
),
|
||||
}
|
||||
|
||||
|
@ -535,10 +540,10 @@ func repoListWithNewestImage(
|
|||
}
|
||||
|
||||
pageInput := repodb.PageInput{
|
||||
Limit: safeDerefferencing(requestedPage.Limit, 0),
|
||||
Offset: safeDerefferencing(requestedPage.Offset, 0),
|
||||
Limit: safeDereferencing(requestedPage.Limit, 0),
|
||||
Offset: safeDereferencing(requestedPage.Offset, 0),
|
||||
SortBy: repodb.SortCriteria(
|
||||
safeDerefferencing(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime),
|
||||
safeDereferencing(requestedPage.SortBy, gql_generated.SortCriteriaUpdateTime),
|
||||
),
|
||||
}
|
||||
|
||||
|
@ -620,10 +625,10 @@ func getFilteredPaginatedRepos(
|
|||
}
|
||||
|
||||
pageInput := repodb.PageInput{
|
||||
Limit: safeDerefferencing(requestedPage.Limit, 0),
|
||||
Offset: safeDerefferencing(requestedPage.Offset, 0),
|
||||
Limit: safeDereferencing(requestedPage.Limit, 0),
|
||||
Offset: safeDereferencing(requestedPage.Offset, 0),
|
||||
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{
|
||||
Limit: safeDerefferencing(requestedPage.Limit, 0),
|
||||
Offset: safeDerefferencing(requestedPage.Offset, 0),
|
||||
Limit: safeDereferencing(requestedPage.Limit, 0),
|
||||
Offset: safeDereferencing(requestedPage.Offset, 0),
|
||||
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{
|
||||
Limit: safeDerefferencing(requestedPage.Limit, 0),
|
||||
Offset: safeDerefferencing(requestedPage.Offset, 0),
|
||||
Limit: safeDereferencing(requestedPage.Limit, 0),
|
||||
Offset: safeDereferencing(requestedPage.Offset, 0),
|
||||
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{
|
||||
Limit: safeDerefferencing(requestedPage.Limit, 0),
|
||||
Offset: safeDerefferencing(requestedPage.Offset, 0),
|
||||
Limit: safeDereferencing(requestedPage.Limit, 0),
|
||||
Offset: safeDereferencing(requestedPage.Offset, 0),
|
||||
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{
|
||||
Limit: safeDerefferencing(requestedPage.Limit, 0),
|
||||
Offset: safeDerefferencing(requestedPage.Offset, 0),
|
||||
Limit: safeDereferencing(requestedPage.Limit, 0),
|
||||
Offset: safeDereferencing(requestedPage.Offset, 0),
|
||||
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
|
||||
}
|
||||
|
||||
manifestMeta, err := repoDB.GetManifestMeta(repo, godigest.Digest(digest))
|
||||
manifestData, err := repoDB.GetManifestData(godigest.Digest(digest))
|
||||
if err != nil {
|
||||
graphql.AddError(ctx, fmt.Errorf("resolver: failed to get manifest meta for image %s:%s with manifest digest %s %w",
|
||||
repo, tag, digest, err))
|
||||
|
@ -1125,7 +1130,10 @@ func expandedRepoInfo(ctx context.Context, repo string, repoDB repodb.RepoDB, cv
|
|||
continue
|
||||
}
|
||||
|
||||
manifestMetaMap[digest] = manifestMeta
|
||||
manifestMetaMap[digest] = repodb.ManifestMetadata{
|
||||
ManifestBlob: manifestData.ManifestBlob,
|
||||
ConfigBlob: manifestData.ConfigBlob,
|
||||
}
|
||||
case ispec.MediaTypeImageIndex:
|
||||
digest := descriptor.Digest
|
||||
|
||||
|
@ -1154,7 +1162,7 @@ func expandedRepoInfo(ctx context.Context, repo string, repoDB repodb.RepoDB, cv
|
|||
var errorOccured bool
|
||||
|
||||
for _, descriptor := range indexContent.Manifests {
|
||||
manifestMeta, err := repoDB.GetManifestMeta(repo, descriptor.Digest)
|
||||
manifestData, err := repoDB.GetManifestData(descriptor.Digest)
|
||||
if err != nil {
|
||||
graphql.AddError(ctx,
|
||||
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
|
||||
}
|
||||
|
||||
manifestMetaMap[descriptor.Digest.String()] = manifestMeta
|
||||
manifestMetaMap[descriptor.Digest.String()] = repodb.ManifestMetadata{
|
||||
ManifestBlob: manifestData.ManifestBlob,
|
||||
ConfigBlob: manifestData.ConfigBlob,
|
||||
}
|
||||
}
|
||||
|
||||
if errorOccured {
|
||||
|
@ -1210,7 +1221,7 @@ func (p timeSlice) Swap(i, j int) {
|
|||
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 {
|
||||
return *pointer
|
||||
}
|
||||
|
@ -1236,10 +1247,10 @@ func getImageList(ctx context.Context, repo string, repoDB repodb.RepoDB, cveInf
|
|||
}
|
||||
|
||||
pageInput := repodb.PageInput{
|
||||
Limit: safeDerefferencing(requestedPage.Limit, 0),
|
||||
Offset: safeDerefferencing(requestedPage.Offset, 0),
|
||||
Limit: safeDereferencing(requestedPage.Limit, 0),
|
||||
Offset: safeDereferencing(requestedPage.Offset, 0),
|
||||
SortBy: repodb.SortCriteria(
|
||||
safeDerefferencing(requestedPage.SortBy, gql_generated.SortCriteriaRelevance),
|
||||
safeDereferencing(requestedPage.SortBy, gql_generated.SortCriteriaRelevance),
|
||||
),
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -1987,7 +1988,12 @@ func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
|
|||
ScanImageFn: func(image string) (map[string]cvemodel.CVE, error) {
|
||||
digest, ok := tagsMap[image]
|
||||
if !ok {
|
||||
return map[string]cvemodel.CVE{}, nil
|
||||
if !strings.Contains(image, "@") {
|
||||
return map[string]cvemodel.CVE{}, nil
|
||||
}
|
||||
|
||||
_, digestStr := common.GetImageDirAndDigest(image)
|
||||
digest = godigest.Digest(digestStr)
|
||||
}
|
||||
|
||||
if digest.String() == digest1.String() {
|
||||
|
@ -2075,7 +2081,7 @@ func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
|
|||
repoWithDigestRef := fmt.Sprintf("repo@%s", dig)
|
||||
|
||||
_, 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)
|
||||
So(err, ShouldBeNil)
|
||||
|
@ -3304,12 +3310,12 @@ func TestExpandedRepoInfo(t *testing.T) {
|
|||
},
|
||||
}, nil
|
||||
},
|
||||
GetManifestMetaFn: func(repo string, manifestDigest godigest.Digest) (repodb.ManifestMetadata, error) {
|
||||
GetManifestDataFn: func(manifestDigest godigest.Digest) (repodb.ManifestData, error) {
|
||||
switch manifestDigest {
|
||||
case "errorDigest":
|
||||
return repodb.ManifestMetadata{}, ErrTestError
|
||||
return repodb.ManifestData{}, ErrTestError
|
||||
default:
|
||||
return repodb.ManifestMetadata{
|
||||
return repodb.ManifestData{
|
||||
ManifestBlob: []byte("{}"),
|
||||
ConfigBlob: []byte("{}"),
|
||||
}, nil
|
||||
|
|
|
@ -592,7 +592,7 @@ type Query {
|
|||
Returns a CVE list for the image specified in the argument
|
||||
"""
|
||||
CVEListForImage(
|
||||
"Image name in format `repository:tag`"
|
||||
"Image name in format `repository:tag` or `repository@digest`"
|
||||
image: String!,
|
||||
"Sets the parameters of the requested page"
|
||||
requestedPage: PageInput
|
||||
|
|
|
@ -235,7 +235,9 @@ func getMockCveInfo(repoDB repodb.RepoDB, log log.Logger) cveinfo.CveInfo {
|
|||
// Setup test CVE data in mock scanner
|
||||
scanner := mocks.CveScannerMock{
|
||||
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{
|
||||
"CVE1": {
|
||||
ID: "CVE1",
|
||||
|
@ -258,7 +260,9 @@ func getMockCveInfo(repoDB repodb.RepoDB, log log.Logger) cveinfo.CveInfo {
|
|||
}, 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{
|
||||
"CVE3": {
|
||||
ID: "CVE3",
|
||||
|
@ -275,7 +279,8 @@ func getMockCveInfo(repoDB repodb.RepoDB, log log.Logger) cveinfo.CveInfo {
|
|||
}, nil
|
||||
}
|
||||
|
||||
if image == "test-repo:latest" {
|
||||
if image == "test-repo:latest" ||
|
||||
image == "test-repo@sha256:9f8e1a125c4fb03a0f157d75999b73284ccc5cba18eb772e4643e3499343607e" {
|
||||
return map[string]cvemodel.CVE{
|
||||
"CVE1": {
|
||||
ID: "CVE1",
|
||||
|
@ -320,12 +325,20 @@ func getMockCveInfo(repoDB repodb.RepoDB, log log.Logger) cveinfo.CveInfo {
|
|||
return false, err
|
||||
}
|
||||
|
||||
manifestDigestStr, ok := repoMeta.Tags[inputTag]
|
||||
if !ok {
|
||||
return false, zerr.ErrTagMetaNotFound
|
||||
manifestDigestStr := reference
|
||||
|
||||
if zcommon.IsTag(reference) {
|
||||
var ok bool
|
||||
|
||||
descriptor, ok := repoMeta.Tags[inputTag]
|
||||
if !ok {
|
||||
return false, zerr.ErrTagMetaNotFound
|
||||
}
|
||||
|
||||
manifestDigestStr = descriptor.Digest
|
||||
}
|
||||
|
||||
manifestDigest, err := godigest.Parse(manifestDigestStr.Digest)
|
||||
manifestDigest, err := godigest.Parse(manifestDigestStr)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
@ -758,6 +771,7 @@ func TestRepoListWithNewestImage(t *testing.T) {
|
|||
Name
|
||||
NewestImage{
|
||||
Tag
|
||||
Digest
|
||||
Vulnerabilities{
|
||||
MaxSeverity
|
||||
Count
|
||||
|
|
|
@ -176,7 +176,7 @@ func GetRepoTag(searchText string) (string, string, error) {
|
|||
splitSlice := strings.Split(searchText, ":")
|
||||
|
||||
if len(splitSlice) != repoTagCount {
|
||||
return "", "", zerr.ErrInvalidRepoTagFormat
|
||||
return "", "", zerr.ErrInvalidRepoRefFormat
|
||||
}
|
||||
|
||||
repo := strings.TrimSpace(splitSlice[0])
|
||||
|
@ -329,6 +329,7 @@ func FilterDataByRepo(foundRepos []repodb.RepoMetadata, manifestMetadataMap map[
|
|||
|
||||
foundindexDataMap[descriptor.Digest] = indexData
|
||||
default:
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1339,7 +1339,6 @@ func (bdw *DBWrapper) FilterTags(ctx context.Context, filter repodb.FilterFunc,
|
|||
matchedTags := make(map[string]repodb.Descriptor)
|
||||
// take all manifestMetas
|
||||
for tag, descriptor := range repoMeta.Tags {
|
||||
matchedTags[tag] = descriptor
|
||||
switch descriptor.MediaType {
|
||||
case ispec.MediaTypeImageManifest:
|
||||
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)
|
||||
}
|
||||
|
||||
if !filter(repoMeta, manifestMeta) {
|
||||
delete(matchedTags, tag)
|
||||
|
||||
continue
|
||||
if filter(repoMeta, manifestMeta) {
|
||||
matchedTags[tag] = descriptor
|
||||
manifestMetadataMap[manifestDigest] = manifestMeta
|
||||
}
|
||||
|
||||
manifestMetadataMap[manifestDigest] = manifestMeta
|
||||
case ispec.MediaTypeImageIndex:
|
||||
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)
|
||||
}
|
||||
|
||||
manifestHasBeenMatched := false
|
||||
matchedManifests := []ispec.Descriptor{}
|
||||
|
||||
for _, manifest := range indexContent.Manifests {
|
||||
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)
|
||||
}
|
||||
|
||||
manifestMetadataMap[manifestDigest] = manifestMeta
|
||||
|
||||
if filter(repoMeta, manifestMeta) {
|
||||
manifestHasBeenMatched = true
|
||||
matchedManifests = append(matchedManifests, manifest)
|
||||
manifestMetadataMap[manifestDigest] = manifestMeta
|
||||
}
|
||||
}
|
||||
|
||||
if !manifestHasBeenMatched {
|
||||
delete(matchedTags, tag)
|
||||
if len(matchedManifests) > 0 {
|
||||
indexContent.Manifests = matchedManifests
|
||||
|
||||
for _, manifest := range indexContent.Manifests {
|
||||
delete(manifestMetadataMap, manifest.Digest.String())
|
||||
indexBlob, err := json.Marshal(indexContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
indexData.IndexBlob = indexBlob
|
||||
|
||||
indexDataMap[indexDigest] = indexData
|
||||
indexDataMap[indexDigest] = indexData
|
||||
matchedTags[tag] = descriptor
|
||||
}
|
||||
default:
|
||||
bdw.Log.Error().Str("mediaType", descriptor.MediaType).Msg("Unsupported media type")
|
||||
|
||||
|
|
|
@ -2,6 +2,11 @@ package repodb
|
|||
|
||||
import (
|
||||
"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
|
||||
|
@ -55,3 +60,33 @@ func SortByDownloads(pageBuffer []DetailedRepoMeta) func(i, j int) bool {
|
|||
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)
|
||||
|
||||
matchedTags := make(map[string]repodb.Descriptor)
|
||||
for tag, descriptor := range repoMeta.Tags {
|
||||
matchedTags[tag] = descriptor
|
||||
|
||||
for tag, descriptor := range repoMeta.Tags {
|
||||
switch descriptor.MediaType {
|
||||
case ispec.MediaTypeImageManifest:
|
||||
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)
|
||||
}
|
||||
|
||||
if !filter(repoMeta, manifestMeta) {
|
||||
delete(matchedTags, tag)
|
||||
|
||||
continue
|
||||
if filter(repoMeta, manifestMeta) {
|
||||
matchedTags[tag] = descriptor
|
||||
manifestMetadataMap[manifestDigest] = manifestMeta
|
||||
}
|
||||
|
||||
manifestMetadataMap[manifestDigest] = manifestMeta
|
||||
case ispec.MediaTypeImageIndex:
|
||||
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)
|
||||
}
|
||||
|
||||
manifestHasBeenMatched := false
|
||||
matchedManifests := []ispec.Descriptor{}
|
||||
|
||||
for _, manifest := range indexContent.Manifests {
|
||||
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)
|
||||
}
|
||||
|
||||
manifestMetadataMap[manifestDigest] = manifestMeta
|
||||
|
||||
if filter(repoMeta, manifestMeta) {
|
||||
manifestHasBeenMatched = true
|
||||
matchedManifests = append(matchedManifests, manifest)
|
||||
manifestMetadataMap[manifestDigest] = manifestMeta
|
||||
}
|
||||
}
|
||||
|
||||
if !manifestHasBeenMatched {
|
||||
delete(matchedTags, tag)
|
||||
if len(matchedManifests) > 0 {
|
||||
indexContent.Manifests = matchedManifests
|
||||
|
||||
for _, manifest := range indexContent.Manifests {
|
||||
delete(manifestMetadataMap, manifest.Digest.String())
|
||||
indexBlob, err := json.Marshal(indexContent)
|
||||
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:
|
||||
dwr.Log.Error().Str("mediaType", descriptor.MediaType).Msg("Unsupported media type")
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
@ -45,6 +46,7 @@ import (
|
|||
"oras.land/oras-go/v2/registry/remote"
|
||||
"oras.land/oras-go/v2/registry/remote/auth"
|
||||
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
"zotregistry.io/zot/pkg/meta/repodb"
|
||||
"zotregistry.io/zot/pkg/storage"
|
||||
"zotregistry.io/zot/pkg/test/inject"
|
||||
|
@ -56,6 +58,8 @@ const (
|
|||
SleepTime = 100 * time.Millisecond
|
||||
)
|
||||
|
||||
var vulnerableLayer []byte //nolint: gochecknoglobals
|
||||
|
||||
var NotationPathLock = new(sync.Mutex) //nolint: gochecknoglobals
|
||||
|
||||
// which: manifest, config, layer
|
||||
|
@ -604,15 +608,8 @@ func GetRandomImageComponents(layerSize int) (ispec.Image, [][]byte, ispec.Manif
|
|||
|
||||
configDigest := godigest.FromBytes(configBlob)
|
||||
|
||||
layer := make([]byte, layerSize)
|
||||
|
||||
_, err = rand.Read(layer)
|
||||
if err != nil {
|
||||
return ispec.Image{}, [][]byte{}, ispec.Manifest{}, err
|
||||
}
|
||||
|
||||
layers := [][]byte{
|
||||
layer,
|
||||
GetRandomLayer(layerSize),
|
||||
}
|
||||
|
||||
schemaVersion := 2
|
||||
|
@ -639,6 +636,138 @@ func GetRandomImageComponents(layerSize int) (ispec.Image, [][]byte, ispec.Manif
|
|||
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) {
|
||||
const layerSize = 20
|
||||
|
||||
|
|
|
@ -2,17 +2,18 @@ package mocks
|
|||
|
||||
import (
|
||||
"zotregistry.io/zot/pkg/common"
|
||||
cveinfo "zotregistry.io/zot/pkg/extensions/search/cve"
|
||||
cvemodel "zotregistry.io/zot/pkg/extensions/search/cve/model"
|
||||
)
|
||||
|
||||
type CveInfoMock struct {
|
||||
GetImageListForCVEFn 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)
|
||||
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
|
||||
UpdateDBFn func() error
|
||||
}
|
||||
|
@ -34,7 +35,7 @@ func (cveInfo CveInfoMock) GetImageListWithCVEFixed(repo, cveID string) ([]cvemo
|
|||
}
|
||||
|
||||
func (cveInfo CveInfoMock) GetCVEListForImage(repo string, reference string,
|
||||
searchedCVE string, pageInput cveinfo.PageInput,
|
||||
searchedCVE string, pageInput cvemodel.PageInput,
|
||||
) (
|
||||
[]cvemodel.CVE,
|
||||
common.PageInfo,
|
||||
|
@ -48,12 +49,21 @@ func (cveInfo CveInfoMock) GetCVEListForImage(repo string, reference string,
|
|||
}
|
||||
|
||||
func (cveInfo CveInfoMock) GetCVESummaryForImage(repo string, reference string,
|
||||
) (cveinfo.ImageCVESummary, error) {
|
||||
) (cvemodel.ImageCVESummary, error) {
|
||||
if cveInfo.GetCVESummaryForImageFn != nil {
|
||||
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 {
|
||||
|
@ -74,6 +84,7 @@ func (cveInfo CveInfoMock) UpdateDB() error {
|
|||
|
||||
type CveScannerMock struct {
|
||||
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)
|
||||
CompareSeveritiesFn func(severity1, severity2 string) int
|
||||
UpdateDBFn func() error
|
||||
|
@ -87,6 +98,14 @@ func (scanner CveScannerMock) IsImageFormatScannable(repo string, reference stri
|
|||
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) {
|
||||
if scanner.ScanImageFn != nil {
|
||||
return scanner.ScanImageFn(image)
|
||||
|
|
|
@ -216,7 +216,7 @@ func (olu BaseOciLayoutUtils) GetImageTagsWithTimestamp(repo string) ([]cvemodel
|
|||
|
||||
tagsInfo = append(tagsInfo,
|
||||
cvemodel.TagInfo{
|
||||
Name: val,
|
||||
Tag: val,
|
||||
Timestamp: timeStamp,
|
||||
Descriptor: cvemodel.Descriptor{
|
||||
Digest: digest,
|
||||
|
|
|
@ -450,7 +450,7 @@ func TestTagsInfo(t *testing.T) {
|
|||
allTags := make([]cvemodel.TagInfo, 0)
|
||||
|
||||
firstTag := cvemodel.TagInfo{
|
||||
Name: "1.0.0",
|
||||
Tag: "1.0.0",
|
||||
Descriptor: cvemodel.Descriptor{
|
||||
Digest: "sha256:eca04f027f414362596f2632746d8a178362170b9ac9af772011fedcc3877ebb",
|
||||
MediaType: ispec.MediaTypeImageManifest,
|
||||
|
@ -458,7 +458,7 @@ func TestTagsInfo(t *testing.T) {
|
|||
Timestamp: time.Now(),
|
||||
}
|
||||
secondTag := cvemodel.TagInfo{
|
||||
Name: "1.0.1",
|
||||
Tag: "1.0.1",
|
||||
Descriptor: cvemodel.Descriptor{
|
||||
Digest: "sha256:eca04f027f414362596f2632746d8a179362170b9ac9af772011fedcc3877ebb",
|
||||
MediaType: ispec.MediaTypeImageManifest,
|
||||
|
@ -466,7 +466,7 @@ func TestTagsInfo(t *testing.T) {
|
|||
Timestamp: time.Now(),
|
||||
}
|
||||
thirdTag := cvemodel.TagInfo{
|
||||
Name: "1.0.2",
|
||||
Tag: "1.0.2",
|
||||
Descriptor: cvemodel.Descriptor{
|
||||
Digest: "sha256:eca04f027f414362596f2632746d8a170362170b9ac9af772011fedcc3877ebb",
|
||||
MediaType: ispec.MediaTypeImageManifest,
|
||||
|
@ -474,7 +474,7 @@ func TestTagsInfo(t *testing.T) {
|
|||
Timestamp: time.Now(),
|
||||
}
|
||||
fourthTag := cvemodel.TagInfo{
|
||||
Name: "1.0.3",
|
||||
Tag: "1.0.3",
|
||||
Descriptor: cvemodel.Descriptor{
|
||||
Digest: "sha256:eca04f027f414362596f2632746d8a171362170b9ac9af772011fedcc3877ebb",
|
||||
MediaType: ispec.MediaTypeImageManifest,
|
||||
|
@ -485,6 +485,6 @@ func TestTagsInfo(t *testing.T) {
|
|||
allTags = append(allTags, firstTag, secondTag, thirdTag, fourthTag)
|
||||
|
||||
latestTag := ocilayout.GetLatestTag(allTags)
|
||||
So(latestTag.Name, ShouldEqual, "1.0.3")
|
||||
So(latestTag.Tag, ShouldEqual, "1.0.3")
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue