From 4b06a160e14528fadce77618fbf39f63fce39a23 Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Tue, 7 Nov 2023 14:17:36 -0600 Subject: [PATCH] chore: account for syft package metadata changes (#1423) Signed-off-by: Alex Goodman Signed-off-by: Keith Zantow Co-authored-by: Keith Zantow --- Makefile | 7 +- go.mod | 2 + go.sum | 2 + .../db/v4/pkg/resolver/java/resolver_test.go | 7 +- .../db/v5/pkg/resolver/java/resolver_test.go | 7 +- grype/internal/generate.go | 3 + .../packagemetadata/discover_type_names.go | 150 +++++++ .../internal/packagemetadata/generate/main.go | 55 +++ grype/internal/packagemetadata/generated.go | 10 + grype/internal/packagemetadata/names.go | 81 ++++ grype/internal/packagemetadata/names_test.go | 68 ++++ grype/matcher/golang/matcher_test.go | 16 +- grype/matcher/java/matcher_test.go | 11 +- grype/matcher/rpm/matcher_test.go | 27 +- grype/pkg/metadata.go | 14 - grype/pkg/package.go | 125 +++--- grype/pkg/package_test.go | 365 +++++++++++++++++- .../qualifier/platformcpe/qualifier_test.go | 34 +- .../pkg/qualifier/rpmmodularity/qualifier.go | 36 +- .../qualifier/rpmmodularity/qualifier_test.go | 32 +- grype/pkg/syft_sbom_provider_test.go | 13 +- grype/presenter/explain/explain.go | 9 +- .../snapshot/TestJsonDirsPresenter.golden | 7 +- .../snapshot/TestJsonImgsPresenter.golden | 7 +- grype/presenter/models/package.go | 5 +- 25 files changed, 882 insertions(+), 211 deletions(-) create mode 100644 grype/internal/generate.go create mode 100644 grype/internal/packagemetadata/discover_type_names.go create mode 100644 grype/internal/packagemetadata/generate/main.go create mode 100644 grype/internal/packagemetadata/generated.go create mode 100644 grype/internal/packagemetadata/names.go create mode 100644 grype/internal/packagemetadata/names_test.go delete mode 100644 grype/pkg/metadata.go diff --git a/Makefile b/Makefile index dda1e0a47bc..de7c58dab72 100644 --- a/Makefile +++ b/Makefile @@ -259,8 +259,11 @@ compare-test-rpm-package-install: $(TEMP_DIR) $(SNAPSHOT_DIR) $(COMPARE_TEST_IMAGE) \ $(TEMP_DIR) -## Code generation targets ################################# -## TODO (cphillips) what does grype have here? +## Code and data generation targets ################################# + +.PHONY: generate +generate: ## Generate any code or data required by the project + cd grype/internal && go generate . ## Build-related targets ################################# diff --git a/go.mod b/go.mod index 98a42ca3a0f..6cf0a44ad6d 100644 --- a/go.mod +++ b/go.mod @@ -60,6 +60,8 @@ require ( gorm.io/gorm v1.25.5 ) +require github.com/dave/jennifer v1.7.0 + require ( cloud.google.com/go v0.110.7 // indirect cloud.google.com/go/compute v1.23.0 // indirect diff --git a/go.sum b/go.sum index 1d1218fbb77..01a88758cd0 100644 --- a/go.sum +++ b/go.sum @@ -361,6 +361,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/dave/jennifer v1.7.0 h1:uRbSBH9UTS64yXbh4FrMHfgfY762RD+C7bUPKODpSJE= +github.com/dave/jennifer v1.7.0/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= diff --git a/grype/db/v4/pkg/resolver/java/resolver_test.go b/grype/db/v4/pkg/resolver/java/resolver_test.go index ade2c8a9e15..a0e9b207204 100644 --- a/grype/db/v4/pkg/resolver/java/resolver_test.go +++ b/grype/db/v4/pkg/resolver/java/resolver_test.go @@ -53,10 +53,9 @@ func TestResolver_Resolve(t *testing.T) { { name: "both artifact and manifest 1", pkg: grypePkg.Package{ - Name: "ABCD", - Version: "1.2.3.4", - Language: "java", - MetadataType: "", + Name: "ABCD", + Version: "1.2.3.4", + Language: "java", Metadata: grypePkg.JavaMetadata{ VirtualPath: "virtual-path-info", PomArtifactID: "pom-ARTIFACT-ID-info", diff --git a/grype/db/v5/pkg/resolver/java/resolver_test.go b/grype/db/v5/pkg/resolver/java/resolver_test.go index ade2c8a9e15..a0e9b207204 100644 --- a/grype/db/v5/pkg/resolver/java/resolver_test.go +++ b/grype/db/v5/pkg/resolver/java/resolver_test.go @@ -53,10 +53,9 @@ func TestResolver_Resolve(t *testing.T) { { name: "both artifact and manifest 1", pkg: grypePkg.Package{ - Name: "ABCD", - Version: "1.2.3.4", - Language: "java", - MetadataType: "", + Name: "ABCD", + Version: "1.2.3.4", + Language: "java", Metadata: grypePkg.JavaMetadata{ VirtualPath: "virtual-path-info", PomArtifactID: "pom-ARTIFACT-ID-info", diff --git a/grype/internal/generate.go b/grype/internal/generate.go new file mode 100644 index 00000000000..72895379e7f --- /dev/null +++ b/grype/internal/generate.go @@ -0,0 +1,3 @@ +package internal + +//go:generate go run ./packagemetadata/generate/main.go diff --git a/grype/internal/packagemetadata/discover_type_names.go b/grype/internal/packagemetadata/discover_type_names.go new file mode 100644 index 00000000000..9e42401ab7c --- /dev/null +++ b/grype/internal/packagemetadata/discover_type_names.go @@ -0,0 +1,150 @@ +package packagemetadata + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" + "os/exec" + "path/filepath" + "sort" + "strings" + "unicode" + + "github.com/scylladb/go-set/strset" +) + +var metadataExceptions = strset.New( + "FileMetadata", +) + +func DiscoverTypeNames() ([]string, error) { + root, err := RepoRoot() + if err != nil { + return nil, err + } + files, err := filepath.Glob(filepath.Join(root, "grype/pkg/*.go")) + if err != nil { + return nil, err + } + return findMetadataDefinitionNames(files...) +} + +func RepoRoot() (string, error) { + root, err := exec.Command("git", "rev-parse", "--show-toplevel").Output() + if err != nil { + return "", fmt.Errorf("unable to find repo root dir: %+v", err) + } + absRepoRoot, err := filepath.Abs(strings.TrimSpace(string(root))) + if err != nil { + return "", fmt.Errorf("unable to get abs path to repo root: %w", err) + } + return absRepoRoot, nil +} + +func findMetadataDefinitionNames(paths ...string) ([]string, error) { + names := strset.New() + usedNames := strset.New() + for _, path := range paths { + metadataDefinitions, usedTypeNames, err := findMetadataDefinitionNamesInFile(path) + if err != nil { + return nil, err + } + + // useful for debugging... + // fmt.Println(path) + // fmt.Println("Defs:", metadataDefinitions) + // fmt.Println("Used Types:", usedTypeNames) + // fmt.Println() + + names.Add(metadataDefinitions...) + usedNames.Add(usedTypeNames...) + } + + // any definition that is used within another struct should not be considered a top-level metadata definition + names.Remove(usedNames.List()...) + + strNames := names.List() + sort.Strings(strNames) + + // note: 3 is a point-in-time gut check. This number could be updated if new metadata definitions are added, but is not required. + // it is really intended to catch any major issues with the generation process that would generate, say, 0 definitions. + if len(strNames) < 3 { + return nil, fmt.Errorf("not enough metadata definitions found (discovered: " + fmt.Sprintf("%d", len(strNames)) + ")") + } + + return strNames, nil +} + +func findMetadataDefinitionNamesInFile(path string) ([]string, []string, error) { + // set up the parser + fs := token.NewFileSet() + f, err := parser.ParseFile(fs, path, nil, parser.ParseComments) + if err != nil { + return nil, nil, err + } + + var metadataDefinitions []string + var usedTypeNames []string + for _, decl := range f.Decls { + // check if the declaration is a type declaration + spec, ok := decl.(*ast.GenDecl) + if !ok || spec.Tok != token.TYPE { + continue + } + + // loop over all types declared in the type declaration + for _, typ := range spec.Specs { + // check if the type is a struct type + spec, ok := typ.(*ast.TypeSpec) + if !ok || spec.Type == nil { + continue + } + + structType, ok := spec.Type.(*ast.StructType) + if !ok { + continue + } + + // check if the struct type ends with "Metadata" + name := spec.Name.String() + + // only look for exported types that end with "Metadata" + if isMetadataTypeCandidate(name) { + // print the full declaration of the struct type + metadataDefinitions = append(metadataDefinitions, name) + usedTypeNames = append(usedTypeNames, typeNamesUsedInStruct(structType)...) + } + } + } + return metadataDefinitions, usedTypeNames, nil +} + +func typeNamesUsedInStruct(structType *ast.StructType) []string { + // recursively find all type names used in the struct type + var names []string + for i := range structType.Fields.List { + // capture names of all of the types (not field names) + ast.Inspect(structType.Fields.List[i].Type, func(n ast.Node) bool { + ident, ok := n.(*ast.Ident) + if !ok { + return true + } + + // add the type name to the list + names = append(names, ident.Name) + + // continue inspecting + return true + }) + } + + return names +} + +func isMetadataTypeCandidate(name string) bool { + return len(name) > 0 && + strings.HasSuffix(name, "Metadata") && + unicode.IsUpper(rune(name[0])) && // must be exported + !metadataExceptions.Has(name) +} diff --git a/grype/internal/packagemetadata/generate/main.go b/grype/internal/packagemetadata/generate/main.go new file mode 100644 index 00000000000..0b1d9700c8e --- /dev/null +++ b/grype/internal/packagemetadata/generate/main.go @@ -0,0 +1,55 @@ +package main + +import ( + "fmt" + "os" + + "github.com/dave/jennifer/jen" + + "github.com/anchore/grype/grype/internal/packagemetadata" +) + +// This program is invoked from grype/internal and generates packagemetadata/generated.go + +const ( + pkgImport = "github.com/anchore/grype/grype/pkg" + path = "packagemetadata/generated.go" +) + +func main() { + typeNames, err := packagemetadata.DiscoverTypeNames() + if err != nil { + panic(fmt.Errorf("unable to get all metadata type names: %w", err)) + } + + fmt.Printf("updating package metadata type list with %+v types\n", len(typeNames)) + + f := jen.NewFile("packagemetadata") + f.HeaderComment("DO NOT EDIT: generated by grype/internal/packagemetadata/generate/main.go") + f.ImportName(pkgImport, "pkg") + f.Comment("AllTypes returns a list of all pkg metadata types that grype supports (that are represented in the pkg.Package.Metadata field).") + + f.Func().Id("AllTypes").Params().Index().Any().BlockFunc(func(g *jen.Group) { + g.ReturnFunc(func(g *jen.Group) { + g.Index().Any().ValuesFunc(func(g *jen.Group) { + for _, typeName := range typeNames { + g.Qual(pkgImport, typeName).Values() + } + }) + }) + }) + + rendered := fmt.Sprintf("%#v", f) + + fh, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + panic(fmt.Errorf("unable to open file: %w", err)) + } + _, err = fh.WriteString(rendered) + if err != nil { + panic(fmt.Errorf("unable to write file: %w", err)) + } + if err := fh.Close(); err != nil { + panic(fmt.Errorf("unable to close file: %w", err)) + } +} diff --git a/grype/internal/packagemetadata/generated.go b/grype/internal/packagemetadata/generated.go new file mode 100644 index 00000000000..eef1f434507 --- /dev/null +++ b/grype/internal/packagemetadata/generated.go @@ -0,0 +1,10 @@ +// DO NOT EDIT: generated by grype/internal/packagemetadata/generate/main.go + +package packagemetadata + +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.GolangBinMetadata{}, pkg.GolangModMetadata{}, pkg.JavaMetadata{}, pkg.RpmMetadata{}} +} diff --git a/grype/internal/packagemetadata/names.go b/grype/internal/packagemetadata/names.go new file mode 100644 index 00000000000..b0151da4fba --- /dev/null +++ b/grype/internal/packagemetadata/names.go @@ -0,0 +1,81 @@ +package packagemetadata + +import ( + "reflect" + "sort" + "strings" + + "github.com/anchore/grype/grype/pkg" +) + +// jsonNameFromType is a map of all known package metadata types to their current JSON name and all previously known aliases. +// TODO: in the future the metadata type names should match how it is used in syft. However, since the data shapes are +// not the same it may be important to select different names. This design decision has been deferred, for now +// the same metadata types that have been used in the past should be used here. +var jsonNameFromType = map[reflect.Type][]string{ + reflect.TypeOf(pkg.GolangBinMetadata{}): nameList("GolangBinMetadata"), + reflect.TypeOf(pkg.GolangModMetadata{}): nameList("GolangModMetadata"), + reflect.TypeOf(pkg.JavaMetadata{}): nameList("JavaMetadata"), + reflect.TypeOf(pkg.RpmMetadata{}): nameList("RpmMetadata"), +} + +//nolint:unparam +func nameList(id string, others ...string) []string { + names := []string{id} + for _, o := range others { + names = append(names, expandLegacyNameVariants(o)...) + } + return names +} + +func expandLegacyNameVariants(name string) []string { + candidates := []string{name} + if strings.HasSuffix(name, "MetadataType") { + candidates = append(candidates, strings.TrimSuffix(name, "Type")) + } else if strings.HasSuffix(name, "Metadata") { + candidates = append(candidates, name+"Type") + } + return candidates +} + +func AllTypeNames() []string { + names := make([]string, 0) + for _, t := range AllTypes() { + names = append(names, reflect.TypeOf(t).Name()) + } + return names +} + +func JSONName(metadata any) string { + if vs, exists := jsonNameFromType[reflect.TypeOf(metadata)]; exists { + return vs[0] + } + return "" +} + +func ReflectTypeFromJSONName(name string) reflect.Type { + name = strings.ToLower(name) + for _, t := range sortedTypes(jsonNameFromType) { + vs := jsonNameFromType[t] + for _, v := range vs { + if strings.ToLower(v) == name { + return t + } + } + } + return nil +} + +func sortedTypes(typeNameMapping map[reflect.Type][]string) []reflect.Type { + types := make([]reflect.Type, 0) + for t := range typeNameMapping { + types = append(types, t) + } + + // sort the types by their first JSON name + sort.Slice(types, func(i, j int) bool { + return typeNameMapping[types[i]][0] < typeNameMapping[types[j]][0] + }) + + return types +} diff --git a/grype/internal/packagemetadata/names_test.go b/grype/internal/packagemetadata/names_test.go new file mode 100644 index 00000000000..8f3d64e45f7 --- /dev/null +++ b/grype/internal/packagemetadata/names_test.go @@ -0,0 +1,68 @@ +package packagemetadata + +import ( + "reflect" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/anchore/grype/grype/pkg" +) + +func TestAllNames(t *testing.T) { + // note: this is a form of completion testing relative to the current code base. + + expected, err := DiscoverTypeNames() + require.NoError(t, err) + + actual := AllTypeNames() + + // ensure that the codebase (from ast analysis) reflects the latest code generated state + if !assert.ElementsMatch(t, expected, actual) { + t.Errorf("metadata types not fully represented: \n%s", cmp.Diff(expected, actual)) + t.Log("did you add a new pkg.*Metadata type without updating the JSON schema?") + t.Log("if so, you need to update the schema version and regenerate the JSON schema (make generate-json-schema)") + } + + for _, ty := range AllTypes() { + assert.NotEmpty(t, JSONName(ty), "metadata type %q does not have a JSON name", ty) + } +} + +func TestReflectTypeFromJSONName(t *testing.T) { + + tests := []struct { + name string + lookup string + wantRecord reflect.Type + }{ + { + name: "GolangBinMetadata lookup", + lookup: "GolangBinMetadata", + wantRecord: reflect.TypeOf(pkg.GolangBinMetadata{}), + }, + { + name: "GolangModMetadata lookup", + lookup: "GolangModMetadata", + wantRecord: reflect.TypeOf(pkg.GolangModMetadata{}), + }, + { + name: "JavaMetadata lookup", + lookup: "JavaMetadata", + wantRecord: reflect.TypeOf(pkg.JavaMetadata{}), + }, + { + name: "RpmMetadata lookup", + lookup: "RpmMetadata", + wantRecord: reflect.TypeOf(pkg.RpmMetadata{}), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := ReflectTypeFromJSONName(tt.lookup) + assert.Equal(t, tt.wantRecord.Name(), got.Name()) + }) + } +} diff --git a/grype/matcher/golang/matcher_test.go b/grype/matcher/golang/matcher_test.go index 939b2cc41c0..0019296baf1 100644 --- a/grype/matcher/golang/matcher_test.go +++ b/grype/matcher/golang/matcher_test.go @@ -22,13 +22,12 @@ func TestMatcher_DropMainPackage(t *testing.T) { } subjectWithoutMainModule := pkg.Package{ - ID: pkg.ID(uuid.NewString()), - Name: "istio.io/istio", - Version: "v0.0.0-20220606222826-f59ce19ec6b6", - Type: syftPkg.GoModulePkg, - Language: syftPkg.Go, - MetadataType: pkg.GolangBinMetadataType, - Metadata: pkg.GolangBinMetadata{}, + ID: pkg.ID(uuid.NewString()), + Name: "istio.io/istio", + Version: "v0.0.0-20220606222826-f59ce19ec6b6", + Type: syftPkg.GoModulePkg, + Language: syftPkg.Go, + Metadata: pkg.GolangBinMetadata{}, } subjectWithMainModule := subjectWithoutMainModule @@ -65,8 +64,7 @@ func TestMatcher_SearchForStdlib(t *testing.T) { CPEs: []cpe.CPE{ cpe.Must("cpe:2.3:a:golang:go:1.18.3:-:*:*:*:*:*:*"), }, - MetadataType: pkg.GolangBinMetadataType, - Metadata: pkg.GolangBinMetadata{}, + Metadata: pkg.GolangBinMetadata{}, } cases := []struct { diff --git a/grype/matcher/java/matcher_test.go b/grype/matcher/java/matcher_test.go index b3dcdf64371..649c3df229a 100644 --- a/grype/matcher/java/matcher_test.go +++ b/grype/matcher/java/matcher_test.go @@ -15,12 +15,11 @@ import ( func TestMatcherJava_matchUpstreamMavenPackage(t *testing.T) { p := pkg.Package{ - ID: pkg.ID(uuid.NewString()), - Name: "org.springframework.spring-webmvc", - Version: "5.1.5.RELEASE", - Language: syftPkg.Java, - Type: syftPkg.JavaPkg, - MetadataType: pkg.JavaMetadataType, + ID: pkg.ID(uuid.NewString()), + Name: "org.springframework.spring-webmvc", + Version: "5.1.5.RELEASE", + Language: syftPkg.Java, + Type: syftPkg.JavaPkg, Metadata: pkg.JavaMetadata{ ArchiveDigests: []pkg.Digest{ { diff --git a/grype/matcher/rpm/matcher_test.go b/grype/matcher/rpm/matcher_test.go index d7959b0aca2..0c0e9c1284d 100644 --- a/grype/matcher/rpm/matcher_test.go +++ b/grype/matcher/rpm/matcher_test.go @@ -248,11 +248,10 @@ func TestMatcherRpm(t *testing.T) { { name: "package with modularity label 1", p: pkg.Package{ - ID: pkg.ID(uuid.NewString()), - Name: "maniac", - Version: "0.1", - Type: syftPkg.RpmPkg, - MetadataType: pkg.RpmMetadataType, + ID: pkg.ID(uuid.NewString()), + Name: "maniac", + Version: "0.1", + Type: syftPkg.RpmPkg, Metadata: pkg.RpmMetadata{ ModularityLabel: "containertools:3:1234:5678", }, @@ -276,11 +275,10 @@ func TestMatcherRpm(t *testing.T) { { name: "package with modularity label 2", p: pkg.Package{ - ID: pkg.ID(uuid.NewString()), - Name: "maniac", - Version: "0.1", - Type: syftPkg.RpmPkg, - MetadataType: pkg.RpmMetadataType, + ID: pkg.ID(uuid.NewString()), + Name: "maniac", + Version: "0.1", + Type: syftPkg.RpmPkg, Metadata: pkg.RpmMetadata{ ModularityLabel: "containertools:1:abc:123", }, @@ -303,11 +301,10 @@ func TestMatcherRpm(t *testing.T) { { name: "package without modularity label", p: pkg.Package{ - ID: pkg.ID(uuid.NewString()), - Name: "maniac", - Version: "0.1", - Type: syftPkg.RpmPkg, - MetadataType: pkg.RpmMetadataType, + ID: pkg.ID(uuid.NewString()), + Name: "maniac", + Version: "0.1", + Type: syftPkg.RpmPkg, }, setup: func() (vulnerability.Provider, *distro.Distro, Matcher) { matcher := Matcher{} diff --git a/grype/pkg/metadata.go b/grype/pkg/metadata.go deleted file mode 100644 index ab09e70c1c2..00000000000 --- a/grype/pkg/metadata.go +++ /dev/null @@ -1,14 +0,0 @@ -package pkg - -// MetadataType represents the data shape stored within pkg.Package.Metadata. -type MetadataType string - -const ( - // this is the full set of data shapes that can be represented within the pkg.Package.Metadata field - - UnknownMetadataType MetadataType = "UnknownMetadata" - JavaMetadataType MetadataType = "JavaMetadata" - RpmMetadataType MetadataType = "RpmMetadata" - GolangBinMetadataType MetadataType = "GolangBinMetadata" - GolangModMetadataType MetadataType = "GolangModMetadata" -) diff --git a/grype/pkg/package.go b/grype/pkg/package.go index 58bd25d94b7..da74375cc49 100644 --- a/grype/pkg/package.go +++ b/grype/pkg/package.go @@ -29,22 +29,21 @@ type ID string // Package represents an application or library that has been bundled into a distributable format. type Package struct { - ID ID - Name string // the package name - Version string // the version of the package - Locations file.LocationSet // the locations that lead to the discovery of this package (note: this is not necessarily the locations that make up this package) - Language pkg.Language // the language ecosystem this package belongs to (e.g. JavaScript, Python, etc) - Licenses []string - Type pkg.Type // the package type (e.g. Npm, Yarn, Python, Rpm, Deb, etc) - CPEs []cpe.CPE // all possible Common Platform Enumerators - PURL string // the Package URL (see https://github.com/package-url/purl-spec) - Upstreams []UpstreamPackage - MetadataType MetadataType - Metadata interface{} // This is NOT 1-for-1 the syft metadata! Only the select data needed for vulnerability matching + ID ID + Name string // the package name + Version string // the version of the package + Locations file.LocationSet // the locations that lead to the discovery of this package (note: this is not necessarily the locations that make up this package) + Language pkg.Language // the language ecosystem this package belongs to (e.g. JavaScript, Python, etc) + Licenses []string + Type pkg.Type // the package type (e.g. Npm, Yarn, Python, Rpm, Deb, etc) + CPEs []cpe.CPE // all possible Common Platform Enumerators + PURL string // the Package URL (see https://github.com/package-url/purl-spec) + Upstreams []UpstreamPackage + Metadata interface{} // This is NOT 1-for-1 the syft metadata! Only the select data needed for vulnerability matching } func New(p pkg.Package) Package { - metadataType, metadata, upstreams := dataFromPkg(p) + metadata, upstreams := dataFromPkg(p) licenseObjs := p.Licenses.ToSlice() // note: this is used for presentation downstream and is a collection, thus should always be allocated @@ -57,18 +56,17 @@ func New(p pkg.Package) Package { } return Package{ - ID: ID(p.ID()), - Name: p.Name, - Version: p.Version, - Locations: p.Locations, - Licenses: licenses, - Language: p.Language, - Type: p.Type, - CPEs: p.CPEs, - PURL: p.PURL, - Upstreams: upstreams, - MetadataType: metadataType, - Metadata: metadata, + ID: ID(p.ID()), + Name: p.Name, + Version: p.Version, + Locations: p.Locations, + Licenses: licenses, + Language: p.Language, + Type: p.Type, + CPEs: p.CPEs, + PURL: p.PURL, + Upstreams: upstreams, + Metadata: metadata, } } @@ -198,14 +196,13 @@ func isOSPackage(p pkg.Package) bool { } } -func dataFromPkg(p pkg.Package) (MetadataType, interface{}, []UpstreamPackage) { +func dataFromPkg(p pkg.Package) (interface{}, []UpstreamPackage) { var metadata interface{} var upstreams []UpstreamPackage - var metadataType MetadataType switch p.Metadata.(type) { case pkg.GolangModuleEntry, pkg.GolangBinaryBuildinfoEntry: - metadataType, metadata = golangMetadataFromPkg(p) + metadata = golangMetadataFromPkg(p) case pkg.DpkgDBEntry: upstreams = dpkgDataFromPkg(p) case pkg.RpmArchive, pkg.RpmDBEntry: @@ -213,20 +210,18 @@ func dataFromPkg(p pkg.Package) (MetadataType, interface{}, []UpstreamPackage) { upstreams = u if m != nil { metadata = *m - metadataType = RpmMetadataType } case pkg.JavaArchive: if m := javaDataFromPkg(p); m != nil { metadata = *m - metadataType = JavaMetadataType } case pkg.ApkDBEntry: upstreams = apkDataFromPkg(p) } - return metadataType, metadata, upstreams + return metadata, upstreams } -func golangMetadataFromPkg(p pkg.Package) (MetadataType, interface{}) { +func golangMetadataFromPkg(p pkg.Package) interface{} { switch value := p.Metadata.(type) { case pkg.GolangBinaryBuildinfoEntry: metadata := GolangBinMetadata{} @@ -237,13 +232,13 @@ func golangMetadataFromPkg(p pkg.Package) (MetadataType, interface{}) { metadata.Architecture = value.Architecture metadata.H1Digest = value.H1Digest metadata.MainModule = value.MainModule - return GolangBinMetadataType, metadata + return metadata case pkg.GolangModuleEntry: metadata := GolangModMetadata{} metadata.H1Digest = value.H1Digest - return GolangModMetadataType, metadata + return metadata } - return "", nil + return nil } func dpkgDataFromPkg(p pkg.Package) (upstreams []UpstreamPackage) { @@ -261,48 +256,48 @@ func dpkgDataFromPkg(p pkg.Package) (upstreams []UpstreamPackage) { } func rpmDataFromPkg(p pkg.Package) (metadata *RpmMetadata, upstreams []UpstreamPackage) { - switch value := p.Metadata.(type) { + switch m := p.Metadata.(type) { case pkg.RpmDBEntry: - if value.SourceRpm != "" { - name, version := getNameAndELVersion(value.SourceRpm) - if name == "" && version == "" { - log.Warnf("unable to extract name and version from SourceRPM=%q ", value.SourceRpm) - } else if name != p.Name { - // don't include matches if the source package name matches the current package name - upstreams = append(upstreams, UpstreamPackage{ - Name: name, - Version: version, - }) - } + if m.SourceRpm != "" { + upstreams = handleSourceRPM(p.Name, m.SourceRpm) } + metadata = &RpmMetadata{ - Epoch: value.Epoch, - ModularityLabel: value.ModularityLabel, + Epoch: m.Epoch, + ModularityLabel: m.ModularityLabel, } case pkg.RpmArchive: - if value.SourceRpm != "" { - name, version := getNameAndELVersion(value.SourceRpm) - if name == "" && version == "" { - log.Warnf("unable to extract name and version from SourceRPM=%q ", value.SourceRpm) - } else if name != p.Name { - // don't include matches if the source package name matches the current package name - upstreams = append(upstreams, UpstreamPackage{ - Name: name, - Version: version, - }) - } + if m.SourceRpm != "" { + upstreams = handleSourceRPM(p.Name, m.SourceRpm) } + metadata = &RpmMetadata{ - Epoch: value.Epoch, - ModularityLabel: value.ModularityLabel, + Epoch: m.Epoch, + ModularityLabel: m.ModularityLabel, } - default: - log.Warnf("unable to extract RPM metadata for %s", p) } - return metadata, upstreams } +func handleSourceRPM(pkgName, sourceRpm string) []UpstreamPackage { + var upstreams []UpstreamPackage + name, version := getNameAndELVersion(sourceRpm) + if name == "" && version == "" { + log.Warnf("unable to extract name and version from SourceRPM=%q ", sourceRpm) + } else if name != pkgName { + // don't include matches if the source package name matches the current package name + if name != "" && version != "" { + upstreams = append(upstreams, + UpstreamPackage{ + Name: name, + Version: version, + }, + ) + } + } + return upstreams +} + func getNameAndELVersion(sourceRpm string) (string, string) { groupMatches := stringutil.MatchCaptureGroups(rpmPackageNamePattern, sourceRpm) version := groupMatches["version"] + "-" + groupMatches["release"] diff --git a/grype/pkg/package_test.go b/grype/pkg/package_test.go index 957048c898e..950d01cbc06 100644 --- a/grype/pkg/package_test.go +++ b/grype/pkg/package_test.go @@ -14,9 +14,10 @@ import ( "github.com/anchore/syft/syft/linux" syftPkg "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/sbom" + "github.com/anchore/syft/syft/testutil" ) -func TestNew_UpstreamFromMetadata(t *testing.T) { +func TestNew(t *testing.T) { tests := []struct { name string syftPkg syftPkg.Package @@ -144,7 +145,7 @@ func TestNew_UpstreamFromMetadata(t *testing.T) { }, }, { - name: "rpm archove with source info that matches the package info", + name: "rpm archive with source info that matches the package info", syftPkg: syftPkg.Package{ Name: "sqlite", Metadata: syftPkg.RpmArchive{ @@ -227,12 +228,368 @@ func TestNew_UpstreamFromMetadata(t *testing.T) { }, }, }, + // the below packages are those that have no metadata or upstream info to parse out + { + name: "npm-metadata", + syftPkg: syftPkg.Package{ + Metadata: syftPkg.NpmPackage{ + Author: "a", + Homepage: "a", + Description: "a", + URL: "a", + }, + }, + }, + { + name: "python-metadata", + syftPkg: syftPkg.Package{ + Metadata: syftPkg.PythonPackage{ + Name: "a", + Version: "a", + Author: "a", + AuthorEmail: "a", + Platform: "a", + SitePackagesRootPath: "a", + }, + }, + }, + { + name: "gem-metadata", + syftPkg: syftPkg.Package{ + Metadata: syftPkg.RubyGemspec{ + Name: "a", + Version: "a", + Homepage: "a", + }, + }, + }, + { + name: "kb-metadata", + syftPkg: syftPkg.Package{ + Metadata: syftPkg.MicrosoftKbPatch{ + ProductID: "a", + Kb: "a", + }, + }, + }, + { + name: "rust-metadata", + syftPkg: syftPkg.Package{ + Metadata: syftPkg.RustCargoLockEntry{ + Name: "a", + Version: "a", + Source: "a", + Checksum: "a", + }, + }, + }, + { + name: "golang-metadata", + syftPkg: syftPkg.Package{ + Metadata: syftPkg.GolangBinaryBuildinfoEntry{ + BuildSettings: map[string]string{}, + GoCompiledVersion: "1.0.0", + H1Digest: "a", + MainModule: "myMainModule", + }, + }, + metadata: GolangBinMetadata{ + BuildSettings: map[string]string{}, + GoCompiledVersion: "1.0.0", + H1Digest: "a", + MainModule: "myMainModule", + }, + }, + { + name: "golang-mod-metadata", + syftPkg: syftPkg.Package{ + Metadata: syftPkg.GolangModuleEntry{ + H1Digest: "h1:as234NweNNTNWEtt13nwNENTt", + }, + }, + metadata: GolangModMetadata{ + H1Digest: "h1:as234NweNNTNWEtt13nwNENTt", + }, + }, + { + name: "php-composer-lock-metadata", + syftPkg: syftPkg.Package{ + Metadata: syftPkg.PhpComposerLockEntry{ + Name: "a", + Version: "a", + }, + }, + }, + { + name: "php-composer-installed-metadata", + syftPkg: syftPkg.Package{ + Metadata: syftPkg.PhpComposerInstalledEntry{ + Name: "a", + Version: "a", + }, + }, + }, + { + name: "dart-pub-metadata", + syftPkg: syftPkg.Package{ + Metadata: syftPkg.DartPubspecLockEntry{ + Name: "a", + Version: "a", + }, + }, + }, + { + name: "dotnet-metadata", + syftPkg: syftPkg.Package{ + Metadata: syftPkg.DotnetDepsEntry{ + Name: "a", + Version: "a", + Path: "a", + Sha512: "a", + HashPath: "a", + }, + }, + }, + { + name: "cpp conan-metadata", + syftPkg: syftPkg.Package{ + Metadata: syftPkg.ConanfileEntry{ + Ref: "catch2/2.13.8", + }, + }, + }, + { + name: "cpp conan lock metadata", + syftPkg: syftPkg.Package{ + Metadata: syftPkg.ConanLockEntry{ + Ref: "zlib/1.2.12", + Options: map[string]string{ + "fPIC": "True", + "shared": "False", + }, + Path: "all/conanfile.py", + Context: "host", + }, + }, + }, + { + name: "cocoapods cocoapods-metadata", + syftPkg: syftPkg.Package{ + Metadata: syftPkg.CocoaPodfileLockEntry{ + Checksum: "123eere234", + }, + }, + }, + { + name: "portage-metadata", + syftPkg: syftPkg.Package{ + Metadata: syftPkg.PortageEntry{ + InstalledSize: 1, + Files: []syftPkg.PortageFileRecord{}, + }, + }, + }, + { + name: "hackage-stack-lock-metadata", + syftPkg: syftPkg.Package{ + Metadata: syftPkg.HackageStackYamlLockEntry{ + PkgHash: "some-hash", + }, + }, + }, + { + name: "hackage-stack-metadata", + syftPkg: syftPkg.Package{ + Metadata: syftPkg.HackageStackYamlEntry{ + PkgHash: "some-hash", + }, + }, + }, + { + name: "rebar-metadata", + syftPkg: syftPkg.Package{ + Metadata: syftPkg.ErlangRebarLockEntry{ + Name: "rebar", + Version: "v0.1.1", + }, + }, + }, + { + name: "npm-package-lock-metadata", + syftPkg: syftPkg.Package{ + Metadata: syftPkg.NpmPackageLockEntry{ + Resolved: "resolved", + Integrity: "sha1:ab7d8979989b7a98d97", + }, + }, + }, + { + name: "mix-lock-metadata", + syftPkg: syftPkg.Package{ + Metadata: syftPkg.ElixirMixLockEntry{ + Name: "mix-lock", + Version: "v0.1.2", + }, + }, + }, + { + name: "pipfile-lock-metadata", + syftPkg: syftPkg.Package{ + Metadata: syftPkg.PythonPipfileLockEntry{ + Hashes: []string{ + "sha1:ab8v88a8b88d8d8c88b8s765s47", + }, + Index: "1", + }, + }, + }, + { + name: "python-requirements-metadata", + syftPkg: syftPkg.Package{ + Metadata: syftPkg.PythonRequirementsEntry{ + Name: "a", + Extras: []string{"a"}, + VersionConstraint: "a", + URL: "a", + Markers: "a", + }, + }, + }, + { + name: "binary-metadata", + syftPkg: syftPkg.Package{ + Metadata: syftPkg.BinarySignature{ + Matches: []syftPkg.ClassifierMatch{ + { + Classifier: "node", + }, + }, + }, + }, + }, + { + name: "nix-store-metadata", + syftPkg: syftPkg.Package{ + Metadata: syftPkg.NixStoreEntry{ + OutputHash: "a", + Output: "a", + Files: []string{ + "a", + }, + }, + }, + }, + { + name: "linux-kernel-metadata", + syftPkg: syftPkg.Package{ + Metadata: syftPkg.LinuxKernel{ + Name: "a", + Architecture: "a", + Version: "a", + ExtendedVersion: "a", + BuildTime: "a", + Author: "a", + Format: "a", + RWRootFS: true, + SwapDevice: 10, + RootDevice: 11, + VideoMode: "a", + }, + }, + }, + { + name: "linux-kernel-module-metadata", + syftPkg: syftPkg.Package{ + Metadata: syftPkg.LinuxKernelModule{ + Name: "a", + Version: "a", + SourceVersion: "a", + Path: "a", + Description: "a", + Author: "a", + License: "a", + KernelVersion: "a", + VersionMagic: "a", + Parameters: map[string]syftPkg.LinuxKernelModuleParameter{ + "a": { + Type: "a", + Description: "a", + }, + }, + }, + }, + }, + { + name: "r-description-file-metadata", + syftPkg: syftPkg.Package{ + Metadata: syftPkg.RDescription{ + Title: "a", + Description: "a", + Author: "a", + Maintainer: "a", + URL: []string{"a"}, + Repository: "a", + Built: "a", + NeedsCompilation: true, + Imports: []string{"a"}, + Depends: []string{"a"}, + Suggests: []string{"a"}, + }, + }, + }, + { + name: "dotnet-portable-executable-metadata", + syftPkg: syftPkg.Package{ + Metadata: syftPkg.DotnetPortableExecutableEntry{ + AssemblyVersion: "a", + LegalCopyright: "a", + Comments: "a", + InternalName: "a", + CompanyName: "a", + ProductName: "a", + ProductVersion: "a", + }, + }, + }, + { + name: "swift-package-manager-metadata", + syftPkg: syftPkg.Package{ + Metadata: syftPkg.SwiftPackageManagerResolvedEntry{ + Revision: "a", + }, + }, + }, + { + name: "conaninfo-entry", + syftPkg: syftPkg.Package{ + Metadata: syftPkg.ConaninfoEntry{ + Ref: "a", + PackageID: "a", + }, + }, + }, + { + name: "rust-binary-audit-entry", + syftPkg: syftPkg.Package{ + Metadata: syftPkg.RustBinaryAuditEntry{ + Name: "a", + Version: "a", + Source: "a", + }, + }, + }, } + // capture each observed metadata type, we should see all of them relate to what syft provides by the end of testing + tester := testutil.NewPackageMetadataCompletionTester(t) + + // run all of our cases for _, test := range tests { t.Run(test.name, func(t *testing.T) { - assert.Equal(t, test.metadata, New(test.syftPkg).Metadata, "unexpected metadata") - assert.Equal(t, test.upstreams, New(test.syftPkg).Upstreams, "unexpected upstream") + tester.Tested(t, test.syftPkg.Metadata) + p := New(test.syftPkg) + assert.Equal(t, test.metadata, p.Metadata, "unexpected metadata") + assert.Equal(t, test.upstreams, p.Upstreams, "unexpected upstream") }) } } diff --git a/grype/pkg/qualifier/platformcpe/qualifier_test.go b/grype/pkg/qualifier/platformcpe/qualifier_test.go index 91f4e6ea3f4..bfd9f26cd95 100644 --- a/grype/pkg/qualifier/platformcpe/qualifier_test.go +++ b/grype/pkg/qualifier/platformcpe/qualifier_test.go @@ -22,7 +22,7 @@ func TestPlatformCPE_Satisfied(t *testing.T) { { name: "no filter on nil distro", platformCPE: New("cpe:2.3:o:microsoft:windows:-:*:*:*:*:*:*:*"), - pkg: pkg.Package{MetadataType: pkg.UnknownMetadataType}, + pkg: pkg.Package{}, distro: nil, satisfied: true, hasError: false, @@ -30,7 +30,7 @@ func TestPlatformCPE_Satisfied(t *testing.T) { { name: "no filter when platform CPE is empty", platformCPE: New(""), - pkg: pkg.Package{MetadataType: pkg.UnknownMetadataType}, + pkg: pkg.Package{}, distro: &distro.Distro{Type: distro.Windows}, satisfied: true, hasError: false, @@ -38,7 +38,7 @@ func TestPlatformCPE_Satisfied(t *testing.T) { { name: "no filter when platform CPE is invalid", platformCPE: New(";;;"), - pkg: pkg.Package{MetadataType: pkg.UnknownMetadataType}, + pkg: pkg.Package{}, distro: &distro.Distro{Type: distro.Windows}, satisfied: true, hasError: true, @@ -47,7 +47,7 @@ func TestPlatformCPE_Satisfied(t *testing.T) { { name: "filter windows platform vuln when distro is not windows", platformCPE: New("cpe:2.3:o:microsoft:windows:-:*:*:*:*:*:*:*"), - pkg: pkg.Package{MetadataType: pkg.UnknownMetadataType}, + pkg: pkg.Package{}, distro: &distro.Distro{Type: distro.Debian}, satisfied: false, hasError: false, @@ -55,7 +55,7 @@ func TestPlatformCPE_Satisfied(t *testing.T) { { name: "filter windows server platform vuln when distro is not windows", platformCPE: New("cpe:2.3:o:microsoft:windows_server_2022:-:*:*:*:*:*:*:*"), - pkg: pkg.Package{MetadataType: pkg.UnknownMetadataType}, + pkg: pkg.Package{}, distro: &distro.Distro{Type: distro.Debian}, satisfied: false, hasError: false, @@ -63,7 +63,7 @@ func TestPlatformCPE_Satisfied(t *testing.T) { { name: "no filter windows platform vuln when distro is windows", platformCPE: New("cpe:2.3:o:microsoft:windows:-:*:*:*:*:*:*:*"), - pkg: pkg.Package{MetadataType: pkg.UnknownMetadataType}, + pkg: pkg.Package{}, distro: &distro.Distro{Type: distro.Windows}, satisfied: true, hasError: false, @@ -71,7 +71,7 @@ func TestPlatformCPE_Satisfied(t *testing.T) { { name: "no filter windows server platform vuln when distro is windows", platformCPE: New("cpe:2.3:o:microsoft:windows_server_2022:-:*:*:*:*:*:*:*"), - pkg: pkg.Package{MetadataType: pkg.UnknownMetadataType}, + pkg: pkg.Package{}, distro: &distro.Distro{Type: distro.Windows}, satisfied: true, hasError: false, @@ -80,7 +80,7 @@ func TestPlatformCPE_Satisfied(t *testing.T) { { name: "filter debian platform vuln when distro is not debian", platformCPE: New("cpe:2.3:o:debian:debian_linux:-:*:*:*:*:*:*:*"), - pkg: pkg.Package{MetadataType: pkg.UnknownMetadataType}, + pkg: pkg.Package{}, distro: &distro.Distro{Type: distro.Ubuntu}, satisfied: false, hasError: false, @@ -88,7 +88,7 @@ func TestPlatformCPE_Satisfied(t *testing.T) { { name: "filter debian platform vuln when distro is not debian (alternate encountered cpe)", platformCPE: New("cpe:2.3:o:debian:linux:-:*:*:*:*:*:*:*"), - pkg: pkg.Package{MetadataType: pkg.UnknownMetadataType}, + pkg: pkg.Package{}, distro: &distro.Distro{Type: distro.SLES}, satisfied: false, hasError: false, @@ -96,7 +96,7 @@ func TestPlatformCPE_Satisfied(t *testing.T) { { name: "no filter debian platform vuln when distro is debian", platformCPE: New("cpe:2.3:o:debian:debian_linux:-:*:*:*:*:*:*:*"), - pkg: pkg.Package{MetadataType: pkg.UnknownMetadataType}, + pkg: pkg.Package{}, distro: &distro.Distro{Type: distro.Debian}, satisfied: true, hasError: false, @@ -104,7 +104,7 @@ func TestPlatformCPE_Satisfied(t *testing.T) { { name: "no filter debian platform vuln when distro is debian (alternate encountered cpe)", platformCPE: New("cpe:2.3:o:debian:linux:-:*:*:*:*:*:*:*"), - pkg: pkg.Package{MetadataType: pkg.UnknownMetadataType}, + pkg: pkg.Package{}, distro: &distro.Distro{Type: distro.Debian}, satisfied: true, hasError: false, @@ -113,7 +113,7 @@ func TestPlatformCPE_Satisfied(t *testing.T) { { name: "filter ubuntu platform vuln when distro is not ubuntu", platformCPE: New("cpe:2.3:o:canonical:ubuntu_linux:-:*:*:*:*:*:*:*"), - pkg: pkg.Package{MetadataType: pkg.UnknownMetadataType}, + pkg: pkg.Package{}, distro: &distro.Distro{Type: distro.SLES}, satisfied: false, hasError: false, @@ -121,7 +121,7 @@ func TestPlatformCPE_Satisfied(t *testing.T) { { name: "filter ubuntu platform vuln when distro is not ubuntu (alternate encountered cpe)", platformCPE: New("cpe:2.3:o:ubuntu:vivid:-:*:*:*:*:*:*:*"), - pkg: pkg.Package{MetadataType: pkg.UnknownMetadataType}, + pkg: pkg.Package{}, distro: &distro.Distro{Type: distro.Alpine}, satisfied: false, hasError: false, @@ -129,7 +129,7 @@ func TestPlatformCPE_Satisfied(t *testing.T) { { name: "no filter ubuntu platform vuln when distro is ubuntu", platformCPE: New("cpe:2.3:o:canonical:ubuntu_linux:-:*:*:*:*:*:*:*"), - pkg: pkg.Package{MetadataType: pkg.UnknownMetadataType}, + pkg: pkg.Package{}, distro: &distro.Distro{Type: distro.Ubuntu}, satisfied: true, hasError: false, @@ -137,7 +137,7 @@ func TestPlatformCPE_Satisfied(t *testing.T) { { name: "no filter ubuntu platform vuln when distro is ubuntu (alternate encountered cpe)", platformCPE: New("cpe:2.3:o:ubuntu:vivid:-:*:*:*:*:*:*:*"), - pkg: pkg.Package{MetadataType: pkg.UnknownMetadataType}, + pkg: pkg.Package{}, distro: &distro.Distro{Type: distro.Ubuntu}, satisfied: true, hasError: false, @@ -146,7 +146,7 @@ func TestPlatformCPE_Satisfied(t *testing.T) { { name: "always filter wordpress platform vulns (no known distro)", platformCPE: New("cpe:2.3:o:wordpress:wordpress:-:*:*:*:*:*:*:*"), - pkg: pkg.Package{MetadataType: pkg.UnknownMetadataType}, + pkg: pkg.Package{}, distro: nil, satisfied: false, hasError: false, @@ -154,7 +154,7 @@ func TestPlatformCPE_Satisfied(t *testing.T) { { name: "always filter wordpress platform vulns (known distro)", platformCPE: New("cpe:2.3:o:ubuntu:vivid:-:*:*:*:*:*:*:*"), - pkg: pkg.Package{MetadataType: pkg.UnknownMetadataType}, + pkg: pkg.Package{}, distro: &distro.Distro{Type: distro.Alpine}, satisfied: false, hasError: false, diff --git a/grype/pkg/qualifier/rpmmodularity/qualifier.go b/grype/pkg/qualifier/rpmmodularity/qualifier.go index 0071eb33656..2a46ff076ae 100644 --- a/grype/pkg/qualifier/rpmmodularity/qualifier.go +++ b/grype/pkg/qualifier/rpmmodularity/qualifier.go @@ -17,32 +17,24 @@ func New(module string) qualifier.Qualifier { } func (r rpmModularity) Satisfied(_ *distro.Distro, p pkg.Package) (bool, error) { - if p.MetadataType == pkg.RpmMetadataType { + if p.Metadata == nil { // If unable to determine package modularity, the constraint should be considered satisfied - if p.Metadata == nil { - return true, nil - } - - m, ok := p.Metadata.(pkg.RpmMetadata) - - // If the package metadata was the rpm type but casting failed - // we assume it would have been satisfied to - // avoid dropping potential matches - if !ok { - return true, nil - } + return true, nil + } - // If the package modularity is empty (""), the constraint should be considered satisfied - if m.ModularityLabel == "" { - return true, nil - } + m, ok := p.Metadata.(pkg.RpmMetadata) + if !ok { + return false, nil + } - if r.module == "" { - return false, nil - } + // If the package modularity is empty (""), the constraint should be considered satisfied + if m.ModularityLabel == "" { + return true, nil + } - return strings.HasPrefix(m.ModularityLabel, r.module), nil + if r.module == "" { + return false, nil } - return false, nil + return strings.HasPrefix(m.ModularityLabel, r.module), nil } diff --git a/grype/pkg/qualifier/rpmmodularity/qualifier_test.go b/grype/pkg/qualifier/rpmmodularity/qualifier_test.go index 30377e6faec..49846b91b7f 100644 --- a/grype/pkg/qualifier/rpmmodularity/qualifier_test.go +++ b/grype/pkg/qualifier/rpmmodularity/qualifier_test.go @@ -19,37 +19,27 @@ func TestRpmModularity_Satisfied(t *testing.T) { { name: "non rpm metadata", rpmModularity: New("test:1"), - pkg: pkg.Package{MetadataType: pkg.UnknownMetadataType}, - satisfied: false, - }, - { - name: "invalid rpm metadata", - rpmModularity: New("test:1"), - pkg: pkg.Package{MetadataType: pkg.RpmMetadataType, Metadata: pkg.GolangBinMetadata{ - BuildSettings: nil, - GoCompiledVersion: "", - Architecture: "", - H1Digest: "", - MainModule: "", - }}, - satisfied: true, + pkg: pkg.Package{ + Metadata: pkg.JavaMetadata{}, + }, + satisfied: false, }, { name: "module with package rpm metadata lacking actual metadata 1", rpmModularity: New("test:1"), - pkg: pkg.Package{MetadataType: pkg.RpmMetadataType, Metadata: nil}, + pkg: pkg.Package{Metadata: nil}, satisfied: true, }, { name: "empty module with rpm metadata lacking actual metadata 2", rpmModularity: New(""), - pkg: pkg.Package{MetadataType: pkg.RpmMetadataType, Metadata: nil}, + pkg: pkg.Package{Metadata: nil}, satisfied: true, }, { name: "no modularity label with no module", rpmModularity: New(""), - pkg: pkg.Package{MetadataType: pkg.RpmMetadataType, Metadata: pkg.RpmMetadata{ + pkg: pkg.Package{Metadata: pkg.RpmMetadata{ Epoch: nil, }}, satisfied: true, @@ -57,7 +47,7 @@ func TestRpmModularity_Satisfied(t *testing.T) { { name: "no modularity label with module", rpmModularity: New("abc"), - pkg: pkg.Package{MetadataType: pkg.RpmMetadataType, Metadata: pkg.RpmMetadata{ + pkg: pkg.Package{Metadata: pkg.RpmMetadata{ Epoch: nil, }}, satisfied: true, @@ -65,7 +55,7 @@ func TestRpmModularity_Satisfied(t *testing.T) { { name: "modularity label with no module", rpmModularity: New(""), - pkg: pkg.Package{MetadataType: pkg.RpmMetadataType, Metadata: pkg.RpmMetadata{ + pkg: pkg.Package{Metadata: pkg.RpmMetadata{ Epoch: nil, ModularityLabel: "x:3:1234567:abcd", }}, @@ -74,7 +64,7 @@ func TestRpmModularity_Satisfied(t *testing.T) { { name: "modularity label in module", rpmModularity: New("x:3"), - pkg: pkg.Package{MetadataType: pkg.RpmMetadataType, Metadata: pkg.RpmMetadata{ + pkg: pkg.Package{Metadata: pkg.RpmMetadata{ Epoch: nil, ModularityLabel: "x:3:1234567:abcd", }}, @@ -83,7 +73,7 @@ func TestRpmModularity_Satisfied(t *testing.T) { { name: "modularity label not in module", rpmModularity: New("x:3"), - pkg: pkg.Package{MetadataType: pkg.RpmMetadataType, Metadata: pkg.RpmMetadata{ + pkg: pkg.Package{Metadata: pkg.RpmMetadata{ Epoch: nil, ModularityLabel: "x:1:1234567:abcd", }}, diff --git a/grype/pkg/syft_sbom_provider_test.go b/grype/pkg/syft_sbom_provider_test.go index 4d1b0a12164..3bb95dca7a7 100644 --- a/grype/pkg/syft_sbom_provider_test.go +++ b/grype/pkg/syft_sbom_provider_test.go @@ -92,8 +92,7 @@ func TestParseSyftJSON(t *testing.T) { cpe.Must("cpe:2.3:a:*:gmp:6.2.0-r0:*:*:*:*:*:*:*"), cpe.Must("cpe:2.3:a:gmp:gmp:6.2.0-r0:*:*:*:*:*:*:*"), }, - PURL: "pkg:alpine/gmp@6.2.0-r0?arch=x86_64", - MetadataType: JavaMetadataType, + PURL: "pkg:alpine/gmp@6.2.0-r0?arch=x86_64", Metadata: JavaMetadata{ PomArtifactID: "aid", PomGroupID: "gid", @@ -195,9 +194,8 @@ var springImageTestCase = struct { cpe.Must("cpe:2.3:a:charsets:charsets:*:*:*:*:*:java:*:*"), cpe.Must("cpe:2.3:a:charsets:charsets:*:*:*:*:*:maven:*:*"), }, - PURL: "", - MetadataType: JavaMetadataType, - Metadata: JavaMetadata{VirtualPath: "/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/charsets.jar"}, + PURL: "", + Metadata: JavaMetadata{VirtualPath: "/usr/lib/jvm/java-8-openjdk-amd64/jre/lib/charsets.jar"}, }, { Name: "tomcat-embed-el", @@ -215,9 +213,8 @@ var springImageTestCase = struct { cpe.Must("cpe:2.3:a:tomcat_embed_el:tomcat-embed-el:9.0.27:*:*:*:*:java:*:*"), cpe.Must("cpe:2.3:a:tomcat-embed-el:tomcat_embed_el:9.0.27:*:*:*:*:maven:*:*"), }, - PURL: "", - MetadataType: JavaMetadataType, - Metadata: JavaMetadata{VirtualPath: "/app/libs/tomcat-embed-el-9.0.27.jar"}, + PURL: "", + Metadata: JavaMetadata{VirtualPath: "/app/libs/tomcat-embed-el-9.0.27.jar"}, }, }, Context: Context{ diff --git a/grype/presenter/explain/explain.go b/grype/presenter/explain/explain.go index 92038be7574..9edb321b377 100644 --- a/grype/presenter/explain/explain.go +++ b/grype/presenter/explain/explain.go @@ -9,7 +9,6 @@ import ( "text/template" "github.com/anchore/grype/grype/match" - "github.com/anchore/grype/grype/pkg" "github.com/anchore/grype/grype/presenter/models" "github.com/anchore/syft/syft/file" ) @@ -405,11 +404,9 @@ func (b *viewModelBuilder) dedupeAndSortURLs(primaryVulnerability models.Vulnera func explainLocation(match models.Match, location file.Coordinates) explainedEvidence { path := location.RealPath - if match.Artifact.MetadataType == pkg.JavaMetadataType { - if javaMeta, ok := match.Artifact.Metadata.(map[string]any); ok { - if virtPath, ok := javaMeta["virtualPath"].(string); ok { - path = virtPath - } + if javaMeta, ok := match.Artifact.Metadata.(map[string]any); ok { + if virtPath, ok := javaMeta["virtualPath"].(string); ok { + path = virtPath } } return explainedEvidence{ diff --git a/grype/presenter/json/test-fixtures/snapshot/TestJsonDirsPresenter.golden b/grype/presenter/json/test-fixtures/snapshot/TestJsonDirsPresenter.golden index b4c01d62277..15b97583a25 100644 --- a/grype/presenter/json/test-fixtures/snapshot/TestJsonDirsPresenter.golden +++ b/grype/presenter/json/test-fixtures/snapshot/TestJsonDirsPresenter.golden @@ -57,12 +57,7 @@ "cpe:2.3:a:anchore:engine:0.9.2:*:*:python:*:*:*:*" ], "purl": "", - "upstreams": [ - { - "name": "", - "version": "-" - } - ], + "upstreams": [], "metadataType": "RpmMetadata", "metadata": { "epoch": 2, diff --git a/grype/presenter/json/test-fixtures/snapshot/TestJsonImgsPresenter.golden b/grype/presenter/json/test-fixtures/snapshot/TestJsonImgsPresenter.golden index bfd2bc8adba..3f0da406d75 100644 --- a/grype/presenter/json/test-fixtures/snapshot/TestJsonImgsPresenter.golden +++ b/grype/presenter/json/test-fixtures/snapshot/TestJsonImgsPresenter.golden @@ -57,12 +57,7 @@ "cpe:2.3:a:anchore:engine:0.9.2:*:*:python:*:*:*:*" ], "purl": "", - "upstreams": [ - { - "name": "", - "version": "-" - } - ], + "upstreams": [], "metadataType": "RpmMetadata", "metadata": { "epoch": 2, diff --git a/grype/presenter/models/package.go b/grype/presenter/models/package.go index 43e0c1aa6e9..d890f8db6a1 100644 --- a/grype/presenter/models/package.go +++ b/grype/presenter/models/package.go @@ -1,6 +1,7 @@ package models import ( + "github.com/anchore/grype/grype/internal/packagemetadata" "github.com/anchore/grype/grype/pkg" "github.com/anchore/syft/syft/file" syftPkg "github.com/anchore/syft/syft/pkg" @@ -18,7 +19,7 @@ type Package struct { CPEs []string `json:"cpes"` PURL string `json:"purl"` Upstreams []UpstreamPackage `json:"upstreams"` - MetadataType pkg.MetadataType `json:"metadataType,omitempty"` + MetadataType string `json:"metadataType,omitempty"` Metadata interface{} `json:"metadata,omitempty"` } @@ -63,7 +64,7 @@ func newPackage(p pkg.Package) Package { CPEs: cpes, PURL: p.PURL, Upstreams: upstreams, - MetadataType: p.MetadataType, + MetadataType: packagemetadata.JSONName(p.Metadata), Metadata: p.Metadata, } }