From 90d27ff2acb2a0c90e2797d2202e3debc6ca7b8c Mon Sep 17 00:00:00 2001 From: LaurentiuNiculae Date: Wed, 29 Nov 2023 20:59:00 +0200 Subject: [PATCH] feat(cve): expand search domain to cve description and package info (#2086) * feat(cve): add reference url for cve Signed-off-by: Laurentiu Niculae * feat(cve): expand search domain to cve description and package info Signed-off-by: Laurentiu Niculae --------- Signed-off-by: Laurentiu Niculae --- go.mod | 2 +- pkg/extensions/search/cve/cve.go | 11 +++- pkg/extensions/search/cve/model/models.go | 1 + pkg/extensions/search/cve/trivy/scanner.go | 30 ++++++++++ .../search/cve/trivy/scanner_internal_test.go | 16 ++++++ .../search/gql_generated/generated.go | 57 +++++++++++++++++++ .../search/gql_generated/models_gen.go | 2 + pkg/extensions/search/resolver.go | 2 + pkg/extensions/search/schema.graphql | 4 ++ pkg/extensions/search/schema.resolvers.go | 6 +- 10 files changed, 124 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 02a6701b..b4c04e80 100644 --- a/go.mod +++ b/go.mod @@ -496,7 +496,7 @@ require ( go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect - golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect + golang.org/x/exp v0.0.0-20231006140011-7918f672742d golang.org/x/mod v0.13.0 // indirect golang.org/x/net v0.18.0 // indirect golang.org/x/term v0.14.0 // indirect diff --git a/pkg/extensions/search/cve/cve.go b/pkg/extensions/search/cve/cve.go index 18205ce7..44118e55 100644 --- a/pkg/extensions/search/cve/cve.go +++ b/pkg/extensions/search/cve/cve.go @@ -8,6 +8,7 @@ import ( godigest "github.com/opencontainers/go-digest" ispec "github.com/opencontainers/image-spec/specs-go/v1" + "golang.org/x/exp/slices" zerr "zotregistry.io/zot/errors" zcommon "zotregistry.io/zot/pkg/common" @@ -334,7 +335,15 @@ func filterCVEList(cveMap map[string]cvemodel.CVE, searchedCVE string, pageFinde for _, cve := range cveMap { if strings.Contains(strings.ToUpper(cve.Title), searchedCVE) || - strings.Contains(strings.ToUpper(cve.ID), searchedCVE) { + strings.Contains(strings.ToUpper(cve.ID), searchedCVE) || + strings.Contains(strings.ToUpper(cve.Description), searchedCVE) || + strings.Contains(strings.ToUpper(cve.Reference), searchedCVE) || + strings.Contains(strings.ToUpper(cve.Severity), searchedCVE) || + slices.ContainsFunc(cve.PackageList, func(pack cvemodel.Package) bool { + return strings.Contains(strings.ToUpper(pack.Name), searchedCVE) || + strings.Contains(strings.ToUpper(pack.FixedVersion), searchedCVE) || + strings.Contains(strings.ToUpper(pack.InstalledVersion), searchedCVE) + }) { pageFinder.Add(cve) } } diff --git a/pkg/extensions/search/cve/model/models.go b/pkg/extensions/search/cve/model/models.go index 28aee326..fbc20b51 100644 --- a/pkg/extensions/search/cve/model/models.go +++ b/pkg/extensions/search/cve/model/models.go @@ -17,6 +17,7 @@ type CVE struct { Description string `json:"Description"` Severity string `json:"Severity"` Title string `json:"Title"` + Reference string `json:"Reference"` PackageList []Package `json:"PackageList"` } diff --git a/pkg/extensions/search/cve/trivy/scanner.go b/pkg/extensions/search/cve/trivy/scanner.go index a337c03b..167be343 100644 --- a/pkg/extensions/search/cve/trivy/scanner.go +++ b/pkg/extensions/search/cve/trivy/scanner.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "path" + "strings" "sync" "github.com/aquasecurity/trivy-db/pkg/metadata" @@ -427,6 +428,7 @@ func (scanner Scanner) scanManifest(ctx context.Context, repo, digest string) (m ID: vulnerability.VulnerabilityID, Title: vulnerability.Title, Description: vulnerability.Description, + Reference: getCVEReference(vulnerability.PrimaryURL, vulnerability.References), Severity: convertSeverity(vulnerability.Severity), PackageList: newPkgList, } @@ -439,6 +441,34 @@ func (scanner Scanner) scanManifest(ctx context.Context, repo, digest string) (m return cveidMap, nil } +func getCVEReference(primaryURL string, references []string) string { + if primaryURL != "" { + return primaryURL + } + + if len(references) > 0 { + nvdReference, found := getNVDReference(references) + + if found { + return nvdReference + } + + return references[0] + } + + return "" +} + +func getNVDReference(references []string) (string, bool) { + for i := range references { + if strings.Contains(references[i], "nvd.nist.gov") { + return references[i], true + } + } + + return "", false +} + func (scanner Scanner) scanIndex(ctx context.Context, repo, digest string) (map[string]cvemodel.CVE, error) { if cachedMap := scanner.cache.Get(digest); cachedMap != nil { return cachedMap, nil diff --git a/pkg/extensions/search/cve/trivy/scanner_internal_test.go b/pkg/extensions/search/cve/trivy/scanner_internal_test.go index 42fdf3e7..d94a1fc8 100644 --- a/pkg/extensions/search/cve/trivy/scanner_internal_test.go +++ b/pkg/extensions/search/cve/trivy/scanner_internal_test.go @@ -488,3 +488,19 @@ func TestIsIndexScannableErrors(t *testing.T) { }) }) } + +func TestGetCVEReference(t *testing.T) { + Convey("getCVEReference", t, func() { + ref := getCVEReference("primary", []string{}) + So(ref, ShouldResemble, "primary") + + ref = getCVEReference("", []string{"secondary"}) + So(ref, ShouldResemble, "secondary") + + ref = getCVEReference("", []string{""}) + So(ref, ShouldResemble, "") + + ref = getCVEReference("", []string{"https://nvd.nist.gov/vuln/detail/CVE-2023-2650"}) + So(ref, ShouldResemble, "https://nvd.nist.gov/vuln/detail/CVE-2023-2650") + }) +} diff --git a/pkg/extensions/search/gql_generated/generated.go b/pkg/extensions/search/gql_generated/generated.go index d34d147d..d85e4d23 100644 --- a/pkg/extensions/search/gql_generated/generated.go +++ b/pkg/extensions/search/gql_generated/generated.go @@ -54,6 +54,7 @@ type ComplexityRoot struct { Description func(childComplexity int) int ID func(childComplexity int) int PackageList func(childComplexity int) int + Reference func(childComplexity int) int Severity func(childComplexity int) int Title func(childComplexity int) int } @@ -281,6 +282,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.CVE.PackageList(childComplexity), true + case "CVE.Reference": + if e.complexity.CVE.Reference == nil { + break + } + + return e.complexity.CVE.Reference(childComplexity), true + case "CVE.Severity": if e.complexity.CVE.Severity == nil { break @@ -1184,6 +1192,10 @@ type CVE { """ Description: String """ + Reference for the given CVE + """ + Reference: String + """ The impact the CVE has, one of "UNKNOWN", "LOW", "MEDIUM", "HIGH", "CRITICAL" """ Severity: String @@ -2510,6 +2522,47 @@ func (ec *executionContext) fieldContext_CVE_Description(ctx context.Context, fi return fc, nil } +func (ec *executionContext) _CVE_Reference(ctx context.Context, field graphql.CollectedField, obj *Cve) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_CVE_Reference(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Reference, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2áš–string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_CVE_Reference(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "CVE", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _CVE_Severity(ctx context.Context, field graphql.CollectedField, obj *Cve) (ret graphql.Marshaler) { fc, err := ec.fieldContext_CVE_Severity(ctx, field) if err != nil { @@ -2683,6 +2736,8 @@ func (ec *executionContext) fieldContext_CVEResultForImage_CVEList(ctx context.C return ec.fieldContext_CVE_Title(ctx, field) case "Description": return ec.fieldContext_CVE_Description(ctx, field) + case "Reference": + return ec.fieldContext_CVE_Reference(ctx, field) case "Severity": return ec.fieldContext_CVE_Severity(ctx, field) case "PackageList": @@ -9407,6 +9462,8 @@ func (ec *executionContext) _CVE(ctx context.Context, sel ast.SelectionSet, obj out.Values[i] = ec._CVE_Title(ctx, field, obj) case "Description": out.Values[i] = ec._CVE_Description(ctx, field, obj) + case "Reference": + out.Values[i] = ec._CVE_Reference(ctx, field, obj) case "Severity": out.Values[i] = ec._CVE_Severity(ctx, field, obj) case "PackageList": diff --git a/pkg/extensions/search/gql_generated/models_gen.go b/pkg/extensions/search/gql_generated/models_gen.go index 14e6ee19..c889595e 100644 --- a/pkg/extensions/search/gql_generated/models_gen.go +++ b/pkg/extensions/search/gql_generated/models_gen.go @@ -27,6 +27,8 @@ type Cve struct { Title *string `json:"Title,omitempty"` // A detailed description of the CVE Description *string `json:"Description,omitempty"` + // Reference for the given CVE + Reference *string `json:"Reference,omitempty"` // The impact the CVE has, one of "UNKNOWN", "LOW", "MEDIUM", "HIGH", "CRITICAL" Severity *string `json:"Severity,omitempty"` // Information on the packages in which the CVE was found diff --git a/pkg/extensions/search/resolver.go b/pkg/extensions/search/resolver.go index f5b3e053..47b1d8ea 100644 --- a/pkg/extensions/search/resolver.go +++ b/pkg/extensions/search/resolver.go @@ -228,6 +228,7 @@ func getCVEListForImage( desc := cveDetail.Description title := cveDetail.Title severity := cveDetail.Severity + referenceURL := cveDetail.Reference pkgList := make([]*gql_generated.PackageInfo, 0) @@ -249,6 +250,7 @@ func getCVEListForImage( Title: &title, Description: &desc, Severity: &severity, + Reference: &referenceURL, PackageList: pkgList, }, ) diff --git a/pkg/extensions/search/schema.graphql b/pkg/extensions/search/schema.graphql index d38d1a3b..19175c62 100644 --- a/pkg/extensions/search/schema.graphql +++ b/pkg/extensions/search/schema.graphql @@ -46,6 +46,10 @@ type CVE { """ Description: String """ + Reference for the given CVE + """ + Reference: String + """ The impact the CVE has, one of "UNKNOWN", "LOW", "MEDIUM", "HIGH", "CRITICAL" """ Severity: String diff --git a/pkg/extensions/search/schema.resolvers.go b/pkg/extensions/search/schema.resolvers.go index 06f2edd2..28fb34b5 100644 --- a/pkg/extensions/search/schema.resolvers.go +++ b/pkg/extensions/search/schema.resolvers.go @@ -20,11 +20,7 @@ func (r *queryResolver) CVEListForImage(ctx context.Context, image string, reque return &gql_generated.CVEResultForImage{}, zerr.ErrCVESearchDisabled } - if searchedCve == nil { - return getCVEListForImage(ctx, image, r.cveInfo, requestedPage, "", r.log) - } - - return getCVEListForImage(ctx, image, r.cveInfo, requestedPage, *searchedCve, r.log) + return getCVEListForImage(ctx, image, r.cveInfo, requestedPage, deref(searchedCve, ""), r.log) } // ImageListForCve is the resolver for the ImageListForCVE field.