mirror of
https://github.com/project-zot/zot.git
synced 2024-12-30 22:34:13 -05:00
feat: add verbose mode for cves for image listing (#2308)
Signed-off-by: Vishwas Rajashekar <vrajashe@cisco.com>
This commit is contained in:
parent
413514c0d4
commit
c7472a2dda
4 changed files with 214 additions and 9 deletions
|
@ -213,6 +213,43 @@ func TestSearchCVECmd(t *testing.T) {
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Convey("Test CVE by image name - in text format - in verbose mode", t, func() {
|
||||||
|
args := []string{"list", "dummyImageName:tag", "--url", baseURL, "--verbose"}
|
||||||
|
configPath := makeConfigFile(`{"configs":[{"_name":"cvetest","showspinner":false}]}`)
|
||||||
|
defer os.Remove(configPath)
|
||||||
|
cveCmd := NewCVECommand(new(mockService))
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
cveCmd.SetOut(buff)
|
||||||
|
cveCmd.SetErr(buff)
|
||||||
|
cveCmd.SetArgs(args)
|
||||||
|
err := cveCmd.Execute()
|
||||||
|
|
||||||
|
outputLines := strings.Split(buff.String(), "\n")
|
||||||
|
expected := []string{
|
||||||
|
"CRITICAL 0, HIGH 1, MEDIUM 0, LOW 0, UNKNOWN 0, TOTAL 1",
|
||||||
|
"",
|
||||||
|
"dummyCVEID",
|
||||||
|
"Severity: HIGH",
|
||||||
|
"Title: Title of that CVE",
|
||||||
|
"Description:",
|
||||||
|
"Description of the CVE",
|
||||||
|
"",
|
||||||
|
"Vulnerable Packages:",
|
||||||
|
" Package Name: packagename",
|
||||||
|
" Package Path: ",
|
||||||
|
" Installed Version: installedver",
|
||||||
|
" Fixed Version: fixedver",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
}
|
||||||
|
|
||||||
|
for index, expectedLine := range expected {
|
||||||
|
So(outputLines[index], ShouldEqual, expectedLine)
|
||||||
|
}
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
Convey("Test CVE by image name - in json format", t, func() {
|
Convey("Test CVE by image name - in json format", t, func() {
|
||||||
args := []string{"list", "dummyImageName:tag", "--url", baseURL, "-f", "json"}
|
args := []string{"list", "dummyImageName:tag", "--url", baseURL, "-f", "json"}
|
||||||
configPath := makeConfigFile(`{"configs":[{"_name":"cvetest","showspinner":false}]}`)
|
configPath := makeConfigFile(`{"configs":[{"_name":"cvetest","showspinner":false}]}`)
|
||||||
|
|
|
@ -253,11 +253,13 @@ func SearchCVEForImageGQL(config SearchConfig, image, searchedCveID string) erro
|
||||||
|
|
||||||
fmt.Fprint(config.ResultWriter, statsStr)
|
fmt.Fprint(config.ResultWriter, statsStr)
|
||||||
|
|
||||||
printCVETableHeader(&builder)
|
if !config.Verbose {
|
||||||
fmt.Fprint(config.ResultWriter, builder.String())
|
printCVETableHeader(&builder)
|
||||||
|
fmt.Fprint(config.ResultWriter, builder.String())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
out, err := cveList.string(config.OutputFormat)
|
out, err := cveList.string(config.OutputFormat, config.Verbose)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -303,7 +305,7 @@ func SearchCVEDiffList(config SearchConfig, minuend, subtrahend ImageIdentifier)
|
||||||
fmt.Fprint(config.ResultWriter, builder.String())
|
fmt.Fprint(config.ResultWriter, builder.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
out, err := result.string(config.OutputFormat)
|
out, err := result.string(config.OutputFormat, config.Verbose)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -322,7 +322,7 @@ func TestSearchImagesForDigestGQL(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSearchCVEForImageGQL(t *testing.T) {
|
func TestSearchCVEForImageGQL(t *testing.T) {
|
||||||
Convey("SearchCVEForImageGQL", t, func() {
|
Convey("SearchCVEForImageGQL normal mode", t, func() {
|
||||||
buff := bytes.NewBufferString("")
|
buff := bytes.NewBufferString("")
|
||||||
searchConfig := getMockSearchConfig(buff, mockService{
|
searchConfig := getMockSearchConfig(buff, mockService{
|
||||||
getCveByImageGQLFn: func(ctx context.Context, config SearchConfig, username string, password string,
|
getCveByImageGQLFn: func(ctx context.Context, config SearchConfig, username string, password string,
|
||||||
|
@ -403,6 +403,130 @@ func TestSearchCVEForImageGQL(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Convey("SearchCVEForImageGQL verbose mode", t, func() {
|
||||||
|
buff := bytes.NewBufferString("")
|
||||||
|
searchConfig := getMockSearchConfig(buff, mockService{
|
||||||
|
getCveByImageGQLFn: func(ctx context.Context, config SearchConfig, username string, password string,
|
||||||
|
imageName string, searchedCVE string) (*cveResult, error,
|
||||||
|
) {
|
||||||
|
return &cveResult{
|
||||||
|
Data: cveData{
|
||||||
|
CVEListForImage: cveListForImage{
|
||||||
|
CVEList: []cve{
|
||||||
|
{
|
||||||
|
ID: "CVE-100",
|
||||||
|
Description: "",
|
||||||
|
Title: "CVE-100 Title",
|
||||||
|
Severity: "HIGH",
|
||||||
|
PackageList: []packageList{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "CVE-101",
|
||||||
|
Description: "Desc 101\n",
|
||||||
|
Title: "CVE-101 Title",
|
||||||
|
Severity: "HIGH",
|
||||||
|
PackageList: []packageList{
|
||||||
|
{
|
||||||
|
Name: "Pkg1",
|
||||||
|
FixedVersion: "2.0.0",
|
||||||
|
InstalledVersion: "1.0.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "CVE-102",
|
||||||
|
Description: "Desc 102",
|
||||||
|
Title: "CVE-102 Title",
|
||||||
|
Severity: "HIGH",
|
||||||
|
PackageList: []packageList{
|
||||||
|
{
|
||||||
|
Name: "dummy-java",
|
||||||
|
PackagePath: "/usr/bin/dummy.jar",
|
||||||
|
FixedVersion: "4.0.0",
|
||||||
|
InstalledVersion: "3.0.0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "dummy-ruby",
|
||||||
|
PackagePath: "/usr/bin/dummy.gem",
|
||||||
|
FixedVersion: "5.0.0",
|
||||||
|
InstalledVersion: "1.0.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Summary: common.ImageVulnerabilitySummary{
|
||||||
|
Count: 3,
|
||||||
|
UnknownCount: 0,
|
||||||
|
LowCount: 0,
|
||||||
|
MediumCount: 0,
|
||||||
|
HighCount: 3,
|
||||||
|
CriticalCount: 0,
|
||||||
|
MaxSeverity: "HIGH",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
searchConfig.Verbose = true
|
||||||
|
err := SearchCVEForImageGQL(searchConfig, "repo-test", "dummyCVEID")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
bufferContent := buff.String()
|
||||||
|
bufferLines := strings.Split(bufferContent, "\n")
|
||||||
|
|
||||||
|
// Expected result - each row indicates a line in the output
|
||||||
|
expected := []string{
|
||||||
|
"CRITICAL 0, HIGH 3, MEDIUM 0, LOW 0, UNKNOWN 0, TOTAL 3",
|
||||||
|
"",
|
||||||
|
"CVE-100",
|
||||||
|
"Severity: HIGH",
|
||||||
|
"Title: CVE-100 Title",
|
||||||
|
"Description:",
|
||||||
|
"Not Specified",
|
||||||
|
"",
|
||||||
|
"Vulnerable Packages:",
|
||||||
|
"No Vulnerable Packages",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"CVE-101",
|
||||||
|
"Severity: HIGH",
|
||||||
|
"Title: CVE-101 Title",
|
||||||
|
"Description:",
|
||||||
|
"Desc 101",
|
||||||
|
"",
|
||||||
|
"Vulnerable Packages:",
|
||||||
|
" Package Name: Pkg1",
|
||||||
|
" Package Path: ",
|
||||||
|
" Installed Version: 1.0.0",
|
||||||
|
" Fixed Version: 2.0.0",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"CVE-102",
|
||||||
|
"Severity: HIGH",
|
||||||
|
"Title: CVE-102 Title",
|
||||||
|
"Description:",
|
||||||
|
"Desc 102",
|
||||||
|
"",
|
||||||
|
"Vulnerable Packages:",
|
||||||
|
" Package Name: dummy-java",
|
||||||
|
" Package Path: /usr/bin/dummy.jar",
|
||||||
|
" Installed Version: 3.0.0",
|
||||||
|
" Fixed Version: 4.0.0",
|
||||||
|
"",
|
||||||
|
" Package Name: dummy-ruby",
|
||||||
|
" Package Path: /usr/bin/dummy.gem",
|
||||||
|
" Installed Version: 1.0.0",
|
||||||
|
" Fixed Version: 5.0.0",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
}
|
||||||
|
|
||||||
|
for index, expectedLine := range expected {
|
||||||
|
So(bufferLines[index], ShouldEqual, expectedLine)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
Convey("SearchCVEForImageGQL with injected error", t, func() {
|
Convey("SearchCVEForImageGQL with injected error", t, func() {
|
||||||
buff := bytes.NewBufferString("")
|
buff := bytes.NewBufferString("")
|
||||||
searchConfig := getMockSearchConfig(buff, mockService{
|
searchConfig := getMockSearchConfig(buff, mockService{
|
||||||
|
|
|
@ -817,10 +817,19 @@ type cveData struct {
|
||||||
CVEListForImage cveListForImage `json:"cveListForImage"`
|
CVEListForImage cveListForImage `json:"cveListForImage"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cve cveResult) string(format string) (string, error) {
|
func (cve cveResult) string(format string, verbose bool) (string, error) {
|
||||||
switch strings.ToLower(format) {
|
switch strings.ToLower(format) {
|
||||||
case "", defaultOutputFormat:
|
case "", defaultOutputFormat:
|
||||||
return cve.stringPlainText()
|
{
|
||||||
|
var out string
|
||||||
|
if verbose {
|
||||||
|
out = cve.stringPlainTextDetailed()
|
||||||
|
} else {
|
||||||
|
out = cve.stringPlainText()
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
case jsonFormat:
|
case jsonFormat:
|
||||||
return cve.stringJSON()
|
return cve.stringJSON()
|
||||||
case ymlFormat, yamlFormat:
|
case ymlFormat, yamlFormat:
|
||||||
|
@ -830,7 +839,40 @@ func (cve cveResult) string(format string) (string, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cve cveResult) stringPlainText() (string, error) {
|
func (cve cveResult) stringPlainTextDetailed() string {
|
||||||
|
var builder strings.Builder
|
||||||
|
|
||||||
|
for _, cveListItem := range cve.Data.CVEListForImage.CVEList {
|
||||||
|
cveDesc := strings.TrimSpace(cveListItem.Description)
|
||||||
|
if len(cveDesc) == 0 {
|
||||||
|
cveDesc = "Not Specified"
|
||||||
|
}
|
||||||
|
cveMetaData := fmt.Sprintf(
|
||||||
|
"%s\nSeverity: %s\nTitle: %s\nDescription:\n%s\n\n",
|
||||||
|
cveListItem.ID, cveListItem.Severity, cveListItem.Title, cveDesc,
|
||||||
|
)
|
||||||
|
fmt.Fprint(&builder, cveMetaData)
|
||||||
|
fmt.Fprint(&builder, "Vulnerable Packages:\n")
|
||||||
|
|
||||||
|
for _, pkg := range cveListItem.PackageList {
|
||||||
|
pkgMetaData := fmt.Sprintf(
|
||||||
|
" Package Name: %s\n Package Path: %s\n Installed Version: %s\n Fixed Version: %s\n\n",
|
||||||
|
pkg.Name, pkg.PackagePath, pkg.InstalledVersion, pkg.FixedVersion,
|
||||||
|
)
|
||||||
|
fmt.Fprint(&builder, pkgMetaData)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cveListItem.PackageList) == 0 {
|
||||||
|
fmt.Fprintf(&builder, "No Vulnerable Packages\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprint(&builder, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cve cveResult) stringPlainText() string {
|
||||||
var builder strings.Builder
|
var builder strings.Builder
|
||||||
|
|
||||||
table := getCVETableWriter(&builder)
|
table := getCVETableWriter(&builder)
|
||||||
|
@ -849,7 +891,7 @@ func (cve cveResult) stringPlainText() (string, error) {
|
||||||
|
|
||||||
table.Render()
|
table.Render()
|
||||||
|
|
||||||
return builder.String(), nil
|
return builder.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cve cveResult) stringJSON() (string, error) {
|
func (cve cveResult) stringJSON() (string, error) {
|
||||||
|
|
Loading…
Reference in a new issue