mirror of
https://github.com/anchore/syft.git
synced 2025-11-17 16:33:21 +01:00
SBOM cataloger (#1029)
* SBOM cataloger Signed-off-by: Patrik Beno <patrik.beno@greenhorn.sk> * sbom-cataloger: turn off by default and add integration test Signed-off-by: Patrik Beno <patrik.beno@greenhorn.sk> * SBOM cataloger Signed-off-by: Patrik Beno <patrik.beno@greenhorn.sk> * SBOM cataloger (optimize) Signed-off-by: Patrik Beno <patrik.beno@greenhorn.sk> * SBOM cataloger (fix) Signed-off-by: Patrik Beno <patrik.beno@greenhorn.sk> * SBOM cataloger (fix imports #1172) Signed-off-by: Patrik Beno <patrik.beno@greenhorn.sk> * SBOM cataloger (fix: support group attribute in CDX SBOMs) Signed-off-by: Patrik Beno <patrik.beno@greenhorn.sk> * port to generic cataloger and add relationship to original file Signed-off-by: Alex Goodman <alex.goodman@anchore.com> * generalize parser for all format globs Signed-off-by: Alex Goodman <alex.goodman@anchore.com> Signed-off-by: Patrik Beno <patrik.beno@greenhorn.sk> Signed-off-by: Alex Goodman <alex.goodman@anchore.com> Co-authored-by: Tom Fay <tomfay@microsoft.com> Co-authored-by: Alex Goodman <alex.goodman@anchore.com>
This commit is contained in:
parent
0774ad15e2
commit
0c4b99c1c2
@ -1,28 +1,35 @@
|
|||||||
package options
|
package options
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/anchore/syft/syft"
|
"github.com/anchore/syft/syft/formats/cyclonedxjson"
|
||||||
|
"github.com/anchore/syft/syft/formats/cyclonedxxml"
|
||||||
|
"github.com/anchore/syft/syft/formats/github"
|
||||||
|
"github.com/anchore/syft/syft/formats/spdx22json"
|
||||||
|
"github.com/anchore/syft/syft/formats/spdx22tagvalue"
|
||||||
|
"github.com/anchore/syft/syft/formats/syftjson"
|
||||||
|
"github.com/anchore/syft/syft/formats/table"
|
||||||
|
"github.com/anchore/syft/syft/formats/text"
|
||||||
"github.com/anchore/syft/syft/sbom"
|
"github.com/anchore/syft/syft/sbom"
|
||||||
)
|
)
|
||||||
|
|
||||||
func FormatAliases(ids ...sbom.FormatID) (aliases []string) {
|
func FormatAliases(ids ...sbom.FormatID) (aliases []string) {
|
||||||
for _, id := range ids {
|
for _, id := range ids {
|
||||||
switch id {
|
switch id {
|
||||||
case syft.JSONFormatID:
|
case syftjson.ID:
|
||||||
aliases = append(aliases, "syft-json")
|
aliases = append(aliases, "syft-json")
|
||||||
case syft.TextFormatID:
|
case text.ID:
|
||||||
aliases = append(aliases, "text")
|
aliases = append(aliases, "text")
|
||||||
case syft.TableFormatID:
|
case table.ID:
|
||||||
aliases = append(aliases, "table")
|
aliases = append(aliases, "table")
|
||||||
case syft.SPDXJSONFormatID:
|
case spdx22json.ID:
|
||||||
aliases = append(aliases, "spdx-json")
|
aliases = append(aliases, "spdx-json")
|
||||||
case syft.SPDXTagValueFormatID:
|
case spdx22tagvalue.ID:
|
||||||
aliases = append(aliases, "spdx-tag-value")
|
aliases = append(aliases, "spdx-tag-value")
|
||||||
case syft.CycloneDxXMLFormatID:
|
case cyclonedxxml.ID:
|
||||||
aliases = append(aliases, "cyclonedx-xml")
|
aliases = append(aliases, "cyclonedx-xml")
|
||||||
case syft.CycloneDxJSONFormatID:
|
case cyclonedxjson.ID:
|
||||||
aliases = append(aliases, "cyclonedx-json")
|
aliases = append(aliases, "cyclonedx-json")
|
||||||
case syft.GitHubID:
|
case github.ID:
|
||||||
aliases = append(aliases, "github", "github-json")
|
aliases = append(aliases, "github", "github-json")
|
||||||
default:
|
default:
|
||||||
aliases = append(aliases, string(id))
|
aliases = append(aliases, string(id))
|
||||||
|
|||||||
@ -12,6 +12,9 @@ const (
|
|||||||
|
|
||||||
// DependencyOfRelationship is a proxy for the SPDX 2.2.1 DEPENDENCY_OF relationship.
|
// DependencyOfRelationship is a proxy for the SPDX 2.2.1 DEPENDENCY_OF relationship.
|
||||||
DependencyOfRelationship RelationshipType = "dependency-of"
|
DependencyOfRelationship RelationshipType = "dependency-of"
|
||||||
|
|
||||||
|
// DescribedByRelationship is a proxy for the SPDX 2.2.2 DESCRIBED_BY relationship.
|
||||||
|
DescribedByRelationship RelationshipType = "described-by"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RelationshipType string
|
type RelationshipType string
|
||||||
|
|||||||
@ -1,36 +1,18 @@
|
|||||||
package syft
|
package syft
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/formats"
|
||||||
"github.com/anchore/syft/syft/sbom"
|
"github.com/anchore/syft/syft/sbom"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Encode takes all SBOM elements and a format option and encodes an SBOM document.
|
// TODO: deprecated, moved to syft/formats/formats.go. will be removed in v1.0.0
|
||||||
func Encode(s sbom.SBOM, f sbom.Format) ([]byte, error) {
|
func Encode(s sbom.SBOM, f sbom.Format) ([]byte, error) {
|
||||||
buff := bytes.Buffer{}
|
return formats.Encode(s, f)
|
||||||
|
|
||||||
if err := f.Encode(&buff, s); err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to encode sbom: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return buff.Bytes(), nil
|
// TODO: deprecated, moved to syft/formats/formats.go. will be removed in v1.0.0
|
||||||
}
|
|
||||||
|
|
||||||
// Decode takes a reader for an SBOM and generates all internal SBOM elements.
|
|
||||||
func Decode(reader io.Reader) (*sbom.SBOM, sbom.Format, error) {
|
func Decode(reader io.Reader) (*sbom.SBOM, sbom.Format, error) {
|
||||||
by, err := io.ReadAll(reader)
|
return formats.Decode(reader)
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("unable to read sbom: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
f := IdentifyFormat(by)
|
|
||||||
if f == nil {
|
|
||||||
return nil, nil, fmt.Errorf("unable to identify format")
|
|
||||||
}
|
|
||||||
|
|
||||||
s, err := f.Decode(bytes.NewReader(by))
|
|
||||||
return s, f, err
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,7 @@
|
|||||||
package syft
|
package syft
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"github.com/anchore/syft/syft/formats"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/anchore/syft/syft/formats/cyclonedxjson"
|
"github.com/anchore/syft/syft/formats/cyclonedxjson"
|
||||||
"github.com/anchore/syft/syft/formats/cyclonedxxml"
|
"github.com/anchore/syft/syft/formats/cyclonedxxml"
|
||||||
"github.com/anchore/syft/syft/formats/github"
|
"github.com/anchore/syft/syft/formats/github"
|
||||||
@ -17,94 +15,35 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// these have been exported for the benefit of API users
|
// these have been exported for the benefit of API users
|
||||||
|
// TODO: deprecated: now that the formats package has been moved to syft/formats, will be removed in v1.0.0
|
||||||
const (
|
const (
|
||||||
JSONFormatID = syftjson.ID
|
JSONFormatID = syftjson.ID
|
||||||
TextFormatID = text.ID
|
TextFormatID = text.ID
|
||||||
TableFormatID = table.ID
|
TableFormatID = table.ID
|
||||||
CycloneDxXMLFormatID = cyclonedxxml.ID
|
CycloneDxXMLFormatID = cyclonedxxml.ID
|
||||||
CycloneDxJSONFormatID = cyclonedxjson.ID
|
CycloneDxJSONFormatID = cyclonedxjson.ID
|
||||||
GitHubID = github.ID
|
GitHubFormatID = github.ID
|
||||||
SPDXTagValueFormatID = spdx22tagvalue.ID
|
SPDXTagValueFormatID = spdx22tagvalue.ID
|
||||||
SPDXJSONFormatID = spdx22json.ID
|
SPDXJSONFormatID = spdx22json.ID
|
||||||
TemplateFormatID = template.ID
|
TemplateFormatID = template.ID
|
||||||
)
|
)
|
||||||
|
|
||||||
var formats []sbom.Format
|
// TODO: deprecated, moved to syft/formats/formats.go. will be removed in v1.0.0
|
||||||
|
|
||||||
func init() {
|
|
||||||
formats = []sbom.Format{
|
|
||||||
syftjson.Format(),
|
|
||||||
cyclonedxxml.Format(),
|
|
||||||
cyclonedxjson.Format(),
|
|
||||||
github.Format(),
|
|
||||||
spdx22tagvalue.Format(),
|
|
||||||
spdx22json.Format(),
|
|
||||||
table.Format(),
|
|
||||||
text.Format(),
|
|
||||||
template.Format(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func FormatIDs() (ids []sbom.FormatID) {
|
func FormatIDs() (ids []sbom.FormatID) {
|
||||||
for _, f := range formats {
|
return formats.IDs()
|
||||||
ids = append(ids, f.ID())
|
|
||||||
}
|
|
||||||
return ids
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: deprecated, moved to syft/formats/formats.go. will be removed in v1.0.0
|
||||||
func FormatByID(id sbom.FormatID) sbom.Format {
|
func FormatByID(id sbom.FormatID) sbom.Format {
|
||||||
for _, f := range formats {
|
return formats.ByID(id)
|
||||||
if f.ID() == id {
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: deprecated, moved to syft/formats/formats.go. will be removed in v1.0.0
|
||||||
func FormatByName(name string) sbom.Format {
|
func FormatByName(name string) sbom.Format {
|
||||||
cleanName := cleanFormatName(name)
|
return formats.ByName(name)
|
||||||
for _, f := range formats {
|
|
||||||
if cleanFormatName(string(f.ID())) == cleanName {
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle any aliases for any supported format
|
|
||||||
switch cleanName {
|
|
||||||
case "json", "syftjson":
|
|
||||||
return FormatByID(syftjson.ID)
|
|
||||||
case "cyclonedx", "cyclone", "cyclonedxxml":
|
|
||||||
return FormatByID(cyclonedxxml.ID)
|
|
||||||
case "cyclonedxjson":
|
|
||||||
return FormatByID(cyclonedxjson.ID)
|
|
||||||
case "github", "githubjson":
|
|
||||||
return FormatByID(github.ID)
|
|
||||||
case "spdx", "spdxtv", "spdxtagvalue":
|
|
||||||
return FormatByID(spdx22tagvalue.ID)
|
|
||||||
case "spdxjson":
|
|
||||||
return FormatByID(spdx22json.ID)
|
|
||||||
case "table":
|
|
||||||
return FormatByID(table.ID)
|
|
||||||
case "text":
|
|
||||||
return FormatByID(text.ID)
|
|
||||||
case "template":
|
|
||||||
FormatByID(template.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func cleanFormatName(name string) string {
|
|
||||||
r := strings.NewReplacer("-", "", "_", "")
|
|
||||||
return strings.ToLower(r.Replace(name))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: deprecated, moved to syft/formats/formats.go. will be removed in v1.0.0
|
||||||
func IdentifyFormat(by []byte) sbom.Format {
|
func IdentifyFormat(by []byte) sbom.Format {
|
||||||
for _, f := range formats {
|
return formats.Identify(by)
|
||||||
if err := f.Validate(bytes.NewReader(by)); err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -46,6 +46,9 @@ func SourceInfo(p pkg.Package) string {
|
|||||||
default:
|
default:
|
||||||
answer = "acquired package info from the following paths"
|
answer = "acquired package info from the following paths"
|
||||||
}
|
}
|
||||||
|
if p.FoundBy == "sbom-cataloger" {
|
||||||
|
answer = "acquired package info from SBOM"
|
||||||
|
}
|
||||||
var paths []string
|
var paths []string
|
||||||
for _, l := range p.Locations.ToSlice() {
|
for _, l := range p.Locations.ToSlice() {
|
||||||
paths = append(paths, l.RealPath)
|
paths = append(paths, l.RealPath)
|
||||||
|
|||||||
124
syft/formats/formats.go
Normal file
124
syft/formats/formats.go
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
package formats
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/formats/cyclonedxjson"
|
||||||
|
"github.com/anchore/syft/syft/formats/cyclonedxxml"
|
||||||
|
"github.com/anchore/syft/syft/formats/github"
|
||||||
|
"github.com/anchore/syft/syft/formats/spdx22json"
|
||||||
|
"github.com/anchore/syft/syft/formats/spdx22tagvalue"
|
||||||
|
"github.com/anchore/syft/syft/formats/syftjson"
|
||||||
|
"github.com/anchore/syft/syft/formats/table"
|
||||||
|
"github.com/anchore/syft/syft/formats/template"
|
||||||
|
"github.com/anchore/syft/syft/formats/text"
|
||||||
|
"github.com/anchore/syft/syft/sbom"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Formats() []sbom.Format {
|
||||||
|
return []sbom.Format{
|
||||||
|
syftjson.Format(),
|
||||||
|
cyclonedxxml.Format(),
|
||||||
|
cyclonedxjson.Format(),
|
||||||
|
github.Format(),
|
||||||
|
spdx22tagvalue.Format(),
|
||||||
|
spdx22json.Format(),
|
||||||
|
table.Format(),
|
||||||
|
text.Format(),
|
||||||
|
template.Format(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Identify(by []byte) sbom.Format {
|
||||||
|
for _, f := range Formats() {
|
||||||
|
if err := f.Validate(bytes.NewReader(by)); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ByName(name string) sbom.Format {
|
||||||
|
cleanName := cleanFormatName(name)
|
||||||
|
for _, f := range Formats() {
|
||||||
|
if cleanFormatName(string(f.ID())) == cleanName {
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle any aliases for any supported format
|
||||||
|
switch cleanName {
|
||||||
|
case "json", "syftjson":
|
||||||
|
return ByID(syftjson.ID)
|
||||||
|
case "cyclonedx", "cyclone", "cyclonedxxml":
|
||||||
|
return ByID(cyclonedxxml.ID)
|
||||||
|
case "cyclonedxjson":
|
||||||
|
return ByID(cyclonedxjson.ID)
|
||||||
|
case "github", "githubjson":
|
||||||
|
return ByID(github.ID)
|
||||||
|
case "spdx", "spdxtv", "spdxtagvalue":
|
||||||
|
return ByID(spdx22tagvalue.ID)
|
||||||
|
case "spdxjson":
|
||||||
|
return ByID(spdx22json.ID)
|
||||||
|
case "table":
|
||||||
|
return ByID(table.ID)
|
||||||
|
case "text":
|
||||||
|
return ByID(text.ID)
|
||||||
|
case "template":
|
||||||
|
ByID(template.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func IDs() (ids []sbom.FormatID) {
|
||||||
|
for _, f := range Formats() {
|
||||||
|
ids = append(ids, f.ID())
|
||||||
|
}
|
||||||
|
return ids
|
||||||
|
}
|
||||||
|
|
||||||
|
func ByID(id sbom.FormatID) sbom.Format {
|
||||||
|
for _, f := range Formats() {
|
||||||
|
if f.ID() == id {
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanFormatName(name string) string {
|
||||||
|
r := strings.NewReplacer("-", "", "_", "")
|
||||||
|
return strings.ToLower(r.Replace(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode takes all SBOM elements and a format option and encodes an SBOM document.
|
||||||
|
func Encode(s sbom.SBOM, f sbom.Format) ([]byte, error) {
|
||||||
|
buff := bytes.Buffer{}
|
||||||
|
|
||||||
|
if err := f.Encode(&buff, s); err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to encode sbom: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return buff.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode takes a reader for an SBOM and generates all internal SBOM elements.
|
||||||
|
func Decode(reader io.Reader) (*sbom.SBOM, sbom.Format, error) {
|
||||||
|
by, err := io.ReadAll(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("unable to read sbom: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f := Identify(by)
|
||||||
|
if f == nil {
|
||||||
|
return nil, nil, fmt.Errorf("unable to identify format")
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := f.Decode(bytes.NewReader(by))
|
||||||
|
return s, f, err
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package syft
|
package formats
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -37,7 +37,7 @@ func TestIdentify(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
by, err := io.ReadAll(f)
|
by, err := io.ReadAll(f)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
frmt := IdentifyFormat(by)
|
frmt := Identify(by)
|
||||||
assert.NotNil(t, frmt)
|
assert.NotNil(t, frmt)
|
||||||
assert.Equal(t, test.expected, frmt.ID())
|
assert.Equal(t, test.expected, frmt.ID())
|
||||||
})
|
})
|
||||||
@ -45,7 +45,7 @@ func TestIdentify(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFormats_EmptyInput(t *testing.T) {
|
func TestFormats_EmptyInput(t *testing.T) {
|
||||||
for _, format := range formats {
|
for _, format := range Formats() {
|
||||||
t.Run(format.ID().String(), func(t *testing.T) {
|
t.Run(format.ID().String(), func(t *testing.T) {
|
||||||
t.Run("format.Decode", func(t *testing.T) {
|
t.Run("format.Decode", func(t *testing.T) {
|
||||||
input := bytes.NewReader(nil)
|
input := bytes.NewReader(nil)
|
||||||
@ -69,7 +69,7 @@ func TestFormats_EmptyInput(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFormatByName(t *testing.T) {
|
func TestByName(t *testing.T) {
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -190,7 +190,7 @@ func TestFormatByName(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
f := FormatByName(tt.name)
|
f := ByName(tt.name)
|
||||||
if tt.want == "" {
|
if tt.want == "" {
|
||||||
require.Nil(t, f)
|
require.Nil(t, f)
|
||||||
return
|
return
|
||||||
@ -26,6 +26,7 @@ import (
|
|||||||
"github.com/anchore/syft/syft/pkg/cataloger/rpm"
|
"github.com/anchore/syft/syft/pkg/cataloger/rpm"
|
||||||
"github.com/anchore/syft/syft/pkg/cataloger/ruby"
|
"github.com/anchore/syft/syft/pkg/cataloger/ruby"
|
||||||
"github.com/anchore/syft/syft/pkg/cataloger/rust"
|
"github.com/anchore/syft/syft/pkg/cataloger/rust"
|
||||||
|
"github.com/anchore/syft/syft/pkg/cataloger/sbom"
|
||||||
"github.com/anchore/syft/syft/pkg/cataloger/swift"
|
"github.com/anchore/syft/syft/pkg/cataloger/swift"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -47,6 +48,7 @@ func ImageCatalogers(cfg Config) []pkg.Cataloger {
|
|||||||
golang.NewGoModuleBinaryCataloger(),
|
golang.NewGoModuleBinaryCataloger(),
|
||||||
dotnet.NewDotnetDepsCataloger(),
|
dotnet.NewDotnetDepsCataloger(),
|
||||||
portage.NewPortageCataloger(),
|
portage.NewPortageCataloger(),
|
||||||
|
sbom.NewSBOMCataloger(),
|
||||||
}, cfg.Catalogers)
|
}, cfg.Catalogers)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,6 +77,7 @@ func DirectoryCatalogers(cfg Config) []pkg.Cataloger {
|
|||||||
cpp.NewConanCataloger(),
|
cpp.NewConanCataloger(),
|
||||||
portage.NewPortageCataloger(),
|
portage.NewPortageCataloger(),
|
||||||
haskell.NewHackageCataloger(),
|
haskell.NewHackageCataloger(),
|
||||||
|
sbom.NewSBOMCataloger(),
|
||||||
}, cfg.Catalogers)
|
}, cfg.Catalogers)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,6 +110,7 @@ func AllCatalogers(cfg Config) []pkg.Cataloger {
|
|||||||
cpp.NewConanCataloger(),
|
cpp.NewConanCataloger(),
|
||||||
portage.NewPortageCataloger(),
|
portage.NewPortageCataloger(),
|
||||||
haskell.NewHackageCataloger(),
|
haskell.NewHackageCataloger(),
|
||||||
|
sbom.NewSBOMCataloger(),
|
||||||
}, cfg.Catalogers)
|
}, cfg.Catalogers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -124,6 +124,11 @@ func (p *CatalogTester) IgnoreLocationLayer() *CatalogTester {
|
|||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *CatalogTester) IgnorePackageFields(fields ...string) *CatalogTester {
|
||||||
|
p.compareOptions = append(p.compareOptions, cmpopts.IgnoreFields(pkg.Package{}, fields...))
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
func (p *CatalogTester) Expects(pkgs []pkg.Package, relationships []artifact.Relationship) *CatalogTester {
|
func (p *CatalogTester) Expects(pkgs []pkg.Package, relationships []artifact.Relationship) *CatalogTester {
|
||||||
p.expectedPkgs = pkgs
|
p.expectedPkgs = pkgs
|
||||||
p.expectedRelationships = relationships
|
p.expectedRelationships = relationships
|
||||||
|
|||||||
62
syft/pkg/cataloger/sbom/cataloger.go
Normal file
62
syft/pkg/cataloger/sbom/cataloger.go
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
package sbom
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/anchore/syft/internal/log"
|
||||||
|
"github.com/anchore/syft/syft/artifact"
|
||||||
|
"github.com/anchore/syft/syft/formats"
|
||||||
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||||
|
"github.com/anchore/syft/syft/source"
|
||||||
|
)
|
||||||
|
|
||||||
|
const catalogerName = "sbom-cataloger"
|
||||||
|
|
||||||
|
// NewSBOMCataloger returns a new SBOM cataloger object loaded from saved SBOM JSON.
|
||||||
|
func NewSBOMCataloger() *generic.Cataloger {
|
||||||
|
return generic.NewCataloger(catalogerName).
|
||||||
|
WithParserByGlobs(parseSBOM,
|
||||||
|
"**/*.syft.json",
|
||||||
|
"**/*.bom.*",
|
||||||
|
"**/*.bom",
|
||||||
|
"**/bom",
|
||||||
|
"**/*.sbom.*",
|
||||||
|
"**/*.sbom",
|
||||||
|
"**/sbom",
|
||||||
|
"**/*.cdx.*",
|
||||||
|
"**/*.cdx",
|
||||||
|
"**/*.spdx.*",
|
||||||
|
"**/*.spdx",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSBOM(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||||
|
s, _, err := formats.Decode(reader)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if s == nil {
|
||||||
|
log.WithFields("path", reader.Location.RealPath).Trace("file is not an SBOM")
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var pkgs []pkg.Package
|
||||||
|
var relationships []artifact.Relationship
|
||||||
|
for _, p := range s.Artifacts.PackageCatalog.Sorted() {
|
||||||
|
// replace all locations on the package with the location of the SBOM file.
|
||||||
|
// Why not keep the original list of locations? Since the "locations" field is meant to capture
|
||||||
|
// where there is evidence of this file, and the catalogers have not run against any file other than,
|
||||||
|
// the SBOM, this is the only location that is relevant for this cataloger.
|
||||||
|
p.Locations = source.NewLocationSet(reader.Location)
|
||||||
|
p.FoundBy = catalogerName
|
||||||
|
|
||||||
|
pkgs = append(pkgs, p)
|
||||||
|
relationships = append(relationships, artifact.Relationship{
|
||||||
|
From: p,
|
||||||
|
To: reader.Location.Coordinates,
|
||||||
|
Type: artifact.DescribedByRelationship,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return pkgs, relationships, nil
|
||||||
|
}
|
||||||
291
syft/pkg/cataloger/sbom/cataloger_test.go
Normal file
291
syft/pkg/cataloger/sbom/cataloger_test.go
Normal file
@ -0,0 +1,291 @@
|
|||||||
|
package sbom
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/artifact"
|
||||||
|
"github.com/anchore/syft/syft/formats/syftjson"
|
||||||
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||||
|
"github.com/anchore/syft/syft/sbom"
|
||||||
|
"github.com/anchore/syft/syft/source"
|
||||||
|
)
|
||||||
|
|
||||||
|
func mustCPEs(s ...string) (c []pkg.CPE) {
|
||||||
|
for _, i := range s {
|
||||||
|
c = append(c, mustCPE(i))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustCPE(c string) pkg.CPE {
|
||||||
|
return must(pkg.NewCPE(c))
|
||||||
|
}
|
||||||
|
func must(c pkg.CPE, e error) pkg.CPE {
|
||||||
|
if e != nil {
|
||||||
|
panic(e)
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_parseSBOM(t *testing.T) {
|
||||||
|
|
||||||
|
expectedPkgs := []pkg.Package{
|
||||||
|
{
|
||||||
|
Name: "alpine-baselayout",
|
||||||
|
Version: "3.2.0-r23",
|
||||||
|
Type: "apk",
|
||||||
|
Locations: source.NewLocationSet(source.NewLocation("sbom.syft.json")),
|
||||||
|
Licenses: []string{"GPL-2.0-only"},
|
||||||
|
FoundBy: "sbom-cataloger",
|
||||||
|
PURL: "pkg:alpine/alpine-baselayout@3.2.0-r23?arch=x86_64&upstream=alpine-baselayout&distro=alpine-3.16.3",
|
||||||
|
CPEs: mustCPEs(
|
||||||
|
"cpe:2.3:a:alpine-baselayout:alpine-baselayout:3.2.0-r23:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:alpine-baselayout:alpine_baselayout:3.2.0-r23:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:alpine_baselayout:alpine-baselayout:3.2.0-r23:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:alpine_baselayout:alpine_baselayout:3.2.0-r23:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:alpine:alpine-baselayout:3.2.0-r23:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:alpine:alpine_baselayout:3.2.0-r23:*:*:*:*:*:*:*",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "alpine-baselayout-data",
|
||||||
|
Version: "3.2.0-r23",
|
||||||
|
Type: "apk",
|
||||||
|
Locations: source.NewLocationSet(source.NewLocation("sbom.syft.json")),
|
||||||
|
Licenses: []string{"GPL-2.0-only"},
|
||||||
|
FoundBy: "sbom-cataloger",
|
||||||
|
PURL: "pkg:alpine/alpine-baselayout-data@3.2.0-r23?arch=x86_64&upstream=alpine-baselayout&distro=alpine-3.16.3",
|
||||||
|
CPEs: mustCPEs(
|
||||||
|
"cpe:2.3:a:alpine-baselayout-data:alpine-baselayout-data:3.2.0-r23:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:alpine-baselayout-data:alpine_baselayout_data:3.2.0-r23:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:alpine_baselayout_data:alpine-baselayout-data:3.2.0-r23:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:alpine_baselayout_data:alpine_baselayout_data:3.2.0-r23:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:alpine-baselayout:alpine-baselayout-data:3.2.0-r23:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:alpine-baselayout:alpine_baselayout_data:3.2.0-r23:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:alpine_baselayout:alpine-baselayout-data:3.2.0-r23:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:alpine_baselayout:alpine_baselayout_data:3.2.0-r23:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:alpine:alpine-baselayout-data:3.2.0-r23:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:alpine:alpine_baselayout_data:3.2.0-r23:*:*:*:*:*:*:*",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "alpine-keys",
|
||||||
|
Version: "2.4-r1",
|
||||||
|
Type: "apk",
|
||||||
|
Locations: source.NewLocationSet(source.NewLocation("sbom.syft.json")),
|
||||||
|
Licenses: []string{"MIT"},
|
||||||
|
FoundBy: "sbom-cataloger",
|
||||||
|
PURL: "pkg:alpine/alpine-keys@2.4-r1?arch=x86_64&upstream=alpine-keys&distro=alpine-3.16.3",
|
||||||
|
CPEs: mustCPEs(
|
||||||
|
"cpe:2.3:a:alpine-keys:alpine-keys:2.4-r1:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:alpine-keys:alpine_keys:2.4-r1:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:alpine_keys:alpine-keys:2.4-r1:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:alpine_keys:alpine_keys:2.4-r1:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:alpine:alpine-keys:2.4-r1:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:alpine:alpine_keys:2.4-r1:*:*:*:*:*:*:*",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "apk-tools",
|
||||||
|
Version: "2.12.9-r3",
|
||||||
|
Type: "apk",
|
||||||
|
Locations: source.NewLocationSet(source.NewLocation("sbom.syft.json")),
|
||||||
|
Licenses: []string{"GPL-2.0-only"},
|
||||||
|
FoundBy: "sbom-cataloger",
|
||||||
|
PURL: "pkg:alpine/apk-tools@2.12.9-r3?arch=x86_64&upstream=apk-tools&distro=alpine-3.16.3",
|
||||||
|
CPEs: mustCPEs(
|
||||||
|
"cpe:2.3:a:apk-tools:apk-tools:2.12.9-r3:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:apk-tools:apk_tools:2.12.9-r3:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:apk_tools:apk-tools:2.12.9-r3:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:apk_tools:apk_tools:2.12.9-r3:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:apk:apk-tools:2.12.9-r3:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:apk:apk_tools:2.12.9-r3:*:*:*:*:*:*:*",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "busybox",
|
||||||
|
Version: "1.35.0-r17",
|
||||||
|
Type: "apk",
|
||||||
|
Locations: source.NewLocationSet(source.NewLocation("sbom.syft.json")),
|
||||||
|
Licenses: []string{"GPL-2.0-only"},
|
||||||
|
FoundBy: "sbom-cataloger",
|
||||||
|
PURL: "pkg:alpine/busybox@1.35.0-r17?arch=x86_64&upstream=busybox&distro=alpine-3.16.3",
|
||||||
|
CPEs: mustCPEs(
|
||||||
|
"cpe:2.3:a:busybox:busybox:1.35.0-r17:*:*:*:*:*:*:*",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "ca-certificates-bundle",
|
||||||
|
Version: "20220614-r0",
|
||||||
|
Type: "apk",
|
||||||
|
Locations: source.NewLocationSet(source.NewLocation("sbom.syft.json")),
|
||||||
|
Licenses: []string{"MPL-2.0", "AND", "MIT"},
|
||||||
|
FoundBy: "sbom-cataloger",
|
||||||
|
PURL: "pkg:alpine/ca-certificates-bundle@20220614-r0?arch=x86_64&upstream=ca-certificates&distro=alpine-3.16.3",
|
||||||
|
CPEs: mustCPEs(
|
||||||
|
"cpe:2.3:a:ca-certificates-bundle:ca-certificates-bundle:20220614-r0:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:ca-certificates-bundle:ca_certificates_bundle:20220614-r0:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:ca_certificates_bundle:ca-certificates-bundle:20220614-r0:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:ca_certificates_bundle:ca_certificates_bundle:20220614-r0:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:ca-certificates:ca-certificates-bundle:20220614-r0:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:ca-certificates:ca_certificates_bundle:20220614-r0:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:ca_certificates:ca-certificates-bundle:20220614-r0:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:ca_certificates:ca_certificates_bundle:20220614-r0:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:ca:ca-certificates-bundle:20220614-r0:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:ca:ca_certificates_bundle:20220614-r0:*:*:*:*:*:*:*",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "libc-utils",
|
||||||
|
Version: "0.7.2-r3",
|
||||||
|
Type: "apk",
|
||||||
|
Locations: source.NewLocationSet(source.NewLocation("sbom.syft.json")),
|
||||||
|
Licenses: []string{"BSD-2-Clause", "AND", "BSD-3-Clause"},
|
||||||
|
FoundBy: "sbom-cataloger",
|
||||||
|
PURL: "pkg:alpine/libc-utils@0.7.2-r3?arch=x86_64&upstream=libc-dev&distro=alpine-3.16.3",
|
||||||
|
CPEs: mustCPEs(
|
||||||
|
"cpe:2.3:a:libc-utils:libc-utils:0.7.2-r3:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:libc-utils:libc_utils:0.7.2-r3:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:libc_utils:libc-utils:0.7.2-r3:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:libc_utils:libc_utils:0.7.2-r3:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:libc:libc-utils:0.7.2-r3:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:libc:libc_utils:0.7.2-r3:*:*:*:*:*:*:*",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "libcrypto1.1",
|
||||||
|
Version: "1.1.1s-r0",
|
||||||
|
Type: "apk",
|
||||||
|
Locations: source.NewLocationSet(source.NewLocation("sbom.syft.json")),
|
||||||
|
Licenses: []string{"OpenSSL"},
|
||||||
|
FoundBy: "sbom-cataloger",
|
||||||
|
PURL: "pkg:alpine/libcrypto1.1@1.1.1s-r0?arch=x86_64&upstream=openssl&distro=alpine-3.16.3",
|
||||||
|
CPEs: mustCPEs(
|
||||||
|
"cpe:2.3:a:libcrypto1.1:libcrypto1.1:1.1.1s-r0:*:*:*:*:*:*:*",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "libssl1.1",
|
||||||
|
Version: "1.1.1s-r0",
|
||||||
|
Type: "apk",
|
||||||
|
Locations: source.NewLocationSet(source.NewLocation("sbom.syft.json")),
|
||||||
|
Licenses: []string{"OpenSSL"},
|
||||||
|
FoundBy: "sbom-cataloger",
|
||||||
|
PURL: "pkg:alpine/libssl1.1@1.1.1s-r0?arch=x86_64&upstream=openssl&distro=alpine-3.16.3",
|
||||||
|
CPEs: mustCPEs(
|
||||||
|
"cpe:2.3:a:libssl1.1:libssl1.1:1.1.1s-r0:*:*:*:*:*:*:*",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "musl",
|
||||||
|
Version: "1.2.3-r1",
|
||||||
|
Type: "apk",
|
||||||
|
Locations: source.NewLocationSet(source.NewLocation("sbom.syft.json")),
|
||||||
|
Licenses: []string{"MIT"},
|
||||||
|
FoundBy: "sbom-cataloger",
|
||||||
|
PURL: "pkg:alpine/musl@1.2.3-r1?arch=x86_64&upstream=musl&distro=alpine-3.16.3",
|
||||||
|
CPEs: mustCPEs(
|
||||||
|
"cpe:2.3:a:musl:musl:1.2.3-r1:*:*:*:*:*:*:*",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "musl-utils",
|
||||||
|
Version: "1.2.3-r1",
|
||||||
|
Type: "apk",
|
||||||
|
Locations: source.NewLocationSet(source.NewLocation("sbom.syft.json")),
|
||||||
|
Licenses: []string{"MIT", "BSD", "GPL2+"},
|
||||||
|
FoundBy: "sbom-cataloger",
|
||||||
|
PURL: "pkg:alpine/musl-utils@1.2.3-r1?arch=x86_64&upstream=musl&distro=alpine-3.16.3",
|
||||||
|
CPEs: mustCPEs(
|
||||||
|
"cpe:2.3:a:musl-utils:musl-utils:1.2.3-r1:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:musl-utils:musl_utils:1.2.3-r1:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:musl_utils:musl-utils:1.2.3-r1:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:musl_utils:musl_utils:1.2.3-r1:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:musl:musl-utils:1.2.3-r1:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:musl:musl_utils:1.2.3-r1:*:*:*:*:*:*:*",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "scanelf",
|
||||||
|
Version: "1.3.4-r0",
|
||||||
|
Type: "apk",
|
||||||
|
Locations: source.NewLocationSet(source.NewLocation("sbom.syft.json")),
|
||||||
|
Licenses: []string{"GPL-2.0-only"},
|
||||||
|
FoundBy: "sbom-cataloger",
|
||||||
|
PURL: "pkg:alpine/scanelf@1.3.4-r0?arch=x86_64&upstream=pax-utils&distro=alpine-3.16.3",
|
||||||
|
CPEs: mustCPEs(
|
||||||
|
"cpe:2.3:a:scanelf:scanelf:1.3.4-r0:*:*:*:*:*:*:*",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "ssl_client",
|
||||||
|
Version: "1.35.0-r17",
|
||||||
|
Type: "apk",
|
||||||
|
Locations: source.NewLocationSet(source.NewLocation("sbom.syft.json")),
|
||||||
|
Licenses: []string{"GPL-2.0-only"},
|
||||||
|
FoundBy: "sbom-cataloger",
|
||||||
|
PURL: "pkg:alpine/ssl_client@1.35.0-r17?arch=x86_64&upstream=busybox&distro=alpine-3.16.3",
|
||||||
|
CPEs: mustCPEs(
|
||||||
|
"cpe:2.3:a:ssl-client:ssl-client:1.35.0-r17:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:ssl-client:ssl_client:1.35.0-r17:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:ssl_client:ssl-client:1.35.0-r17:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:ssl_client:ssl_client:1.35.0-r17:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:ssl:ssl-client:1.35.0-r17:*:*:*:*:*:*:*",
|
||||||
|
"cpe:2.3:a:ssl:ssl_client:1.35.0-r17:*:*:*:*:*:*:*",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "zlib",
|
||||||
|
Version: "1.2.12-r3",
|
||||||
|
Type: "apk",
|
||||||
|
Locations: source.NewLocationSet(source.NewLocation("sbom.syft.json")),
|
||||||
|
Licenses: []string{"Zlib"},
|
||||||
|
FoundBy: "sbom-cataloger",
|
||||||
|
PURL: "pkg:alpine/zlib@1.2.12-r3?arch=x86_64&upstream=zlib&distro=alpine-3.16.3",
|
||||||
|
CPEs: mustCPEs(
|
||||||
|
"cpe:2.3:a:zlib:zlib:1.2.12-r3:*:*:*:*:*:*:*",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var expectedRelationships []artifact.Relationship
|
||||||
|
|
||||||
|
for _, p := range expectedPkgs {
|
||||||
|
expectedRelationships = append(expectedRelationships, artifact.Relationship{
|
||||||
|
From: p,
|
||||||
|
To: source.Coordinates{
|
||||||
|
RealPath: "sbom.syft.json",
|
||||||
|
},
|
||||||
|
Type: artifact.DescribedByRelationship,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
format sbom.Format
|
||||||
|
fixture string
|
||||||
|
wantPkgs []pkg.Package
|
||||||
|
wantRelationships []artifact.Relationship
|
||||||
|
wantErr require.ErrorAssertionFunc
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "parse syft JSON",
|
||||||
|
format: syftjson.Format(),
|
||||||
|
fixture: "test-fixtures/alpine/syft-json",
|
||||||
|
wantPkgs: expectedPkgs,
|
||||||
|
wantRelationships: expectedRelationships,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
pkgtest.NewCatalogTester().
|
||||||
|
FromDirectory(t, tt.fixture).
|
||||||
|
IgnorePackageFields("Metadata", "MetadataType").
|
||||||
|
Expects(tt.wantPkgs, tt.wantRelationships).
|
||||||
|
TestCataloger(t, NewSBOMCataloger())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
34
test/integration/sbom_cataloger_test.go
Normal file
34
test/integration/sbom_cataloger_test.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
"github.com/anchore/syft/syft/source"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSbomCataloger(t *testing.T) {
|
||||||
|
// The image contains a go.mod file with 2 dependencies and an spdx json sbom.
|
||||||
|
// The go.mod file contains 2 dependencies, and the sbom includes a go dependency
|
||||||
|
// that overlaps with the go.mod
|
||||||
|
sbom, _ := catalogFixtureImage(t, "image-sbom-cataloger", source.SquashedScope, []string{"all"})
|
||||||
|
|
||||||
|
expectedSbomCatalogerPkgs := 1
|
||||||
|
expectedGoModCatalogerPkgs := 2
|
||||||
|
actualSbomPkgs := 0
|
||||||
|
actualGoModPkgs := 0
|
||||||
|
for pkg := range sbom.Artifacts.PackageCatalog.Enumerate(pkg.GoModulePkg) {
|
||||||
|
if pkg.FoundBy == "go-mod-file-cataloger" {
|
||||||
|
actualGoModPkgs += 1
|
||||||
|
} else if pkg.FoundBy == "sbom-cataloger" {
|
||||||
|
actualSbomPkgs += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if actualGoModPkgs != expectedGoModCatalogerPkgs {
|
||||||
|
t.Errorf("unexpected number of packages from go mod cataloger: %d != %d", expectedGoModCatalogerPkgs, actualGoModPkgs)
|
||||||
|
}
|
||||||
|
if actualSbomPkgs != expectedSbomCatalogerPkgs {
|
||||||
|
t.Errorf("unexpected number of packages from sbom cataloger: %d != %d", expectedSbomCatalogerPkgs, actualSbomPkgs)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
FROM scratch
|
||||||
|
COPY go.mod /
|
||||||
|
COPY test.spdx.json /
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
module github.com/anchore/syft
|
||||||
|
|
||||||
|
go 1.14
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/anchore/packageurl-go v0.1.1-0.20220428202044-a072fa3cb6d7
|
||||||
|
github.com/bmatcuk/doublestar v1.3.1
|
||||||
|
)
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"SPDXID": "SPDXRef-DOCUMENT",
|
||||||
|
"name": "test/integration/test-fixtures/image-sbom-cataloger",
|
||||||
|
"spdxVersion": "SPDX-2.2",
|
||||||
|
"creationInfo": {
|
||||||
|
"created": "2022-08-18T05:23:38.066146511Z",
|
||||||
|
"creators": [
|
||||||
|
"Organization: Anchore, Inc",
|
||||||
|
"Tool: syft-0.53.0"
|
||||||
|
],
|
||||||
|
"licenseListVersion": "3.17"
|
||||||
|
},
|
||||||
|
"dataLicense": "CC0-1.0",
|
||||||
|
"documentNamespace": "https://anchore.com/syft/dir/test/integration/test-fixtures/image-sbom-cataloger-057e7aa2-332c-4b50-bbd1-a17dd62ddce4",
|
||||||
|
"packages": [
|
||||||
|
{
|
||||||
|
"SPDXID": "SPDXRef-45209ca0cdcbafa",
|
||||||
|
"name": "github.com/bmatcuk/doublestar",
|
||||||
|
"licenseConcluded": "NONE",
|
||||||
|
"downloadLocation": "NOASSERTION",
|
||||||
|
"externalRefs": [
|
||||||
|
{
|
||||||
|
"referenceCategory": "SECURITY",
|
||||||
|
"referenceLocator": "cpe:2.3:a:bmatcuk:doublestar:v1.3.1:*:*:*:*:*:*:*",
|
||||||
|
"referenceType": "cpe23Type"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"referenceCategory": "PACKAGE_MANAGER",
|
||||||
|
"referenceLocator": "pkg:golang/github.com/bmatcuk/doublestar@v1.3.1",
|
||||||
|
"referenceType": "purl"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"filesAnalyzed": false,
|
||||||
|
"licenseDeclared": "NONE",
|
||||||
|
"sourceInfo": "acquired package info from go module information: go.mod",
|
||||||
|
"versionInfo": "v1.3.1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user