Skip to content

Commit

Permalink
add integration tests
Browse files Browse the repository at this point in the history
Signed-off-by: Alex Goodman <[email protected]>
  • Loading branch information
wagoodman committed Sep 17, 2024
1 parent a07fe7d commit 8086627
Show file tree
Hide file tree
Showing 16 changed files with 450 additions and 83 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ require (
github.com/anchore/go-version v1.2.2-0.20210903204242-51efa5b487c4
github.com/anchore/packageurl-go v0.1.1-0.20240507183024-848e011fc24f
github.com/anchore/stereoscope v0.0.3
github.com/anchore/syft v1.12.2
github.com/anchore/syft v1.12.3-0.20240916182519-7c617fd14e11
github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46
github.com/bmatcuk/doublestar/v2 v2.0.4
github.com/charmbracelet/bubbletea v1.1.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,8 @@ github.com/anchore/stereoscope v0.0.3 h1:JRPHySy8S6P+Ff3IDiQ29ap1i8/laUQxDk9K1eF
github.com/anchore/stereoscope v0.0.3/go.mod h1:5DJheGPjVRsSqegTB24Zi6SCHnYQnA519yeIG+RG+I4=
github.com/anchore/syft v1.12.2 h1:K5YXJ2Ox4C3+Q+rA4jDpsLAoYNd27RMfinvY2JmbEiM=
github.com/anchore/syft v1.12.2/go.mod h1:xFMGMFmhWTK0CJvaKwz6OPVgRdcyCkl7QO/3O/JAXI0=
github.com/anchore/syft v1.12.3-0.20240916182519-7c617fd14e11 h1:M7xJv6jPxyS2GaPmbS+l02YGnO77SGxwcprDhEUupVg=
github.com/anchore/syft v1.12.3-0.20240916182519-7c617fd14e11/go.mod h1:xFMGMFmhWTK0CJvaKwz6OPVgRdcyCkl7QO/3O/JAXI0=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
Expand Down
2 changes: 2 additions & 0 deletions grype/cpe/cpe.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ func NewSlice(cpeStrs ...string) ([]cpe.CPE, error) {
func MatchWithoutVersion(c cpe.CPE, candidates []cpe.CPE) []cpe.CPE {
matches := make([]cpe.CPE, 0)
a := wfn.Attributes(c.Attributes)
a.Update = wfn.Any
for _, candidate := range candidates {
canCopy := wfn.Attributes(candidate.Attributes)
canCopy.Update = wfn.Any
if a.MatchWithoutVersion(&canCopy) {
matches = append(matches, candidate)
}
Expand Down
2 changes: 1 addition & 1 deletion grype/internal/packagemetadata/generated.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ import "github.com/anchore/grype/grype/pkg"

// AllTypes returns a list of all pkg metadata types that grype supports (that are represented in the pkg.Package.Metadata field).
func AllTypes() []any {
return []any{pkg.ApkMetadata{}, pkg.GolangBinMetadata{}, pkg.GolangModMetadata{}, pkg.JavaMetadata{}, pkg.RpmMetadata{}}
return []any{pkg.ApkMetadata{}, pkg.GolangBinMetadata{}, pkg.GolangModMetadata{}, pkg.JavaMetadata{}, pkg.JavaVMInstallationMetadata{}, pkg.RpmMetadata{}}
}
5 changes: 5 additions & 0 deletions grype/internal/packagemetadata/names_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ func TestReflectTypeFromJSONName(t *testing.T) {
lookup: "RpmMetadata",
wantRecord: reflect.TypeOf(pkg.RpmMetadata{}),
},
{
name: "JavaVMInstallationMetadata lookup",
lookup: "JavaVMInstallationMetadata",
wantRecord: reflect.TypeOf(pkg.JavaVMInstallationMetadata{}),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
152 changes: 152 additions & 0 deletions grype/matcher/jvm/matcher_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package jvm

import (
"testing"

"github.com/google/uuid"
"github.com/scylladb/go-set/strset"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/anchore/grype/grype/distro"
"github.com/anchore/grype/grype/match"
"github.com/anchore/grype/grype/pkg"
"github.com/anchore/grype/grype/version"
"github.com/anchore/grype/grype/vulnerability"
"github.com/anchore/syft/syft/cpe"
syftPkg "github.com/anchore/syft/syft/pkg"
)

func TestMatcher(t *testing.T) {
p := pkg.Package{
ID: pkg.ID(uuid.NewString()),
Name: "java_se",
Version: "1.8.0_400",
Type: syftPkg.BinaryPkg,
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:a:oracle:java_se:1.8.0:update400:*:*:*:*:*:*", cpe.DeclaredSource),
},
}
matcher := Matcher{
cfg: MatcherConfig{
UseCPEs: true,
},
}
store := newMockProvider()
actual, _ := matcher.Match(store, nil, p)

foundCVEs := strset.New()
for _, v := range actual {
foundCVEs.Add(v.Vulnerability.ID)

require.NotEmpty(t, v.Details)
for _, d := range v.Details {
assert.Equal(t, match.CPEMatch, d.Type, "indirect match not indicated")
assert.Equal(t, matcher.Type(), d.Matcher, "failed to capture matcher type")
}
assert.Equal(t, p.Name, v.Package.Name, "failed to capture original package name")
}

expected := strset.New(
"CVE-2024-20919-real",
"CVE-2024-20919-underscore",
"CVE-2024-20919-bonkers-format",
"CVE-2024-20919-post-jep223",
)

for _, id := range expected.List() {
if !foundCVEs.Has(id) {
t.Errorf("missing CVE: %s", id)
}
}

extra := strset.Difference(foundCVEs, expected)

for _, id := range extra.List() {
t.Errorf("unexpected CVE: %s", id)
}

if t.Failed() {
t.Logf("discovered CVES: %d", foundCVEs.Size())
for _, id := range foundCVEs.List() {
t.Logf(" - %s", id)
}
}
}

func newMockProvider() *mockProvider {
mp := mockProvider{
data: make(map[syftPkg.Language]map[string][]vulnerability.Vulnerability),
}

mp.populateData()

return &mp
}

type mockProvider struct {
data map[syftPkg.Language]map[string][]vulnerability.Vulnerability
}

func (mp *mockProvider) Get(_, _ string) ([]vulnerability.Vulnerability, error) {
// TODO implement me
panic("not implemented")
}

func (mp *mockProvider) populateData() {

// derived from vuln data found on CVE-2024-20919
hit := "< 1.8.0_401 || >= 1.9-ea, < 8.0.401 || >= 9-ea, < 11.0.22 || >= 12-ea, < 17.0.10 || >= 18-ea, < 21.0.2"

mp.data["nvd:cpe"] = map[string][]vulnerability.Vulnerability{
"java_se": {
{
// positive cases
Constraint: version.MustGetConstraint(hit, version.JVMFormat),
ID: "CVE-2024-20919-real",
},
{
// positive cases
Constraint: version.MustGetConstraint("< 22.22.22", version.UnknownFormat),
ID: "CVE-2024-20919-bonkers-format",
},
{
// positive cases
Constraint: version.MustGetConstraint(hit, version.JVMFormat),
ID: "CVE-2024-20919-underscore",
},
{
// negative case
Constraint: version.MustGetConstraint("< 1.8.0_399 || >= 1.9-ea, < 8.0.399 || >= 9-ea, < 11.0.22 || >= 12-ea, < 17.0.10 || >= 18-ea, < 21.0.2", version.JVMFormat),
ID: "CVE-FAKE-bad-update",
},
{
// positive case
Constraint: version.MustGetConstraint("< 8.0.401", version.JVMFormat),
ID: "CVE-2024-20919-post-jep223",
},
{
// negative case
Constraint: version.MustGetConstraint("< 8.0.399", version.JVMFormat),
ID: "CVE-FAKE-bad-range-post-jep223",
},
{
// negative case
Constraint: version.MustGetConstraint("< 7.0.0", version.JVMFormat),
ID: "CVE-FAKE-bad-range-post-jep223",
},
},
}
}

func (mp *mockProvider) GetByCPE(p cpe.CPE) ([]vulnerability.Vulnerability, error) {
return mp.data["nvd:cpe"][p.Attributes.Product], nil
}

func (mp *mockProvider) GetByDistro(d *distro.Distro, p pkg.Package) ([]vulnerability.Vulnerability, error) {
return []vulnerability.Vulnerability{}, nil
}

func (mp *mockProvider) GetByLanguage(l syftPkg.Language, p pkg.Package) ([]vulnerability.Vulnerability, error) {
return mp.data[l][p.Name], nil
}
18 changes: 12 additions & 6 deletions grype/pkg/java_metadata.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package pkg

import (
"strings"
"github.com/scylladb/go-set/strset"

"github.com/anchore/syft/syft/pkg"
)
Expand Down Expand Up @@ -31,15 +31,21 @@ type JavaVMReleaseMetadata struct {
}

func IsJvmPackage(p Package) bool {
if _, ok := p.Metadata.(JavaVMInstallationMetadata); ok {
return true
}

if p.Type == pkg.BinaryPkg {
if strings.Contains(p.Name, "jdk") || strings.Contains(p.Name, "jre") || strings.Contains(p.Name, "java") {
if HasJvmPackageName(p.Name) {
return true
}
}

if _, ok := p.Metadata.(JavaVMInstallationMetadata); ok {
return true
}

return false
}

var jvmIndications = strset.New("java_se", "jre", "jdk", "zulu", "openjdk", "java")

func HasJvmPackageName(name string) bool {
return jvmIndications.Has(name)
}
99 changes: 99 additions & 0 deletions grype/pkg/java_metadata_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package pkg

import (
"testing"

"github.com/stretchr/testify/assert"

syftPkg "github.com/anchore/syft/syft/pkg"
)

func TestIsJvmPackage(t *testing.T) {
tests := []struct {
name string
pkg Package
expected bool
}{
{
name: "binary package with jdk in name set",
pkg: Package{
Type: syftPkg.BinaryPkg,
Name: "jdk",
},
expected: true,
},
{
name: "binary package with jre in name set",
pkg: Package{
Type: syftPkg.BinaryPkg,
Name: "jre",
},
expected: true,
},
{
name: "binary package with java_se in name set",
pkg: Package{
Type: syftPkg.BinaryPkg,
Name: "java_se",
},
expected: true,
},
{
name: "binary package with zulu in name set",
pkg: Package{
Type: syftPkg.BinaryPkg,
Name: "zulu",
},
expected: true,
},
{
name: "binary package with openjdk in name set",
pkg: Package{
Type: syftPkg.BinaryPkg,
Name: "openjdk",
},
expected: true,
},
{
name: "binary package without jvm-related name",
pkg: Package{
Type: syftPkg.BinaryPkg,
Name: "nodejs",
},
expected: false,
},
{
name: "non-binary package with jvm-related name",
pkg: Package{
Type: syftPkg.NpmPkg, // we know this could not be a JVM package installation
Name: "jdk",
},
expected: false,
},
{
name: "package with JavaVMInstallationMetadata",
pkg: Package{
Type: syftPkg.RpmPkg,
Name: "random-package",
Metadata: JavaVMInstallationMetadata{},
},
expected: true,
},
{
name: "package without JavaVMInstallationMetadata",
pkg: Package{
Type: syftPkg.RpmPkg,
Name: "non-jvm-package",
Metadata: nil,
},
expected: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := IsJvmPackage(tt.pkg)
assert.Equal(t, tt.expected, result)
})
}
}
14 changes: 13 additions & 1 deletion grype/search/cpe.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,19 @@ func filterCPEsByVersion(pkgVersion version.Version, allCPEs []cpe.CPE) (matched
continue
}

constraint, err := version.GetConstraint(c.Attributes.Version, version.UnknownFormat)
ver := c.Attributes.Version

if pkgVersion.Format == version.JVMFormat {
if c.Attributes.Update != wfn.Any && c.Attributes.Update != wfn.NA {
if strings.HasPrefix(ver, "1.") && !strings.Contains(ver, "_") {
ver = fmt.Sprintf("%s_%s", ver, strings.TrimPrefix(strings.TrimPrefix(c.Attributes.Update, "update"), "_"))
}
}
}

ver = "< " + ver

constraint, err := version.GetConstraint(ver, pkgVersion.Format)
if err != nil {
// if we can't get a version constraint, don't filter out the CPE
matchedCPEs = append(matchedCPEs, c)
Expand Down
38 changes: 38 additions & 0 deletions grype/version/generic_constraint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package version

import "fmt"

var _ Constraint = (*genericConstraint)(nil)

type genericConstraint struct {
raw string
expression constraintExpression
name string
}

func newGenericConstraint(raw string, genFn comparatorGenerator, name string) (genericConstraint, error) {
constraints, err := newConstraintExpression(raw, genFn)
if err != nil {
return genericConstraint{}, err
}
return genericConstraint{
expression: constraints,
raw: raw,
name: name,
}, nil
}

func (g genericConstraint) String() string {
value := "none"
if g.raw != "" {
value = g.raw
}
return fmt.Sprintf("%s (%s)", value, g.name)
}

func (g genericConstraint) Satisfied(version *Version) (bool, error) {
if g.raw == "" {
return true, nil // the empty constraint is always satisfied
}
return g.expression.satisfied(version)
}
Loading

0 comments on commit 8086627

Please sign in to comment.