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

fix(vex): CSAF filtering should consider relationships #5923

Merged
merged 11 commits into from
Feb 22, 2024
106 changes: 80 additions & 26 deletions pkg/vex/csaf.go
Original file line number Diff line number Diff line change
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 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: In what cases can a single product id have multiple PURLs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be honest, I can't think of any situation like that but the official library is returning a slice:

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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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