Skip to content

Commit

Permalink
feat(vex): improve relationship support in CSAF VEX
Browse files Browse the repository at this point in the history
Signed-off-by: knqyf263 <knqyf263@gmail.com>
  • Loading branch information
knqyf263 committed May 21, 2024
1 parent 9515695 commit 8d2ef73
Show file tree
Hide file tree
Showing 6 changed files with 198 additions and 254 deletions.
162 changes: 95 additions & 67 deletions pkg/vex/csaf.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package vex

import (
"github.com/csaf-poc/csaf_distribution/v3/csaf"
"github.com/package-url/packageurl-go"
"github.com/samber/lo"

"github.com/aquasecurity/trivy/pkg/log"
Expand All @@ -16,127 +15,156 @@ type CSAF struct {
logger *log.Logger
}

type relationship struct {
Product *purl.PackageURL
SubProducts []*purl.PackageURL
}

func newCSAF(advisory csaf.Advisory) VEX {
return &CSAF{
advisory: advisory,
logger: log.WithPrefix("vex").With(log.String("format", "CSAF")),
}
}

func (v *CSAF) Filter(result *types.Result, _ *core.BOM) {
result.Vulnerabilities = lo.Filter(result.Vulnerabilities, func(vuln types.DetectedVulnerability, _ int) bool {
found, ok := lo.Find(v.advisory.Vulnerabilities, func(item *csaf.Vulnerability) bool {
return string(*item.CVE) == vuln.VulnerabilityID
})
if !ok {
return true
}
func (v *CSAF) Filter(result *types.Result, bom *core.BOM) {
filterVulnerabilities(result, bom, v.NotAffected)
}

if status := v.match(found, vuln.PkgIdentifier.PURL); status != "" {
result.ModifiedFindings = append(result.ModifiedFindings,
types.NewModifiedFinding(vuln, status, statement(found), "CSAF VEX"))
return false
}
return true
func (v *CSAF) NotAffected(vuln types.DetectedVulnerability, product, subProduct *core.Component) (types.ModifiedFinding, bool) {
found, ok := lo.Find(v.advisory.Vulnerabilities, func(item *csaf.Vulnerability) bool {
return string(*item.CVE) == vuln.VulnerabilityID
})
}
if !ok {
return types.ModifiedFinding{}, false
}

func (v *CSAF) match(vuln *csaf.Vulnerability, pkgURL *packageurl.PackageURL) types.FindingStatus {
if pkgURL == nil || vuln.ProductStatus == nil {
return ""
status := v.match(found, product, subProduct)
if status == "" {
return types.ModifiedFinding{}, false
}
return types.NewModifiedFinding(vuln, status, v.statement(found), "CSAF VEX"), true
}

matchProduct := func(purls []*purl.PackageURL, pkgURL *packageurl.PackageURL) bool {
for _, p := range purls {
if p.Match(pkgURL) {
return true
}
}
return false
func (v *CSAF) match(vuln *csaf.Vulnerability, product, subProduct *core.Component) types.FindingStatus {
if product == nil || product.PkgIdentifier.PURL == nil || vuln.ProductStatus == nil {
return ""
}

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.Info("Filtered out the detected vulnerability",
log.String("vulnerability-id", string(*vuln.CVE)),
log.String("status", string(status)))
for _, p := range productRange {
productID := lo.FromPtr(p)
logger := v.logger.With(log.String("vulnerability-id", string(*vuln.CVE)),
log.String("product-id", string(productID)), log.String("status", string(status)))

// Check if the product is affected
if v.matchProduct(productID, product) {
logger.Info("Filtered out the detected vulnerability")
return status
}
for relationship, purls := range v.inspectProductRelationships(lo.FromPtr(product)) {
if matchProduct(purls, pkgURL) {
v.logger.Warn("Filtered out the detected vulnerability",
log.String("vulnerability-id", string(*vuln.CVE)),
log.String("status", string(status)),
log.String("relationship", string(relationship)))
return status
}

// Check if the relationship between the product and the subcomponent is affected
if category, match := v.matchRelationship(productID, product, subProduct); match {
logger.Info("Filtered out the detected vulnerability",
log.String("relationship", string(category)))
return status
}
}
}

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))
func (v *CSAF) matchProduct(productID csaf.ProductID, product *core.Component) bool {
for _, productPURL := range v.productPURLs(productID) {
if productPURL.Match(product.PkgIdentifier.PURL) {
return true
}
}
return false
}

func (v *CSAF) matchRelationship(fullProductID csaf.ProductID, product, subProduct *core.Component) (
csaf.RelationshipCategory, bool) {

for category, relationships := range v.inspectProductRelationships(fullProductID) {
for _, rel := range relationships {
if !rel.Product.Match(product.PkgIdentifier.PURL) {
continue
}
for _, subProductPURL := range rel.SubProducts {
if subProductPURL.Match(subProduct.PkgIdentifier.PURL) {
return category, true
}
}
}
}
return "", false
}

// productPURLs returns a slice of PackageURLs associated to a given product
func (v *CSAF) productPURLs(product csaf.ProductID) []*purl.PackageURL {
return v.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)
func (v *CSAF) inspectProductRelationships(fullProductID csaf.ProductID) map[csaf.RelationshipCategory][]relationship {
if v.advisory.ProductTree.RelationShips == nil {
return nil
}

relationships := make(map[csaf.RelationshipCategory][]relationship)
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)
}
}
if rel == nil || rel.FullProductName == nil {
continue
} else if lo.FromPtr(rel.FullProductName.ProductID) != fullProductID {
continue
}
}

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))...)
category := lo.FromPtr(rel.Category)
switch category {
case csaf.CSAFRelationshipCategoryDefaultComponentOf,
csaf.CSAFRelationshipCategoryInstalledOn,
csaf.CSAFRelationshipCategoryInstalledWith:

productID := lo.FromPtr(rel.RelatesToProductReference)
productPURLs := v.productPURLs(productID)

subProductID := lo.FromPtr(rel.ProductReference)
subProductPURLs := v.productPURLs(subProductID)

for _, productPURL := range productPURLs {
relationships[category] = append(relationships[category], relationship{
Product: productPURL,
SubProducts: subProductPURLs,
})
}
}
purlsMap[relationship] = purlsFromProductIdentificationHelpers(helpers)
}

