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
14 changed files with 212 additions and 64 deletions
+1
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 `+
+55
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))
+2 -2
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)
+8 -4
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
}
+8 -8
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)