0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2024-12-16 21:56:37 -05:00

fix(cve): Search by CVE title/id (full or partial) when listing an image's CVEs (#1264)

Signed-off-by: Ana-Roberta Lisca <ana.kagome@yahoo.com>
This commit is contained in:
Lisca Ana-Roberta 2023-03-16 21:13:07 +02:00 committed by GitHub
parent 4d0bbf1e00
commit eea6f3f85a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 212 additions and 64 deletions

View file

@ -117,6 +117,7 @@ func NewCveCommand(searchService SearchService) *cobra.Command {
func setupCveFlags(cveCmd *cobra.Command, variables cveFlagVariables) {
variables.searchCveParams["imageName"] = cveCmd.Flags().StringP("image", "I", "", "List CVEs by IMAGENAME[:TAG]")
variables.searchCveParams["cveID"] = cveCmd.Flags().StringP("cve-id", "i", "", "List images affected by a CVE")
variables.searchCveParams["searchedCVE"] = cveCmd.Flags().StringP("search", "s", "", "Search specific CVEs by name/id")
cveCmd.Flags().StringVar(variables.servURL, "url", "", "Specify zot server URL if config-name is not mentioned")
cveCmd.Flags().StringVarP(variables.user, "user", "u", "", `User Credentials of `+

View file

@ -604,6 +604,61 @@ func TestServerCVEResponse(t *testing.T) {
So(str, ShouldContainSubstring, "CVE")
})
Convey("Test CVE by image name - GQL - search CVE by title in results", t, func() {
args := []string{"cvetest", "--image", "zot-cve-test:0.0.1", "--search", "CVE-C1"}
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
defer os.Remove(configPath)
cveCmd := NewCveCommand(new(searchService))
buff := bytes.NewBufferString("")
cveCmd.SetOut(buff)
cveCmd.SetErr(buff)
cveCmd.SetArgs(args)
err = cveCmd.Execute()
space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ")
str = strings.TrimSpace(str)
So(err, ShouldBeNil)
So(str, ShouldContainSubstring, "ID SEVERITY TITLE")
So(str, ShouldContainSubstring, "CVE-C1")
So(str, ShouldNotContainSubstring, "CVE-2")
})
Convey("Test CVE by image name - GQL - search CVE by id in results", t, func() {
args := []string{"cvetest", "--image", "zot-cve-test:0.0.1", "--search", "CVE-2"}
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
defer os.Remove(configPath)
cveCmd := NewCveCommand(new(searchService))
buff := bytes.NewBufferString("")
cveCmd.SetOut(buff)
cveCmd.SetErr(buff)
cveCmd.SetArgs(args)
err = cveCmd.Execute()
space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ")
str = strings.TrimSpace(str)
So(err, ShouldBeNil)
So(str, ShouldContainSubstring, "ID SEVERITY TITLE")
So(str, ShouldContainSubstring, "CVE-2")
So(str, ShouldNotContainSubstring, "CVE-1")
})
Convey("Test CVE by image name - GQL - search nonexistent CVE", t, func() {
args := []string{"cvetest", "--image", "zot-cve-test:0.0.1", "--search", "CVE-100"}
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))
defer os.Remove(configPath)
cveCmd := NewCveCommand(new(searchService))
buff := bytes.NewBufferString("")
cveCmd.SetOut(buff)
cveCmd.SetErr(buff)
cveCmd.SetArgs(args)
err = cveCmd.Execute()
space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ")
str = strings.TrimSpace(str)
So(err, ShouldBeNil)
So(str, ShouldContainSubstring, "No CVEs found for image")
})
Convey("Test CVE by image name - GQL - invalid image", t, func() {
args := []string{"cvetest", "--image", "invalid:0.0.1"}
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"cvetest","url":"%s","showspinner":false}]}`, url))

View file

@ -1691,7 +1691,7 @@ func (service mockService) getFixedTagsForCVEGQL(ctx context.Context, config sea
}
func (service mockService) getCveByImageGQL(ctx context.Context, config searchConfig, username, password,
imageName string,
imageName, searchedCVE string,
) (*cveResult, error) {
cveRes := &cveResult{}
cveRes.Data = cveData{
@ -1797,7 +1797,7 @@ func (service mockService) getImageByName(ctx context.Context, config searchConf
}
func (service mockService) getCveByImage(ctx context.Context, config searchConfig, username, password,
imageName string, rch chan stringResult, wtgrp *sync.WaitGroup,
imageName, searchedCVE string, rch chan stringResult, wtgrp *sync.WaitGroup,
) {
defer wtgrp.Done()
defer close(rch)

View file

@ -302,7 +302,8 @@ func (search imagesByDigestSearcherGQL) search(config searchConfig) (bool, error
type cveByImageSearcher struct{}
func (search cveByImageSearcher) search(config searchConfig) (bool, error) {
if !canSearch(config.params, newSet("imageName")) || *config.fixedFlag {
if (!canSearch(config.params, newSet("imageName")) &&
!canSearch(config.params, newSet("imageName", "searchedCVE"))) || *config.fixedFlag {
return false, nil
}
@ -318,7 +319,8 @@ func (search cveByImageSearcher) search(config searchConfig) (bool, error) {
wg.Add(1)
go config.searchService.getCveByImage(ctx, config, username, password, *config.params["imageName"], strErr, &wg)
go config.searchService.getCveByImage(ctx, config, username, password, *config.params["imageName"],
*config.params["searchedCVE"], strErr, &wg)
wg.Add(1)
errCh := make(chan error, 1)
@ -337,7 +339,8 @@ func (search cveByImageSearcher) search(config searchConfig) (bool, error) {
type cveByImageSearcherGQL struct{}
func (search cveByImageSearcherGQL) search(config searchConfig) (bool, error) {
if !canSearch(config.params, newSet("imageName")) || *config.fixedFlag {
if (!canSearch(config.params, newSet("imageName")) &&
!canSearch(config.params, newSet("imageName", "searchedCVE"))) || *config.fixedFlag {
return false, nil
}
@ -352,7 +355,8 @@ func (search cveByImageSearcherGQL) search(config searchConfig) (bool, error) {
defer cancel()
cveList, err := config.searchService.getCveByImageGQL(ctx, config, username, password, *config.params["imageName"])
cveList, err := config.searchService.getCveByImageGQL(ctx, config, username, password,
*config.params["imageName"], *config.params["searchedCVE"])
if err != nil {
return true, err
}

View file

@ -29,7 +29,7 @@ type SearchService interface { //nolint:interfacebloat
getImagesByDigestGQL(ctx context.Context, config searchConfig, username, password string,
digest string) (*imageListStructForDigestGQL, error)
getCveByImageGQL(ctx context.Context, config searchConfig, username, password,
imageName string) (*cveResult, error)
imageName string, searchedCVE string) (*cveResult, error)
getImagesByCveIDGQL(ctx context.Context, config searchConfig, username, password string,
digest string) (*imagesForCve, error)
getTagsForCVEGQL(ctx context.Context, config searchConfig, username, password, imageName,
@ -43,7 +43,7 @@ type SearchService interface { //nolint:interfacebloat
getAllImages(ctx context.Context, config searchConfig, username, password string,
channel chan stringResult, wtgrp *sync.WaitGroup)
getCveByImage(ctx context.Context, config searchConfig, username, password, imageName string,
getCveByImage(ctx context.Context, config searchConfig, username, password, imageName, searchedCVE string,
channel chan stringResult, wtgrp *sync.WaitGroup)
getImagesByCveID(ctx context.Context, config searchConfig, username, password, cvid string,
channel chan stringResult, wtgrp *sync.WaitGroup)
@ -226,11 +226,11 @@ func (service searchService) getImagesByCveIDGQL(ctx context.Context, config sea
}
func (service searchService) getCveByImageGQL(ctx context.Context, config searchConfig, username, password,
imageName string,
imageName, searchedCVE string,
) (*cveResult, error) {
query := fmt.Sprintf(`{ CVEListForImage (image:"%s")`+
query := fmt.Sprintf(`{ CVEListForImage (image:"%s", searchedCVE:"%s")`+
` { Tag CVEList { Id Title Severity Description `+
`PackageList {Name InstalledVersion FixedVersion}} } }`, imageName)
`PackageList {Name InstalledVersion FixedVersion}} } }`, imageName, searchedCVE)
result := &cveResult{}
err := service.makeGraphQLQuery(ctx, config, username, password, query, result)
@ -618,14 +618,14 @@ func (service searchService) getImageByNameAndCVEID(ctx context.Context, config
}
func (service searchService) getCveByImage(ctx context.Context, config searchConfig, username, password,
imageName string, rch chan stringResult, wtgrp *sync.WaitGroup,
imageName, searchedCVE string, rch chan stringResult, wtgrp *sync.WaitGroup,
) {
defer wtgrp.Done()
defer close(rch)
query := fmt.Sprintf(`{ CVEListForImage (image:"%s")`+
query := fmt.Sprintf(`{ CVEListForImage (image:"%s", searchedCVE:"%s")`+
` { Tag CVEList { Id Title Severity Description `+
`PackageList {Name InstalledVersion FixedVersion}} } }`, imageName)
`PackageList {Name InstalledVersion FixedVersion}} } }`, imageName, searchedCVE)
result := &cveResult{}
err := service.makeGraphQLQuery(ctx, config, username, password, query, result)

View file

@ -3,6 +3,7 @@ package cveinfo
import (
"encoding/json"
"fmt"
"strings"
godigest "github.com/opencontainers/go-digest"
ispec "github.com/opencontainers/image-spec/specs-go/v1"
@ -19,7 +20,7 @@ import (
type CveInfo interface {
GetImageListForCVE(repo, cveID string) ([]common.TagInfo, error)
GetImageListWithCVEFixed(repo, cveID string) ([]common.TagInfo, error)
GetCVEListForImage(repo, tag string, pageinput PageInput) ([]cvemodel.CVE, PageInfo, error)
GetCVEListForImage(repo, tag string, searchedCVE string, pageinput PageInput) ([]cvemodel.CVE, PageInfo, error)
GetCVESummaryForImage(repo, tag string) (ImageCVESummary, error)
CompareSeverities(severity1, severity2 string) int
UpdateDB() error
@ -215,7 +216,18 @@ func (cveinfo BaseCveInfo) GetImageListWithCVEFixed(repo, cveID string) ([]commo
return fixedTags, nil
}
func (cveinfo BaseCveInfo) GetCVEListForImage(repo, tag string, pageInput PageInput) (
func filterCVEList(cveMap map[string]cvemodel.CVE, searchedCVE 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) {
pageFinder.Add(cve)
}
}
}
func (cveinfo BaseCveInfo) GetCVEListForImage(repo, tag string, searchedCVE string, pageInput PageInput) (
[]cvemodel.CVE,
PageInfo,
error,
@ -237,9 +249,7 @@ func (cveinfo BaseCveInfo) GetCVEListForImage(repo, tag string, pageInput PageIn
return []cvemodel.CVE{}, PageInfo{}, err
}
for _, cve := range cveMap {
pageFinder.Add(cve)
}
filterCVEList(cveMap, searchedCVE, pageFinder)
cveList, pageInfo := pageFinder.Page()

View file

@ -1206,14 +1206,14 @@ func TestCVEStruct(t *testing.T) {
}
// Image is found
cveList, pageInfo, err := cveInfo.GetCVEListForImage("repo1", "0.1.0", pageInput)
cveList, pageInfo, err := cveInfo.GetCVEListForImage("repo1", "0.1.0", "", pageInput)
So(err, ShouldBeNil)
So(len(cveList), ShouldEqual, 1)
So(cveList[0].ID, ShouldEqual, "CVE1")
So(pageInfo.ItemCount, ShouldEqual, 1)
So(pageInfo.TotalCount, ShouldEqual, 1)
cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "1.0.0", pageInput)
cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "1.0.0", "", pageInput)
So(err, ShouldBeNil)
So(len(cveList), ShouldEqual, 3)
So(cveList[0].ID, ShouldEqual, "CVE2")
@ -1222,7 +1222,7 @@ func TestCVEStruct(t *testing.T) {
So(pageInfo.ItemCount, ShouldEqual, 3)
So(pageInfo.TotalCount, ShouldEqual, 3)
cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "1.0.1", pageInput)
cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "1.0.1", "", pageInput)
So(err, ShouldBeNil)
So(len(cveList), ShouldEqual, 2)
So(cveList[0].ID, ShouldEqual, "CVE1")
@ -1230,42 +1230,42 @@ func TestCVEStruct(t *testing.T) {
So(pageInfo.ItemCount, ShouldEqual, 2)
So(pageInfo.TotalCount, ShouldEqual, 2)
cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "1.1.0", pageInput)
cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "1.1.0", "", pageInput)
So(err, ShouldBeNil)
So(len(cveList), ShouldEqual, 1)
So(cveList[0].ID, ShouldEqual, "CVE3")
So(pageInfo.ItemCount, ShouldEqual, 1)
So(pageInfo.TotalCount, ShouldEqual, 1)
cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo6", "1.0.0", pageInput)
cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo6", "1.0.0", "", pageInput)
So(err, ShouldBeNil)
So(len(cveList), ShouldEqual, 0)
So(pageInfo.ItemCount, ShouldEqual, 0)
So(pageInfo.TotalCount, ShouldEqual, 0)
// Image is not scannable
cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo2", "1.0.0", pageInput)
cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo2", "1.0.0", "", pageInput)
So(err, ShouldEqual, zerr.ErrScanNotSupported)
So(len(cveList), ShouldEqual, 0)
So(pageInfo.ItemCount, ShouldEqual, 0)
So(pageInfo.TotalCount, ShouldEqual, 0)
// Tag is not found
cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo3", "1.0.0", pageInput)
cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo3", "1.0.0", "", pageInput)
So(err, ShouldEqual, zerr.ErrTagMetaNotFound)
So(len(cveList), ShouldEqual, 0)
So(pageInfo.ItemCount, ShouldEqual, 0)
So(pageInfo.TotalCount, ShouldEqual, 0)
// Manifest is not found
cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo5", "nonexitent-manifest", pageInput)
cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo5", "nonexitent-manifest", "", pageInput)
So(err, ShouldEqual, zerr.ErrManifestDataNotFound)
So(len(cveList), ShouldEqual, 0)
So(pageInfo.ItemCount, ShouldEqual, 0)
So(pageInfo.TotalCount, ShouldEqual, 0)
// Repo is not found
cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo100", "1.0.0", pageInput)
cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo100", "1.0.0", "", pageInput)
So(err, ShouldEqual, zerr.ErrRepoMetaNotFound)
So(len(cveList), ShouldEqual, 0)
So(pageInfo.ItemCount, ShouldEqual, 0)
@ -1385,7 +1385,7 @@ func TestCVEStruct(t *testing.T) {
So(cveSummary.Count, ShouldEqual, 0)
So(cveSummary.MaxSeverity, ShouldEqual, "")
cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "0.1.0", pageInput)
cveList, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "0.1.0", "", pageInput)
So(err, ShouldNotBeNil)
So(cveList, ShouldBeEmpty)
So(pageInfo.ItemCount, ShouldEqual, 0)

View file

@ -182,7 +182,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", "", cveinfo.PageInput{})
So(err, ShouldBeNil)
So(len(cves), ShouldEqual, 5)
So(pageInfo.ItemCount, ShouldEqual, 5)
@ -193,7 +193,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", "", cveinfo.PageInput{})
So(err, ShouldBeNil)
So(len(cves), ShouldEqual, 30)
So(pageInfo.ItemCount, ShouldEqual, 30)
@ -211,7 +211,7 @@ func TestCVEPagination(t *testing.T) {
cveIds = append(cveIds, fmt.Sprintf("CVE%d", i))
}
cves, pageInfo, err := cveInfo.GetCVEListForImage("repo1", "0.1.0",
cves, pageInfo, err := cveInfo.GetCVEListForImage("repo1", "0.1.0", "",
cveinfo.PageInput{SortBy: cveinfo.AlphabeticAsc})
So(err, ShouldBeNil)
So(len(cves), ShouldEqual, 5)
@ -222,7 +222,8 @@ func TestCVEPagination(t *testing.T) {
}
sort.Strings(cveIds)
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "1.0.0", cveinfo.PageInput{SortBy: cveinfo.AlphabeticAsc})
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "1.0.0", "",
cveinfo.PageInput{SortBy: cveinfo.AlphabeticAsc})
So(err, ShouldBeNil)
So(len(cves), ShouldEqual, 30)
So(pageInfo.ItemCount, ShouldEqual, 30)
@ -232,7 +233,8 @@ 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})
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "1.0.0", "",
cveinfo.PageInput{SortBy: cveinfo.AlphabeticDsc})
So(err, ShouldBeNil)
So(len(cves), ShouldEqual, 30)
So(pageInfo.ItemCount, ShouldEqual, 30)
@ -241,7 +243,8 @@ func TestCVEPagination(t *testing.T) {
So(cve.ID, ShouldEqual, cveIds[i])
}
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "1.0.0", cveinfo.PageInput{SortBy: cveinfo.SeverityDsc})
cves, pageInfo, err = cveInfo.GetCVEListForImage("repo1", "1.0.0", "",
cveinfo.PageInput{SortBy: cveinfo.SeverityDsc})
So(err, ShouldBeNil)
So(len(cves), ShouldEqual, 30)
So(pageInfo.ItemCount, ShouldEqual, 30)
@ -259,11 +262,12 @@ 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", "", cveinfo.PageInput{
Limit: 3,
Offset: 1,
SortBy: cveinfo.AlphabeticAsc,
})
},
)
So(err, ShouldBeNil)
So(len(cves), ShouldEqual, 3)
So(pageInfo.ItemCount, ShouldEqual, 3)
@ -272,11 +276,12 @@ 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", "", cveinfo.PageInput{
Limit: 2,
Offset: 1,
SortBy: cveinfo.AlphabeticDsc,
})
},
)
So(err, ShouldBeNil)
So(len(cves), ShouldEqual, 2)
So(pageInfo.ItemCount, ShouldEqual, 2)
@ -284,11 +289,12 @@ 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", "", cveinfo.PageInput{
Limit: 3,
Offset: 1,
SortBy: cveinfo.SeverityDsc,
})
},
)
So(err, ShouldBeNil)
So(len(cves), ShouldEqual, 3)
So(pageInfo.ItemCount, ShouldEqual, 3)
@ -300,11 +306,12 @@ 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", "", cveinfo.PageInput{
Limit: 5,
Offset: 20,
SortBy: cveinfo.AlphabeticAsc,
})
},
)
So(err, ShouldBeNil)
So(len(cves), ShouldEqual, 5)
So(pageInfo.ItemCount, ShouldEqual, 5)
@ -315,11 +322,12 @@ 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", "", cveinfo.PageInput{
Limit: 6,
Offset: 3,
SortBy: cveinfo.AlphabeticAsc,
})
},
)
So(err, ShouldBeNil)
So(len(cves), ShouldEqual, 2)
So(pageInfo.ItemCount, ShouldEqual, 2)
@ -327,11 +335,12 @@ 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", "", cveinfo.PageInput{
Limit: 6,
Offset: 3,
SortBy: cveinfo.AlphabeticDsc,
})
},
)
So(err, ShouldBeNil)
So(len(cves), ShouldEqual, 2)
So(pageInfo.ItemCount, ShouldEqual, 2)
@ -339,11 +348,12 @@ 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", "", cveinfo.PageInput{
Limit: 6,
Offset: 3,
SortBy: cveinfo.SeverityDsc,
})
},
)
So(err, ShouldBeNil)
So(len(cves), ShouldEqual, 2)
So(pageInfo.ItemCount, ShouldEqual, 2)

View file

@ -152,7 +152,7 @@ type ComplexityRoot struct {
Query struct {
BaseImageList func(childComplexity int, image string, digest *string, requestedPage *PageInput) int
CVEListForImage func(childComplexity int, image string, requestedPage *PageInput) int
CVEListForImage func(childComplexity int, image string, requestedPage *PageInput, searchedCve *string) int
DerivedImageList func(childComplexity int, image string, digest *string, requestedPage *PageInput) int
ExpandedRepoInfo func(childComplexity int, repo string) int
GlobalSearch func(childComplexity int, query string, filter *Filter, requestedPage *PageInput) int
@ -194,7 +194,7 @@ type ComplexityRoot struct {
}
type QueryResolver interface {
CVEListForImage(ctx context.Context, image string, requestedPage *PageInput) (*CVEResultForImage, error)
CVEListForImage(ctx context.Context, image string, requestedPage *PageInput, searchedCve *string) (*CVEResultForImage, error)
ImageListForCve(ctx context.Context, id string, requestedPage *PageInput) (*PaginatedImagesResult, error)
ImageListWithCVEFixed(ctx context.Context, id string, image string, requestedPage *PageInput) (*PaginatedImagesResult, error)
ImageListForDigest(ctx context.Context, id string, requestedPage *PageInput) (*PaginatedImagesResult, error)
@ -686,7 +686,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return 0, false
}
return e.complexity.Query.CVEListForImage(childComplexity, args["image"].(string), args["requestedPage"].(*PageInput)), true
return e.complexity.Query.CVEListForImage(childComplexity, args["image"].(string), args["requestedPage"].(*PageInput), args["searchedCVE"].(*string)), true
case "Query.DerivedImageList":
if e.complexity.Query.DerivedImageList == nil {
@ -1541,6 +1541,8 @@ type Query {
image: String!,
"Sets the parameters of the requested page"
requestedPage: PageInput
"Search term for specific CVE by title/id"
searchedCVE: String
): CVEResultForImage!
"""
@ -1724,6 +1726,15 @@ func (ec *executionContext) field_Query_CVEListForImage_args(ctx context.Context
}
}
args["requestedPage"] = arg1
var arg2 *string
if tmp, ok := rawArgs["searchedCVE"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("searchedCVE"))
arg2, err = ec.unmarshalOString2ᚖstring(ctx, tmp)
if err != nil {
return nil, err
}
}
args["searchedCVE"] = arg2
return args, nil
}
@ -4882,7 +4893,7 @@ func (ec *executionContext) _Query_CVEListForImage(ctx context.Context, field gr
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Query().CVEListForImage(rctx, fc.Args["image"].(string), fc.Args["requestedPage"].(*PageInput))
return ec.resolvers.Query().CVEListForImage(rctx, fc.Args["image"].(string), fc.Args["requestedPage"].(*PageInput), fc.Args["searchedCVE"].(*string))
})
if err != nil {
ec.Error(ctx, err)

View file

@ -292,6 +292,7 @@ func getCVEListForImage(
image string,
cveInfo cveinfo.CveInfo,
requestedPage *gql_generated.PageInput,
searchedCVE string,
log log.Logger, //nolint:unparam // may be used by devs for debugging
) (*gql_generated.CVEResultForImage, error) {
if requestedPage == nil {
@ -316,7 +317,7 @@ func getCVEListForImage(
return &gql_generated.CVEResultForImage{}, gqlerror.Errorf("reference by digest not supported")
}
cveList, pageInfo, err := cveInfo.GetCVEListForImage(repo, ref, pageInput)
cveList, pageInfo, err := cveInfo.GetCVEListForImage(repo, ref, searchedCVE, pageInput)
if err != nil {
return &gql_generated.CVEResultForImage{}, err
}

View file

@ -2080,6 +2080,12 @@ func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
Title: "Title CVE3",
Description: "Description CVE3",
},
"CVE34": {
ID: "CVE34",
Severity: "LOW",
Title: "Title for CVE34",
Description: "Description CVE34",
},
}, nil
}
@ -2140,21 +2146,63 @@ func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
dig := godigest.FromString("dig")
repoWithDigestRef := fmt.Sprintf("repo@%s", dig)
_, err := getCVEListForImage(responseContext, repoWithDigestRef, cveInfo, pageInput, log)
_, err := getCVEListForImage(responseContext, repoWithDigestRef, cveInfo, pageInput, "", log)
So(err.Error(), ShouldContainSubstring, "reference by digest not supported")
cveResult, err := getCVEListForImage(responseContext, "repo1:1.0.0", cveInfo, pageInput, log)
cveResult, err := getCVEListForImage(responseContext, "repo1:1.0.0", cveInfo, pageInput, "", log)
So(err, ShouldBeNil)
So(*cveResult.Tag, ShouldEqual, "1.0.0")
expectedCves := []string{"CVE1", "CVE2", "CVE3"}
expectedCves := []string{"CVE1", "CVE2", "CVE3", "CVE34"}
So(len(cveResult.CVEList), ShouldEqual, len(expectedCves))
for _, cve := range cveResult.CVEList {
So(expectedCves, ShouldContain, *cve.ID)
}
cveResult, err = getCVEListForImage(responseContext, "repo1:1.0.1", cveInfo, pageInput, log)
// test searching CVE by id in results
cveResult, err = getCVEListForImage(responseContext, "repo1:1.0.0", cveInfo, pageInput, "CVE3", log)
So(err, ShouldBeNil)
So(*cveResult.Tag, ShouldEqual, "1.0.0")
expectedCves = []string{"CVE3", "CVE34"}
So(len(cveResult.CVEList), ShouldEqual, len(expectedCves))
for _, cve := range cveResult.CVEList {
So(expectedCves, ShouldContain, *cve.ID)
}
// test searching CVE by id in results - no matches
cveResult, err = getCVEListForImage(responseContext, "repo1:1.0.0", cveInfo, pageInput, "CVE100", log)
So(err, ShouldBeNil)
So(*cveResult.Tag, ShouldEqual, "1.0.0")
So(len(cveResult.CVEList), ShouldEqual, 0)
// test searching CVE by id in results - partial name
cveResult, err = getCVEListForImage(responseContext, "repo1:1.0.0", cveInfo, pageInput, "VE3", log)
So(err, ShouldBeNil)
So(*cveResult.Tag, ShouldEqual, "1.0.0")
expectedCves = []string{"CVE3", "CVE34"}
So(len(cveResult.CVEList), ShouldEqual, len(expectedCves))
for _, cve := range cveResult.CVEList {
So(expectedCves, ShouldContain, *cve.ID)
}
// test searching CVE by title in results
cveResult, err = getCVEListForImage(responseContext, "repo1:1.0.0", cveInfo, pageInput, "Title CVE", log)
So(err, ShouldBeNil)
So(*cveResult.Tag, ShouldEqual, "1.0.0")
expectedCves = []string{"CVE1", "CVE2", "CVE3"}
So(len(cveResult.CVEList), ShouldEqual, len(expectedCves))
for _, cve := range cveResult.CVEList {
So(expectedCves, ShouldContain, *cve.ID)
}
cveResult, err = getCVEListForImage(responseContext, "repo1:1.0.1", cveInfo, pageInput, "", log)
So(err, ShouldBeNil)
So(*cveResult.Tag, ShouldEqual, "1.0.1")
@ -2165,7 +2213,7 @@ func TestCVEResolvers(t *testing.T) { //nolint:gocyclo
So(expectedCves, ShouldContain, *cve.ID)
}
cveResult, err = getCVEListForImage(responseContext, "repo1:1.1.0", cveInfo, pageInput, log)
cveResult, err = getCVEListForImage(responseContext, "repo1:1.1.0", cveInfo, pageInput, "", log)
So(err, ShouldBeNil)
So(*cveResult.Tag, ShouldEqual, "1.1.0")

View file

@ -550,6 +550,8 @@ type Query {
image: String!,
"Sets the parameters of the requested page"
requestedPage: PageInput
"Search term for specific CVE by title/id"
searchedCVE: String
): CVEResultForImage!
"""

View file

@ -14,12 +14,16 @@ import (
)
// CVEListForImage is the resolver for the CVEListForImage field.
func (r *queryResolver) CVEListForImage(ctx context.Context, image string, requestedPage *gql_generated.PageInput) (*gql_generated.CVEResultForImage, error) {
func (r *queryResolver) CVEListForImage(ctx context.Context, image string, requestedPage *gql_generated.PageInput, searchedCve *string) (*gql_generated.CVEResultForImage, error) {
if r.cveInfo == nil {
return &gql_generated.CVEResultForImage{}, zerr.ErrCVESearchDisabled
}
return getCVEListForImage(ctx, image, r.cveInfo, requestedPage, r.log)
if searchedCve == nil {
return getCVEListForImage(ctx, image, r.cveInfo, requestedPage, "", r.log)
}
return getCVEListForImage(ctx, image, r.cveInfo, requestedPage, *searchedCve, r.log)
}
// ImageListForCve is the resolver for the ImageListForCVE field.

View file

@ -9,7 +9,7 @@ import (
type CveInfoMock struct {
GetImageListForCVEFn func(repo, cveID string) ([]common.TagInfo, error)
GetImageListWithCVEFixedFn func(repo, cveID string) ([]common.TagInfo, error)
GetCVEListForImageFn func(repo string, reference string, pageInput cveinfo.PageInput,
GetCVEListForImageFn func(repo string, reference string, searchedCVE string, pageInput cveinfo.PageInput,
) ([]cvemodel.CVE, cveinfo.PageInfo, error)
GetCVESummaryForImageFn func(repo string, reference string,
) (cveinfo.ImageCVESummary, error)
@ -33,13 +33,15 @@ func (cveInfo CveInfoMock) GetImageListWithCVEFixed(repo, cveID string) ([]commo
return []common.TagInfo{}, nil
}
func (cveInfo CveInfoMock) GetCVEListForImage(repo string, reference string, pageInput cveinfo.PageInput) (
func (cveInfo CveInfoMock) GetCVEListForImage(repo string, reference string,
searchedCVE string, pageInput cveinfo.PageInput,
) (
[]cvemodel.CVE,
cveinfo.PageInfo,
error,
) {
if cveInfo.GetCVEListForImageFn != nil {
return cveInfo.GetCVEListForImageFn(repo, reference, pageInput)
return cveInfo.GetCVEListForImageFn(repo, reference, searchedCVE, pageInput)
}
return []cvemodel.CVE{}, cveinfo.PageInfo{}, nil