Skip to content

Commit

Permalink
feat: include PackagePath data in CVEs for image queries (#2241)
Browse files Browse the repository at this point in the history
Signed-off-by: Vishwas Rajashekar <vrajashe@cisco.com>
  • Loading branch information
vrajashkr authored Feb 15, 2024
1 parent cc2eda0 commit 0aa6bf0
Show file tree
Hide file tree
Showing 13 changed files with 227 additions and 17 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ $(TESTDATA): check-skopeo
skopeo --insecure-policy copy -q docker://public.ecr.aws/t0x7q1g8/centos:8 oci:${TESTDATA}/zot-cve-test:0.0.1; \
skopeo --insecure-policy copy -q docker://ghcr.io/project-zot/test-images/java:0.0.1 oci:${TESTDATA}/zot-cve-java-test:0.0.1; \
skopeo --insecure-policy copy -q docker://ghcr.io/project-zot/test-images/alpine:3.17.3 oci:${TESTDATA}/alpine:3.17.3; \
skopeo --insecure-policy copy -q docker://ghcr.io/project-zot/test-images/spring-web:5.3.31 oci:${TESTDATA}/spring-web:5.3.31; \
chmod -R a=rwx ${TESTDATA}
ls -R -l ${TESTDATA}

Expand Down
4 changes: 2 additions & 2 deletions pkg/cli/client/cve_cmd_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ 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"}]}],"Summary":`+
`"PackagePath":"","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 @@ -247,7 +247,7 @@ 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 packagepath: "" installedversion: installedver fixedversion: fixedver `+
`summary: maxseverity: HIGH unknowncount: 0 lowcount: 0 mediumcount: 0 highcount: 1 criticalcount: 0 count: 1`)
So(err, ShouldBeNil)
})
Expand Down
47 changes: 40 additions & 7 deletions pkg/cli/client/search_functions_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,13 +345,33 @@ func TestSearchCVEForImageGQL(t *testing.T) {
},
},
},
{
ID: "test-cve-id2",
Description: "Test CVE ID 2",
Title: "Test CVE 2",
Severity: "HIGH",
PackageList: []packageList{
{
Name: "packagename",
PackagePath: "/usr/bin/dummy.jar",
FixedVersion: "fixedver",
InstalledVersion: "installedver",
},
{
Name: "packagename",
PackagePath: "/usr/bin/dummy.gem",
FixedVersion: "fixedver",
InstalledVersion: "installedver",
},
},
},
},
Summary: common.ImageVulnerabilitySummary{
Count: 1,
Count: 2,
UnknownCount: 0,
LowCount: 0,
MediumCount: 0,
HighCount: 1,
HighCount: 2,
CriticalCount: 0,
MaxSeverity: "HIGH",
},
Expand All @@ -363,14 +383,27 @@ func TestSearchCVEForImageGQL(t *testing.T) {

err := SearchCVEForImageGQL(searchConfig, "repo-test", "dummyCVEID")
So(err, ShouldBeNil)
bufferContent := buff.String()
bufferLines := strings.Split(bufferContent, "\n")

// Expected result - each row indicates a row of the table with reduced spaces
expected := []string{
"CRITICAL 0, HIGH 2, MEDIUM 0, LOW 0, UNKNOWN 0, TOTAL 2",
"",
"ID SEVERITY TITLE",
"dummyCVEID HIGH Title of that CVE",
"test-cve-id2 HIGH Test CVE 2",
}

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")

for lineIndex := 0; lineIndex < len(expected); lineIndex++ {
line := space.ReplaceAllString(bufferLines[lineIndex], " ")
So(line, ShouldEqualTrimSpace, expected[lineIndex])
}
})

