Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(CVE): add CVE severity counters to returned images #2131

Merged
merged 1 commit into from
Dec 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions pkg/cli/client/cve_cmd_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,8 @@ func TestSearchCVECmd(t *testing.T) {
err := cmd.Execute()
space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ")
So(strings.TrimSpace(str), ShouldEqual, "ID SEVERITY TITLE dummyCVEID HIGH Title of that CVE")
So(strings.TrimSpace(str), ShouldEqual, "CRITICAL 0, HIGH 1, MEDIUM 0, LOW 0, UNKNOWN 0, TOTAL 1 "+
"ID SEVERITY TITLE dummyCVEID HIGH Title of that CVE")
So(err, ShouldBeNil)
})

Expand Down Expand Up @@ -207,7 +208,8 @@ func TestSearchCVECmd(t *testing.T) {
err := cveCmd.Execute()
space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ")
So(strings.TrimSpace(str), ShouldEqual, "ID SEVERITY TITLE dummyCVEID HIGH Title of that CVE")
So(strings.TrimSpace(str), ShouldEqual, "CRITICAL 0, HIGH 1, MEDIUM 0, LOW 0, UNKNOWN 0, TOTAL 1 "+
"ID SEVERITY TITLE dummyCVEID HIGH Title of that CVE")
So(err, ShouldBeNil)
})

Expand All @@ -225,7 +227,9 @@ func TestSearchCVECmd(t *testing.T) {
So(buff.String(), ShouldEqual, `{"Tag":"dummyImageName:tag","CVEList":`+
`[{"Id":"dummyCVEID","Severity":"HIGH","Title":"Title of that CVE",`+
`"Description":"Description of the CVE","PackageList":[{"Name":"packagename",`+
`"InstalledVersion":"installedver","FixedVersion":"fixedver"}]}]}`+"\n")
`"InstalledVersion":"installedver","FixedVersion":"fixedver"}]}],"Summary":`+
`{"maxSeverity":"HIGH","unknownCount":0,"lowCount":0,"mediumCount":0,"highCount":1,`+
`"criticalCount":0,"count":1}}`+"\n")
So(err, ShouldBeNil)
})

Expand All @@ -243,7 +247,8 @@ func TestSearchCVECmd(t *testing.T) {
str := space.ReplaceAllString(buff.String(), " ")
So(strings.TrimSpace(str), ShouldEqual, `--- tag: dummyImageName:tag cvelist: - id: dummyCVEID`+
` severity: HIGH title: Title of that CVE description: Description of the CVE packagelist: `+
`- name: packagename installedversion: installedver fixedversion: fixedver`)
`- name: packagename installedversion: installedver fixedversion: fixedver `+
`summary: maxseverity: HIGH unknowncount: 0 lowcount: 0 mediumcount: 0 highcount: 1 criticalcount: 0 count: 1`)
So(err, ShouldBeNil)
})
Convey("Test CVE by image name - invalid format", t, func() {
Expand Down Expand Up @@ -508,6 +513,7 @@ func TestCVECommandGQL(t *testing.T) {
space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "CRITICAL 0, HIGH 1, MEDIUM 0, LOW 0, UNKNOWN 0, TOTAL 1")
So(actual, ShouldContainSubstring, "dummyCVEID HIGH Title of that CVE")
})