return purlsMap
return relationships
}

// purlsFromProductIdentificationHelpers returns a slice of PackageURLs given a slice of ProductIdentificationHelpers.
func purlsFromProductIdentificationHelpers(helpers []*csaf.ProductIdentificationHelper) []*purl.PackageURL {
// purlsFromProductIdentificationHelpers returns a slice of PURLs given a slice of ProductIdentificationHelpers.
func (v *CSAF) 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.Error("Invalid PURL", log.String("purl", string(*helper.PURL)), log.Err(err))
v.logger.Error("Invalid PURL", log.String("purl", string(*helper.PURL)), log.Err(err))
return nil, false
}
return p, true
})
}

func statement(vuln *csaf.Vulnerability) string {
func (v *CSAF) statement(vuln *csaf.Vulnerability) string {
threat, ok := lo.Find(vuln.Threats, func(threat *csaf.Threat) bool {
return lo.FromPtr(threat.Category) == csaf.CSAFThreatCategoryImpact
})
Expand Down
93 changes: 0 additions & 93 deletions pkg/vex/testdata/csaf-affected.json

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -60,18 +60,18 @@
"branches": [
{
"category": "product_version",
"name": "v1.24.2",
"name": "v0.24.2",
"product": {
"name": "Kubernetes v1.24.2",
"product_id": "kubernetes-v1.24.2",
"name": "client-go v0.24.2",
"product_id": "client-go-v0.24.2",
"product_identification_helper": {
"purl": "pkg:golang/k8s.io/kubernetes@v1.24.2"
"purl": "pkg:golang/k8s.io/client-go@0.24.2"
}
}
}
],
"category": "product_name",
"name": "kubernetes"
"name": "client-go"
}
],
"category": "vendor",
Expand All @@ -80,11 +80,11 @@
],
"relationships": [
{
"product_reference": "kubernetes-v1.24.2",
"product_reference": "client-go-v0.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",
"product_id": "argo-cd-2.9.3-2-amd64-debian-12-client-go",
"name": "Argo CD uses kubernetes golang library"
}
}
Expand All @@ -98,7 +98,7 @@
"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"
"argo-cd-2.9.3-2-amd64-debian-12-client-go"
]
}
],
Expand All @@ -111,7 +111,7 @@
],
"product_status": {
"known_not_affected": [
"argo-cd-2.9.3-2-amd64-debian-12-kubernetes"
"argo-cd-2.9.3-2-amd64-debian-12-client-go"
]
},
"threats": [
Expand Down
Loading

0 comments on commit 8d2ef73

Please sign in to comment.