Skip to content

Commit

Permalink
feat(vex): add PURL matching
Browse files Browse the repository at this point in the history
Signed-off-by: knqyf263 <[email protected]>
  • Loading branch information
knqyf263 committed Jan 8, 2024
1 parent dbd8447 commit 528413c
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 15 deletions.
30 changes: 30 additions & 0 deletions pkg/purl/purl.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,36 @@ func (p *PackageURL) Package() *ftypes.Package {
return pkg
}

// Match returns true if the given PURL "target" satisfies the constraint PURL "p".
// - If the constraint does not have a version, it will match any version in the target.
// - If the constraint has qualifiers, the target must have the same set of qualifiers to match.
func (p *PackageURL) Match(target *packageurl.PackageURL) bool {
if target == nil {
return false
}
switch {
case p.Type != target.Type:
return false
case p.Namespace != target.Namespace:
return false
case p.Name != target.Name:
return false
case p.Version != "" && p.Version != target.Version:
return false
case p.Subpath != "" && p.Subpath != target.Subpath:
return false
}

// All qualifiers in the constraint must be in the target to match
q := target.Qualifiers.Map()
for k, v1 := range p.Qualifiers.Map() {
if v2, ok := q[k]; !ok || v1 != v2 {
return false
}
}
return true
}

// ref. https://github.com/package-url/purl-spec/blob/a748c36ad415c8aeffe2b8a4a5d8a50d16d6d85f/PURL-TYPES.rst#oci
func parseOCI(metadata types.Metadata) (packageurl.PackageURL, error) {
if len(metadata.RepoDigests) == 0 {
Expand Down
73 changes: 71 additions & 2 deletions pkg/purl/purl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,7 @@ func TestFromString(t *testing.T) {
}
}

func TestToPackage(t *testing.T) {
func TestPackageURL_Package(t *testing.T) {
tests := []struct {
name string
pkgURL *purl.PackageURL
Expand Down Expand Up @@ -769,7 +769,7 @@ func TestToPackage(t *testing.T) {
}
}

func TestLangType(t *testing.T) {
func TestPackageURL_LangType(t *testing.T) {
tests := []struct {
name string
purl packageurl.PackageURL
Expand Down Expand Up @@ -812,3 +812,72 @@ func TestLangType(t *testing.T) {
})
}
}

func TestPackageURL_Match(t *testing.T) {
tests := []struct {
name string
constraint string
target string
want bool
}{
{
name: "same purl",
constraint: "pkg:golang/github.com/aquasecurity/[email protected]",
target: "pkg:golang/github.com/aquasecurity/[email protected]",
want: true,
},
{
name: "different type",
constraint: "pkg:golang/github.com/aquasecurity/[email protected]",
target: "pkg:maven/github.com/aquasecurity/[email protected]",
want: false,
},
{
name: "different namespace",
constraint: "pkg:golang/github.com/aquasecurity/[email protected]",
target: "pkg:golang/github.com/aquasecurity2/[email protected]",
want: false,
},
{
name: "different name",
constraint: "pkg:golang/github.com/aquasecurity/[email protected]",
target: "pkg:golang/github.com/aquasecurity/[email protected]",
want: false,
},
{
name: "different version",
constraint: "pkg:golang/github.com/aquasecurity/[email protected]",
target: "pkg:golang/github.com/aquasecurity/[email protected]",
want: false,
},
{
name: "version wildcard",
constraint: "pkg:golang/github.com/aquasecurity/trivy",
target: "pkg:golang/github.com/aquasecurity/[email protected]",
want: true,
},
{
name: "different qualifier",
constraint: "pkg:bitnami/[email protected]?arch=arm64&distro=debian-12",
target: "pkg:bitnami/[email protected]?arch=arm64&distro=debian-13",
want: false,
},
{
name: "target more qualifiers",
constraint: "pkg:bitnami/[email protected]?arch=arm64",
target: "pkg:bitnami/[email protected]?arch=arm64&distro=debian-13",
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c, err := purl.FromString(tt.constraint)
require.NoError(t, err)

p, err := purl.FromString(tt.target)
require.NoError(t, err)

assert.Equalf(t, tt.want, c.Match(p.Unwrap()), "Match()")
})
}
}
32 changes: 19 additions & 13 deletions pkg/vex/csaf.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package vex

import (
csaf "github.com/csaf-poc/csaf_distribution/v3/csaf"
"github.com/samber/lo"
"go.uber.org/zap"
"golang.org/x/exp/slices"

"github.com/aquasecurity/trivy/pkg/log"

Check failure on line 4 in pkg/vex/csaf.go

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest)

File is not `gci`-ed with --skip-generated -s standard -s default -s prefix(github.com/aquasecurity/) -s blank -s dot (gci)
"github.com/aquasecurity/trivy/pkg/purl"
"github.com/aquasecurity/trivy/pkg/types"
csaf "github.com/csaf-poc/csaf_distribution/v3/csaf"
"github.com/package-url/packageurl-go"
"github.com/samber/lo"
"go.uber.org/zap"

Check failure on line 10 in pkg/vex/csaf.go

View workflow job for this annotation

GitHub Actions / Test (ubuntu-latest)

File is not `gci`-ed with --skip-generated -s standard -s default -s prefix(github.com/aquasecurity/) -s blank -s dot (gci)
)

type CSAF struct {
Expand All @@ -32,11 +31,11 @@ func (v *CSAF) Filter(vulns []types.DetectedVulnerability) []types.DetectedVulne
return true
}

return v.affected(found, purl.WithPath(vuln.PkgIdentifier.PURL, vuln.PkgPath))
return v.affected(found, vuln.PkgIdentifier.PURL)
})
}

func (v *CSAF) affected(vuln *csaf.Vulnerability, pkgURL *purl.PackageURL) bool {
func (v *CSAF) affected(vuln *csaf.Vulnerability, pkgURL *packageurl.PackageURL) bool {
if pkgURL == nil || vuln.ProductStatus == nil {
return true
}
Expand All @@ -60,17 +59,24 @@ func (v *CSAF) affected(vuln *csaf.Vulnerability, pkgURL *purl.PackageURL) bool
}

// matchPURL returns true if the given PackageURL is found in the ProductTree.
func (v *CSAF) matchPURL(products *csaf.Products, pkgURL *purl.PackageURL) bool {
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) (string, bool) {
purls := lo.FilterMap(helpers, func(helper *csaf.ProductIdentificationHelper, _ int) (*purl.PackageURL, bool) {
if helper == nil || helper.PURL == nil {
return "", false
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 string(*helper.PURL), true
return p, true
})
if slices.Contains(purls, pkgURL.String()) {
return true
for _, p := range purls {
if p.Match(pkgURL) {
return true
}
}
}

Expand Down

0 comments on commit 528413c

Please sign in to comment.