Expand Down
6 changes: 3 additions & 3 deletions pkg/cli/client/cve_cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -634,7 +634,7 @@ func TestCVESort(t *testing.T) {
str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str)
So(actual, ShouldResemble,
"ID SEVERITY TITLE "+
"CRITICAL 1, HIGH 1, MEDIUM 2, LOW 1, UNKNOWN 0, TOTAL 5 ID SEVERITY TITLE "+
"CVE-2023-3446 CRITICAL Excessive time spent checking DH keys and par... "+
"CVE-2023-2975 HIGH AES-SIV cipher implementation contains a bug ... "+
"CVE-2023-2650 MEDIUM Possible DoS translating ASN.1 object identif... "+
Expand All @@ -652,7 +652,7 @@ func TestCVESort(t *testing.T) {
str = space.ReplaceAllString(buff.String(), " ")
actual = strings.TrimSpace(str)
So(actual, ShouldResemble,
"ID SEVERITY TITLE "+
"CRITICAL 1, HIGH 1, MEDIUM 2, LOW 1, UNKNOWN 0, TOTAL 5 ID SEVERITY TITLE "+
"CVE-2023-1255 LOW Input buffer over-read in AES-XTS implementat... "+
"CVE-2023-2650 MEDIUM Possible DoS translating ASN.1 object identif... "+
"CVE-2023-2975 HIGH AES-SIV cipher implementation contains a bug ... "+
Expand All @@ -670,7 +670,7 @@ func TestCVESort(t *testing.T) {
str = space.ReplaceAllString(buff.String(), " ")
actual = strings.TrimSpace(str)
So(actual, ShouldResemble,
"ID SEVERITY TITLE "+
"CRITICAL 1, HIGH 1, MEDIUM 2, LOW 1, UNKNOWN 0, TOTAL 5 ID SEVERITY TITLE "+
"CVE-2023-3817 MEDIUM Excessive time spent checking DH q parameter ... "+
"CVE-2023-3446 CRITICAL Excessive time spent checking DH keys and par... "+
"CVE-2023-2975 HIGH AES-SIV cipher implementation contains a bug ... "+
Expand Down
29 changes: 23 additions & 6 deletions pkg/cli/client/image_cmd_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -384,11 +384,13 @@ func TestOutputFormat(t *testing.T) {
`"lastUpdated":"0001-01-01T00:00:00Z","size":"123445","platform":{"os":"os","arch":"arch",`+
`"variant":""},"isSigned":false,"downloadCount":0,`+
`"layers":[{"size":"","digest":"sha256:c122a146f0d02349be211bb95cc2530f4a5793f96edbdfa00860f741e5d8c0e6",`+
`"score":0}],"history":null,"vulnerabilities":{"maxSeverity":"","count":0},`+
`"score":0}],"history":null,"vulnerabilities":{"maxSeverity":"","unknownCount":0,"lowCount":0,`+
`"mediumCount":0,"highCount":0,"criticalCount":0,"count":0},`+
`"referrers":null,"artifactType":"","signatureInfo":null}],"size":"123445",`+
`"downloadCount":0,"lastUpdated":"0001-01-01T00:00:00Z","description":"","isSigned":false,"licenses":"",`+
`"labels":"","title":"","source":"","documentation":"","authors":"","vendor":"",`+
`"vulnerabilities":{"maxSeverity":"","count":0},"referrers":null,"signatureInfo":null}`+"\n")
`"vulnerabilities":{"maxSeverity":"","unknownCount":0,"lowCount":0,"mediumCount":0,"highCount":0,`+
`"criticalCount":0,"count":0},"referrers":null,"signatureInfo":null}`+"\n")
So(err, ShouldBeNil)
})

Expand All @@ -415,10 +417,13 @@ func TestOutputFormat(t *testing.T) {
`lastupdated: 0001-01-01T00:00:00Z size: "123445" platform: os: os arch: arch variant: "" `+
`issigned: false downloadcount: 0 layers: - size: "" `+
`digest: sha256:c122a146f0d02349be211bb95cc2530f4a5793f96edbdfa00860f741e5d8c0e6 score: 0 `+
`history: [] vulnerabilities: maxseverity: "" count: 0 referrers: [] artifacttype: "" `+
`history: [] vulnerabilities: maxseverity: "" `+
`unknowncount: 0 lowcount: 0 mediumcount: 0 highcount: 0 criticalcount: 0 count: 0 `+
`referrers: [] artifacttype: "" `+
`signatureinfo: [] size: "123445" downloadcount: 0 `+
`lastupdated: 0001-01-01T00:00:00Z description: "" issigned: false licenses: "" labels: "" `+
`title: "" source: "" documentation: "" authors: "" vendor: "" vulnerabilities: maxseverity: "" `+
`unknowncount: 0 lowcount: 0 mediumcount: 0 highcount: 0 criticalcount: 0 `+
`count: 0 referrers: [] signatureinfo: []`,
)
So(err, ShouldBeNil)
Expand Down Expand Up @@ -449,11 +454,13 @@ func TestOutputFormat(t *testing.T) {
`lastupdated: 0001-01-01T00:00:00Z size: "123445" platform: os: os arch: arch variant: "" `+
`issigned: false downloadcount: 0 layers: - size: "" `+
`digest: sha256:c122a146f0d02349be211bb95cc2530f4a5793f96edbdfa00860f741e5d8c0e6 score: 0 `+
`history: [] vulnerabilities: maxseverity: "" count: 0 referrers: [] artifacttype: "" `+
`history: [] vulnerabilities: maxseverity: "" unknowncount: 0 lowcount: 0 mediumcount: 0 `+
`highcount: 0 criticalcount: 0 count: 0 referrers: [] artifacttype: "" `+
`signatureinfo: [] size: "123445" downloadcount: 0 `+
`lastupdated: 0001-01-01T00:00:00Z description: "" issigned: false licenses: "" labels: "" `+
`title: "" source: "" documentation: "" authors: "" vendor: "" vulnerabilities: maxseverity: `+
`"" count: 0 referrers: [] signatureinfo: []`,
`title: "" source: "" documentation: "" authors: "" vendor: "" vulnerabilities: maxseverity: "" `+
`unknowncount: 0 lowcount: 0 mediumcount: 0 highcount: 0 criticalcount: 0 `+
`count: 0 referrers: [] signatureinfo: []`,
)
So(err, ShouldBeNil)
})
Expand Down Expand Up @@ -783,6 +790,7 @@ func TestImagesCommandGQL(t *testing.T) {
space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "CRITICAL 0, HIGH 1, MEDIUM 0, LOW 0, UNKNOWN 0, TOTAL 1")
So(actual, ShouldContainSubstring, "dummyCVEID HIGH Title of that CVE")
})

