Skip to content

Commit

Permalink
fix(vex): CSAF filtering should consider relationships (#5923)
Browse files Browse the repository at this point in the history
Signed-off-by: juan131 <jariza@vmware.com>
Co-authored-by: Teppei Fukuda <knqyf263@gmail.com>
  • Loading branch information
juan131 and knqyf263 committed Feb 22, 2024
1 parent 388f476 commit 9c5e5a0
Show file tree
Hide file tree
Showing 3 changed files with 230 additions and 26 deletions.
106 changes: 80 additions & 26 deletions pkg/vex/csaf.go
Expand Up @@ -46,40 +46,94 @@ func (v *CSAF) match(vuln *csaf.Vulnerability, pkgURL *packageurl.PackageURL) ty
return ""
}

var status types.FindingStatus
switch {
case v.matchPURL(vuln.ProductStatus.KnownNotAffected, pkgURL):
status = types.FindingStatusNotAffected
case v.matchPURL(vuln.ProductStatus.Fixed, pkgURL):
status = types.FindingStatusFixed
matchProduct := func(purls []*purl.PackageURL, pkgURL *packageurl.PackageURL) bool {
for _, p := range purls {
if p.Match(pkgURL) {
return true
}
}
return false
}

return status
}

// matchPURL returns true if the given PackageURL is found in the ProductTree.
func (v *CSAF) matchPURL(products *csaf.Products, pkgURL *packageurl.PackageURL) bool {
for _, product := range lo.FromPtr(products) {
helpers := v.advisory.ProductTree.CollectProductIdentificationHelpers(lo.FromPtr(product))
purls := lo.FilterMap(helpers, func(helper *csaf.ProductIdentificationHelper, _ int) (*purl.PackageURL, bool) {
if helper == nil || helper.PURL == nil {
return nil, false
productStatusMap := map[types.FindingStatus]csaf.Products{
types.FindingStatusNotAffected: lo.FromPtr(vuln.ProductStatus.KnownNotAffected),
types.FindingStatusFixed: lo.FromPtr(vuln.ProductStatus.Fixed),
}
for status, productRange := range productStatusMap {
for _, product := range productRange {
if matchProduct(v.getProductPurls(lo.FromPtr(product)), pkgURL) {
v.logger.Infow("Filtered out the detected vulnerability",
zap.String("vulnerability-id", string(*vuln.CVE)),
zap.String("status", string(status)))
return status
}
p, err := purl.FromString(string(*helper.PURL))
if err != nil {
v.logger.Errorw("Invalid PURL", zap.String("purl", string(*helper.PURL)), zap.Error(err))
return nil, false
for relationship, purls := range v.inspectProductRelationships(lo.FromPtr(product)) {
if matchProduct(purls, pkgURL) {
v.logger.Warnw("Filtered out the detected vulnerability",
zap.String("vulnerability-id", string(*vuln.CVE)),
zap.String("status", string(status)),
zap.String("relationship", string(relationship)))
return status
}
}
return p, true
})
for _, p := range purls {
if p.Match(pkgURL) {
return true
}
}

return ""
}

// getProductPurls returns a slice of PackageURLs associated to a given product
func (v *CSAF) getProductPurls(product csaf.ProductID) []*purl.PackageURL {
return purlsFromProductIdentificationHelpers(v.advisory.ProductTree.CollectProductIdentificationHelpers(product))
}

// inspectProductRelationships returns a map of PackageURLs associated to each relationship category
// iterating over relationships looking for sub-products that might be part of the original product
func (v *CSAF) inspectProductRelationships(product csaf.ProductID) map[csaf.RelationshipCategory][]*purl.PackageURL {
subProductsMap := make(map[csaf.RelationshipCategory]csaf.Products)
if v.advisory.ProductTree.RelationShips == nil {
return nil
}

for _, rel := range lo.FromPtr(v.advisory.ProductTree.RelationShips) {
if rel != nil {
relationship := lo.FromPtr(rel.Category)
switch relationship {
case csaf.CSAFRelationshipCategoryDefaultComponentOf,
csaf.CSAFRelationshipCategoryInstalledOn,
csaf.CSAFRelationshipCategoryInstalledWith:
if fpn := rel.FullProductName; fpn != nil && lo.FromPtr(fpn.ProductID) == product {
subProductsMap[relationship] = append(subProductsMap[relationship], rel.ProductReference)
}
}
}
}

return false
purlsMap := make(map[csaf.RelationshipCategory][]*purl.PackageURL)
for relationship, subProducts := range subProductsMap {
var helpers []*csaf.ProductIdentificationHelper
for _, subProductRef := range subProducts {
helpers = append(helpers, v.advisory.ProductTree.CollectProductIdentificationHelpers(lo.FromPtr(subProductRef))...)
}
purlsMap[relationship] = purlsFromProductIdentificationHelpers(helpers)
}

return purlsMap
}

// purlsFromProductIdentificationHelpers returns a slice of PackageURLs given a slice of ProductIdentificationHelpers.
func purlsFromProductIdentificationHelpers(helpers []*csaf.ProductIdentificationHelper) []*purl.PackageURL {
return lo.FilterMap(helpers, func(helper *csaf.ProductIdentificationHelper, _ int) (*purl.PackageURL, bool) {
if helper == nil || helper.PURL == nil {
return nil, false
}
p, err := purl.FromString(string(*helper.PURL))
if err != nil {
log.Logger.Errorw("Invalid PURL", zap.String("purl", string(*helper.PURL)), zap.Error(err))
return nil, false
}
return p, true
})
}

func statement(vuln *csaf.Vulnerability) string {
Expand Down
126 changes: 126 additions & 0 deletions pkg/vex/testdata/csaf-not-affected-sub-components.json
@@ -0,0 +1,126 @@
{
"document": {
"category": "csaf_vex",
"csaf_version": "2.0",
"publisher": {
"category": "vendor",
"name": "VMWare, Inc.",
"namespace": "https://tanzu.vmware.com/application-catalog"
},
"title": "ArgoCD 2.9.3-2 Amd64 Debian12 Advisory",
"tracking": {
"current_release_date": "2024-01-04T17:17:25+01:00",
"generator": {
"engine": {
"name": "Bitnami VEX CLI",
"version": "1.0.0"
}
},
"id": "fcf5bd33-41c3-45f9-885a-c2ee812f49c9",
"initial_release_date": "2024-01-04T17:17:25+01:00",
"revision_history": [
{
"date": "2024-01-04T17:17:25+01:00",
"number": "1",
"summary": "Initial version."
}
],
"status": "final",
"version": "1"
}
},
"product_tree": {
"branches": [
{
"branches": [
{
"branches": [
{
"category": "product_version",
"name": "2.9.3-2",
"product": {
"name": "Argo CD 2.9.3-2",
"product_id": "argo-cd-2.9.3-2-amd64-debian-12",
"product_identification_helper": {
"purl": "pkg:bitnami/argo-cd@2.9.3-2?arch=amd64\u0026distro=debian-12"
}
}
}
],
"category": "product_name",
"name": "Argo CD"
}
],
"category": "vendor",
"name": "VMWare, Inc."
},
{
"branches": [
{
"branches": [
{
"category": "product_version",
"name": "v1.24.2",
"product": {
"name": "Kubernetes v1.24.2",
"product_id": "kubernetes-v1.24.2",
"product_identification_helper": {
"purl": "pkg:golang/k8s.io/kubernetes@v1.24.2"
}
}
}
],
"category": "product_name",
"name": "kubernetes"
}
],
"category": "vendor",
"name": "k8s.io"
}
],
"relationships": [
{
"product_reference": "kubernetes-v1.24.2",
"category": "default_component_of",
"relates_to_product_reference": "argo-cd-2.9.3-2-amd64-debian-12",
"full_product_name": {
"product_id": "argo-cd-2.9.3-2-amd64-debian-12-kubernetes",
"name": "Argo CD uses kubernetes golang library"
}
}
]
},
"vulnerabilities": [
{
"cve": "CVE-2023-2727",
"flags": [
{
"date": "2024-01-04T17:17:25+01:00",
"label": "vulnerable_code_cannot_be_controlled_by_adversary",
"product_ids": [
"argo-cd-2.9.3-2-amd64-debian-12-kubernetes"
]
}
],
"notes": [
{
"category": "description",
"text": "Users may be able to launch containers using images that are restricted by ImagePolicyWebhook when using ephemeral containers. Kubernetes clusters are only affected if the ImagePolicyWebhook admission plugin is used together with ephemeral containers.",
"title": "CVE description"
}
],
"product_status": {
"known_not_affected": [
"argo-cd-2.9.3-2-amd64-debian-12-kubernetes"
]
},
"threats": [
{
"category": "impact",
"date": "2024-01-04T17:17:25+01:00",
"details": "The asset uses the component as a dependency in the code, but the vulnerability only affects Kubernetes clusters https://github.com/kubernetes/kubernetes/issues/118640"
}
]
}
]
}
24 changes: 24 additions & 0 deletions pkg/vex/vex_test.go
Expand Up @@ -298,6 +298,30 @@ func TestVEX_Filter(t *testing.T) {
},
},
},
{
name: "CSAF (not affected vuln) with sub components",
fields: fields{
filePath: "testdata/csaf-not-affected-sub-components.json",
},
args: args{
vulns: []types.DetectedVulnerability{
{
VulnerabilityID: "CVE-2023-2727",
PkgName: "kubernetes",
InstalledVersion: "v1.24.2",
PkgIdentifier: ftypes.PkgIdentifier{
PURL: &packageurl.PackageURL{
Type: packageurl.TypeGolang,
Namespace: "k8s.io",
Name: "kubernetes",
Version: "v1.24.2",
},
},
},
},
},
want: []types.DetectedVulnerability{},
},
{
name: "unknown format",
fields: fields{
Expand Down

0 comments on commit 9c5e5a0

Please sign in to comment.