Convey("SearchCVEForImageGQL", t, func() {
Convey("SearchCVEForImageGQL with injected error", t, func() {
buff := bytes.NewBufferString("")
searchConfig := getMockSearchConfig(buff, mockService{
getCveByImageGQLFn: func(ctx context.Context, config SearchConfig, username string, password string,
Expand Down
3 changes: 2 additions & 1 deletion pkg/cli/client/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ func (service searchService) getCveByImageGQL(ctx context.Context, config Search
Tag
CVEList {
Id Title Severity Description
PackageList {Name InstalledVersion FixedVersion}
PackageList {Name PackagePath InstalledVersion FixedVersion}
}
Summary {
Count UnknownCount LowCount MediumCount HighCount CriticalCount MaxSeverity
Expand Down Expand Up @@ -732,6 +732,7 @@ type tagListResp struct {
//nolint:tagliatelle // graphQL schema
type packageList struct {
Name string `json:"Name"`
PackagePath string `json:"PackagePath"`
InstalledVersion string `json:"InstalledVersion"`
FixedVersion string `json:"FixedVersion"`
}
Expand Down
11 changes: 11 additions & 0 deletions pkg/extensions/search/cve/cve_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ func TestUtils(t *testing.T) {
PackageList: []cvemodel.Package{
{
Name: "NameTest",
PackagePath: "/usr/bin/artifacts/dummy.jar",
FixedVersion: "FixedVersionTest",
InstalledVersion: "InstalledVersionTest",
},
{
Name: "NameTest",
PackagePath: "/usr/local/artifacts/dummy.gem",
FixedVersion: "FixedVersionTest",
InstalledVersion: "InstalledVersionTest",
},
Expand All @@ -34,6 +41,10 @@ func TestUtils(t *testing.T) {
So(cve.ContainsStr("NameTest"), ShouldBeTrue)
So(cve.ContainsStr("FixedVersionTest"), ShouldBeTrue)
So(cve.ContainsStr("InstalledVersionTest"), ShouldBeTrue)
So(cve.ContainsStr("/usr/bin/artifacts/dummy.jar"), ShouldBeTrue)
So(cve.ContainsStr("dummy.jar"), ShouldBeTrue)
So(cve.ContainsStr("/usr/local/artifacts/dummy.gem"), ShouldBeTrue)
So(cve.ContainsStr("dummy.gem"), ShouldBeTrue)
})
Convey("getConfigAndDigest", func() {
_, _, err := getConfigAndDigest(mocks.MetaDBMock{}, "bad-digest")
Expand Down
4 changes: 3 additions & 1 deletion pkg/extensions/search/cve/model/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,15 @@ func (cve *CVE) ContainsStr(str string) bool {
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)
strings.Contains(strings.ToUpper(pack.InstalledVersion), str) ||
strings.Contains(strings.ToUpper(pack.PackagePath), str)
})
}

//nolint:tagliatelle // graphQL schema
type Package struct {
Name string `json:"Name"`
PackagePath string `json:"PackagePath"`
InstalledVersion string `json:"InstalledVersion"`
FixedVersion string `json:"FixedVersion"`
}
Expand Down
9 changes: 9 additions & 0 deletions pkg/extensions/search/cve/trivy/scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,13 @@ func (scanner Scanner) scanManifest(ctx context.Context, repo, digest string) (m
fixedVersion = "Not Specified"
}

var packagePath string
if vulnerability.PkgPath != "" {
packagePath = vulnerability.PkgPath
} else {
packagePath = "Not Specified"
}

_, ok := cveidMap[vulnerability.VulnerabilityID]
if ok {
cveDetailStruct := cveidMap[vulnerability.VulnerabilityID]
Expand All @@ -404,6 +411,7 @@ func (scanner Scanner) scanManifest(ctx context.Context, repo, digest string) (m
pkgList,
cvemodel.Package{
Name: pkgName,
PackagePath: packagePath,
InstalledVersion: installedVersion,
FixedVersion: fixedVersion,
},
Expand All @@ -419,6 +427,7 @@ func (scanner Scanner) scanManifest(ctx context.Context, repo, digest string) (m
newPkgList,
cvemodel.Package{
Name: pkgName,
PackagePath: packagePath,
InstalledVersion: installedVersion,
FixedVersion: fixedVersion,
},
Expand Down
78 changes: 78 additions & 0 deletions pkg/extensions/search/cve/trivy/scanner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,84 @@ func TestVulnerableLayer(t *testing.T) {
So(cveMap, ShouldContainKey, "CVE-2023-3817")
So(cveMap, ShouldContainKey, "CVE-2023-3446")
})

Convey("Vulnerable layer with vulnerability in language-specific file", t, func() {
vulnerableLayer, err := GetLayerWithLanguageFileVulnerability()
So(err, ShouldBeNil)

created, err := time.Parse(time.RFC3339, "2024-02-15T09:56:01.500079786Z")
So(err, ShouldBeNil)

config := ispec.Image{
Created: &created,
Platform: ispec.Platform{
Architecture: "amd64",
OS: "linux",
},
Config: ispec.ImageConfig{
Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"},
},
RootFS: ispec.RootFS{
Type: "layers",
DiffIDs: []godigest.Digest{"sha256:d789b0723f3e6e5064d612eb3c84071cc84a7cf7921d549642252c3295e5f937"},
},
}

img := CreateImageWith().
LayerBlobs([][]byte{vulnerableLayer}).
ImageConfig(config).
Build()

tempDir := t.TempDir()

log := log.NewLogger("debug", "")
imageStore := local.NewImageStore(tempDir, false, false,
log, monitoring.NewMetricsServer(false, log), nil, nil)

storeController := storage.StoreController{
DefaultStore: imageStore,
}

err = WriteImageToFileSystem(img, "repo", img.DigestStr(), storeController)
So(err, ShouldBeNil)

params := boltdb.DBParameters{
RootDir: tempDir,
}
boltDriver, err := boltdb.GetBoltDriver(params)
So(err, ShouldBeNil)

metaDB, err := boltdb.New(boltDriver, log)
So(err, ShouldBeNil)

err = meta.ParseStorage(metaDB, storeController, log)
So(err, ShouldBeNil)

scanner := trivy.NewScanner(storeController, metaDB, "ghcr.io/project-zot/trivy-db",
"ghcr.io/aquasecurity/trivy-java-db", log)

err = scanner.UpdateDB(context.Background())
So(err, ShouldBeNil)

cveMap, err := scanner.ScanImage(context.Background(), "repo@"+img.DigestStr())
So(err, ShouldBeNil)
t.Logf("cveMap: %v", cveMap)

// As of Feb 15 2024, there is 1 CVE in this layer:
So(len(cveMap), ShouldBeGreaterThanOrEqualTo, 1)
So(cveMap, ShouldContainKey, "CVE-2016-1000027")

cveData := cveMap["CVE-2016-1000027"]
vulnerablePackages := cveData.PackageList

// There is only 1 vulnerable package in this layer
So(len(vulnerablePackages), ShouldEqual, 1)
vulnerableSpringWebPackage := vulnerablePackages[0]
So(vulnerableSpringWebPackage.Name, ShouldEqual, "org.springframework:spring-web")
So(vulnerableSpringWebPackage.InstalledVersion, ShouldEqual, "5.3.31")
So(vulnerableSpringWebPackage.FixedVersion, ShouldEqual, "6.0.0")
So(vulnerableSpringWebPackage.PackagePath, ShouldEqual, "usr/local/artifacts/spring-web-5.3.31.jar")
})
}

func TestScannerErrors(t *testing.T) {
Expand Down
57 changes: 57 additions & 0 deletions pkg/extensions/search/gql_generated/generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions pkg/extensions/search/gql_generated/models_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pkg/extensions/search/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ func getCVEListForImage(
pkgList = append(pkgList,
&gql_generated.PackageInfo{
Name: &pkg.Name,
PackagePath: &pkg.PackagePath,
InstalledVersion: &pkg.InstalledVersion,
FixedVersion: &pkg.FixedVersion,
},
Expand Down
4 changes: 4 additions & 0 deletions pkg/extensions/search/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ type PackageInfo {
"""
Name: String
"""
Path where the vulnerable package is located
"""
PackagePath: String
"""
Current version of the package, typically affected by the CVE
"""
InstalledVersion: String
Expand Down
Loading

0 comments on commit 0aa6bf0

Please sign in to comment.