Expand Down Expand Up @@ -1342,6 +1350,15 @@ func (service mockService) getCveByImageGQL(ctx context.Context, config SearchCo
},
},
},
Summary: common.ImageVulnerabilitySummary{
Count: 1,
UnknownCount: 0,
LowCount: 0,
MediumCount: 0,
HighCount: 1,
CriticalCount: 0,
MaxSeverity: "HIGH",
},
},
}

Expand Down
39 changes: 27 additions & 12 deletions pkg/cli/client/image_cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -379,11 +379,13 @@ func TestOutputFormatGQL(t *testing.T) {
`"lastUpdated":"2023-01-01T12:00:00Z","size":"528","platform":{"os":"linux","arch":"amd64",` +
`"variant":""},"isSigned":false,"downloadCount":0,"layers":[{"size":"15","digest":` +
`"sha256:b8781e8844f5b7bf6f2f8fa343de18ec471c3b278027355bc34c120585ff04f6","score":0}],` +
`"history":null,"vulnerabilities":{"maxSeverity":"","count":0},` +
`"history":null,"vulnerabilities":{"maxSeverity":"","unknownCount":0,"lowCount":0,"mediumCount":0,` +
`"highCount":0,"criticalCount":0,"count":0},` +
`"referrers":null,"artifactType":"","signatureInfo":null}],` +
`"size":"528","downloadCount":0,"lastUpdated":"2023-01-01T12:00:00Z","description":"","isSigned":false,` +
`"licenses":"","labels":"","title":"","source":"","documentation":"","authors":"","vendor":"",` +
`"vulnerabilities":{"maxSeverity":"","count":0},"referrers":null,"signatureInfo":null}` + "\n" +
`"vulnerabilities":{"maxSeverity":"","unknownCount":0,"lowCount":0,"mediumCount":0,` +
`"highCount":0,"criticalCount":0,"count":0},"referrers":null,"signatureInfo":null}` + "\n" +
`{"repoName":"repo7","tag":"test:2.0",` +
`"digest":"sha256:51e18f508fd7125b0831ff9a22ba74cd79f0b934e77661ff72cfb54896951a06",` +
`"mediaType":"application/vnd.oci.image.manifest.v1+json",` +
Expand All @@ -392,11 +394,13 @@ func TestOutputFormatGQL(t *testing.T) {
`"lastUpdated":"2023-01-01T12:00:00Z","size":"528","platform":{"os":"linux","arch":"amd64",` +
`"variant":""},"isSigned":false,"downloadCount":0,"layers":[{"size":"15","digest":` +
`"sha256:b8781e8844f5b7bf6f2f8fa343de18ec471c3b278027355bc34c120585ff04f6","score":0}],` +
`"history":null,"vulnerabilities":{"maxSeverity":"","count":0},` +
`"history":null,"vulnerabilities":{"maxSeverity":"","unknownCount":0,"lowCount":0,"mediumCount":0,` +
`"highCount":0,"criticalCount":0,"count":0},` +
`"referrers":null,"artifactType":"","signatureInfo":null}],` +
`"size":"528","downloadCount":0,"lastUpdated":"2023-01-01T12:00:00Z","description":"","isSigned":false,` +
`"licenses":"","labels":"","title":"","source":"","documentation":"","authors":"","vendor":"",` +
`"vulnerabilities":{"maxSeverity":"","count":0},"referrers":null,"signatureInfo":null}` + "\n"
`"vulnerabilities":{"maxSeverity":"","unknownCount":0,"lowCount":0,"mediumCount":0,` +
`"highCount":0,"criticalCount":0,"count":0},"referrers":null,"signatureInfo":null}` + "\n"
// Output is supposed to be in json lines format, keep all spaces as is for verification
So(buff.String(), ShouldEqual, expectedStr)
So(err, ShouldBeNil)
Expand Down Expand Up @@ -424,10 +428,13 @@ func TestOutputFormatGQL(t *testing.T) {
`issigned: false downloadcount: 0 layers: - size: "15" ` +
`digest: sha256:b8781e8844f5b7bf6f2f8fa343de18ec471c3b278027355bc34c120585ff04f6 score: 0 ` +
`history: [] vulnerabilities: maxseverity: "" ` +
`count: 0 referrers: [] artifacttype: "" signatureinfo: [] ` +
`unknowncount: 0 lowcount: 0 mediumcount: 0 highcount: 0 criticalcount: 0 count: 0 ` +
`referrers: [] artifacttype: "" signatureinfo: [] ` +
`size: "528" downloadcount: 0 lastupdated: 2023-01-01T12:00:00Z description: "" ` +
`issigned: false licenses: "" labels: "" title: "" source: "" documentation: "" ` +
`authors: "" vendor: "" vulnerabilities: maxseverity: "" count: 0 referrers: [] signatureinfo: [] ` +
`authors: "" vendor: "" vulnerabilities: maxseverity: "" ` +
`unknowncount: 0 lowcount: 0 mediumcount: 0 highcount: 0 criticalcount: 0 count: 0 ` +
`referrers: [] signatureinfo: [] ` +
`--- reponame: repo7 tag: test:2.0 ` +
`digest: sha256:51e18f508fd7125b0831ff9a22ba74cd79f0b934e77661ff72cfb54896951a06 ` +
`mediatype: application/vnd.oci.image.manifest.v1+json manifests: - ` +
Expand All @@ -437,10 +444,13 @@ func TestOutputFormatGQL(t *testing.T) {
`issigned: false downloadcount: 0 layers: - size: "15" ` +
`digest: sha256:b8781e8844f5b7bf6f2f8fa343de18ec471c3b278027355bc34c120585ff04f6 score: 0 ` +
`history: [] vulnerabilities: maxseverity: "" ` +
`count: 0 referrers: [] artifacttype: "" signatureinfo: [] ` +
`unknowncount: 0 lowcount: 0 mediumcount: 0 highcount: 0 criticalcount: 0 count: 0 ` +
`referrers: [] artifacttype: "" signatureinfo: [] ` +
`size: "528" downloadcount: 0 lastupdated: 2023-01-01T12:00:00Z description: "" ` +
`issigned: false licenses: "" labels: "" title: "" source: "" documentation: "" ` +
`authors: "" vendor: "" vulnerabilities: maxseverity: "" count: 0 referrers: [] signatureinfo: []`
`authors: "" vendor: "" vulnerabilities: maxseverity: "" ` +
`unknowncount: 0 lowcount: 0 mediumcount: 0 highcount: 0 criticalcount: 0 count: 0 ` +
`referrers: [] signatureinfo: []`
So(strings.TrimSpace(str), ShouldEqual, expectedStr)
So(err, ShouldBeNil)
})
Expand All @@ -467,11 +477,13 @@ func TestOutputFormatGQL(t *testing.T) {
`issigned: false downloadcount: 0 layers: - size: "15" ` +
`digest: sha256:b8781e8844f5b7bf6f2f8fa343de18ec471c3b278027355bc34c120585ff04f6 score: 0 ` +
`history: [] vulnerabilities: maxseverity: "" ` +
`count: 0 referrers: [] artifacttype: "" signatureinfo: [] ` +
`unknowncount: 0 lowcount: 0 mediumcount: 0 highcount: 0 criticalcount: 0 count: 0 ` +
`referrers: [] artifacttype: "" signatureinfo: [] ` +
`size: "528" downloadcount: 0 lastupdated: 2023-01-01T12:00:00Z description: "" ` +
`issigned: false licenses: "" labels: "" title: "" source: "" documentation: "" ` +
`authors: "" vendor: "" vulnerabilities: maxseverity: "" ` +
`count: 0 referrers: [] signatureinfo: [] ` +
`unknowncount: 0 lowcount: 0 mediumcount: 0 highcount: 0 criticalcount: 0 count: 0 ` +
`referrers: [] signatureinfo: [] ` +
`--- reponame: repo7 tag: test:2.0 ` +
`digest: sha256:51e18f508fd7125b0831ff9a22ba74cd79f0b934e77661ff72cfb54896951a06 ` +
`mediatype: application/vnd.oci.image.manifest.v1+json manifests: - ` +
Expand All @@ -481,10 +493,13 @@ func TestOutputFormatGQL(t *testing.T) {
`issigned: false downloadcount: 0 layers: - size: "15" ` +
`digest: sha256:b8781e8844f5b7bf6f2f8fa343de18ec471c3b278027355bc34c120585ff04f6 score: 0 ` +
`history: [] vulnerabilities: maxseverity: "" ` +
`count: 0 referrers: [] artifacttype: "" signatureinfo: [] ` +
`unknowncount: 0 lowcount: 0 mediumcount: 0 highcount: 0 criticalcount: 0 count: 0 ` +
`referrers: [] artifacttype: "" signatureinfo: [] ` +
`size: "528" downloadcount: 0 lastupdated: 2023-01-01T12:00:00Z description: "" ` +
`issigned: false licenses: "" labels: "" title: "" source: "" documentation: "" ` +
`authors: "" vendor: "" vulnerabilities: maxseverity: "" count: 0 referrers: [] signatureinfo: []`
`authors: "" vendor: "" vulnerabilities: maxseverity: "" ` +
`unknowncount: 0 lowcount: 0 mediumcount: 0 highcount: 0 criticalcount: 0 count: 0 ` +
`referrers: [] signatureinfo: []`
So(strings.TrimSpace(str), ShouldEqual, expectedStr)
So(err, ShouldBeNil)
})
Expand Down
8 changes: 8 additions & 0 deletions pkg/cli/client/search_functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,14 @@ func SearchCVEForImageGQL(config SearchConfig, image, searchedCveID string) erro
var builder strings.Builder

if config.OutputFormat == defaultOutputFormat || config.OutputFormat == "" {
imageCVESummary := cveList.Data.CVEListForImage.Summary

statsStr := fmt.Sprintf("CRITICAL %d, HIGH %d, MEDIUM %d, LOW %d, UNKNOWN %d, TOTAL %d\n\n",
imageCVESummary.CriticalCount, imageCVESummary.HighCount, imageCVESummary.MediumCount,
imageCVESummary.LowCount, imageCVESummary.UnknownCount, imageCVESummary.Count)

fmt.Fprint(config.ResultWriter, statsStr)

printCVETableHeader(&builder)
fmt.Fprint(config.ResultWriter, builder.String())
}
Expand Down
10 changes: 10 additions & 0 deletions pkg/cli/client/search_functions_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,15 @@ func TestSearchCVEForImageGQL(t *testing.T) {
},
},
},
Summary: common.ImageVulnerabilitySummary{
Count: 1,
UnknownCount: 0,
LowCount: 0,
MediumCount: 0,
HighCount: 1,
CriticalCount: 0,
MaxSeverity: "HIGH",
},
},
},
}, nil
Expand All @@ -357,6 +366,7 @@ func TestSearchCVEForImageGQL(t *testing.T) {
space := regexp.MustCompile(`\s+`)
str := space.ReplaceAllString(buff.String(), " ")
actual := strings.TrimSpace(str)
So(actual, ShouldContainSubstring, "CRITICAL 0, HIGH 1, MEDIUM 0, LOW 0, UNKNOWN 0, TOTAL 1")
So(actual, ShouldContainSubstring, "dummyCVEID HIGH Title of that CVE")
})

