From fb2b54a6dc3b2193e53ead9dea5c8bba58d7aa8a Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Tue, 16 Jan 2024 09:18:18 -0500 Subject: [PATCH] condense binary cataloger config in JSON output (#2499) --- syft/pkg/cataloger/binary/cataloger.go | 11 +++++ syft/pkg/cataloger/binary/cataloger_test.go | 48 ++++++++++++++++++++ syft/pkg/cataloger/binary/classifier.go | 26 +++++++++++ syft/pkg/cataloger/binary/classifier_test.go | 45 ++++++++++++++++++ 4 files changed, 130 insertions(+) diff --git a/syft/pkg/cataloger/binary/cataloger.go b/syft/pkg/cataloger/binary/cataloger.go index 5d9b5a2ad..00058b04d 100644 --- a/syft/pkg/cataloger/binary/cataloger.go +++ b/syft/pkg/cataloger/binary/cataloger.go @@ -4,6 +4,8 @@ Package binary provides a concrete Cataloger implementations for surfacing possi package binary import ( + "encoding/json" + "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/file" @@ -28,6 +30,15 @@ func NewCataloger(cfg CatalogerConfig) pkg.Cataloger { } } +func (cfg CatalogerConfig) MarshalJSON() ([]byte, error) { + // only keep the class names + var names []string + for _, cls := range cfg.Classifiers { + names = append(names, cls.Class) + } + return json.Marshal(names) +} + // Cataloger is the cataloger responsible for surfacing evidence of a very limited set of binary files, // which have been identified by the classifiers. The Cataloger is _NOT_ a place to catalog any and every // binary, but rather the specific set that has been curated to be important, predominantly related to toolchain- diff --git a/syft/pkg/cataloger/binary/cataloger_test.go b/syft/pkg/cataloger/binary/cataloger_test.go index 26992889a..c80a2a743 100644 --- a/syft/pkg/cataloger/binary/cataloger_test.go +++ b/syft/pkg/cataloger/binary/cataloger_test.go @@ -13,7 +13,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/anchore/packageurl-go" "github.com/anchore/stereoscope/pkg/imagetest" + "github.com/anchore/syft/syft/cpe" "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/binary/test-fixtures/manager/testutil" @@ -1242,3 +1244,49 @@ func Test_Cataloger_ResilientToErrors(t *testing.T) { assert.NoError(t, err) assert.True(t, resolver.searchCalled) } + +func TestCatalogerConfig_MarshalJSON(t *testing.T) { + + tests := []struct { + name string + cfg CatalogerConfig + want string + wantErr assert.ErrorAssertionFunc + }{ + { + name: "only show names of classes", + cfg: CatalogerConfig{ + Classifiers: []Classifier{ + { + Class: "class", + FileGlob: "glob", + EvidenceMatcher: FileContentsVersionMatcher(".thing"), + Package: "pkg", + PURL: packageurl.PackageURL{ + Type: "type", + Namespace: "namespace", + Name: "name", + Version: "version", + Qualifiers: nil, + Subpath: "subpath", + }, + CPEs: []cpe.CPE{cpe.Must("cpe:2.3:a:some:app:*:*:*:*:*:*:*:*")}, + }, + }, + }, + want: `["class"]`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.wantErr == nil { + tt.wantErr = assert.NoError + } + got, err := tt.cfg.MarshalJSON() + if !tt.wantErr(t, err) { + return + } + assert.Equal(t, tt.want, string(got)) + }) + } +} diff --git a/syft/pkg/cataloger/binary/classifier.go b/syft/pkg/cataloger/binary/classifier.go index a7689ae70..d3aeb56d4 100644 --- a/syft/pkg/cataloger/binary/classifier.go +++ b/syft/pkg/cataloger/binary/classifier.go @@ -5,6 +5,7 @@ import ( "debug/elf" "debug/macho" "debug/pe" + "encoding/json" "fmt" "io" "regexp" @@ -44,6 +45,31 @@ type Classifier struct { CPEs []cpe.CPE `json:"cpes"` } +func (cfg Classifier) MarshalJSON() ([]byte, error) { + type marshalled struct { + Class string `json:"class"` + FileGlob string `json:"fileGlob"` + Package string `json:"package"` + PURL string `json:"purl"` + CPEs []string `json:"cpes"` + } + + var marshalledCPEs []string + for _, c := range cfg.CPEs { + marshalledCPEs = append(marshalledCPEs, c.BindToFmtString()) + } + + m := marshalled{ + Class: cfg.Class, + FileGlob: cfg.FileGlob, + Package: cfg.Package, + PURL: cfg.PURL.String(), + CPEs: marshalledCPEs, + } + + return json.Marshal(m) +} + // EvidenceMatcher is a function called to catalog Packages that match some sort of evidence type EvidenceMatcher func(resolver file.Resolver, classifier Classifier, location file.Location) ([]pkg.Package, error) diff --git a/syft/pkg/cataloger/binary/classifier_test.go b/syft/pkg/cataloger/binary/classifier_test.go index 9fbdf73fe..43397189d 100644 --- a/syft/pkg/cataloger/binary/classifier_test.go +++ b/syft/pkg/cataloger/binary/classifier_test.go @@ -3,8 +3,10 @@ package binary import ( "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/anchore/packageurl-go" "github.com/anchore/syft/syft/cpe" "github.com/anchore/syft/syft/file" ) @@ -83,3 +85,46 @@ func Test_ClassifierCPEs(t *testing.T) { }) } } + +func TestClassifier_MarshalJSON(t *testing.T) { + + tests := []struct { + name string + classifier Classifier + want string + wantErr assert.ErrorAssertionFunc + }{ + { + name: "go case", + classifier: Classifier{ + Class: "class", + FileGlob: "glob", + EvidenceMatcher: FileContentsVersionMatcher(".thing"), + Package: "pkg", + PURL: packageurl.PackageURL{ + Type: "type", + Namespace: "namespace", + Name: "name", + Version: "version", + Qualifiers: nil, + Subpath: "subpath", + }, + CPEs: []cpe.CPE{cpe.Must("cpe:2.3:a:some:app:*:*:*:*:*:*:*:*")}, + }, + want: `{"class":"class","fileGlob":"glob","package":"pkg","purl":"pkg:type/namespace/name@version#subpath","cpes":["cpe:2.3:a:some:app:*:*:*:*:*:*:*:*"]}`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.wantErr == nil { + tt.wantErr = assert.NoError + } + cfg := tt.classifier + got, err := cfg.MarshalJSON() + if !tt.wantErr(t, err) { + return + } + assert.Equal(t, tt.want, string(got)) + }) + } +}