Skip to content

Commit

Permalink
feat(cve): add option to exclude string from cve search (#2163)
Browse files Browse the repository at this point in the history
Signed-off-by: Laurentiu Niculae <[email protected]>
  • Loading branch information
laurentiuNiculae authored Jan 19, 2024
1 parent 355b1ee commit 3f97f87
Show file tree
Hide file tree
Showing 12 changed files with 135 additions and 62 deletions.
2 changes: 1 addition & 1 deletion pkg/cli/client/gql_queries.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func DerivedImageListQuery() GQLQuery {
func CVEListForImageQuery() GQLQuery {
return GQLQuery{
Name: "CVEListForImage",
Args: []string{"image", "requestedPage", "searchedCVE"},
Args: []string{"image", "requestedPage", "searchedCVE", "excludedCVE"},
ReturnType: CVEResultForImage(),
}
}
Expand Down
26 changes: 10 additions & 16 deletions pkg/extensions/search/cve/cve.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (

godigest "github.com/opencontainers/go-digest"
ispec "github.com/opencontainers/image-spec/specs-go/v1"
"golang.org/x/exp/slices"

zerr "zotregistry.io/zot/errors"
zcommon "zotregistry.io/zot/pkg/common"
Expand All @@ -22,8 +21,8 @@ import (
type CveInfo interface {
GetImageListForCVE(ctx context.Context, repo, cveID string) ([]cvemodel.TagInfo, error)
GetImageListWithCVEFixed(ctx context.Context, repo, cveID string) ([]cvemodel.TagInfo, error)
GetCVEListForImage(ctx context.Context, repo, tag string, searchedCVE string, pageinput cvemodel.PageInput,
) ([]cvemodel.CVE, cvemodel.ImageCVESummary, zcommon.PageInfo, error)
GetCVEListForImage(ctx context.Context, repo, tag string, searchedCVE string, excludedCVE string,
pageinput cvemodel.PageInput) ([]cvemodel.CVE, cvemodel.ImageCVESummary, zcommon.PageInfo, error)
GetCVESummaryForImageMedia(ctx context.Context, repo, digestStr, mediaType string) (cvemodel.ImageCVESummary, error)
}

Expand Down Expand Up @@ -330,27 +329,22 @@ func getConfigAndDigest(metaDB mTypes.MetaDB, manifestDigestStr string) (ispec.I
return manifestData.Manifests[0].Config, manifestDigest, err
}

func filterCVEList(cveMap map[string]cvemodel.CVE, searchedCVE string, pageFinder *CvePageFinder) {
func filterCVEList(cveMap map[string]cvemodel.CVE, searchedCVE, excludedCVE string, pageFinder *CvePageFinder) {
searchedCVE = strings.ToUpper(searchedCVE)

for _, cve := range cveMap {
if strings.Contains(strings.ToUpper(cve.Title), searchedCVE) ||
strings.Contains(strings.ToUpper(cve.ID), searchedCVE) ||
strings.Contains(strings.ToUpper(cve.Description), searchedCVE) ||
strings.Contains(strings.ToUpper(cve.Reference), searchedCVE) ||
strings.Contains(strings.ToUpper(cve.Severity), searchedCVE) ||
slices.ContainsFunc(cve.PackageList, func(pack cvemodel.Package) bool {
return strings.Contains(strings.ToUpper(pack.Name), searchedCVE) ||
strings.Contains(strings.ToUpper(pack.FixedVersion), searchedCVE) ||
strings.Contains(strings.ToUpper(pack.InstalledVersion), searchedCVE)
}) {
if excludedCVE != "" && cve.ContainsStr(excludedCVE) {
continue
}

if cve.ContainsStr(searchedCVE) {
pageFinder.Add(cve)
}
}
}

func (cveinfo BaseCveInfo) GetCVEListForImage(ctx context.Context, repo, ref string, searchedCVE string,
pageInput cvemodel.PageInput,
excludedCVE string, pageInput cvemodel.PageInput,
) (
[]cvemodel.CVE, cvemodel.ImageCVESummary, zcommon.PageInfo, error,
) {
Expand Down Expand Up @@ -379,7 +373,7 @@ func (cveinfo BaseCveInfo) GetCVEListForImage(ctx context.Context, repo, ref str
return []cvemodel.CVE{}, imageCVESummary, zcommon.PageInfo{}, err
}

filterCVEList(cveMap, searchedCVE, pageFinder)
filterCVEList(cveMap, searchedCVE, excludedCVE, pageFinder)

cveList, pageInfo := pageFinder.Page()

Expand Down
15 changes: 15 additions & 0 deletions pkg/extensions/search/cve/cve_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,21 @@ var ErrTestError = errors.New("test error")

func TestUtils(t *testing.T) {
Convey("Utils", t, func() {
Convey("cve.ContainsStr for package list", func() {
cve := cvemodel.CVE{
PackageList: []cvemodel.Package{
{
Name: "NameTest",
FixedVersion: "FixedVersionTest",
InstalledVersion: "InstalledVersionTest",
},
},
}

So(cve.ContainsStr("NameTest"), ShouldBeTrue)
So(cve.ContainsStr("FixedVersionTest"), ShouldBeTrue)
So(cve.ContainsStr("InstalledVersionTest"), ShouldBeTrue)
})
Convey("getConfigAndDigest", func() {
_, _, err := getConfigAndDigest(mocks.MetaDBMock{}, "bad-digest")
So(err, ShouldNotBeNil)
Expand Down
26 changes: 13 additions & 13 deletions pkg/extensions/search/cve/cve_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1192,7 +1192,7 @@ func TestCVEStruct(t *testing.T) { //nolint:gocyclo
ctx := context.Background()

// Image is found
cveList, cveSummary, pageInfo, err := cveInfo.GetCVEListForImage(ctx, repo1, "0.1.0", "", pageInput)
cveList, cveSummary, pageInfo, err := cveInfo.GetCVEListForImage(ctx, repo1, "0.1.0", "", "", pageInput)
So(err, ShouldBeNil)
So(len(cveList), ShouldEqual, 1)
So(cveList[0].ID, ShouldEqual, "CVE1")
Expand All @@ -1206,7 +1206,7 @@ func TestCVEStruct(t *testing.T) { //nolint:gocyclo
So(cveSummary.CriticalCount, ShouldEqual, 0)
So(cveSummary.MaxSeverity, ShouldEqual, "MEDIUM")

cveList, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, repo1, "1.0.0", "", pageInput)
cveList, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, repo1, "1.0.0", "", "", pageInput)
So(err, ShouldBeNil)
So(len(cveList), ShouldEqual, 3)
So(cveList[0].ID, ShouldEqual, "CVE2")
Expand All @@ -1222,7 +1222,7 @@ func TestCVEStruct(t *testing.T) { //nolint:gocyclo
So(cveSummary.CriticalCount, ShouldEqual, 0)
So(cveSummary.MaxSeverity, ShouldEqual, "HIGH")

cveList, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, repo1, "1.0.1", "", pageInput)
cveList, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, repo1, "1.0.1", "", "", pageInput)
So(err, ShouldBeNil)
So(len(cveList), ShouldEqual, 2)
So(cveList[0].ID, ShouldEqual, "CVE1")
Expand All @@ -1237,7 +1237,7 @@ func TestCVEStruct(t *testing.T) { //nolint:gocyclo
So(cveSummary.CriticalCount, ShouldEqual, 0)
So(cveSummary.MaxSeverity, ShouldEqual, "MEDIUM")

cveList, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, repo1, "1.1.0", "", pageInput)
cveList, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, repo1, "1.1.0", "", "", pageInput)
So(err, ShouldBeNil)
So(len(cveList), ShouldEqual, 1)
So(cveList[0].ID, ShouldEqual, "CVE3")
Expand All @@ -1251,7 +1251,7 @@ func TestCVEStruct(t *testing.T) { //nolint:gocyclo
So(cveSummary.CriticalCount, ShouldEqual, 0)
So(cveSummary.MaxSeverity, ShouldEqual, "LOW")

cveList, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, repo6, "1.0.0", "", pageInput)
cveList, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, repo6, "1.0.0", "", "", pageInput)
So(err, ShouldBeNil)
So(len(cveList), ShouldEqual, 0)
So(pageInfo.ItemCount, ShouldEqual, 0)
Expand All @@ -1264,7 +1264,7 @@ func TestCVEStruct(t *testing.T) { //nolint:gocyclo
So(cveSummary.CriticalCount, ShouldEqual, 0)
So(cveSummary.MaxSeverity, ShouldEqual, "NONE")

cveList, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, repo8, "1.0.0", "", pageInput)
cveList, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, repo8, "1.0.0", "", "", pageInput)
So(err, ShouldBeNil)
So(len(cveList), ShouldEqual, 7)
So(pageInfo.ItemCount, ShouldEqual, 7)
Expand All @@ -1278,7 +1278,7 @@ func TestCVEStruct(t *testing.T) { //nolint:gocyclo
So(cveSummary.MaxSeverity, ShouldEqual, "CRITICAL")

// Image is multiarch
cveList, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, repoMultiarch, "tagIndex", "", pageInput)
cveList, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, repoMultiarch, "tagIndex", "", "", pageInput)
So(err, ShouldBeNil)
So(len(cveList), ShouldEqual, 1)
So(cveList[0].ID, ShouldEqual, "CVE1")
Expand All @@ -1293,7 +1293,7 @@ func TestCVEStruct(t *testing.T) { //nolint:gocyclo
So(cveSummary.MaxSeverity, ShouldEqual, "MEDIUM")

// Image is not scannable
cveList, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, repo2, "1.0.0", "", pageInput)
cveList, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, repo2, "1.0.0", "", "", pageInput)
So(err, ShouldEqual, zerr.ErrScanNotSupported)
So(len(cveList), ShouldEqual, 0)
So(pageInfo.ItemCount, ShouldEqual, 0)
Expand All @@ -1307,7 +1307,7 @@ func TestCVEStruct(t *testing.T) { //nolint:gocyclo
So(cveSummary.MaxSeverity, ShouldEqual, "")

// Tag is not found
cveList, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, repo3, "1.0.0", "", pageInput)
cveList, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, repo3, "1.0.0", "", "", pageInput)
So(err, ShouldEqual, zerr.ErrTagMetaNotFound)
So(len(cveList), ShouldEqual, 0)
So(pageInfo.ItemCount, ShouldEqual, 0)
Expand All @@ -1321,7 +1321,7 @@ func TestCVEStruct(t *testing.T) { //nolint:gocyclo
So(cveSummary.MaxSeverity, ShouldEqual, "")

// Scan failed
cveList, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, repo7, "1.0.0", "", pageInput)
cveList, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, repo7, "1.0.0", "", "", pageInput)
So(err, ShouldEqual, ErrFailedScan)
So(len(cveList), ShouldEqual, 0)
So(pageInfo.ItemCount, ShouldEqual, 0)
Expand All @@ -1335,7 +1335,7 @@ func TestCVEStruct(t *testing.T) { //nolint:gocyclo
So(cveSummary.MaxSeverity, ShouldEqual, "")

// Tag is not found
cveList, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, "repo-with-bad-tag-digest", "tag", "", pageInput)
cveList, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, "repo-with-bad-tag-digest", "tag", "", "", pageInput)
So(err, ShouldEqual, zerr.ErrImageMetaNotFound)
So(len(cveList), ShouldEqual, 0)
So(pageInfo.ItemCount, ShouldEqual, 0)
Expand All @@ -1349,7 +1349,7 @@ func TestCVEStruct(t *testing.T) { //nolint:gocyclo
So(cveSummary.MaxSeverity, ShouldEqual, "")

// Repo is not found
cveList, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, repo100, "1.0.0", "", pageInput)
cveList, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, repo100, "1.0.0", "", "", pageInput)
So(err, ShouldEqual, zerr.ErrRepoMetaNotFound)
So(len(cveList), ShouldEqual, 0)
So(pageInfo.ItemCount, ShouldEqual, 0)
Expand Down Expand Up @@ -1580,7 +1580,7 @@ func TestCVEStruct(t *testing.T) { //nolint:gocyclo
So(cveSummary.CriticalCount, ShouldEqual, 0)
So(cveSummary.MaxSeverity, ShouldEqual, "")

cveList, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, repo1, "0.1.0", "", pageInput)
cveList, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, repo1, "0.1.0", "", "", pageInput)
So(err, ShouldNotBeNil)
So(cveList, ShouldBeEmpty)
So(pageInfo.ItemCount, ShouldEqual, 0)
Expand Down
17 changes: 17 additions & 0 deletions pkg/extensions/search/cve/model/models.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package model

import (
"strings"
"time"

godigest "github.com/opencontainers/go-digest"
"golang.org/x/exp/slices"
)

type ImageCVESummary struct {
Expand All @@ -26,6 +28,21 @@ type CVE struct {
PackageList []Package `json:"PackageList"`
}

func (cve *CVE) ContainsStr(str string) bool {
str = strings.ToUpper(str)

return strings.Contains(strings.ToUpper(cve.Title), str) ||
strings.Contains(strings.ToUpper(cve.ID), str) ||
strings.Contains(strings.ToUpper(cve.Severity), str) ||
strings.Contains(strings.ToUpper(cve.Reference), str) ||
strings.Contains(strings.ToUpper(cve.Description), str) ||
slices.ContainsFunc(cve.PackageList, func(pack Package) bool {
return strings.Contains(strings.ToUpper(pack.Name), str) ||
strings.Contains(strings.ToUpper(pack.FixedVersion), str) ||
strings.Contains(strings.ToUpper(pack.InstalledVersion), str)
})
}

//nolint:tagliatelle // graphQL schema
type Package struct {
Name string `json:"Name"`
Expand Down
26 changes: 13 additions & 13 deletions pkg/extensions/search/cve/pagination_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ func TestCVEPagination(t *testing.T) {
Convey("Page", func() {
Convey("defaults", func() {
// By default expect unlimitted results sorted by severity
cves, cveSummary, pageInfo, err := cveInfo.GetCVEListForImage(ctx, "repo1", "0.1.0", "", cvemodel.PageInput{})
cves, cveSummary, pageInfo, err := cveInfo.GetCVEListForImage(ctx, "repo1", "0.1.0", "", "", cvemodel.PageInput{})
So(err, ShouldBeNil)
So(len(cves), ShouldEqual, 5)
So(pageInfo.ItemCount, ShouldEqual, 5)
Expand All @@ -158,7 +158,7 @@ func TestCVEPagination(t *testing.T) {
previousSeverity = severityToInt[cve.Severity]
}

cves, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, "repo1", "1.0.0", "", cvemodel.PageInput{})
cves, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, "repo1", "1.0.0", "", "", cvemodel.PageInput{})
So(err, ShouldBeNil)
So(len(cves), ShouldEqual, 30)
So(pageInfo.ItemCount, ShouldEqual, 30)
Expand All @@ -183,7 +183,7 @@ func TestCVEPagination(t *testing.T) {
cveIds = append(cveIds, fmt.Sprintf("CVE%d", i))
}

cves, cveSummary, pageInfo, err := cveInfo.GetCVEListForImage(ctx, "repo1", "0.1.0", "",
cves, cveSummary, pageInfo, err := cveInfo.GetCVEListForImage(ctx, "repo1", "0.1.0", "", "",
cvemodel.PageInput{SortBy: cveinfo.AlphabeticAsc})
So(err, ShouldBeNil)
So(len(cves), ShouldEqual, 5)
Expand All @@ -201,7 +201,7 @@ func TestCVEPagination(t *testing.T) {
}

sort.Strings(cveIds)
cves, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, "repo1", "1.0.0", "",
cves, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, "repo1", "1.0.0", "", "",
cvemodel.PageInput{SortBy: cveinfo.AlphabeticAsc})
So(err, ShouldBeNil)
So(len(cves), ShouldEqual, 30)
Expand All @@ -219,7 +219,7 @@ func TestCVEPagination(t *testing.T) {
}

sort.Sort(sort.Reverse(sort.StringSlice(cveIds)))
cves, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, "repo1", "1.0.0", "",
cves, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, "repo1", "1.0.0", "", "",
cvemodel.PageInput{SortBy: cveinfo.AlphabeticDsc})
So(err, ShouldBeNil)
So(len(cves), ShouldEqual, 30)
Expand All @@ -236,7 +236,7 @@ func TestCVEPagination(t *testing.T) {
So(cve.ID, ShouldEqual, cveIds[i])
}

cves, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, "repo1", "1.0.0", "",
cves, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, "repo1", "1.0.0", "", "",
cvemodel.PageInput{SortBy: cveinfo.SeverityDsc})
So(err, ShouldBeNil)
So(len(cves), ShouldEqual, 30)
Expand All @@ -262,7 +262,7 @@ func TestCVEPagination(t *testing.T) {
cveIds = append(cveIds, fmt.Sprintf("CVE%d", i))
}

cves, cveSummary, pageInfo, err := cveInfo.GetCVEListForImage(ctx, "repo1", "0.1.0", "", cvemodel.PageInput{
cves, cveSummary, pageInfo, err := cveInfo.GetCVEListForImage(ctx, "repo1", "0.1.0", "", "", cvemodel.PageInput{
Limit: 3,
Offset: 1,
SortBy: cveinfo.AlphabeticAsc,
Expand All @@ -283,7 +283,7 @@ func TestCVEPagination(t *testing.T) {
So(cveSummary.CriticalCount, ShouldEqual, 1)
So(cveSummary.MaxSeverity, ShouldEqual, "CRITICAL")

cves, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, "repo1", "0.1.0", "", cvemodel.PageInput{
cves, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, "repo1", "0.1.0", "", "", cvemodel.PageInput{
Limit: 2,
Offset: 1,
SortBy: cveinfo.AlphabeticDsc,
Expand All @@ -303,7 +303,7 @@ func TestCVEPagination(t *testing.T) {
So(cveSummary.CriticalCount, ShouldEqual, 1)
So(cveSummary.MaxSeverity, ShouldEqual, "CRITICAL")

cves, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, "repo1", "0.1.0", "", cvemodel.PageInput{
cves, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, "repo1", "0.1.0", "", "", cvemodel.PageInput{
Limit: 3,
Offset: 1,
SortBy: cveinfo.SeverityDsc,
Expand All @@ -327,7 +327,7 @@ func TestCVEPagination(t *testing.T) {
}

sort.Strings(cveIds)
cves, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, "repo1", "1.0.0", "", cvemodel.PageInput{
cves, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, "repo1", "1.0.0", "", "", cvemodel.PageInput{
Limit: 5,
Offset: 20,
SortBy: cveinfo.AlphabeticAsc,
Expand All @@ -350,7 +350,7 @@ func TestCVEPagination(t *testing.T) {
})

Convey("limit > len(cves)", func() {
cves, cveSummary, pageInfo, err := cveInfo.GetCVEListForImage(ctx, "repo1", "0.1.0", "", cvemodel.PageInput{
cves, cveSummary, pageInfo, err := cveInfo.GetCVEListForImage(ctx, "repo1", "0.1.0", "", "", cvemodel.PageInput{
Limit: 6,
Offset: 3,
SortBy: cveinfo.AlphabeticAsc,
Expand All @@ -370,7 +370,7 @@ func TestCVEPagination(t *testing.T) {
So(cveSummary.CriticalCount, ShouldEqual, 1)
So(cveSummary.MaxSeverity, ShouldEqual, "CRITICAL")

cves, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, "repo1", "0.1.0", "", cvemodel.PageInput{
cves, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, "repo1", "0.1.0", "", "", cvemodel.PageInput{
Limit: 6,
Offset: 3,
SortBy: cveinfo.AlphabeticDsc,
Expand All @@ -390,7 +390,7 @@ func TestCVEPagination(t *testing.T) {
So(cveSummary.CriticalCount, ShouldEqual, 1)
So(cveSummary.MaxSeverity, ShouldEqual, "CRITICAL")

cves, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, "repo1", "0.1.0", "", cvemodel.PageInput{
cves, cveSummary, pageInfo, err = cveInfo.GetCVEListForImage(ctx, "repo1", "0.1.0", "", "", cvemodel.PageInput{
Limit: 6,
Offset: 3,
SortBy: cveinfo.SeverityDsc,
Expand Down
Loading

0 comments on commit 3f97f87

Please sign in to comment.