Expand Down
11 changes: 8 additions & 3 deletions pkg/cli/client/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,10 +305,14 @@ func (service searchService) getCveByImageGQL(ctx context.Context, config Search
query := fmt.Sprintf(`
{
CVEListForImage (image:"%s", searchedCVE:"%s", requestedPage: {sortBy: %s}) {
Tag CVEList {
Tag
CVEList {
Id Title Severity Description
PackageList {Name InstalledVersion FixedVersion}
}
Summary {
Count UnknownCount LowCount MediumCount HighCount CriticalCount MaxSeverity
}
}
}`, imageName, searchedCVE, Flag2SortCriteria(config.SortBy))
result := &cveResult{}
Expand Down Expand Up @@ -743,8 +747,9 @@ type cve struct {

//nolint:tagliatelle // graphQL schema
type cveListForImage struct {
Tag string `json:"Tag"`
CVEList []cve `json:"CVEList"`
Tag string `json:"Tag"`
CVEList []cve `json:"CVEList"`
Summary common.ImageVulnerabilitySummary `json:"Summary"`
}

//nolint:tagliatelle // graphQL schema
Expand Down
9 changes: 7 additions & 2 deletions pkg/common/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,13 @@ type Platform struct {
}

type ImageVulnerabilitySummary struct {
MaxSeverity string `json:"maxSeverity"`
Count int `json:"count"`
MaxSeverity string `json:"maxSeverity"`
UnknownCount int `json:"unknownCount"`
LowCount int `json:"lowCount"`
MediumCount int `json:"mediumCount"`
HighCount int `json:"highCount"`
CriticalCount int `json:"criticalCount"`
Count int `json:"count"`
}

type LayerSummary struct {
Expand Down
Loading
Loading