mirror of
https://github.com/anchore/syft.git
synced 2025-11-18 00:43:20 +01:00
feat: report unknowns in sbom (#2998)
Signed-off-by: Keith Zantow <kzantow@gmail.com> Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> Co-authored-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
parent
4d7ed9f749
commit
ccbee94b87
@ -53,6 +53,9 @@ type Catalog struct {
|
|||||||
Platform string `yaml:"platform" json:"platform" mapstructure:"platform"`
|
Platform string `yaml:"platform" json:"platform" mapstructure:"platform"`
|
||||||
Source sourceConfig `yaml:"source" json:"source" mapstructure:"source"`
|
Source sourceConfig `yaml:"source" json:"source" mapstructure:"source"`
|
||||||
Exclusions []string `yaml:"exclude" json:"exclude" mapstructure:"exclude"`
|
Exclusions []string `yaml:"exclude" json:"exclude" mapstructure:"exclude"`
|
||||||
|
|
||||||
|
// configuration for inclusion of unknown information within elements
|
||||||
|
Unknowns unknownsConfig `yaml:"unknowns" mapstructure:"unknowns"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ interface {
|
var _ interface {
|
||||||
@ -71,6 +74,7 @@ func DefaultCatalog() Catalog {
|
|||||||
Java: defaultJavaConfig(),
|
Java: defaultJavaConfig(),
|
||||||
File: defaultFileConfig(),
|
File: defaultFileConfig(),
|
||||||
Relationships: defaultRelationshipsConfig(),
|
Relationships: defaultRelationshipsConfig(),
|
||||||
|
Unknowns: defaultUnknowns(),
|
||||||
Source: defaultSourceConfig(),
|
Source: defaultSourceConfig(),
|
||||||
Parallelism: 1,
|
Parallelism: 1,
|
||||||
}
|
}
|
||||||
@ -82,6 +86,7 @@ func (cfg Catalog) ToSBOMConfig(id clio.Identification) *syft.CreateSBOMConfig {
|
|||||||
WithParallelism(cfg.Parallelism).
|
WithParallelism(cfg.Parallelism).
|
||||||
WithRelationshipsConfig(cfg.ToRelationshipsConfig()).
|
WithRelationshipsConfig(cfg.ToRelationshipsConfig()).
|
||||||
WithComplianceConfig(cfg.ToComplianceConfig()).
|
WithComplianceConfig(cfg.ToComplianceConfig()).
|
||||||
|
WithUnknownsConfig(cfg.ToUnknownsConfig()).
|
||||||
WithSearchConfig(cfg.ToSearchConfig()).
|
WithSearchConfig(cfg.ToSearchConfig()).
|
||||||
WithPackagesConfig(cfg.ToPackagesConfig()).
|
WithPackagesConfig(cfg.ToPackagesConfig()).
|
||||||
WithFilesConfig(cfg.ToFilesConfig()).
|
WithFilesConfig(cfg.ToFilesConfig()).
|
||||||
@ -114,6 +119,13 @@ func (cfg Catalog) ToComplianceConfig() cataloging.ComplianceConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cfg Catalog) ToUnknownsConfig() cataloging.UnknownsConfig {
|
||||||
|
return cataloging.UnknownsConfig{
|
||||||
|
IncludeExecutablesWithoutPackages: cfg.Unknowns.ExecutablesWithoutPackages,
|
||||||
|
IncludeUnexpandedArchives: cfg.Unknowns.UnexpandedArchives,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (cfg Catalog) ToFilesConfig() filecataloging.Config {
|
func (cfg Catalog) ToFilesConfig() filecataloging.Config {
|
||||||
hashers, err := intFile.Hashers(cfg.File.Metadata.Digests...)
|
hashers, err := intFile.Hashers(cfg.File.Metadata.Digests...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
31
cmd/syft/internal/options/unknowns.go
Normal file
31
cmd/syft/internal/options/unknowns.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package options
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/anchore/clio"
|
||||||
|
"github.com/anchore/syft/syft/cataloging"
|
||||||
|
)
|
||||||
|
|
||||||
|
type unknownsConfig struct {
|
||||||
|
RemoveWhenPackagesDefined bool `json:"remove-when-packages-defined" yaml:"remove-when-packages-defined" mapstructure:"remove-when-packages-defined"`
|
||||||
|
ExecutablesWithoutPackages bool `json:"executables-without-packages" yaml:"executables-without-packages" mapstructure:"executables-without-packages"`
|
||||||
|
UnexpandedArchives bool `json:"unexpanded-archives" yaml:"unexpanded-archives" mapstructure:"unexpanded-archives"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ interface {
|
||||||
|
clio.FieldDescriber
|
||||||
|
} = (*unknownsConfig)(nil)
|
||||||
|
|
||||||
|
func (o *unknownsConfig) DescribeFields(descriptions clio.FieldDescriptionSet) {
|
||||||
|
descriptions.Add(&o.RemoveWhenPackagesDefined, `remove unknown errors on files with discovered packages`)
|
||||||
|
descriptions.Add(&o.ExecutablesWithoutPackages, `include executables without any identified packages`)
|
||||||
|
descriptions.Add(&o.UnexpandedArchives, `include archives which were not expanded and searched`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultUnknowns() unknownsConfig {
|
||||||
|
def := cataloging.DefaultUnknownsConfig()
|
||||||
|
return unknownsConfig{
|
||||||
|
RemoveWhenPackagesDefined: def.RemoveWhenPackagesDefined,
|
||||||
|
ExecutablesWithoutPackages: def.IncludeExecutablesWithoutPackages,
|
||||||
|
UnexpandedArchives: def.IncludeUnexpandedArchives,
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,5 +3,5 @@ package internal
|
|||||||
const (
|
const (
|
||||||
// JSONSchemaVersion is the current schema version output by the JSON encoder
|
// JSONSchemaVersion is the current schema version output by the JSON encoder
|
||||||
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
|
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
|
||||||
JSONSchemaVersion = "16.0.17"
|
JSONSchemaVersion = "16.0.18"
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
package file
|
package file
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
@ -19,12 +18,13 @@ func NewZipFileManifest(archivePath string) (ZipFileManifest, error) {
|
|||||||
zipReader, err := OpenZip(archivePath)
|
zipReader, err := OpenZip(archivePath)
|
||||||
manifest := make(ZipFileManifest)
|
manifest := make(ZipFileManifest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return manifest, fmt.Errorf("unable to open zip archive (%s): %w", archivePath, err)
|
log.Debugf("unable to open zip archive (%s): %v", archivePath, err)
|
||||||
|
return manifest, err
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
err = zipReader.Close()
|
err = zipReader.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("unable to close zip archive (%s): %+v", archivePath, err)
|
log.Debugf("unable to close zip archive (%s): %+v", archivePath, err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,8 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/internal/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// directoryEndLen, readByf, directoryEnd, and findSignatureInBlock were copied from the golang stdlib, specifically:
|
// directoryEndLen, readByf, directoryEnd, and findSignatureInBlock were copied from the golang stdlib, specifically:
|
||||||
@ -46,7 +48,8 @@ func OpenZip(filepath string) (*ZipReadCloser, error) {
|
|||||||
// need to find the start of the archive and keep track of this offset.
|
// need to find the start of the archive and keep track of this offset.
|
||||||
offset, err := findArchiveStartOffset(f, fi.Size())
|
offset, err := findArchiveStartOffset(f, fi.Size())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot find beginning of zip archive=%q : %w", filepath, err)
|
log.Debugf("cannot find beginning of zip archive=%q : %v", filepath, err)
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := f.Seek(0, io.SeekStart); err != nil {
|
if _, err := f.Seek(0, io.SeekStart); err != nil {
|
||||||
@ -62,7 +65,8 @@ func OpenZip(filepath string) (*ZipReadCloser, error) {
|
|||||||
|
|
||||||
r, err := zip.NewReader(io.NewSectionReader(f, offset64, size), size)
|
r, err := zip.NewReader(io.NewSectionReader(f, offset64, size), size)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to open ZipReadCloser @ %q: %w", filepath, err)
|
log.Debugf("unable to open ZipReadCloser @ %q: %v", filepath, err)
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ZipReadCloser{
|
return &ZipReadCloser{
|
||||||
|
|||||||
@ -11,8 +11,10 @@ import (
|
|||||||
|
|
||||||
"github.com/anchore/syft/internal/log"
|
"github.com/anchore/syft/internal/log"
|
||||||
"github.com/anchore/syft/internal/sbomsync"
|
"github.com/anchore/syft/internal/sbomsync"
|
||||||
|
"github.com/anchore/syft/internal/unknown"
|
||||||
"github.com/anchore/syft/syft/event/monitor"
|
"github.com/anchore/syft/syft/event/monitor"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
|
"github.com/anchore/syft/syft/sbom"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Executor struct {
|
type Executor struct {
|
||||||
@ -35,6 +37,12 @@ func NewTaskExecutor(tasks []Task, numWorkers int) *Executor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Executor) Execute(ctx context.Context, resolver file.Resolver, s sbomsync.Builder, prog *monitor.CatalogerTaskProgress) error {
|
func (p *Executor) Execute(ctx context.Context, resolver file.Resolver, s sbomsync.Builder, prog *monitor.CatalogerTaskProgress) error {
|
||||||
|
var lock sync.Mutex
|
||||||
|
withLock := func(fn func()) {
|
||||||
|
lock.Lock()
|
||||||
|
defer lock.Unlock()
|
||||||
|
fn()
|
||||||
|
}
|
||||||
var errs error
|
var errs error
|
||||||
wg := &sync.WaitGroup{}
|
wg := &sync.WaitGroup{}
|
||||||
for i := 0; i < p.numWorkers; i++ {
|
for i := 0; i < p.numWorkers; i++ {
|
||||||
@ -48,9 +56,16 @@ func (p *Executor) Execute(ctx context.Context, resolver file.Resolver, s sbomsy
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := runTaskSafely(ctx, tsk, resolver, s); err != nil {
|
err := runTaskSafely(ctx, tsk, resolver, s)
|
||||||
|
unknowns, err := unknown.ExtractCoordinateErrors(err)
|
||||||
|
if len(unknowns) > 0 {
|
||||||
|
appendUnknowns(s, tsk.Name(), unknowns)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
withLock(func() {
|
||||||
errs = multierror.Append(errs, fmt.Errorf("failed to run task: %w", err))
|
errs = multierror.Append(errs, fmt.Errorf("failed to run task: %w", err))
|
||||||
prog.SetError(err)
|
prog.SetError(err)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
prog.Increment()
|
prog.Increment()
|
||||||
}
|
}
|
||||||
@ -62,6 +77,19 @@ func (p *Executor) Execute(ctx context.Context, resolver file.Resolver, s sbomsy
|
|||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func appendUnknowns(builder sbomsync.Builder, taskName string, unknowns []unknown.CoordinateError) {
|
||||||
|
if accessor, ok := builder.(sbomsync.Accessor); ok {
|
||||||
|
accessor.WriteToSBOM(func(sb *sbom.SBOM) {
|
||||||
|
for _, u := range unknowns {
|
||||||
|
if sb.Artifacts.Unknowns == nil {
|
||||||
|
sb.Artifacts.Unknowns = map[file.Coordinates][]string{}
|
||||||
|
}
|
||||||
|
sb.Artifacts.Unknowns[u.Coordinates] = append(sb.Artifacts.Unknowns[u.Coordinates], formatUnknown(u.Reason.Error(), taskName))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func runTaskSafely(ctx context.Context, t Task, resolver file.Resolver, s sbomsync.Builder) (err error) {
|
func runTaskSafely(ctx context.Context, t Task, resolver file.Resolver, s sbomsync.Builder) (err error) {
|
||||||
// handle individual cataloger panics
|
// handle individual cataloger panics
|
||||||
defer func() {
|
defer func() {
|
||||||
|
|||||||
@ -3,7 +3,6 @@ package task
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto"
|
"crypto"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/anchore/syft/internal/sbomsync"
|
"github.com/anchore/syft/internal/sbomsync"
|
||||||
"github.com/anchore/syft/syft/artifact"
|
"github.com/anchore/syft/syft/artifact"
|
||||||
@ -32,15 +31,12 @@ func NewFileDigestCatalogerTask(selection file.Selection, hashers ...crypto.Hash
|
|||||||
}
|
}
|
||||||
|
|
||||||
result, err := digestsCataloger.Catalog(ctx, resolver, coordinates...)
|
result, err := digestsCataloger.Catalog(ctx, resolver, coordinates...)
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to catalog file digests: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
accessor.WriteToSBOM(func(sbom *sbom.SBOM) {
|
accessor.WriteToSBOM(func(sbom *sbom.SBOM) {
|
||||||
sbom.Artifacts.FileDigests = result
|
sbom.Artifacts.FileDigests = result
|
||||||
})
|
})
|
||||||
|
|
||||||
return nil
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewTask("file-digest-cataloger", fn)
|
return NewTask("file-digest-cataloger", fn)
|
||||||
@ -62,15 +58,12 @@ func NewFileMetadataCatalogerTask(selection file.Selection) Task {
|
|||||||
}
|
}
|
||||||
|
|
||||||
result, err := metadataCataloger.Catalog(ctx, resolver, coordinates...)
|
result, err := metadataCataloger.Catalog(ctx, resolver, coordinates...)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
accessor.WriteToSBOM(func(sbom *sbom.SBOM) {
|
accessor.WriteToSBOM(func(sbom *sbom.SBOM) {
|
||||||
sbom.Artifacts.FileMetadata = result
|
sbom.Artifacts.FileMetadata = result
|
||||||
})
|
})
|
||||||
|
|
||||||
return nil
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewTask("file-metadata-cataloger", fn)
|
return NewTask("file-metadata-cataloger", fn)
|
||||||
@ -87,15 +80,12 @@ func NewFileContentCatalogerTask(cfg filecontent.Config) Task {
|
|||||||
accessor := builder.(sbomsync.Accessor)
|
accessor := builder.(sbomsync.Accessor)
|
||||||
|
|
||||||
result, err := cat.Catalog(ctx, resolver)
|
result, err := cat.Catalog(ctx, resolver)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
accessor.WriteToSBOM(func(sbom *sbom.SBOM) {
|
accessor.WriteToSBOM(func(sbom *sbom.SBOM) {
|
||||||
sbom.Artifacts.FileContents = result
|
sbom.Artifacts.FileContents = result
|
||||||
})
|
})
|
||||||
|
|
||||||
return nil
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewTask("file-content-cataloger", fn)
|
return NewTask("file-content-cataloger", fn)
|
||||||
@ -112,15 +102,12 @@ func NewExecutableCatalogerTask(selection file.Selection, cfg executable.Config)
|
|||||||
accessor := builder.(sbomsync.Accessor)
|
accessor := builder.(sbomsync.Accessor)
|
||||||
|
|
||||||
result, err := cat.Catalog(resolver)
|
result, err := cat.Catalog(resolver)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
accessor.WriteToSBOM(func(sbom *sbom.SBOM) {
|
accessor.WriteToSBOM(func(sbom *sbom.SBOM) {
|
||||||
sbom.Artifacts.Executables = result
|
sbom.Artifacts.Executables = result
|
||||||
})
|
})
|
||||||
|
|
||||||
return nil
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewTask("file-executable-cataloger", fn)
|
return NewTask("file-executable-cataloger", fn)
|
||||||
|
|||||||
@ -103,9 +103,6 @@ func NewPackageTask(cfg CatalogingFactoryConfig, c pkg.Cataloger, tags ...string
|
|||||||
t := bus.StartCatalogerTask(info, -1, "")
|
t := bus.StartCatalogerTask(info, -1, "")
|
||||||
|
|
||||||
pkgs, relationships, err := c.Catalog(ctx, resolver)
|
pkgs, relationships, err := c.Catalog(ctx, resolver)
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to catalog packages with %q: %w", catalogerName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.WithFields("cataloger", catalogerName).Debugf("discovered %d packages", len(pkgs))
|
log.WithFields("cataloger", catalogerName).Debugf("discovered %d packages", len(pkgs))
|
||||||
|
|
||||||
@ -120,7 +117,7 @@ func NewPackageTask(cfg CatalogingFactoryConfig, c pkg.Cataloger, tags ...string
|
|||||||
t.SetCompleted()
|
t.SetCompleted()
|
||||||
log.WithFields("name", catalogerName).Trace("package cataloger completed")
|
log.WithFields("name", catalogerName).Trace("package cataloger completed")
|
||||||
|
|
||||||
return nil
|
return err
|
||||||
}
|
}
|
||||||
tags = append(tags, pkgcataloging.PackageTag)
|
tags = append(tags, pkgcataloging.PackageTag)
|
||||||
|
|
||||||
|
|||||||
114
internal/task/unknowns_tasks.go
Normal file
114
internal/task/unknowns_tasks.go
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
package task
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mholt/archiver/v3"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/internal/log"
|
||||||
|
"github.com/anchore/syft/internal/sbomsync"
|
||||||
|
"github.com/anchore/syft/syft/cataloging"
|
||||||
|
"github.com/anchore/syft/syft/file"
|
||||||
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
"github.com/anchore/syft/syft/sbom"
|
||||||
|
)
|
||||||
|
|
||||||
|
const unknownsLabelerTaskName = "unknowns-labeler"
|
||||||
|
|
||||||
|
func NewUnknownsLabelerTask(cfg cataloging.UnknownsConfig) Task {
|
||||||
|
return NewTask(unknownsLabelerTaskName, unknownsLabelerTask{cfg}.processUnknowns)
|
||||||
|
}
|
||||||
|
|
||||||
|
type unknownsLabelerTask struct {
|
||||||
|
cataloging.UnknownsConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// processUnknowns removes unknown entries that have valid packages reported for the locations
|
||||||
|
func (c unknownsLabelerTask) processUnknowns(_ context.Context, resolver file.Resolver, builder sbomsync.Builder) error {
|
||||||
|
accessor := builder.(sbomsync.Accessor)
|
||||||
|
accessor.WriteToSBOM(func(s *sbom.SBOM) {
|
||||||
|
c.finalize(resolver, s)
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c unknownsLabelerTask) finalize(resolver file.Resolver, s *sbom.SBOM) {
|
||||||
|
hasPackageReference := coordinateReferenceLookup(resolver, s)
|
||||||
|
|
||||||
|
if c.RemoveWhenPackagesDefined {
|
||||||
|
for coords := range s.Artifacts.Unknowns {
|
||||||
|
if !hasPackageReference(coords) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
delete(s.Artifacts.Unknowns, coords)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Artifacts.Unknowns == nil {
|
||||||
|
s.Artifacts.Unknowns = map[file.Coordinates][]string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.IncludeExecutablesWithoutPackages {
|
||||||
|
for coords := range s.Artifacts.Executables {
|
||||||
|
if !hasPackageReference(coords) {
|
||||||
|
s.Artifacts.Unknowns[coords] = append(s.Artifacts.Unknowns[coords], formatUnknown("no package identified in executable file", unknownsLabelerTaskName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.IncludeUnexpandedArchives {
|
||||||
|
for coords := range s.Artifacts.FileMetadata {
|
||||||
|
unarchiver, notArchiveErr := archiver.ByExtension(coords.RealPath)
|
||||||
|
if unarchiver != nil && notArchiveErr == nil && !hasPackageReference(coords) {
|
||||||
|
s.Artifacts.Unknowns[coords] = append(s.Artifacts.Unknowns[coords], "archive not cataloged")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatUnknown(err string, task ...string) string {
|
||||||
|
return strings.Join(task, "/") + ": " + err
|
||||||
|
}
|
||||||
|
|
||||||
|
func coordinateReferenceLookup(resolver file.Resolver, s *sbom.SBOM) func(coords file.Coordinates) bool {
|
||||||
|
allPackageCoords := file.NewCoordinateSet()
|
||||||
|
|
||||||
|
// include all directly included locations that result in packages
|
||||||
|
for p := range s.Artifacts.Packages.Enumerate() {
|
||||||
|
allPackageCoords.Add(p.Locations.CoordinateSet().ToSlice()...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// include owned files, for example specified by package managers.
|
||||||
|
// relationships for these owned files may be disabled, but we always want to include them
|
||||||
|
for p := range s.Artifacts.Packages.Enumerate() {
|
||||||
|
if f, ok := p.Metadata.(pkg.FileOwner); ok {
|
||||||
|
for _, ownedFilePath := range f.OwnedFiles() {
|
||||||
|
// resolve these owned files, as they may have symlinks
|
||||||
|
// but coordinates we will test against are always absolute paths
|
||||||
|
locations, err := resolver.FilesByPath(ownedFilePath)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("unable to resolve owned file '%s': %v", ownedFilePath, err)
|
||||||
|
}
|
||||||
|
for _, loc := range locations {
|
||||||
|
allPackageCoords.Add(loc.Coordinates)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// include relationships
|
||||||
|
for _, r := range s.Relationships {
|
||||||
|
_, fromPkgOk := r.From.(pkg.Package)
|
||||||
|
fromFile, fromFileOk := r.From.(file.Coordinates)
|
||||||
|
_, toPkgOk := r.To.(pkg.Package)
|
||||||
|
toFile, toFileOk := r.To.(file.Coordinates)
|
||||||
|
if fromPkgOk && toFileOk {
|
||||||
|
allPackageCoords.Add(toFile)
|
||||||
|
} else if fromFileOk && toPkgOk {
|
||||||
|
allPackageCoords.Add(fromFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allPackageCoords.Contains
|
||||||
|
}
|
||||||
201
internal/unknown/coordinate_error.go
Normal file
201
internal/unknown/coordinate_error.go
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
package unknown
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/internal/log"
|
||||||
|
"github.com/anchore/syft/syft/file"
|
||||||
|
)
|
||||||
|
|
||||||
|
type hasCoordinates interface {
|
||||||
|
GetCoordinates() file.Coordinates
|
||||||
|
}
|
||||||
|
|
||||||
|
type CoordinateError struct {
|
||||||
|
Coordinates file.Coordinates
|
||||||
|
Reason error
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ error = (*CoordinateError)(nil)
|
||||||
|
|
||||||
|
func (u *CoordinateError) Error() string {
|
||||||
|
if u.Coordinates.FileSystemID == "" {
|
||||||
|
return fmt.Sprintf("%s: %v", u.Coordinates.RealPath, u.Reason)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s (%s): %v", u.Coordinates.RealPath, u.Coordinates.FileSystemID, u.Reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new CoordinateError unless the reason is a CoordinateError itself, in which case
|
||||||
|
// reason will be returned directly or if reason is nil, nil will be returned
|
||||||
|
func New(coords hasCoordinates, reason error) *CoordinateError {
|
||||||
|
if reason == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
coordinates := coords.GetCoordinates()
|
||||||
|
reasonCoordinateError := &CoordinateError{}
|
||||||
|
if errors.As(reason, &reasonCoordinateError) {
|
||||||
|
// if the reason is already a coordinate error, it is potentially for a different location,
|
||||||
|
// so we do not want to surface this location having an error
|
||||||
|
return reasonCoordinateError
|
||||||
|
}
|
||||||
|
return &CoordinateError{
|
||||||
|
Coordinates: coordinates,
|
||||||
|
Reason: reason,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Newf returns a new CoordinateError with a reason of an error created from given format and args
|
||||||
|
func Newf(coords hasCoordinates, format string, args ...any) *CoordinateError {
|
||||||
|
return New(coords, fmt.Errorf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append returns an error joined to the first error/set of errors, with a new CoordinateError appended to the end
|
||||||
|
func Append(errs error, coords hasCoordinates, reason error) error {
|
||||||
|
return Join(errs, New(coords, reason))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Appendf returns an error joined to the first error/set of errors, with a new CoordinateError appended to the end,
|
||||||
|
// created from the given reason and args
|
||||||
|
func Appendf(errs error, coords hasCoordinates, format string, args ...any) error {
|
||||||
|
return Append(errs, coords, fmt.Errorf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Join joins the provided sets of errors together in a flattened manner, taking into account nested errors created
|
||||||
|
// from other sources, including errors.Join, multierror.Append, and unknown.Join
|
||||||
|
func Join(errs ...error) error {
|
||||||
|
var out []error
|
||||||
|
for _, err := range errs {
|
||||||
|
// append errors, de-duplicated
|
||||||
|
for _, e := range flatten(err) {
|
||||||
|
if containsErr(out, e) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out = append(out, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(out) == 1 {
|
||||||
|
return out[0]
|
||||||
|
}
|
||||||
|
if len(out) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.Join(out...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Joinf joins the provided sets of errors together in a flattened manner, taking into account nested errors created
|
||||||
|
// from other sources, including errors.Join, multierror.Append, and unknown.Join and appending a new error,
|
||||||
|
// created from the format and args provided -- the error is NOT a CoordinateError
|
||||||
|
func Joinf(errs error, format string, args ...any) error {
|
||||||
|
return Join(errs, fmt.Errorf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IfEmptyf returns a new Errorf-formatted error, only when the provided slice is empty or nil when
|
||||||
|
// the slice has entries
|
||||||
|
func IfEmptyf[T any](emptyTest []T, format string, args ...any) error {
|
||||||
|
if len(emptyTest) == 0 {
|
||||||
|
return fmt.Errorf(format, args...)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractCoordinateErrors extracts all coordinate errors returned, and any _additional_ errors in the graph
|
||||||
|
// are encapsulated in the second, error return parameter
|
||||||
|
func ExtractCoordinateErrors(err error) (coordinateErrors []CoordinateError, remainingErrors error) {
|
||||||
|
remainingErrors = visitErrors(err, func(e error) error {
|
||||||
|
if coordinateError, _ := e.(*CoordinateError); coordinateError != nil {
|
||||||
|
coordinateErrors = append(coordinateErrors, *coordinateError)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
})
|
||||||
|
return coordinateErrors, remainingErrors
|
||||||
|
}
|
||||||
|
|
||||||
|
func flatten(errs ...error) []error {
|
||||||
|
var out []error
|
||||||
|
for _, err := range errs {
|
||||||
|
if err == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// turn all errors nested under a coordinate error to individual coordinate errors
|
||||||
|
if e, ok := err.(*CoordinateError); ok {
|
||||||
|
if e == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, r := range flatten(e.Reason) {
|
||||||
|
out = append(out, New(e.Coordinates, r))
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
// from multierror.Append
|
||||||
|
if e, ok := err.(interface{ WrappedErrors() []error }); ok {
|
||||||
|
if e == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out = append(out, flatten(e.WrappedErrors()...)...)
|
||||||
|
} else
|
||||||
|
// from errors.Join
|
||||||
|
if e, ok := err.(interface{ Unwrap() []error }); ok {
|
||||||
|
if e == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out = append(out, flatten(e.Unwrap()...)...)
|
||||||
|
} else {
|
||||||
|
out = append(out, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// containsErr returns true if a duplicate error is found
|
||||||
|
func containsErr(out []error, err error) bool {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
log.Tracef("error comparing errors: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
for _, e := range out {
|
||||||
|
if e == err {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// visitErrors visits every wrapped error. the returned error replaces the provided error, null errors are omitted from
|
||||||
|
// the object graph
|
||||||
|
func visitErrors(err error, fn func(error) error) error {
|
||||||
|
// unwrap errors from errors.Join
|
||||||
|
if errs, ok := err.(interface{ Unwrap() []error }); ok {
|
||||||
|
var out []error
|
||||||
|
for _, e := range errs.Unwrap() {
|
||||||
|
out = append(out, visitErrors(e, fn))
|
||||||
|
}
|
||||||
|
// errors.Join omits nil errors and will return nil if all passed errors are nil
|
||||||
|
return errors.Join(out...)
|
||||||
|
}
|
||||||
|
// unwrap errors from multierror.Append -- these also implement Unwrap() error, so check this first
|
||||||
|
if errs, ok := err.(interface{ WrappedErrors() []error }); ok {
|
||||||
|
var out []error
|
||||||
|
for _, e := range errs.WrappedErrors() {
|
||||||
|
out = append(out, visitErrors(e, fn))
|
||||||
|
}
|
||||||
|
// errors.Join omits nil errors and will return nil if all passed errors are nil
|
||||||
|
return errors.Join(out...)
|
||||||
|
}
|
||||||
|
// unwrap singly wrapped errors
|
||||||
|
if e, ok := err.(interface{ Unwrap() error }); ok {
|
||||||
|
wrapped := e.Unwrap()
|
||||||
|
got := visitErrors(wrapped, fn)
|
||||||
|
if got == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if wrapped.Error() != got.Error() {
|
||||||
|
prefix := strings.TrimSuffix(err.Error(), wrapped.Error())
|
||||||
|
return fmt.Errorf("%s%w", prefix, got)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fn(err)
|
||||||
|
}
|
||||||
236
internal/unknown/coordinate_error_test.go
Normal file
236
internal/unknown/coordinate_error_test.go
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
package unknown
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/file"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_visitErrors(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
in error
|
||||||
|
transform func(error) error
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "return",
|
||||||
|
in: fmt.Errorf("err1"),
|
||||||
|
transform: func(e error) error {
|
||||||
|
return e
|
||||||
|
},
|
||||||
|
expected: "err1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "omit",
|
||||||
|
in: fmt.Errorf("err1"),
|
||||||
|
transform: func(_ error) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
expected: "<nil>",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wrapped return",
|
||||||
|
in: fmt.Errorf("wrapped: %w", fmt.Errorf("err1")),
|
||||||
|
transform: func(e error) error {
|
||||||
|
return e
|
||||||
|
},
|
||||||
|
expected: "wrapped: err1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wrapped omit",
|
||||||
|
in: fmt.Errorf("wrapped: %w", fmt.Errorf("err1")),
|
||||||
|
transform: func(e error) error {
|
||||||
|
if e.Error() == "err1" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
},
|
||||||
|
expected: "<nil>",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "joined return",
|
||||||
|
in: errors.Join(fmt.Errorf("err1"), fmt.Errorf("err2")),
|
||||||
|
transform: func(e error) error {
|
||||||
|
return e
|
||||||
|
},
|
||||||
|
expected: "err1\nerr2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "joined omit",
|
||||||
|
in: errors.Join(fmt.Errorf("err1"), fmt.Errorf("err2")),
|
||||||
|
transform: func(_ error) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
expected: "<nil>",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "joined omit first",
|
||||||
|
in: errors.Join(fmt.Errorf("err1"), fmt.Errorf("err2")),
|
||||||
|
transform: func(e error) error {
|
||||||
|
if e.Error() == "err1" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
},
|
||||||
|
expected: "err2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "joined wrapped return",
|
||||||
|
in: errors.Join(fmt.Errorf("wrapped: %w", fmt.Errorf("err1")), fmt.Errorf("err2")),
|
||||||
|
transform: func(e error) error {
|
||||||
|
return e
|
||||||
|
},
|
||||||
|
expected: "wrapped: err1\nerr2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "joined wrapped omit first",
|
||||||
|
in: errors.Join(fmt.Errorf("wrapped: %w", fmt.Errorf("err1")), fmt.Errorf("err2")),
|
||||||
|
transform: func(e error) error {
|
||||||
|
if e.Error() == "err1" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
},
|
||||||
|
expected: "err2",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
gotErr := visitErrors(test.in, test.transform)
|
||||||
|
got := fmt.Sprintf("%v", gotErr)
|
||||||
|
require.Equal(t, test.expected, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Join(t *testing.T) {
|
||||||
|
err1 := fmt.Errorf("err1")
|
||||||
|
err2 := fmt.Errorf("err2")
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string ``
|
||||||
|
in []error
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "basic",
|
||||||
|
in: []error{fmt.Errorf("err")},
|
||||||
|
expected: "err",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wrapped",
|
||||||
|
in: []error{fmt.Errorf("outer: %w", fmt.Errorf("err"))},
|
||||||
|
expected: "outer: err",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wrapped joined",
|
||||||
|
in: []error{errors.Join(fmt.Errorf("outer: %w", fmt.Errorf("err1")), fmt.Errorf("err2"))},
|
||||||
|
expected: "outer: err1\nerr2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "duplicates",
|
||||||
|
in: []error{err1, err1, err2},
|
||||||
|
expected: "err1\nerr2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nested duplicates",
|
||||||
|
in: []error{errors.Join(err1, err2), err1, err2},
|
||||||
|
expected: "err1\nerr2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nested duplicates coords",
|
||||||
|
in: []error{New(file.NewLocation("l1"), errors.Join(fmt.Errorf("err1"), fmt.Errorf("err2"))), fmt.Errorf("err1"), fmt.Errorf("err2")},
|
||||||
|
expected: "l1: err1\nl1: err2\nerr1\nerr2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all nil",
|
||||||
|
in: []error{nil, nil, nil},
|
||||||
|
expected: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
got := Join(test.in...)
|
||||||
|
if test.expected == "" {
|
||||||
|
require.Nil(t, got)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NotNil(t, got)
|
||||||
|
require.Equal(t, test.expected, got.Error())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_flatten(t *testing.T) {
|
||||||
|
coords := file.Coordinates{
|
||||||
|
RealPath: "real/path",
|
||||||
|
}
|
||||||
|
e1 := fmt.Errorf("e1")
|
||||||
|
e2 := fmt.Errorf("e2")
|
||||||
|
c1 := New(coords, fmt.Errorf("c1"))
|
||||||
|
c2 := New(coords, fmt.Errorf("c2"))
|
||||||
|
tests := []struct {
|
||||||
|
name string ``
|
||||||
|
in error
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "basic",
|
||||||
|
in: errors.Join(e1, e2),
|
||||||
|
expected: "e1//e2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "coords",
|
||||||
|
in: New(coords, e1),
|
||||||
|
expected: "real/path: e1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "coords with joined children",
|
||||||
|
in: New(coords, errors.Join(e1, e2)),
|
||||||
|
expected: "real/path: e1//real/path: e2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "very nested",
|
||||||
|
in: errors.Join(errors.Join(errors.Join(errors.Join(e1, c1), e2), c2), e2),
|
||||||
|
expected: "e1//real/path: c1//e2//real/path: c2//e2",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
toString := func(errs ...error) string {
|
||||||
|
var parts []string
|
||||||
|
for _, e := range errs {
|
||||||
|
parts = append(parts, e.Error())
|
||||||
|
}
|
||||||
|
return strings.Join(parts, "//")
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
got := flatten(test.in)
|
||||||
|
require.NotNil(t, got)
|
||||||
|
require.Equal(t, test.expected, toString(got...))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Append(t *testing.T) {
|
||||||
|
e1 := New(file.NewLocation("l1"), fmt.Errorf("e1"))
|
||||||
|
e2 := Append(e1, file.NewLocation("l2"), fmt.Errorf("e2"))
|
||||||
|
e3 := Appendf(e2, file.NewLocation("l3"), "%s", "e3")
|
||||||
|
require.Equal(t, "l1: e1\nl2: e2\nl3: e3", e3.Error())
|
||||||
|
|
||||||
|
e1 = New(file.NewLocation("l1"), nil)
|
||||||
|
require.Nil(t, e1)
|
||||||
|
e2 = Append(e1, file.NewLocation("l2"), fmt.Errorf("e2"))
|
||||||
|
e3 = Appendf(e2, file.NewLocation("l3"), "%s", "e3")
|
||||||
|
require.Equal(t, "l2: e2\nl3: e3", e3.Error())
|
||||||
|
|
||||||
|
e1 = New(file.NewLocation("l1"), fmt.Errorf("e1"))
|
||||||
|
e2 = Append(e1, file.NewLocation("l2"), nil)
|
||||||
|
e3 = Appendf(e2, file.NewLocation("l3"), "%s", "e3")
|
||||||
|
require.Equal(t, "l1: e1\nl3: e3", e3.Error())
|
||||||
|
}
|
||||||
2727
schema/json/schema-16.0.18.json
Normal file
2727
schema/json/schema-16.0.18.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"$id": "anchore.io/schema/syft/json/16.0.17/document",
|
"$id": "anchore.io/schema/syft/json/16.0.18/document",
|
||||||
"$ref": "#/$defs/Document",
|
"$ref": "#/$defs/Document",
|
||||||
"$defs": {
|
"$defs": {
|
||||||
"AlpmDbEntry": {
|
"AlpmDbEntry": {
|
||||||
@ -777,6 +777,12 @@
|
|||||||
},
|
},
|
||||||
"executable": {
|
"executable": {
|
||||||
"$ref": "#/$defs/Executable"
|
"$ref": "#/$defs/Executable"
|
||||||
|
},
|
||||||
|
"unknowns": {
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
|||||||
15
syft/cataloging/unknowns.go
Normal file
15
syft/cataloging/unknowns.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package cataloging
|
||||||
|
|
||||||
|
type UnknownsConfig struct {
|
||||||
|
RemoveWhenPackagesDefined bool
|
||||||
|
IncludeExecutablesWithoutPackages bool
|
||||||
|
IncludeUnexpandedArchives bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultUnknownsConfig() UnknownsConfig {
|
||||||
|
return UnknownsConfig{
|
||||||
|
RemoveWhenPackagesDefined: true,
|
||||||
|
IncludeExecutablesWithoutPackages: true,
|
||||||
|
IncludeUnexpandedArchives: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -22,6 +22,7 @@ type CreateSBOMConfig struct {
|
|||||||
Compliance cataloging.ComplianceConfig
|
Compliance cataloging.ComplianceConfig
|
||||||
Search cataloging.SearchConfig
|
Search cataloging.SearchConfig
|
||||||
Relationships cataloging.RelationshipsConfig
|
Relationships cataloging.RelationshipsConfig
|
||||||
|
Unknowns cataloging.UnknownsConfig
|
||||||
DataGeneration cataloging.DataGenerationConfig
|
DataGeneration cataloging.DataGenerationConfig
|
||||||
Packages pkgcataloging.Config
|
Packages pkgcataloging.Config
|
||||||
Files filecataloging.Config
|
Files filecataloging.Config
|
||||||
@ -113,6 +114,12 @@ func (c *CreateSBOMConfig) WithRelationshipsConfig(cfg cataloging.RelationshipsC
|
|||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithUnknownsConfig allows for defining the specific behavior dealing with unknowns
|
||||||
|
func (c *CreateSBOMConfig) WithUnknownsConfig(cfg cataloging.UnknownsConfig) *CreateSBOMConfig {
|
||||||
|
c.Unknowns = cfg
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
// WithDataGenerationConfig allows for defining what data elements that cannot be discovered from the underlying
|
// WithDataGenerationConfig allows for defining what data elements that cannot be discovered from the underlying
|
||||||
// target being scanned that should be generated after package creation.
|
// target being scanned that should be generated after package creation.
|
||||||
func (c *CreateSBOMConfig) WithDataGenerationConfig(cfg cataloging.DataGenerationConfig) *CreateSBOMConfig {
|
func (c *CreateSBOMConfig) WithDataGenerationConfig(cfg cataloging.DataGenerationConfig) *CreateSBOMConfig {
|
||||||
@ -173,6 +180,7 @@ func (c *CreateSBOMConfig) makeTaskGroups(src source.Description) ([][]task.Task
|
|||||||
// generate package and file tasks based on the configuration
|
// generate package and file tasks based on the configuration
|
||||||
environmentTasks := c.environmentTasks()
|
environmentTasks := c.environmentTasks()
|
||||||
relationshipsTasks := c.relationshipTasks(src)
|
relationshipsTasks := c.relationshipTasks(src)
|
||||||
|
unknownTasks := c.unknownsTasks()
|
||||||
fileTasks := c.fileTasks()
|
fileTasks := c.fileTasks()
|
||||||
pkgTasks, selectionEvidence, err := c.packageTasks(src)
|
pkgTasks, selectionEvidence, err := c.packageTasks(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -192,6 +200,11 @@ func (c *CreateSBOMConfig) makeTaskGroups(src source.Description) ([][]task.Task
|
|||||||
taskGroups = append(taskGroups, relationshipsTasks)
|
taskGroups = append(taskGroups, relationshipsTasks)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// all unknowns tasks should happen after all scanning is complete
|
||||||
|
if len(unknownTasks) > 0 {
|
||||||
|
taskGroups = append(taskGroups, unknownTasks)
|
||||||
|
}
|
||||||
|
|
||||||
// identifying the environment (i.e. the linux release) must be done first as this is required for package cataloging
|
// identifying the environment (i.e. the linux release) must be done first as this is required for package cataloging
|
||||||
taskGroups = append(
|
taskGroups = append(
|
||||||
[][]task.Task{
|
[][]task.Task{
|
||||||
@ -338,6 +351,18 @@ func (c *CreateSBOMConfig) environmentTasks() []task.Task {
|
|||||||
return tsks
|
return tsks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// unknownsTasks returns a set of tasks that perform any necessary post-processing
|
||||||
|
// to identify SBOM elements as unknowns
|
||||||
|
func (c *CreateSBOMConfig) unknownsTasks() []task.Task {
|
||||||
|
var tasks []task.Task
|
||||||
|
|
||||||
|
if t := task.NewUnknownsLabelerTask(c.Unknowns); t != nil {
|
||||||
|
tasks = append(tasks, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tasks
|
||||||
|
}
|
||||||
|
|
||||||
func (c *CreateSBOMConfig) validate() error {
|
func (c *CreateSBOMConfig) validate() error {
|
||||||
if c.Relationships.ExcludeBinaryPackagesWithFileOwnershipOverlap {
|
if c.Relationships.ExcludeBinaryPackagesWithFileOwnershipOverlap {
|
||||||
if !c.Relationships.PackageFileOwnershipOverlap {
|
if !c.Relationships.PackageFileOwnershipOverlap {
|
||||||
|
|||||||
@ -90,6 +90,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
|||||||
pkgCatalogerNamesWithTagOrName(t, "image"),
|
pkgCatalogerNamesWithTagOrName(t, "image"),
|
||||||
fileCatalogerNames(true, true, true),
|
fileCatalogerNames(true, true, true),
|
||||||
relationshipCatalogerNames(),
|
relationshipCatalogerNames(),
|
||||||
|
unknownsTaskNames(),
|
||||||
},
|
},
|
||||||
wantManifest: &catalogerManifest{
|
wantManifest: &catalogerManifest{
|
||||||
Requested: pkgcataloging.SelectionRequest{
|
Requested: pkgcataloging.SelectionRequest{
|
||||||
@ -108,6 +109,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
|||||||
pkgCatalogerNamesWithTagOrName(t, "directory"),
|
pkgCatalogerNamesWithTagOrName(t, "directory"),
|
||||||
fileCatalogerNames(true, true, true),
|
fileCatalogerNames(true, true, true),
|
||||||
relationshipCatalogerNames(),
|
relationshipCatalogerNames(),
|
||||||
|
unknownsTaskNames(),
|
||||||
},
|
},
|
||||||
wantManifest: &catalogerManifest{
|
wantManifest: &catalogerManifest{
|
||||||
Requested: pkgcataloging.SelectionRequest{
|
Requested: pkgcataloging.SelectionRequest{
|
||||||
@ -127,6 +129,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
|||||||
pkgCatalogerNamesWithTagOrName(t, "directory"),
|
pkgCatalogerNamesWithTagOrName(t, "directory"),
|
||||||
fileCatalogerNames(true, true, true),
|
fileCatalogerNames(true, true, true),
|
||||||
relationshipCatalogerNames(),
|
relationshipCatalogerNames(),
|
||||||
|
unknownsTaskNames(),
|
||||||
},
|
},
|
||||||
wantManifest: &catalogerManifest{
|
wantManifest: &catalogerManifest{
|
||||||
Requested: pkgcataloging.SelectionRequest{
|
Requested: pkgcataloging.SelectionRequest{
|
||||||
@ -145,6 +148,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
|||||||
pkgCatalogerNamesWithTagOrName(t, "image"),
|
pkgCatalogerNamesWithTagOrName(t, "image"),
|
||||||
fileCatalogerNames(false, true, true), // note: the digest cataloger is not included
|
fileCatalogerNames(false, true, true), // note: the digest cataloger is not included
|
||||||
relationshipCatalogerNames(),
|
relationshipCatalogerNames(),
|
||||||
|
unknownsTaskNames(),
|
||||||
},
|
},
|
||||||
wantManifest: &catalogerManifest{
|
wantManifest: &catalogerManifest{
|
||||||
Requested: pkgcataloging.SelectionRequest{
|
Requested: pkgcataloging.SelectionRequest{
|
||||||
@ -163,6 +167,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
|||||||
pkgCatalogerNamesWithTagOrName(t, "image"),
|
pkgCatalogerNamesWithTagOrName(t, "image"),
|
||||||
// note: there are no file catalogers in their own group
|
// note: there are no file catalogers in their own group
|
||||||
relationshipCatalogerNames(),
|
relationshipCatalogerNames(),
|
||||||
|
unknownsTaskNames(),
|
||||||
},
|
},
|
||||||
wantManifest: &catalogerManifest{
|
wantManifest: &catalogerManifest{
|
||||||
Requested: pkgcataloging.SelectionRequest{
|
Requested: pkgcataloging.SelectionRequest{
|
||||||
@ -184,6 +189,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
|||||||
fileCatalogerNames(true, true, true)...,
|
fileCatalogerNames(true, true, true)...,
|
||||||
),
|
),
|
||||||
relationshipCatalogerNames(),
|
relationshipCatalogerNames(),
|
||||||
|
unknownsTaskNames(),
|
||||||
},
|
},
|
||||||
wantManifest: &catalogerManifest{
|
wantManifest: &catalogerManifest{
|
||||||
Requested: pkgcataloging.SelectionRequest{
|
Requested: pkgcataloging.SelectionRequest{
|
||||||
@ -204,6 +210,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
|||||||
addTo(pkgCatalogerNamesWithTagOrName(t, "image"), "persistent"),
|
addTo(pkgCatalogerNamesWithTagOrName(t, "image"), "persistent"),
|
||||||
fileCatalogerNames(true, true, true),
|
fileCatalogerNames(true, true, true),
|
||||||
relationshipCatalogerNames(),
|
relationshipCatalogerNames(),
|
||||||
|
unknownsTaskNames(),
|
||||||
},
|
},
|
||||||
wantManifest: &catalogerManifest{
|
wantManifest: &catalogerManifest{
|
||||||
Requested: pkgcataloging.SelectionRequest{
|
Requested: pkgcataloging.SelectionRequest{
|
||||||
@ -224,6 +231,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
|||||||
addTo(pkgCatalogerNamesWithTagOrName(t, "directory"), "persistent"),
|
addTo(pkgCatalogerNamesWithTagOrName(t, "directory"), "persistent"),
|
||||||
fileCatalogerNames(true, true, true),
|
fileCatalogerNames(true, true, true),
|
||||||
relationshipCatalogerNames(),
|
relationshipCatalogerNames(),
|
||||||
|
unknownsTaskNames(),
|
||||||
},
|
},
|
||||||
wantManifest: &catalogerManifest{
|
wantManifest: &catalogerManifest{
|
||||||
Requested: pkgcataloging.SelectionRequest{
|
Requested: pkgcataloging.SelectionRequest{
|
||||||
@ -244,6 +252,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
|||||||
addTo(pkgIntersect("image", "javascript"), "persistent"),
|
addTo(pkgIntersect("image", "javascript"), "persistent"),
|
||||||
fileCatalogerNames(true, true, true),
|
fileCatalogerNames(true, true, true),
|
||||||
relationshipCatalogerNames(),
|
relationshipCatalogerNames(),
|
||||||
|
unknownsTaskNames(),
|
||||||
},
|
},
|
||||||
wantManifest: &catalogerManifest{
|
wantManifest: &catalogerManifest{
|
||||||
Requested: pkgcataloging.SelectionRequest{
|
Requested: pkgcataloging.SelectionRequest{
|
||||||
@ -265,6 +274,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
|||||||
addTo(pkgCatalogerNamesWithTagOrName(t, "image"), "user-provided"),
|
addTo(pkgCatalogerNamesWithTagOrName(t, "image"), "user-provided"),
|
||||||
fileCatalogerNames(true, true, true),
|
fileCatalogerNames(true, true, true),
|
||||||
relationshipCatalogerNames(),
|
relationshipCatalogerNames(),
|
||||||
|
unknownsTaskNames(),
|
||||||
},
|
},
|
||||||
wantManifest: &catalogerManifest{
|
wantManifest: &catalogerManifest{
|
||||||
Requested: pkgcataloging.SelectionRequest{
|
Requested: pkgcataloging.SelectionRequest{
|
||||||
@ -285,6 +295,7 @@ func TestCreateSBOMConfig_makeTaskGroups(t *testing.T) {
|
|||||||
pkgCatalogerNamesWithTagOrName(t, "image"),
|
pkgCatalogerNamesWithTagOrName(t, "image"),
|
||||||
fileCatalogerNames(true, true, true),
|
fileCatalogerNames(true, true, true),
|
||||||
relationshipCatalogerNames(),
|
relationshipCatalogerNames(),
|
||||||
|
unknownsTaskNames(),
|
||||||
},
|
},
|
||||||
wantManifest: &catalogerManifest{
|
wantManifest: &catalogerManifest{
|
||||||
Requested: pkgcataloging.SelectionRequest{
|
Requested: pkgcataloging.SelectionRequest{
|
||||||
@ -385,6 +396,10 @@ func relationshipCatalogerNames() []string {
|
|||||||
return []string{"relationships-cataloger"}
|
return []string{"relationships-cataloger"}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func unknownsTaskNames() []string {
|
||||||
|
return []string{"unknowns-labeler"}
|
||||||
|
}
|
||||||
|
|
||||||
func environmentCatalogerNames() []string {
|
func environmentCatalogerNames() []string {
|
||||||
return []string{"environment-cataloger"}
|
return []string{"environment-cataloger"}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/anchore/syft/internal/bus"
|
"github.com/anchore/syft/internal/bus"
|
||||||
"github.com/anchore/syft/internal/log"
|
"github.com/anchore/syft/internal/log"
|
||||||
"github.com/anchore/syft/internal/mimetype"
|
"github.com/anchore/syft/internal/mimetype"
|
||||||
|
"github.com/anchore/syft/internal/unknown"
|
||||||
"github.com/anchore/syft/syft/event/monitor"
|
"github.com/anchore/syft/syft/event/monitor"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
"github.com/anchore/syft/syft/internal/unionreader"
|
"github.com/anchore/syft/syft/internal/unionreader"
|
||||||
@ -45,6 +46,8 @@ func NewCataloger(cfg Config) *Cataloger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (i *Cataloger) Catalog(resolver file.Resolver) (map[file.Coordinates]file.Executable, error) {
|
func (i *Cataloger) Catalog(resolver file.Resolver) (map[file.Coordinates]file.Executable, error) {
|
||||||
|
var errs error
|
||||||
|
|
||||||
locs, err := resolver.FilesByMIMEType(i.config.MIMETypes...)
|
locs, err := resolver.FilesByMIMEType(i.config.MIMETypes...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to get file locations for binaries: %w", err)
|
return nil, fmt.Errorf("unable to get file locations for binaries: %w", err)
|
||||||
@ -61,7 +64,10 @@ func (i *Cataloger) Catalog(resolver file.Resolver) (map[file.Coordinates]file.E
|
|||||||
for _, loc := range locs {
|
for _, loc := range locs {
|
||||||
prog.AtomicStage.Set(loc.Path())
|
prog.AtomicStage.Set(loc.Path())
|
||||||
|
|
||||||
exec := processExecutableLocation(loc, resolver)
|
exec, err := processExecutableLocation(loc, resolver)
|
||||||
|
if err != nil {
|
||||||
|
errs = unknown.Append(errs, loc, err)
|
||||||
|
}
|
||||||
|
|
||||||
if exec != nil {
|
if exec != nil {
|
||||||
prog.Increment()
|
prog.Increment()
|
||||||
@ -74,30 +80,24 @@ func (i *Cataloger) Catalog(resolver file.Resolver) (map[file.Coordinates]file.E
|
|||||||
prog.AtomicStage.Set(fmt.Sprintf("%s executables", humanize.Comma(prog.Current())))
|
prog.AtomicStage.Set(fmt.Sprintf("%s executables", humanize.Comma(prog.Current())))
|
||||||
prog.SetCompleted()
|
prog.SetCompleted()
|
||||||
|
|
||||||
return results, nil
|
return results, errs
|
||||||
}
|
}
|
||||||
|
|
||||||
func processExecutableLocation(loc file.Location, resolver file.Resolver) *file.Executable {
|
func processExecutableLocation(loc file.Location, resolver file.Resolver) (*file.Executable, error) {
|
||||||
reader, err := resolver.FileContentsByLocation(loc)
|
reader, err := resolver.FileContentsByLocation(loc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO: known-unknowns
|
|
||||||
log.WithFields("error", err).Warnf("unable to get file contents for %q", loc.RealPath)
|
log.WithFields("error", err).Warnf("unable to get file contents for %q", loc.RealPath)
|
||||||
return nil
|
return nil, fmt.Errorf("unable to get file contents: %w", err)
|
||||||
}
|
}
|
||||||
defer internal.CloseAndLogError(reader, loc.RealPath)
|
defer internal.CloseAndLogError(reader, loc.RealPath)
|
||||||
|
|
||||||
uReader, err := unionreader.GetUnionReader(reader)
|
uReader, err := unionreader.GetUnionReader(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO: known-unknowns
|
|
||||||
log.WithFields("error", err).Warnf("unable to get union reader for %q", loc.RealPath)
|
log.WithFields("error", err).Warnf("unable to get union reader for %q", loc.RealPath)
|
||||||
return nil
|
return nil, fmt.Errorf("unable to get union reader: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
exec, err := processExecutable(loc, uReader)
|
return processExecutable(loc, uReader)
|
||||||
if err != nil {
|
|
||||||
log.WithFields("error", err).Warnf("unable to process executable %q", loc.RealPath)
|
|
||||||
}
|
|
||||||
return exec
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func catalogingProgress(locations int64) *monitor.CatalogerTaskProgress {
|
func catalogingProgress(locations int64) *monitor.CatalogerTaskProgress {
|
||||||
@ -153,10 +153,12 @@ func processExecutable(loc file.Location, reader unionreader.UnionReader) (*file
|
|||||||
|
|
||||||
format, err := findExecutableFormat(reader)
|
format, err := findExecutableFormat(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Debugf("unable to determine executable kind for %v: %v", loc.RealPath, err)
|
||||||
return nil, fmt.Errorf("unable to determine executable kind: %w", err)
|
return nil, fmt.Errorf("unable to determine executable kind: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if format == "" {
|
if format == "" {
|
||||||
|
// this is not an "unknown", so just log -- this binary does not have parseable data in it
|
||||||
log.Debugf("unable to determine executable format for %q", loc.RealPath)
|
log.Debugf("unable to determine executable format for %q", loc.RealPath)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@ -165,16 +167,19 @@ func processExecutable(loc file.Location, reader unionreader.UnionReader) (*file
|
|||||||
|
|
||||||
switch format {
|
switch format {
|
||||||
case file.ELF:
|
case file.ELF:
|
||||||
if err := findELFFeatures(&data, reader); err != nil {
|
if err = findELFFeatures(&data, reader); err != nil {
|
||||||
log.WithFields("error", err).Tracef("unable to determine ELF features for %q", loc.RealPath)
|
log.WithFields("error", err).Tracef("unable to determine ELF features for %q", loc.RealPath)
|
||||||
|
err = fmt.Errorf("unable to determine ELF features: %w", err)
|
||||||
}
|
}
|
||||||
case file.PE:
|
case file.PE:
|
||||||
if err := findPEFeatures(&data, reader); err != nil {
|
if err = findPEFeatures(&data, reader); err != nil {
|
||||||
log.WithFields("error", err).Tracef("unable to determine PE features for %q", loc.RealPath)
|
log.WithFields("error", err).Tracef("unable to determine PE features for %q", loc.RealPath)
|
||||||
|
err = fmt.Errorf("unable to determine PE features: %w", err)
|
||||||
}
|
}
|
||||||
case file.MachO:
|
case file.MachO:
|
||||||
if err := findMachoFeatures(&data, reader); err != nil {
|
if err = findMachoFeatures(&data, reader); err != nil {
|
||||||
log.WithFields("error", err).Tracef("unable to determine Macho features for %q", loc.RealPath)
|
log.WithFields("error", err).Tracef("unable to determine Macho features for %q", loc.RealPath)
|
||||||
|
err = fmt.Errorf("unable to determine Macho features: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,7 +188,7 @@ func processExecutable(loc file.Location, reader unionreader.UnionReader) (*file
|
|||||||
data.ImportedLibraries = []string{}
|
data.ImportedLibraries = []string{}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &data, nil
|
return &data, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func findExecutableFormat(reader unionreader.UnionReader) (file.ExecutableFormat, error) {
|
func findExecutableFormat(reader unionreader.UnionReader) (file.ExecutableFormat, error) {
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/scylladb/go-set/strset"
|
"github.com/scylladb/go-set/strset"
|
||||||
|
|
||||||
"github.com/anchore/syft/internal/log"
|
"github.com/anchore/syft/internal/log"
|
||||||
|
"github.com/anchore/syft/internal/unknown"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
"github.com/anchore/syft/syft/internal/unionreader"
|
"github.com/anchore/syft/syft/internal/unionreader"
|
||||||
)
|
)
|
||||||
@ -20,8 +21,8 @@ func findELFFeatures(data *file.Executable, reader unionreader.UnionReader) erro
|
|||||||
|
|
||||||
libs, err := f.ImportedLibraries()
|
libs, err := f.ImportedLibraries()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO: known-unknowns
|
|
||||||
log.WithFields("error", err).Trace("unable to read imported libraries from elf file")
|
log.WithFields("error", err).Trace("unable to read imported libraries from elf file")
|
||||||
|
err = unknown.Joinf(err, "unable to read imported libraries from elf file: %w", err)
|
||||||
libs = nil
|
libs = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,7 +35,7 @@ func findELFFeatures(data *file.Executable, reader unionreader.UnionReader) erro
|
|||||||
data.HasEntrypoint = elfHasEntrypoint(f)
|
data.HasEntrypoint = elfHasEntrypoint(f)
|
||||||
data.HasExports = elfHasExports(f)
|
data.HasExports = elfHasExports(f)
|
||||||
|
|
||||||
return nil
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func findELFSecurityFeatures(f *elf.File) *file.ELFSecurityFeatures {
|
func findELFSecurityFeatures(f *elf.File) *file.ELFSecurityFeatures {
|
||||||
@ -62,7 +63,6 @@ func checkElfStackCanary(file *elf.File) *bool {
|
|||||||
func hasAnyDynamicSymbols(file *elf.File, symbolNames ...string) *bool {
|
func hasAnyDynamicSymbols(file *elf.File, symbolNames ...string) *bool {
|
||||||
dynSyms, err := file.DynamicSymbols()
|
dynSyms, err := file.DynamicSymbols()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO: known-unknowns
|
|
||||||
log.WithFields("error", err).Trace("unable to read dynamic symbols from elf file")
|
log.WithFields("error", err).Trace("unable to read dynamic symbols from elf file")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -129,7 +129,6 @@ func hasBindNowDynTagOrFlag(f *elf.File) bool {
|
|||||||
func hasElfDynFlag(f *elf.File, flag elf.DynFlag) bool {
|
func hasElfDynFlag(f *elf.File, flag elf.DynFlag) bool {
|
||||||
vals, err := f.DynValue(elf.DT_FLAGS)
|
vals, err := f.DynValue(elf.DT_FLAGS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO: known-unknowns
|
|
||||||
log.WithFields("error", err).Trace("unable to read DT_FLAGS from elf file")
|
log.WithFields("error", err).Trace("unable to read DT_FLAGS from elf file")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -144,7 +143,6 @@ func hasElfDynFlag(f *elf.File, flag elf.DynFlag) bool {
|
|||||||
func hasElfDynFlag1(f *elf.File, flag elf.DynFlag1) bool {
|
func hasElfDynFlag1(f *elf.File, flag elf.DynFlag1) bool {
|
||||||
vals, err := f.DynValue(elf.DT_FLAGS_1)
|
vals, err := f.DynValue(elf.DT_FLAGS_1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO: known-unknowns
|
|
||||||
log.WithFields("error", err).Trace("unable to read DT_FLAGS_1 from elf file")
|
log.WithFields("error", err).Trace("unable to read DT_FLAGS_1 from elf file")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -203,7 +201,6 @@ func checkLLVMControlFlowIntegrity(file *elf.File) *bool {
|
|||||||
// look for any symbols that are functions and end with ".cfi"
|
// look for any symbols that are functions and end with ".cfi"
|
||||||
dynSyms, err := file.Symbols()
|
dynSyms, err := file.Symbols()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO: known-unknowns
|
|
||||||
log.WithFields("error", err).Trace("unable to read symbols from elf file")
|
log.WithFields("error", err).Trace("unable to read symbols from elf file")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -225,7 +222,6 @@ var fortifyPattern = regexp.MustCompile(`__\w+_chk@.+`)
|
|||||||
func checkClangFortifySource(file *elf.File) *bool {
|
func checkClangFortifySource(file *elf.File) *bool {
|
||||||
dynSyms, err := file.Symbols()
|
dynSyms, err := file.Symbols()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO: known-unknowns
|
|
||||||
log.WithFields("error", err).Trace("unable to read symbols from elf file")
|
log.WithFields("error", err).Trace("unable to read symbols from elf file")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -254,7 +250,7 @@ func elfHasExports(f *elf.File) bool {
|
|||||||
// really anything that is not marked with 'U' (undefined) is considered an export.
|
// really anything that is not marked with 'U' (undefined) is considered an export.
|
||||||
symbols, err := f.DynamicSymbols()
|
symbols, err := f.DynamicSymbols()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO: known-unknowns?
|
log.WithFields("error", err).Trace("unable to get ELF dynamic symbols")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -27,6 +27,7 @@ func Test_findELFSecurityFeatures(t *testing.T) {
|
|||||||
name string
|
name string
|
||||||
fixture string
|
fixture string
|
||||||
want *file.ELFSecurityFeatures
|
want *file.ELFSecurityFeatures
|
||||||
|
wantErr require.ErrorAssertionFunc
|
||||||
wantStripped bool
|
wantStripped bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
@ -221,6 +222,7 @@ func Test_elfHasExports(t *testing.T) {
|
|||||||
f, err := elf.NewFile(readerForFixture(t, tt.fixture))
|
f, err := elf.NewFile(readerForFixture(t, tt.fixture))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, tt.want, elfHasExports(f))
|
assert.Equal(t, tt.want, elfHasExports(f))
|
||||||
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/anchore/syft/internal/bus"
|
"github.com/anchore/syft/internal/bus"
|
||||||
intFile "github.com/anchore/syft/internal/file"
|
intFile "github.com/anchore/syft/internal/file"
|
||||||
"github.com/anchore/syft/internal/log"
|
"github.com/anchore/syft/internal/log"
|
||||||
|
"github.com/anchore/syft/internal/unknown"
|
||||||
"github.com/anchore/syft/syft/event/monitor"
|
"github.com/anchore/syft/syft/event/monitor"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
)
|
)
|
||||||
@ -46,6 +47,7 @@ func NewCataloger(cfg Config) *Cataloger {
|
|||||||
func (i *Cataloger) Catalog(_ context.Context, resolver file.Resolver) (map[file.Coordinates]string, error) {
|
func (i *Cataloger) Catalog(_ context.Context, resolver file.Resolver) (map[file.Coordinates]string, error) {
|
||||||
results := make(map[file.Coordinates]string)
|
results := make(map[file.Coordinates]string)
|
||||||
var locations []file.Location
|
var locations []file.Location
|
||||||
|
var errs error
|
||||||
|
|
||||||
locations, err := resolver.FilesByGlob(i.globs...)
|
locations, err := resolver.FilesByGlob(i.globs...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -59,8 +61,9 @@ func (i *Cataloger) Catalog(_ context.Context, resolver file.Resolver) (map[file
|
|||||||
|
|
||||||
metadata, err := resolver.FileMetadataByLocation(location)
|
metadata, err := resolver.FileMetadataByLocation(location)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
errs = unknown.Append(errs, location, err)
|
||||||
prog.SetError(err)
|
prog.SetError(err)
|
||||||
return nil, err
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if i.skipFilesAboveSizeInBytes > 0 && metadata.Size() > i.skipFilesAboveSizeInBytes {
|
if i.skipFilesAboveSizeInBytes > 0 && metadata.Size() > i.skipFilesAboveSizeInBytes {
|
||||||
@ -69,12 +72,12 @@ func (i *Cataloger) Catalog(_ context.Context, resolver file.Resolver) (map[file
|
|||||||
|
|
||||||
result, err := i.catalogLocation(resolver, location)
|
result, err := i.catalogLocation(resolver, location)
|
||||||
if internal.IsErrPathPermission(err) {
|
if internal.IsErrPathPermission(err) {
|
||||||
log.Debugf("file contents cataloger skipping - %+v", err)
|
errs = unknown.Append(errs, location, fmt.Errorf("permission error reading file contents: %w", err))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
prog.SetError(err)
|
errs = unknown.Append(errs, location, err)
|
||||||
return nil, err
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
prog.Increment()
|
prog.Increment()
|
||||||
@ -87,7 +90,7 @@ func (i *Cataloger) Catalog(_ context.Context, resolver file.Resolver) (map[file
|
|||||||
prog.AtomicStage.Set(fmt.Sprintf("%s files", humanize.Comma(prog.Current())))
|
prog.AtomicStage.Set(fmt.Sprintf("%s files", humanize.Comma(prog.Current())))
|
||||||
prog.SetCompleted()
|
prog.SetCompleted()
|
||||||
|
|
||||||
return results, nil
|
return results, errs
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Cataloger) catalogLocation(resolver file.Resolver, location file.Location) (string, error) {
|
func (i *Cataloger) catalogLocation(resolver file.Resolver, location file.Location) (string, error) {
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/anchore/syft/internal/bus"
|
"github.com/anchore/syft/internal/bus"
|
||||||
intFile "github.com/anchore/syft/internal/file"
|
intFile "github.com/anchore/syft/internal/file"
|
||||||
"github.com/anchore/syft/internal/log"
|
"github.com/anchore/syft/internal/log"
|
||||||
|
"github.com/anchore/syft/internal/unknown"
|
||||||
"github.com/anchore/syft/syft/event/monitor"
|
"github.com/anchore/syft/syft/event/monitor"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
intCataloger "github.com/anchore/syft/syft/file/cataloger/internal"
|
intCataloger "github.com/anchore/syft/syft/file/cataloger/internal"
|
||||||
@ -33,6 +34,7 @@ func NewCataloger(hashes []crypto.Hash) *Cataloger {
|
|||||||
func (i *Cataloger) Catalog(ctx context.Context, resolver file.Resolver, coordinates ...file.Coordinates) (map[file.Coordinates][]file.Digest, error) {
|
func (i *Cataloger) Catalog(ctx context.Context, resolver file.Resolver, coordinates ...file.Coordinates) (map[file.Coordinates][]file.Digest, error) {
|
||||||
results := make(map[file.Coordinates][]file.Digest)
|
results := make(map[file.Coordinates][]file.Digest)
|
||||||
var locations []file.Location
|
var locations []file.Location
|
||||||
|
var errs error
|
||||||
|
|
||||||
if len(coordinates) == 0 {
|
if len(coordinates) == 0 {
|
||||||
locations = intCataloger.AllRegularFiles(ctx, resolver)
|
locations = intCataloger.AllRegularFiles(ctx, resolver)
|
||||||
@ -58,12 +60,14 @@ func (i *Cataloger) Catalog(ctx context.Context, resolver file.Resolver, coordin
|
|||||||
|
|
||||||
if internal.IsErrPathPermission(err) {
|
if internal.IsErrPathPermission(err) {
|
||||||
log.Debugf("file digests cataloger skipping %q: %+v", location.RealPath, err)
|
log.Debugf("file digests cataloger skipping %q: %+v", location.RealPath, err)
|
||||||
|
errs = unknown.Append(errs, location, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
prog.SetError(err)
|
prog.SetError(err)
|
||||||
return nil, fmt.Errorf("failed to process file %q: %w", location.RealPath, err)
|
errs = unknown.Append(errs, location, err)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
prog.Increment()
|
prog.Increment()
|
||||||
@ -76,7 +80,7 @@ func (i *Cataloger) Catalog(ctx context.Context, resolver file.Resolver, coordin
|
|||||||
prog.AtomicStage.Set(fmt.Sprintf("%s files", humanize.Comma(prog.Current())))
|
prog.AtomicStage.Set(fmt.Sprintf("%s files", humanize.Comma(prog.Current())))
|
||||||
prog.SetCompleted()
|
prog.SetCompleted()
|
||||||
|
|
||||||
return results, nil
|
return results, errs
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Cataloger) catalogLocation(resolver file.Resolver, location file.Location) ([]file.Digest, error) {
|
func (i *Cataloger) catalogLocation(resolver file.Resolver, location file.Location) ([]file.Digest, error) {
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/anchore/syft/internal/bus"
|
"github.com/anchore/syft/internal/bus"
|
||||||
"github.com/anchore/syft/internal/log"
|
"github.com/anchore/syft/internal/log"
|
||||||
|
"github.com/anchore/syft/internal/unknown"
|
||||||
"github.com/anchore/syft/syft/event/monitor"
|
"github.com/anchore/syft/syft/event/monitor"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
)
|
)
|
||||||
@ -20,6 +21,7 @@ func NewCataloger() *Cataloger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (i *Cataloger) Catalog(ctx context.Context, resolver file.Resolver, coordinates ...file.Coordinates) (map[file.Coordinates]file.Metadata, error) {
|
func (i *Cataloger) Catalog(ctx context.Context, resolver file.Resolver, coordinates ...file.Coordinates) (map[file.Coordinates]file.Metadata, error) {
|
||||||
|
var errs error
|
||||||
results := make(map[file.Coordinates]file.Metadata)
|
results := make(map[file.Coordinates]file.Metadata)
|
||||||
var locations <-chan file.Location
|
var locations <-chan file.Location
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
@ -34,7 +36,7 @@ func (i *Cataloger) Catalog(ctx context.Context, resolver file.Resolver, coordin
|
|||||||
for _, c := range coordinates {
|
for _, c := range coordinates {
|
||||||
locs, err := resolver.FilesByPath(c.RealPath)
|
locs, err := resolver.FilesByPath(c.RealPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("unable to get file locations for path %q: %w", c.RealPath, err)
|
errs = unknown.Append(errs, c, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for _, loc := range locs {
|
for _, loc := range locs {
|
||||||
@ -71,7 +73,7 @@ func (i *Cataloger) Catalog(ctx context.Context, resolver file.Resolver, coordin
|
|||||||
prog.AtomicStage.Set(fmt.Sprintf("%s locations", humanize.Comma(prog.Current())))
|
prog.AtomicStage.Set(fmt.Sprintf("%s locations", humanize.Comma(prog.Current())))
|
||||||
prog.SetCompleted()
|
prog.SetCompleted()
|
||||||
|
|
||||||
return results, nil
|
return results, errs
|
||||||
}
|
}
|
||||||
|
|
||||||
func catalogingProgress(locations int64) *monitor.CatalogerTaskProgress {
|
func catalogingProgress(locations int64) *monitor.CatalogerTaskProgress {
|
||||||
|
|||||||
@ -39,3 +39,7 @@ func (c Coordinates) String() string {
|
|||||||
}
|
}
|
||||||
return fmt.Sprintf("Location<%s>", str)
|
return fmt.Sprintf("Location<%s>", str)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c Coordinates) GetCoordinates() Coordinates {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|||||||
@ -221,6 +221,7 @@ func Test_encodeDecodeFileMetadata(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Unknowns: map[file.Coordinates][]string{},
|
||||||
Executables: map[file.Coordinates]file.Executable{
|
Executables: map[file.Coordinates]file.Executable{
|
||||||
c: {
|
c: {
|
||||||
Format: file.ELF,
|
Format: file.ELF,
|
||||||
|
|||||||
@ -13,6 +13,7 @@ type File struct {
|
|||||||
Digests []file.Digest `json:"digests,omitempty"`
|
Digests []file.Digest `json:"digests,omitempty"`
|
||||||
Licenses []FileLicense `json:"licenses,omitempty"`
|
Licenses []FileLicense `json:"licenses,omitempty"`
|
||||||
Executable *file.Executable `json:"executable,omitempty"`
|
Executable *file.Executable `json:"executable,omitempty"`
|
||||||
|
Unknowns []string `json:"unknowns,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FileMetadataEntry struct {
|
type FileMetadataEntry struct {
|
||||||
|
|||||||
@ -101,6 +101,11 @@ func toFile(s sbom.SBOM) []model.File {
|
|||||||
contents = contentsForLocation
|
contents = contentsForLocation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var unknowns []string
|
||||||
|
if unknownsForLocation, exists := artifacts.Unknowns[coordinates]; exists {
|
||||||
|
unknowns = unknownsForLocation
|
||||||
|
}
|
||||||
|
|
||||||
var licenses []model.FileLicense
|
var licenses []model.FileLicense
|
||||||
for _, l := range artifacts.FileLicenses[coordinates] {
|
for _, l := range artifacts.FileLicenses[coordinates] {
|
||||||
var evidence *model.FileLicenseEvidence
|
var evidence *model.FileLicenseEvidence
|
||||||
@ -132,6 +137,7 @@ func toFile(s sbom.SBOM) []model.File {
|
|||||||
Contents: contents,
|
Contents: contents,
|
||||||
Licenses: licenses,
|
Licenses: licenses,
|
||||||
Executable: executable,
|
Executable: executable,
|
||||||
|
Unknowns: unknowns,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -38,6 +38,7 @@ func toSyftModel(doc model.Document) *sbom.SBOM {
|
|||||||
FileContents: fileArtifacts.FileContents,
|
FileContents: fileArtifacts.FileContents,
|
||||||
FileLicenses: fileArtifacts.FileLicenses,
|
FileLicenses: fileArtifacts.FileLicenses,
|
||||||
Executables: fileArtifacts.Executables,
|
Executables: fileArtifacts.Executables,
|
||||||
|
Unknowns: fileArtifacts.Unknowns,
|
||||||
LinuxDistribution: toSyftLinuxRelease(doc.Distro),
|
LinuxDistribution: toSyftLinuxRelease(doc.Distro),
|
||||||
},
|
},
|
||||||
Source: *toSyftSourceData(doc.Source),
|
Source: *toSyftSourceData(doc.Source),
|
||||||
@ -66,6 +67,7 @@ func deduplicateErrors(errors []error) []string {
|
|||||||
return errorMessages
|
return errorMessages
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//nolint:funlen
|
||||||
func toSyftFiles(files []model.File) sbom.Artifacts {
|
func toSyftFiles(files []model.File) sbom.Artifacts {
|
||||||
ret := sbom.Artifacts{
|
ret := sbom.Artifacts{
|
||||||
FileMetadata: make(map[file.Coordinates]file.Metadata),
|
FileMetadata: make(map[file.Coordinates]file.Metadata),
|
||||||
@ -73,6 +75,7 @@ func toSyftFiles(files []model.File) sbom.Artifacts {
|
|||||||
FileContents: make(map[file.Coordinates]string),
|
FileContents: make(map[file.Coordinates]string),
|
||||||
FileLicenses: make(map[file.Coordinates][]file.License),
|
FileLicenses: make(map[file.Coordinates][]file.License),
|
||||||
Executables: make(map[file.Coordinates]file.Executable),
|
Executables: make(map[file.Coordinates]file.Executable),
|
||||||
|
Unknowns: make(map[file.Coordinates][]string),
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
@ -130,6 +133,10 @@ func toSyftFiles(files []model.File) sbom.Artifacts {
|
|||||||
if f.Executable != nil {
|
if f.Executable != nil {
|
||||||
ret.Executables[coord] = *f.Executable
|
ret.Executables[coord] = *f.Executable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(f.Unknowns) > 0 {
|
||||||
|
ret.Unknowns[coord] = f.Unknowns
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|||||||
@ -234,6 +234,7 @@ func Test_toSyftFiles(t *testing.T) {
|
|||||||
FileMetadata: map[file.Coordinates]file.Metadata{},
|
FileMetadata: map[file.Coordinates]file.Metadata{},
|
||||||
FileDigests: map[file.Coordinates][]file.Digest{},
|
FileDigests: map[file.Coordinates][]file.Digest{},
|
||||||
Executables: map[file.Coordinates]file.Executable{},
|
Executables: map[file.Coordinates]file.Executable{},
|
||||||
|
Unknowns: make(map[file.Coordinates][]string),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -349,6 +350,7 @@ func Test_toSyftFiles(t *testing.T) {
|
|||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
tt.want.FileContents = make(map[file.Coordinates]string)
|
tt.want.FileContents = make(map[file.Coordinates]string)
|
||||||
tt.want.FileLicenses = make(map[file.Coordinates][]file.License)
|
tt.want.FileLicenses = make(map[file.Coordinates][]file.License)
|
||||||
|
tt.want.Unknowns = make(map[file.Coordinates][]string)
|
||||||
assert.Equal(t, tt.want, toSyftFiles(tt.files))
|
assert.Equal(t, tt.want, toSyftFiles(tt.files))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -190,6 +190,14 @@ func TestApkDBCataloger(t *testing.T) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_corruptDb(t *testing.T) {
|
||||||
|
pkgtest.NewCatalogTester().
|
||||||
|
FromDirectory(t, "test-fixtures/corrupt").
|
||||||
|
WithCompareOptions(cmpopts.IgnoreFields(pkg.ApkDBEntry{}, "Files", "GitCommit", "Checksum")).
|
||||||
|
WithError().
|
||||||
|
TestCataloger(t, NewDBCataloger())
|
||||||
|
}
|
||||||
|
|
||||||
func TestCatalogerDependencyTree(t *testing.T) {
|
func TestCatalogerDependencyTree(t *testing.T) {
|
||||||
assertion := func(t *testing.T, pkgs []pkg.Package, relationships []artifact.Relationship) {
|
assertion := func(t *testing.T, pkgs []pkg.Package, relationships []artifact.Relationship) {
|
||||||
expected := map[string][]string{
|
expected := map[string][]string{
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import (
|
|||||||
|
|
||||||
"github.com/anchore/syft/internal"
|
"github.com/anchore/syft/internal"
|
||||||
"github.com/anchore/syft/internal/log"
|
"github.com/anchore/syft/internal/log"
|
||||||
|
"github.com/anchore/syft/internal/unknown"
|
||||||
"github.com/anchore/syft/syft/artifact"
|
"github.com/anchore/syft/syft/artifact"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
"github.com/anchore/syft/syft/linux"
|
"github.com/anchore/syft/syft/linux"
|
||||||
@ -38,6 +39,7 @@ type parsedData struct {
|
|||||||
func parseApkDB(_ context.Context, resolver file.Resolver, env *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
func parseApkDB(_ context.Context, resolver file.Resolver, env *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||||
scanner := bufio.NewScanner(reader)
|
scanner := bufio.NewScanner(reader)
|
||||||
|
|
||||||
|
var errs error
|
||||||
var apks []parsedData
|
var apks []parsedData
|
||||||
var currentEntry parsedData
|
var currentEntry parsedData
|
||||||
entryParsingInProgress := false
|
entryParsingInProgress := false
|
||||||
@ -81,10 +83,12 @@ func parseApkDB(_ context.Context, resolver file.Resolver, env *generic.Environm
|
|||||||
field := parseApkField(line)
|
field := parseApkField(line)
|
||||||
if field == nil {
|
if field == nil {
|
||||||
log.Warnf("unable to parse field data from line %q", line)
|
log.Warnf("unable to parse field data from line %q", line)
|
||||||
|
errs = unknown.Appendf(errs, reader, "unable to parse field data from line %q", line)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if len(field.name) == 0 {
|
if len(field.name) == 0 {
|
||||||
log.Warnf("failed to parse field name from line %q", line)
|
log.Warnf("failed to parse field name from line %q", line)
|
||||||
|
errs = unknown.Appendf(errs, reader, "failed to parse field name from line %q", line)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if len(field.value) == 0 {
|
if len(field.value) == 0 {
|
||||||
@ -131,7 +135,7 @@ func parseApkDB(_ context.Context, resolver file.Resolver, env *generic.Environm
|
|||||||
pkgs = append(pkgs, newPackage(apk, r, reader.Location))
|
pkgs = append(pkgs, newPackage(apk, r, reader.Location))
|
||||||
}
|
}
|
||||||
|
|
||||||
return pkgs, nil, nil
|
return pkgs, nil, errs
|
||||||
}
|
}
|
||||||
|
|
||||||
func findReleases(resolver file.Resolver, dbPath string) []linux.Release {
|
func findReleases(resolver file.Resolver, dbPath string) []linux.Release {
|
||||||
|
|||||||
@ -0,0 +1,43 @@
|
|||||||
|
this is a corrupt db
|
||||||
|
|
||||||
|
C:Q1v4QhLje3kWlC8DJj+ZfJTjlJRSU=
|
||||||
|
P:alpine-baselayout-data
|
||||||
|
V:3.2.0-r22
|
||||||
|
A:x86_64
|
||||||
|
S:11435
|
||||||
|
I:73728
|
||||||
|
o:alpine-baselayout
|
||||||
|
t:1655134784
|
||||||
|
c:cb70ca5c6d6db0399d2dd09189c5d57827bce5cd
|
||||||
|
r:alpine-baselayout
|
||||||
|
F:etc
|
||||||
|
R:fstab
|
||||||
|
Z:Q11Q7hNe8QpDS531guqCdrXBzoA/o=
|
||||||
|
R:group
|
||||||
|
Z:Q13K+olJg5ayzHSVNUkggZJXuB+9Y=
|
||||||
|
R:hostname
|
||||||
|
Z:Q16nVwYVXP/tChvUPdukVD2ifXOmc=
|
||||||
|
R:hosts
|
||||||
|
Z:Q1BD6zJKZTRWyqGnPi4tSfd3krsMU=
|
||||||
|
R:inittab
|
||||||
|
Z:Q1TsthbhW7QzWRe1E/NKwTOuD4pHc=
|
||||||
|
R:modules
|
||||||
|
Z:Q1toogjUipHGcMgECgPJX64SwUT1M=
|
||||||
|
R:mtab
|
||||||
|
a:0:0:777
|
||||||
|
Z:Q1kiljhXXH1LlQroHsEJIkPZg2eiw=
|
||||||
|
R:passwd
|
||||||
|
Z:Q1TchuuLUfur0izvfZQZxgN/LJhB8=
|
||||||
|
R:profile
|
||||||
|
Z:Q1F3DgXUP+jNZDknmQPPb5t9FSfDg=
|
||||||
|
R:protocols
|
||||||
|
Z:Q1omKlp3vgGq2ZqYzyD/KHNdo8rDc=
|
||||||
|
R:services
|
||||||
|
Z:Q19WLCv5ItKg4MH7RWfNRh1I7byQc=
|
||||||
|
R:shadow
|
||||||
|
a:0:42:640
|
||||||
|
Z:Q1ltrPIAW2zHeDiajsex2Bdmq3uqA=
|
||||||
|
R:shells
|
||||||
|
Z:Q1ojm2YdpCJ6B/apGDaZ/Sdb2xJkA=
|
||||||
|
R:sysctl.conf
|
||||||
|
Z:Q14upz3tfnNxZkIEsUhWn7Xoiw96g=
|
||||||
@ -11,6 +11,14 @@ import (
|
|||||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestAlpmUnknowns(t *testing.T) {
|
||||||
|
pkgtest.NewCatalogTester().
|
||||||
|
FromDirectory(t, "test-fixtures/installed").
|
||||||
|
WithCompareOptions(cmpopts.IgnoreFields(pkg.AlpmFileRecord{}, "Time")).
|
||||||
|
WithError().
|
||||||
|
TestCataloger(t, NewDBCataloger())
|
||||||
|
}
|
||||||
|
|
||||||
func TestAlpmCataloger(t *testing.T) {
|
func TestAlpmCataloger(t *testing.T) {
|
||||||
gmpDbLocation := file.NewLocation("var/lib/pacman/local/gmp-6.2.1-2/desc")
|
gmpDbLocation := file.NewLocation("var/lib/pacman/local/gmp-6.2.1-2/desc")
|
||||||
treeSitterDbLocation := file.NewLocation("var/lib/pacman/local/tree-sitter-0.22.6-1/desc")
|
treeSitterDbLocation := file.NewLocation("var/lib/pacman/local/tree-sitter-0.22.6-1/desc")
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import (
|
|||||||
|
|
||||||
"github.com/anchore/syft/internal"
|
"github.com/anchore/syft/internal"
|
||||||
"github.com/anchore/syft/internal/log"
|
"github.com/anchore/syft/internal/log"
|
||||||
|
"github.com/anchore/syft/internal/unknown"
|
||||||
"github.com/anchore/syft/syft/artifact"
|
"github.com/anchore/syft/syft/artifact"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
@ -41,6 +42,8 @@ type parsedData struct {
|
|||||||
|
|
||||||
// parseAlpmDB parses the arch linux pacman database flat-files and returns the packages and relationships found within.
|
// parseAlpmDB parses the arch linux pacman database flat-files and returns the packages and relationships found within.
|
||||||
func parseAlpmDB(_ context.Context, resolver file.Resolver, env *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
func parseAlpmDB(_ context.Context, resolver file.Resolver, env *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||||
|
var errs error
|
||||||
|
|
||||||
data, err := parseAlpmDBEntry(reader)
|
data, err := parseAlpmDBEntry(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
@ -52,24 +55,25 @@ func parseAlpmDB(_ context.Context, resolver file.Resolver, env *generic.Environ
|
|||||||
|
|
||||||
base := path.Dir(reader.RealPath)
|
base := path.Dir(reader.RealPath)
|
||||||
|
|
||||||
|
var locs []file.Location
|
||||||
|
|
||||||
// replace the files found the pacman database with the files from the mtree These contain more metadata and
|
// replace the files found the pacman database with the files from the mtree These contain more metadata and
|
||||||
// thus more useful.
|
// thus more useful.
|
||||||
files, fileLoc := fetchPkgFiles(base, resolver)
|
files, fileLoc, err := fetchPkgFiles(base, resolver)
|
||||||
backups, backupLoc := fetchBackupFiles(base, resolver)
|
errs = unknown.Join(errs, err)
|
||||||
|
if err == nil {
|
||||||
var locs []file.Location
|
|
||||||
if fileLoc != nil {
|
|
||||||
locs = append(locs, fileLoc.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation))
|
locs = append(locs, fileLoc.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation))
|
||||||
data.Files = files
|
data.Files = files
|
||||||
}
|
}
|
||||||
|
backups, backupLoc, err := fetchBackupFiles(base, resolver)
|
||||||
if backupLoc != nil {
|
errs = unknown.Join(errs, err)
|
||||||
|
if err == nil {
|
||||||
locs = append(locs, backupLoc.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation))
|
locs = append(locs, backupLoc.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation))
|
||||||
data.Backup = backups
|
data.Backup = backups
|
||||||
}
|
}
|
||||||
|
|
||||||
if data.Package == "" {
|
if data.Package == "" {
|
||||||
return nil, nil, nil
|
return nil, nil, errs
|
||||||
}
|
}
|
||||||
|
|
||||||
return []pkg.Package{
|
return []pkg.Package{
|
||||||
@ -79,63 +83,56 @@ func parseAlpmDB(_ context.Context, resolver file.Resolver, env *generic.Environ
|
|||||||
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||||
locs...,
|
locs...,
|
||||||
),
|
),
|
||||||
}, nil, nil
|
}, nil, errs
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchPkgFiles(base string, resolver file.Resolver) ([]pkg.AlpmFileRecord, *file.Location) {
|
func fetchPkgFiles(base string, resolver file.Resolver) ([]pkg.AlpmFileRecord, file.Location, error) {
|
||||||
// TODO: probably want to use MTREE and PKGINFO here
|
// TODO: probably want to use MTREE and PKGINFO here
|
||||||
target := path.Join(base, "mtree")
|
target := path.Join(base, "mtree")
|
||||||
|
|
||||||
loc, err := getLocation(target, resolver)
|
loc, err := getLocation(target, resolver)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields("error", err, "path", target).Trace("failed to find mtree file")
|
log.WithFields("error", err, "path", target).Trace("failed to find mtree file")
|
||||||
return []pkg.AlpmFileRecord{}, nil
|
return []pkg.AlpmFileRecord{}, loc, unknown.New(loc, fmt.Errorf("failed to find mtree file: %w", err))
|
||||||
}
|
}
|
||||||
if loc == nil {
|
reader, err := resolver.FileContentsByLocation(loc)
|
||||||
return []pkg.AlpmFileRecord{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
reader, err := resolver.FileContentsByLocation(*loc)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []pkg.AlpmFileRecord{}, nil
|
return []pkg.AlpmFileRecord{}, loc, unknown.New(loc, fmt.Errorf("failed to get contents: %w", err))
|
||||||
}
|
}
|
||||||
defer internal.CloseAndLogError(reader, loc.RealPath)
|
defer internal.CloseAndLogError(reader, loc.RealPath)
|
||||||
|
|
||||||
pkgFiles, err := parseMtree(reader)
|
pkgFiles, err := parseMtree(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields("error", err, "path", target).Trace("failed to parse mtree file")
|
log.WithFields("error", err, "path", target).Trace("failed to parse mtree file")
|
||||||
return []pkg.AlpmFileRecord{}, nil
|
return []pkg.AlpmFileRecord{}, loc, unknown.New(loc, fmt.Errorf("failed to parse mtree: %w", err))
|
||||||
}
|
}
|
||||||
return pkgFiles, loc
|
return pkgFiles, loc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchBackupFiles(base string, resolver file.Resolver) ([]pkg.AlpmFileRecord, *file.Location) {
|
func fetchBackupFiles(base string, resolver file.Resolver) ([]pkg.AlpmFileRecord, file.Location, error) {
|
||||||
// We only really do this to get any backup database entries from the files database
|
// We only really do this to get any backup database entries from the files database
|
||||||
target := filepath.Join(base, "files")
|
target := filepath.Join(base, "files")
|
||||||
|
|
||||||
loc, err := getLocation(target, resolver)
|
loc, err := getLocation(target, resolver)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields("error", err, "path", target).Trace("failed to find alpm files")
|
log.WithFields("error", err, "path", target).Trace("failed to find alpm files")
|
||||||
return []pkg.AlpmFileRecord{}, nil
|
return []pkg.AlpmFileRecord{}, loc, unknown.New(loc, fmt.Errorf("failed to find alpm files: %w", err))
|
||||||
}
|
|
||||||
if loc == nil {
|
|
||||||
return []pkg.AlpmFileRecord{}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reader, err := resolver.FileContentsByLocation(*loc)
|
reader, err := resolver.FileContentsByLocation(loc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []pkg.AlpmFileRecord{}, nil
|
return []pkg.AlpmFileRecord{}, loc, unknown.New(loc, fmt.Errorf("failed to get contents: %w", err))
|
||||||
}
|
}
|
||||||
defer internal.CloseAndLogError(reader, loc.RealPath)
|
defer internal.CloseAndLogError(reader, loc.RealPath)
|
||||||
|
|
||||||
filesMetadata, err := parseAlpmDBEntry(reader)
|
filesMetadata, err := parseAlpmDBEntry(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []pkg.AlpmFileRecord{}, nil
|
return []pkg.AlpmFileRecord{}, loc, unknown.New(loc, fmt.Errorf("failed to parse alpm db entry: %w", err))
|
||||||
}
|
}
|
||||||
if filesMetadata != nil {
|
if filesMetadata != nil {
|
||||||
return filesMetadata.Backup, loc
|
return filesMetadata.Backup, loc, nil
|
||||||
}
|
}
|
||||||
return []pkg.AlpmFileRecord{}, loc
|
return []pkg.AlpmFileRecord{}, loc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseAlpmDBEntry(reader io.Reader) (*parsedData, error) {
|
func parseAlpmDBEntry(reader io.Reader) (*parsedData, error) {
|
||||||
@ -171,20 +168,21 @@ func newScanner(reader io.Reader) *bufio.Scanner {
|
|||||||
return scanner
|
return scanner
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLocation(path string, resolver file.Resolver) (*file.Location, error) {
|
func getLocation(path string, resolver file.Resolver) (file.Location, error) {
|
||||||
|
loc := file.NewLocation(path)
|
||||||
locs, err := resolver.FilesByPath(path)
|
locs, err := resolver.FilesByPath(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return loc, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(locs) == 0 {
|
if len(locs) == 0 {
|
||||||
return nil, fmt.Errorf("could not find file: %s", path)
|
return loc, fmt.Errorf("could not find file: %s", path)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(locs) > 1 {
|
if len(locs) > 1 {
|
||||||
log.WithFields("path", path).Trace("multiple files found for path, using first path")
|
log.WithFields("path", path).Trace("multiple files found for path, using first path")
|
||||||
}
|
}
|
||||||
return &locs[0], nil
|
return locs[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseDatabase(b *bufio.Scanner) (*parsedData, error) {
|
func parseDatabase(b *bufio.Scanner) (*parsedData, error) {
|
||||||
|
|||||||
@ -0,0 +1,34 @@
|
|||||||
|
%NME%
|
||||||
|
tree-sitter
|
||||||
|
|
||||||
|
%VER.6-1
|
||||||
|
|
||||||
|
%BASE%
|
||||||
|
tree-sitter
|
||||||
|
|
||||||
|
%DESC%
|
||||||
|
Incremental parsing library
|
||||||
|
|
||||||
|
|
||||||
|
%BUILDDATE%
|
||||||
|
1714945746
|
||||||
|
|
||||||
|
%INSTALLDATE%
|
||||||
|
1715026360
|
||||||
|
|
||||||
|
%PACKA@archlinux.org>
|
||||||
|
|
||||||
|
%SIZE%
|
||||||
|
223539
|
||||||
|
|
||||||
|
%REASON%
|
||||||
|
1
|
||||||
|
|
||||||
|
%LICENSE%
|
||||||
|
MIT
|
||||||
|
|
||||||
|
%VALIDATION%
|
||||||
|
pgp
|
||||||
|
|
||||||
|
%PROVIDE
|
||||||
|
libtree-sitter.so=0-64
|
||||||
@ -6,8 +6,10 @@ package binary
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/anchore/syft/internal/log"
|
"github.com/anchore/syft/internal/log"
|
||||||
|
"github.com/anchore/syft/internal/unknown"
|
||||||
"github.com/anchore/syft/syft/artifact"
|
"github.com/anchore/syft/syft/artifact"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
@ -59,12 +61,14 @@ func (c cataloger) Name() string {
|
|||||||
func (c cataloger) Catalog(_ context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
|
func (c cataloger) Catalog(_ context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||||
var packages []pkg.Package
|
var packages []pkg.Package
|
||||||
var relationships []artifact.Relationship
|
var relationships []artifact.Relationship
|
||||||
|
var errs error
|
||||||
|
|
||||||
for _, cls := range c.classifiers {
|
for _, cls := range c.classifiers {
|
||||||
log.WithFields("classifier", cls.Class).Trace("cataloging binaries")
|
log.WithFields("classifier", cls.Class).Trace("cataloging binaries")
|
||||||
newPkgs, err := catalog(resolver, cls)
|
newPkgs, err := catalog(resolver, cls)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields("error", err, "classifier", cls.Class).Warn("unable to catalog binary package: %w", err)
|
log.WithFields("error", err, "classifier", cls.Class).Debugf("unable to catalog binary package: %v", err)
|
||||||
|
errs = unknown.Join(errs, fmt.Errorf("%s: %w", cls.Class, err))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
newPackages:
|
newPackages:
|
||||||
@ -82,7 +86,7 @@ func (c cataloger) Catalog(_ context.Context, resolver file.Resolver) ([]pkg.Pac
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return packages, relationships, nil
|
return packages, relationships, errs
|
||||||
}
|
}
|
||||||
|
|
||||||
// mergePackages merges information from the extra package into the target package
|
// mergePackages merges information from the extra package into the target package
|
||||||
@ -98,6 +102,7 @@ func mergePackages(target *pkg.Package, extra *pkg.Package) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func catalog(resolver file.Resolver, cls Classifier) (packages []pkg.Package, err error) {
|
func catalog(resolver file.Resolver, cls Classifier) (packages []pkg.Package, err error) {
|
||||||
|
var errs error
|
||||||
locations, err := resolver.FilesByGlob(cls.FileGlob)
|
locations, err := resolver.FilesByGlob(cls.FileGlob)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -105,11 +110,12 @@ func catalog(resolver file.Resolver, cls Classifier) (packages []pkg.Package, er
|
|||||||
for _, location := range locations {
|
for _, location := range locations {
|
||||||
pkgs, err := cls.EvidenceMatcher(cls, matcherContext{resolver: resolver, location: location})
|
pkgs, err := cls.EvidenceMatcher(cls, matcherContext{resolver: resolver, location: location})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
errs = unknown.Append(errs, location, err)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
packages = append(packages, pkgs...)
|
packages = append(packages, pkgs...)
|
||||||
}
|
}
|
||||||
return packages, nil
|
return packages, errs
|
||||||
}
|
}
|
||||||
|
|
||||||
// packagesMatch returns true if the binary packages "match" based on basic criteria
|
// packagesMatch returns true if the binary packages "match" based on basic criteria
|
||||||
|
|||||||
@ -1668,7 +1668,7 @@ func Test_Cataloger_ResilientToErrors(t *testing.T) {
|
|||||||
|
|
||||||
resolver := &panicyResolver{}
|
resolver := &panicyResolver{}
|
||||||
_, _, err := c.Catalog(context.Background(), resolver)
|
_, _, err := c.Catalog(context.Background(), resolver)
|
||||||
assert.NoError(t, err)
|
assert.Error(t, err)
|
||||||
assert.True(t, resolver.searchCalled)
|
assert.True(t, resolver.searchCalled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/anchore/syft/internal"
|
"github.com/anchore/syft/internal"
|
||||||
"github.com/anchore/syft/internal/log"
|
"github.com/anchore/syft/internal/log"
|
||||||
"github.com/anchore/syft/internal/mimetype"
|
"github.com/anchore/syft/internal/mimetype"
|
||||||
|
"github.com/anchore/syft/internal/unknown"
|
||||||
"github.com/anchore/syft/syft/artifact"
|
"github.com/anchore/syft/syft/artifact"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
"github.com/anchore/syft/syft/internal/unionreader"
|
"github.com/anchore/syft/syft/internal/unionreader"
|
||||||
@ -52,6 +53,7 @@ func (c *elfPackageCataloger) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *elfPackageCataloger) Catalog(_ context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
|
func (c *elfPackageCataloger) Catalog(_ context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||||
|
var errs error
|
||||||
locations, err := resolver.FilesByMIMEType(mimetype.ExecutableMIMETypeSet.List()...)
|
locations, err := resolver.FilesByMIMEType(mimetype.ExecutableMIMETypeSet.List()...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("unable to get binary files by mime type: %w", err)
|
return nil, nil, fmt.Errorf("unable to get binary files by mime type: %w", err)
|
||||||
@ -62,7 +64,8 @@ func (c *elfPackageCataloger) Catalog(_ context.Context, resolver file.Resolver)
|
|||||||
for _, location := range locations {
|
for _, location := range locations {
|
||||||
notes, key, err := parseElfPackageNotes(resolver, location, c)
|
notes, key, err := parseElfPackageNotes(resolver, location, c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
errs = unknown.Append(errs, location, err)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
if notes == nil {
|
if notes == nil {
|
||||||
continue
|
continue
|
||||||
@ -87,7 +90,7 @@ func (c *elfPackageCataloger) Catalog(_ context.Context, resolver file.Resolver)
|
|||||||
// why not return relationships? We have an executable cataloger that will note the dynamic libraries imported by
|
// why not return relationships? We have an executable cataloger that will note the dynamic libraries imported by
|
||||||
// each binary. After all files and packages are processed there is a final task that creates package-to-package
|
// each binary. After all files and packages are processed there is a final task that creates package-to-package
|
||||||
// and package-to-file relationships based on the dynamic libraries imported by each binary.
|
// and package-to-file relationships based on the dynamic libraries imported by each binary.
|
||||||
return pkgs, nil, nil
|
return pkgs, nil, errs
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseElfPackageNotes(resolver file.Resolver, location file.Location, c *elfPackageCataloger) (*elfBinaryPackageNotes, elfPackageKey, error) {
|
func parseElfPackageNotes(resolver file.Resolver, location file.Location, c *elfPackageCataloger) (*elfBinaryPackageNotes, elfPackageKey, error) {
|
||||||
@ -104,7 +107,7 @@ func parseElfPackageNotes(resolver file.Resolver, location file.Location, c *elf
|
|||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields("file", location.Path(), "error", err).Trace("unable to parse ELF notes")
|
log.WithFields("file", location.Path(), "error", err).Trace("unable to parse ELF notes")
|
||||||
return nil, elfPackageKey{}, nil
|
return nil, elfPackageKey{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if notes == nil {
|
if notes == nil {
|
||||||
@ -173,7 +176,7 @@ func getELFNotes(r file.LocationReadCloser) (*elfBinaryPackageNotes, error) {
|
|||||||
if len(notes) > headerSize {
|
if len(notes) > headerSize {
|
||||||
var metadata elfBinaryPackageNotes
|
var metadata elfBinaryPackageNotes
|
||||||
newPayload := bytes.TrimRight(notes[headerSize:], "\x00")
|
newPayload := bytes.TrimRight(notes[headerSize:], "\x00")
|
||||||
if err := json.Unmarshal(newPayload, &metadata); err == nil {
|
if err = json.Unmarshal(newPayload, &metadata); err == nil {
|
||||||
return &metadata, nil
|
return &metadata, nil
|
||||||
}
|
}
|
||||||
log.WithFields("file", r.Location.Path(), "error", err).Trace("unable to unmarshal ELF package notes as JSON")
|
log.WithFields("file", r.Location.Path(), "error", err).Trace("unable to unmarshal ELF package notes as JSON")
|
||||||
|
|||||||
@ -3,6 +3,8 @@ package binary
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||||
@ -14,6 +16,7 @@ func Test_ELF_Package_Cataloger(t *testing.T) {
|
|||||||
name string
|
name string
|
||||||
fixture string
|
fixture string
|
||||||
expected []pkg.Package
|
expected []pkg.Package
|
||||||
|
wantErr require.ErrorAssertionFunc
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "go case",
|
name: "go case",
|
||||||
@ -63,6 +66,7 @@ func Test_ELF_Package_Cataloger(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
wantErr: require.Error,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "fedora 64 bit binaries",
|
name: "fedora 64 bit binaries",
|
||||||
@ -116,6 +120,7 @@ func Test_ELF_Package_Cataloger(t *testing.T) {
|
|||||||
WithImageResolver(t, v.fixture).
|
WithImageResolver(t, v.fixture).
|
||||||
IgnoreLocationLayer(). // this fixture can be rebuilt, thus the layer ID will change
|
IgnoreLocationLayer(). // this fixture can be rebuilt, thus the layer ID will change
|
||||||
Expects(v.expected, nil).
|
Expects(v.expected, nil).
|
||||||
|
WithErrorAssertion(v.wantErr).
|
||||||
TestCataloger(t, NewELFPackageCataloger())
|
TestCataloger(t, NewELFPackageCataloger())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,9 +5,11 @@ RUN dnf update -y; \
|
|||||||
dnf clean all
|
dnf clean all
|
||||||
RUN mkdir -p /usr/local/bin/elftests/elfbinwithnestedlib
|
RUN mkdir -p /usr/local/bin/elftests/elfbinwithnestedlib
|
||||||
RUN mkdir -p /usr/local/bin/elftests/elfbinwithsisterlib
|
RUN mkdir -p /usr/local/bin/elftests/elfbinwithsisterlib
|
||||||
|
RUN mkdir -p /usr/local/bin/elftests/elfbinwithcorrupt
|
||||||
|
|
||||||
COPY ./elfbinwithnestedlib /usr/local/bin/elftests/elfbinwithnestedlib
|
COPY ./elfbinwithnestedlib /usr/local/bin/elftests/elfbinwithnestedlib
|
||||||
COPY ./elfbinwithsisterlib /usr/local/bin/elftests/elfbinwithsisterlib
|
COPY ./elfbinwithsisterlib /usr/local/bin/elftests/elfbinwithsisterlib
|
||||||
|
COPY ./elfbinwithcorrupt /usr/local/bin/elftests/elfbinwithcorrupt
|
||||||
|
|
||||||
ENV LD_LIBRARY_PATH=/usr/local/bin/elftests/elfbinwithnestedlib/bin/lib
|
ENV LD_LIBRARY_PATH=/usr/local/bin/elftests/elfbinwithnestedlib/bin/lib
|
||||||
|
|
||||||
@ -16,6 +18,8 @@ RUN make
|
|||||||
|
|
||||||
WORKDIR /usr/local/bin/elftests/elfbinwithsisterlib
|
WORKDIR /usr/local/bin/elftests/elfbinwithsisterlib
|
||||||
RUN make
|
RUN make
|
||||||
|
WORKDIR /usr/local/bin/elftests/elfbinwithcorrupt
|
||||||
|
RUN make
|
||||||
|
|
||||||
# let's make the test image smaller, since we only require the built binaries and supporting libraries
|
# let's make the test image smaller, since we only require the built binaries and supporting libraries
|
||||||
FROM busybox:1.36.1-musl
|
FROM busybox:1.36.1-musl
|
||||||
|
|||||||
@ -0,0 +1,6 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#include "hello_world.h"
|
||||||
|
|
||||||
|
void print_hello_world() {
|
||||||
|
std::cout << "Hello, World!" << std::endl;
|
||||||
|
}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
#ifndef HELLO_WORLD_H
|
||||||
|
#define HELLO_WORLD_H
|
||||||
|
|
||||||
|
// Function declaration for printing "Hello, World!" to stdout
|
||||||
|
void print_hello_world();
|
||||||
|
|
||||||
|
#endif // HELLO_WORLD_H
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
LDFLAGS := -L/lib64 -lstdc++
|
||||||
|
|
||||||
|
SRC_DIR := ./
|
||||||
|
BUILD_DIR := ../build
|
||||||
|
BIN_DIR := ../bin
|
||||||
|
LIB_DIR := $(BIN_DIR)/lib
|
||||||
|
|
||||||
|
LIB_NAME := hello_world
|
||||||
|
LIB_SRC := $(SRC_DIR)/hello_world.cpp
|
||||||
|
LIB_OBJ := $(BUILD_DIR)/$(LIB_NAME).o
|
||||||
|
LIB_SO := $(LIB_DIR)/lib$(LIB_NAME).so
|
||||||
|
|
||||||
|
EXECUTABLE := elfbinwithnestedlib
|
||||||
|
EXEC_SRC := $(SRC_DIR)/testbin.cpp
|
||||||
|
EXEC_OBJ := $(BUILD_DIR)/$(EXECUTABLE).o
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
all: testfixture
|
||||||
|
|
||||||
|
$(LIB_SO): $(LIB_OBJ) | $(LIB_DIR)
|
||||||
|
$(CC) -shared -o $@ $<
|
||||||
|
echo '{ corrupt json "system": "syftsys","name": "libhello_world.so","version": "0.01","pure:0.01"}' | objcopy --add-section .note.package=/dev/stdin --set-section-flags .note.package=noload,readonly $@
|
||||||
|
|
||||||
|
$(LIB_OBJ): $(LIB_SRC) | $(BUILD_DIR)
|
||||||
|
$(CC) $(CFLAGS) -fPIC -c $< -o $@
|
||||||
|
|
||||||
|
$(EXEC_OBJ): $(EXEC_SRC) | $(BUILD_DIR)
|
||||||
|
$(CC) $(CFLAGS) -c $< -o $@
|
||||||
|
|
||||||
|
$(BIN_DIR):
|
||||||
|
mkdir -p $(BIN_DIR)
|
||||||
|
$(BUILD_DIR):
|
||||||
|
mkdir -p $(BUILD_DIR)
|
||||||
|
$(LIB_DIR):
|
||||||
|
mkdir -p $(LIB_DIR)
|
||||||
|
|
||||||
|
$(BIN_DIR)/$(EXECUTABLE): $(EXEC_OBJ) $(LIB_SO) | $(BIN_DIR)
|
||||||
|
$(CC) $(CFLAGS) -o $@ $^ -L$(LIB_DIR) -l$(LIB_NAME) $(LDFLAGS)
|
||||||
|
echo '{corrupt json ..._syfttestfixture:0.01"}' | objcopy --add-section .note.package=/dev/stdin --set-section-flags .note.package=noload,readonly $@
|
||||||
|
|
||||||
|
testfixture: $(BIN_DIR)/$(EXECUTABLE)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf $(BUILD_DIR) $(LIB_DIR) $(BIN_DIR) $(EXECUTABLE)
|
||||||
|
|
||||||
|
.PHONY: all clean
|
||||||
|
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
#include "hello_world.h"
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
// Call the function from the shared library
|
||||||
|
print_hello_world();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
CC = g++
|
||||||
|
CFLAGS = -std=c++17 -Wall -Wextra -pedantic
|
||||||
|
BUILD_DIR := ./build
|
||||||
|
BIN_DIR := ./bin
|
||||||
|
LIB_DIR := $(BIN_DIR)/lib
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
all: testfixtures
|
||||||
|
|
||||||
|
testfixtures:
|
||||||
|
$(MAKE) -C elfsrc
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf $(BUILD_DIR) $(BIN_DIR)
|
||||||
|
|
||||||
|
.PHONY: all clean testfixtures
|
||||||
|
|
||||||
@ -8,6 +8,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/internal/unknown"
|
||||||
"github.com/anchore/syft/syft/artifact"
|
"github.com/anchore/syft/syft/artifact"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
@ -25,7 +26,7 @@ func parseConanfile(_ context.Context, _ file.Resolver, _ *generic.Environment,
|
|||||||
line, err := r.ReadString('\n')
|
line, err := r.ReadString('\n')
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(err, io.EOF):
|
case errors.Is(err, io.EOF):
|
||||||
return pkgs, nil, nil
|
return pkgs, nil, unknown.IfEmptyf(pkgs, "unable to determine packages")
|
||||||
case err != nil:
|
case err != nil:
|
||||||
return nil, nil, fmt.Errorf("failed to parse conanfile.txt file: %w", err)
|
return nil, nil, fmt.Errorf("failed to parse conanfile.txt file: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/internal/unknown"
|
||||||
"github.com/anchore/syft/syft/artifact"
|
"github.com/anchore/syft/syft/artifact"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
@ -72,7 +73,7 @@ func parseConanLock(_ context.Context, _ file.Resolver, _ *generic.Environment,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return pkgs, relationships, nil
|
return pkgs, relationships, unknown.IfEmptyf(pkgs, "unable to determine packages")
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleConanLockV1 handles the parsing of conan lock v1 files (aka v0.4)
|
// handleConanLockV1 handles the parsing of conan lock v1 files (aka v0.4)
|
||||||
|
|||||||
@ -373,3 +373,10 @@ func TestParseConanLockV2(t *testing.T) {
|
|||||||
|
|
||||||
pkgtest.TestFileParser(t, fixture, parseConanLock, expected, expectedRelationships)
|
pkgtest.TestFileParser(t, fixture, parseConanLock, expected, expectedRelationships)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_corruptConanlock(t *testing.T) {
|
||||||
|
pkgtest.NewCatalogTester().
|
||||||
|
FromFile(t, "test-fixtures/corrupt/conan.lock").
|
||||||
|
WithError().
|
||||||
|
TestParser(t, parseConanLock)
|
||||||
|
}
|
||||||
|
|||||||
10
syft/pkg/cataloger/cpp/test-fixtures/corrupt/conan.lock
Normal file
10
syft/pkg/cataloger/cpp/test-fixtures/corrupt/conan.lock
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
corrupt json
|
||||||
|
version": "0.5",
|
||||||
|
"requires": [
|
||||||
|
"sound32/1.0#83d4b7bf607b3b60a6546f8b58b5cdd7%1675278904.0791488",
|
||||||
|
"matrix/1.1#905c3f0babc520684c84127378fefdd0%1675278901.7527816"
|
||||||
|
],
|
||||||
|
"build_requires": [],
|
||||||
|
"python_requires": []
|
||||||
|
}
|
||||||
@ -9,6 +9,7 @@ import (
|
|||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
"github.com/anchore/syft/internal/log"
|
"github.com/anchore/syft/internal/log"
|
||||||
|
"github.com/anchore/syft/internal/unknown"
|
||||||
"github.com/anchore/syft/syft/artifact"
|
"github.com/anchore/syft/syft/artifact"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
@ -85,7 +86,7 @@ func parsePubspecLock(_ context.Context, _ file.Resolver, _ *generic.Environment
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return pkgs, nil, nil
|
return pkgs, nil, unknown.IfEmptyf(pkgs, "unable to determine packages")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *pubspecLockPackage) getVcsURL() string {
|
func (p *pubspecLockPackage) getVcsURL() string {
|
||||||
|
|||||||
@ -106,3 +106,10 @@ func TestParsePubspecLock(t *testing.T) {
|
|||||||
|
|
||||||
pkgtest.TestFileParser(t, fixture, parsePubspecLock, expected, expectedRelationships)
|
pkgtest.TestFileParser(t, fixture, parsePubspecLock, expected, expectedRelationships)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_corruptPubspecLock(t *testing.T) {
|
||||||
|
pkgtest.NewCatalogTester().
|
||||||
|
FromFile(t, "test-fixtures/corrupt/pubspec.lock").
|
||||||
|
WithError().
|
||||||
|
TestParser(t, parsePubspecLock)
|
||||||
|
}
|
||||||
|
|||||||
@ -0,0 +1,7 @@
|
|||||||
|
pa
|
||||||
|
kages:
|
||||||
|
ale:
|
||||||
|
dependency: transitive
|
||||||
|
descr
|
||||||
|
s ps:
|
||||||
|
dart: ">=2.12.0 <3.0.0"
|
||||||
@ -15,6 +15,7 @@ import (
|
|||||||
|
|
||||||
"github.com/anchore/syft/internal"
|
"github.com/anchore/syft/internal"
|
||||||
"github.com/anchore/syft/internal/log"
|
"github.com/anchore/syft/internal/log"
|
||||||
|
"github.com/anchore/syft/internal/unknown"
|
||||||
"github.com/anchore/syft/syft/artifact"
|
"github.com/anchore/syft/syft/artifact"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
@ -39,7 +40,7 @@ func parseDpkgDB(_ context.Context, resolver file.Resolver, env *generic.Environ
|
|||||||
pkgs = append(pkgs, p)
|
pkgs = append(pkgs, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
return pkgs, nil, nil
|
return pkgs, nil, unknown.IfEmptyf(pkgs, "unable to determine packages")
|
||||||
}
|
}
|
||||||
|
|
||||||
func findDpkgInfoFiles(name string, resolver file.Resolver, dbLocation file.Location) []file.Location {
|
func findDpkgInfoFiles(name string, resolver file.Resolver, dbLocation file.Location) []file.Location {
|
||||||
|
|||||||
@ -257,6 +257,17 @@ func Test_parseDpkgStatus(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_corruptEntry(t *testing.T) {
|
||||||
|
f, err := os.Open("test-fixtures/var/lib/dpkg/status.d/corrupt")
|
||||||
|
require.NoError(t, err)
|
||||||
|
t.Cleanup(func() { require.NoError(t, f.Close()) })
|
||||||
|
|
||||||
|
reader := bufio.NewReader(f)
|
||||||
|
|
||||||
|
_, err = parseDpkgStatus(reader)
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
func TestSourceVersionExtract(t *testing.T) {
|
func TestSourceVersionExtract(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -312,7 +323,7 @@ func Test_parseDpkgStatus_negativeCases(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "no more packages",
|
name: "no more packages",
|
||||||
input: `Package: apt`,
|
input: `Package: apt`,
|
||||||
wantErr: require.NoError,
|
wantErr: requireAs(errors.New("unable to determine packages")),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "duplicated key",
|
name: "duplicated key",
|
||||||
|
|||||||
@ -0,0 +1,6 @@
|
|||||||
|
Pakij: apt
|
||||||
|
Stratus: install ok installed
|
||||||
|
Prioority: required
|
||||||
|
Section: admin
|
||||||
|
Insterface to the configuration settings
|
||||||
|
* apt-key as an interface to manage authentication keys
|
||||||
@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/anchore/syft/internal/log"
|
"github.com/anchore/syft/internal/log"
|
||||||
"github.com/anchore/syft/internal/relationship"
|
"github.com/anchore/syft/internal/relationship"
|
||||||
|
"github.com/anchore/syft/internal/unknown"
|
||||||
"github.com/anchore/syft/syft/artifact"
|
"github.com/anchore/syft/syft/artifact"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
@ -126,5 +127,5 @@ func parseDotnetDeps(_ context.Context, _ file.Resolver, _ *generic.Environment,
|
|||||||
// this will only consider package-to-package relationships.
|
// this will only consider package-to-package relationships.
|
||||||
relationship.Sort(relationships)
|
relationship.Sort(relationships)
|
||||||
|
|
||||||
return pkgs, relationships, nil
|
return pkgs, relationships, unknown.IfEmptyf(pkgs, "unable to determine packages")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,13 @@ import (
|
|||||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func Test_corruptDotnetDeps(t *testing.T) {
|
||||||
|
pkgtest.NewCatalogTester().
|
||||||
|
FromFile(t, "test-fixtures/glob-paths/src/something.deps.json").
|
||||||
|
WithError().
|
||||||
|
TestParser(t, parseDotnetDeps)
|
||||||
|
}
|
||||||
|
|
||||||
func TestParseDotnetDeps(t *testing.T) {
|
func TestParseDotnetDeps(t *testing.T) {
|
||||||
fixture := "test-fixtures/TestLibrary.deps.json"
|
fixture := "test-fixtures/TestLibrary.deps.json"
|
||||||
fixtureLocationSet := file.NewLocationSet(file.NewLocation(fixture))
|
fixtureLocationSet := file.NewLocationSet(file.NewLocation(fixture))
|
||||||
|
|||||||
@ -28,30 +28,26 @@ func parseDotnetPortableExecutable(_ context.Context, _ file.Resolver, _ *generi
|
|||||||
|
|
||||||
peFile, err := pe.NewBytes(by, &pe.Options{})
|
peFile, err := pe.NewBytes(by, &pe.Options{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO: known-unknown
|
|
||||||
log.Tracef("unable to create PE instance for file '%s': %v", f.RealPath, err)
|
log.Tracef("unable to create PE instance for file '%s': %v", f.RealPath, err)
|
||||||
return nil, nil, nil
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = peFile.Parse()
|
err = peFile.Parse()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO: known-unknown
|
|
||||||
log.Tracef("unable to parse PE file '%s': %v", f.RealPath, err)
|
log.Tracef("unable to parse PE file '%s': %v", f.RealPath, err)
|
||||||
return nil, nil, nil
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
versionResources, err := peFile.ParseVersionResources()
|
versionResources, err := peFile.ParseVersionResources()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO: known-unknown
|
|
||||||
log.Tracef("unable to parse version resources in PE file: %s: %v", f.RealPath, err)
|
log.Tracef("unable to parse version resources in PE file: %s: %v", f.RealPath, err)
|
||||||
return nil, nil, nil
|
return nil, nil, fmt.Errorf("unable to parse version resources in PE file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
dotNetPkg, err := buildDotNetPackage(versionResources, f)
|
dotNetPkg, err := buildDotNetPackage(versionResources, f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO: known-unknown
|
log.Tracef("unable to build dotnet package for: %v %v", f.RealPath, err)
|
||||||
log.Tracef("unable to build dotnet package: %v", err)
|
return nil, nil, err
|
||||||
return nil, nil, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return []pkg.Package{dotNetPkg}, nil, nil
|
return []pkg.Package{dotNetPkg}, nil, nil
|
||||||
@ -60,12 +56,12 @@ func parseDotnetPortableExecutable(_ context.Context, _ file.Resolver, _ *generi
|
|||||||
func buildDotNetPackage(versionResources map[string]string, f file.LocationReadCloser) (dnpkg pkg.Package, err error) {
|
func buildDotNetPackage(versionResources map[string]string, f file.LocationReadCloser) (dnpkg pkg.Package, err error) {
|
||||||
name := findName(versionResources)
|
name := findName(versionResources)
|
||||||
if name == "" {
|
if name == "" {
|
||||||
return dnpkg, fmt.Errorf("unable to find PE name in file: %s", f.RealPath)
|
return dnpkg, fmt.Errorf("unable to find PE name in file")
|
||||||
}
|
}
|
||||||
|
|
||||||
version := findVersion(versionResources)
|
version := findVersion(versionResources)
|
||||||
if version == "" {
|
if version == "" {
|
||||||
return dnpkg, fmt.Errorf("unable to find PE version in file: %s", f.RealPath)
|
return dnpkg, fmt.Errorf("unable to find PE version in file")
|
||||||
}
|
}
|
||||||
|
|
||||||
metadata := pkg.DotnetPortableExecutableEntry{
|
metadata := pkg.DotnetPortableExecutableEntry{
|
||||||
|
|||||||
@ -297,6 +297,13 @@ func TestParseDotnetPortableExecutable(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_corruptDotnetPE(t *testing.T) {
|
||||||
|
pkgtest.NewCatalogTester().
|
||||||
|
FromFile(t, "test-fixtures/glob-paths/src/something.exe").
|
||||||
|
WithError().
|
||||||
|
TestParser(t, parseDotnetPortableExecutable)
|
||||||
|
}
|
||||||
|
|
||||||
func Test_extractVersion(t *testing.T) {
|
func Test_extractVersion(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
input string
|
input string
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
"github.com/anchore/syft/internal/log"
|
"github.com/anchore/syft/internal/log"
|
||||||
|
"github.com/anchore/syft/internal/unknown"
|
||||||
"github.com/anchore/syft/syft/artifact"
|
"github.com/anchore/syft/syft/artifact"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
@ -22,25 +23,33 @@ var mixLockDelimiter = regexp.MustCompile(`[%{}\n" ,:]+`)
|
|||||||
|
|
||||||
// parseMixLock parses a mix.lock and returns the discovered Elixir packages.
|
// parseMixLock parses a mix.lock and returns the discovered Elixir packages.
|
||||||
func parseMixLock(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
func parseMixLock(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||||
|
var errs error
|
||||||
r := bufio.NewReader(reader)
|
r := bufio.NewReader(reader)
|
||||||
|
|
||||||
var packages []pkg.Package
|
var packages []pkg.Package
|
||||||
|
lineNum := 0
|
||||||
for {
|
for {
|
||||||
|
lineNum++
|
||||||
line, err := r.ReadString('\n')
|
line, err := r.ReadString('\n')
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(err, io.EOF):
|
case errors.Is(err, io.EOF):
|
||||||
return packages, nil, nil
|
if errs == nil {
|
||||||
|
errs = unknown.IfEmptyf(packages, "unable to determine packages")
|
||||||
|
}
|
||||||
|
return packages, nil, errs
|
||||||
case err != nil:
|
case err != nil:
|
||||||
return nil, nil, fmt.Errorf("failed to parse mix.lock file: %w", err)
|
return nil, nil, fmt.Errorf("failed to parse mix.lock file: %w", err)
|
||||||
}
|
}
|
||||||
tokens := mixLockDelimiter.Split(line, -1)
|
tokens := mixLockDelimiter.Split(line, -1)
|
||||||
if len(tokens) < 6 {
|
if len(tokens) < 6 {
|
||||||
|
errs = unknown.Appendf(errs, reader, "unable to read mix lock line %d: %s", lineNum, line)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
name, version, hash, hashExt := tokens[1], tokens[4], tokens[5], tokens[len(tokens)-2]
|
name, version, hash, hashExt := tokens[1], tokens[4], tokens[5], tokens[len(tokens)-2]
|
||||||
|
|
||||||
if name == "" {
|
if name == "" {
|
||||||
log.WithFields("path", reader.RealPath).Debug("skipping empty package name from mix.lock file")
|
log.WithFields("path", reader.RealPath).Debug("skipping empty package name from mix.lock file")
|
||||||
|
errs = unknown.Appendf(errs, reader, "skipping empty package name from mix.lock file, for line: %d: %s", lineNum, line)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package erlang
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/anchore/syft/internal/log"
|
"github.com/anchore/syft/internal/log"
|
||||||
"github.com/anchore/syft/syft/artifact"
|
"github.com/anchore/syft/syft/artifact"
|
||||||
@ -17,7 +18,7 @@ func parseOTPApp(_ context.Context, _ file.Resolver, _ *generic.Environment, rea
|
|||||||
// there are multiple file formats that use the *.app extension, so it's possible that this is not an OTP app file at all
|
// there are multiple file formats that use the *.app extension, so it's possible that this is not an OTP app file at all
|
||||||
// ... which means we should not return an error here
|
// ... which means we should not return an error here
|
||||||
log.WithFields("error", err).Trace("unable to parse Erlang OTP app")
|
log.WithFields("error", err).Trace("unable to parse Erlang OTP app")
|
||||||
return nil, nil, nil
|
return nil, nil, fmt.Errorf("unable to parse Erlang OTP app")
|
||||||
}
|
}
|
||||||
|
|
||||||
var packages []pkg.Package
|
var packages []pkg.Package
|
||||||
|
|||||||
@ -41,3 +41,10 @@ func TestParseOTPApplication(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_corruptOtpApp(t *testing.T) {
|
||||||
|
pkgtest.NewCatalogTester().
|
||||||
|
FromFile(t, "test-fixtures/corrupt/rabbitmq.app").
|
||||||
|
WithError().
|
||||||
|
TestParser(t, parseOTPApp)
|
||||||
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/anchore/syft/internal/log"
|
"github.com/anchore/syft/internal/log"
|
||||||
|
"github.com/anchore/syft/internal/unknown"
|
||||||
"github.com/anchore/syft/syft/artifact"
|
"github.com/anchore/syft/syft/artifact"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
@ -95,7 +96,7 @@ func parseRebarLock(_ context.Context, _ file.Resolver, _ *generic.Environment,
|
|||||||
p.SetID()
|
p.SetID()
|
||||||
packages = append(packages, *p)
|
packages = append(packages, *p)
|
||||||
}
|
}
|
||||||
return packages, nil, nil
|
return packages, nil, unknown.IfEmptyf(packages, "unable to determine packages")
|
||||||
}
|
}
|
||||||
|
|
||||||
// integrity check
|
// integrity check
|
||||||
|
|||||||
@ -255,3 +255,10 @@ func TestParseRebarLock(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_corruptRebarLock(t *testing.T) {
|
||||||
|
pkgtest.NewCatalogTester().
|
||||||
|
FromFile(t, "test-fixtures/corrupt/rebar.lock").
|
||||||
|
WithError().
|
||||||
|
TestParser(t, parseRebarLock)
|
||||||
|
}
|
||||||
|
|||||||
@ -0,0 +1,9 @@
|
|||||||
|
cation, 'rabbit', [
|
||||||
|
{description, "RabbitMQ"},
|
||||||
|
{vsn, "3.12.10"},
|
||||||
|
{id, "v3.12.9-9-g1f61ca8"},
|
||||||
|
{modules, ['amqqueue','background_gc']},
|
||||||
|
{optional_itmq-server#1593
|
||||||
|
{channel_max, 2047}
|
||||||
|
]}
|
||||||
|
]}.
|
||||||
11
syft/pkg/cataloger/erlang/test-fixtures/corrupt/rebar.lock
Normal file
11
syft/pkg/cataloger/erlang/test-fixtures/corrupt/rebar.lock
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{"1.2.0",
|
||||||
|
[{<<"certifi{pkg,<<"certifi">>,<<"2.9.0">>},0},
|
||||||
|
{<<"idna">>,{pkg,<<"idpkg,<<"parse_trans">>,<<"3.3.1">>},0},
|
||||||
|
{<<"ssl_verify_fun">>,{pkg,<<"ssl_verify_fun">>,<<"1.1.6">>},0},
|
||||||
|
{<<"unicode_util_compat">>,{pkg,<<"unicode_util_compat">>,<<"0.7.0">>},0}]}.
|
||||||
|
[
|
||||||
|
{pkg_hash,[
|
||||||
|
{<<"certifi">>, <<"6F2A475689DD47F19FB74334859D460A2DC4E3252A3324BD2111B8F0429E7E21">>},
|
||||||
|
{<<"idna">>, <<"8A63070E9F7D0C62EB9D9FCB360A7DE382448200FBBD1B106CC96D3D8099DF8D">>},
|
||||||
|
{<<"metrics">>, <<"25F094DEA2CDA98213CECC3AEFF09E940299D950904393B2A29D191C346A8486">>},
|
||||||
|
{<<"mimerl
|
||||||
@ -6,6 +6,7 @@ import (
|
|||||||
"github.com/anchore/go-logger"
|
"github.com/anchore/go-logger"
|
||||||
"github.com/anchore/syft/internal"
|
"github.com/anchore/syft/internal"
|
||||||
"github.com/anchore/syft/internal/log"
|
"github.com/anchore/syft/internal/log"
|
||||||
|
"github.com/anchore/syft/internal/unknown"
|
||||||
"github.com/anchore/syft/syft/artifact"
|
"github.com/anchore/syft/syft/artifact"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
"github.com/anchore/syft/syft/linux"
|
"github.com/anchore/syft/syft/linux"
|
||||||
@ -151,6 +152,7 @@ func (c *Cataloger) Name() string {
|
|||||||
func (c *Cataloger) Catalog(ctx context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
|
func (c *Cataloger) Catalog(ctx context.Context, resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||||
var packages []pkg.Package
|
var packages []pkg.Package
|
||||||
var relationships []artifact.Relationship
|
var relationships []artifact.Relationship
|
||||||
|
var errs error
|
||||||
|
|
||||||
logger := log.Nested("cataloger", c.upstreamCataloger)
|
logger := log.Nested("cataloger", c.upstreamCataloger)
|
||||||
|
|
||||||
@ -166,7 +168,8 @@ func (c *Cataloger) Catalog(ctx context.Context, resolver file.Resolver) ([]pkg.
|
|||||||
|
|
||||||
discoveredPackages, discoveredRelationships, err := invokeParser(ctx, resolver, location, logger, parser, &env)
|
discoveredPackages, discoveredRelationships, err := invokeParser(ctx, resolver, location, logger, parser, &env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue // logging is handled within invokeParser
|
// parsers may return errors and valid packages / relationships
|
||||||
|
errs = unknown.Append(errs, location, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, p := range discoveredPackages {
|
for _, p := range discoveredPackages {
|
||||||
@ -176,7 +179,7 @@ func (c *Cataloger) Catalog(ctx context.Context, resolver file.Resolver) ([]pkg.
|
|||||||
|
|
||||||
relationships = append(relationships, discoveredRelationships...)
|
relationships = append(relationships, discoveredRelationships...)
|
||||||
}
|
}
|
||||||
return c.process(ctx, resolver, packages, relationships, nil)
|
return c.process(ctx, resolver, packages, relationships, errs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Cataloger) process(ctx context.Context, resolver file.Resolver, pkgs []pkg.Package, rels []artifact.Relationship, err error) ([]pkg.Package, []artifact.Relationship, error) {
|
func (c *Cataloger) process(ctx context.Context, resolver file.Resolver, pkgs []pkg.Package, rels []artifact.Relationship, err error) ([]pkg.Package, []artifact.Relationship, error) {
|
||||||
@ -196,11 +199,11 @@ func invokeParser(ctx context.Context, resolver file.Resolver, location file.Loc
|
|||||||
|
|
||||||
discoveredPackages, discoveredRelationships, err := parser(ctx, resolver, env, file.NewLocationReadCloser(location, contentReader))
|
discoveredPackages, discoveredRelationships, err := parser(ctx, resolver, env, file.NewLocationReadCloser(location, contentReader))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.WithFields("location", location.RealPath, "error", err).Warnf("cataloger failed")
|
// these errors are propagated up, and are likely to be coordinate errors
|
||||||
return nil, nil, err
|
logger.WithFields("location", location.RealPath, "error", err).Trace("cataloger returned errors")
|
||||||
}
|
}
|
||||||
|
|
||||||
return discoveredPackages, discoveredRelationships, nil
|
return discoveredPackages, discoveredRelationships, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// selectFiles takes a set of file trees and resolves and file references of interest for future cataloging
|
// selectFiles takes a set of file trees and resolves and file references of interest for future cataloging
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/internal/unknown"
|
||||||
"github.com/anchore/syft/syft/artifact"
|
"github.com/anchore/syft/syft/artifact"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
@ -187,3 +188,27 @@ func TestClosesFileOnParserPanic(t *testing.T) {
|
|||||||
})
|
})
|
||||||
require.True(t, spy.closed)
|
require.True(t, spy.closed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_genericCatalogerReturnsErrors(t *testing.T) {
|
||||||
|
genericErrorReturning := NewCataloger("error returning").WithParserByGlobs(func(ctx context.Context, resolver file.Resolver, environment *Environment, locationReader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||||
|
return []pkg.Package{
|
||||||
|
{
|
||||||
|
Name: "some-package-" + locationReader.Path(),
|
||||||
|
},
|
||||||
|
}, nil, unknown.Newf(locationReader, "unable to read")
|
||||||
|
}, "**/*")
|
||||||
|
|
||||||
|
m := file.NewMockResolverForPaths(
|
||||||
|
"test-fixtures/a-path.txt",
|
||||||
|
"test-fixtures/empty.txt",
|
||||||
|
)
|
||||||
|
|
||||||
|
got, _, errs := genericErrorReturning.Catalog(context.TODO(), m)
|
||||||
|
|
||||||
|
// require packages and errors
|
||||||
|
require.NotEmpty(t, got)
|
||||||
|
|
||||||
|
unknowns, others := unknown.ExtractCoordinateErrors(errs)
|
||||||
|
require.NotEmpty(t, unknowns)
|
||||||
|
require.Empty(t, others)
|
||||||
|
}
|
||||||
|
|||||||
@ -35,7 +35,7 @@ func parsePortageContents(_ context.Context, resolver file.Resolver, _ *generic.
|
|||||||
name, version := cpvMatch[1], cpvMatch[2]
|
name, version := cpvMatch[1], cpvMatch[2]
|
||||||
if name == "" || version == "" {
|
if name == "" || version == "" {
|
||||||
log.WithFields("path", reader.Location.RealPath).Warnf("failed to parse portage name and version")
|
log.WithFields("path", reader.Location.RealPath).Warnf("failed to parse portage name and version")
|
||||||
return nil, nil, nil
|
return nil, nil, fmt.Errorf("failed to parse portage name and version")
|
||||||
}
|
}
|
||||||
|
|
||||||
p := pkg.Package{
|
p := pkg.Package{
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package githubactions
|
package githubactions
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/anchore/packageurl-go"
|
"github.com/anchore/packageurl-go"
|
||||||
@ -9,19 +10,19 @@ import (
|
|||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newPackageFromUsageStatement(use string, location file.Location) *pkg.Package {
|
func newPackageFromUsageStatement(use string, location file.Location) (*pkg.Package, error) {
|
||||||
name, version := parseStepUsageStatement(use)
|
name, version := parseStepUsageStatement(use)
|
||||||
|
|
||||||
if name == "" {
|
if name == "" {
|
||||||
log.WithFields("file", location.RealPath, "statement", use).Trace("unable to parse github action usage statement")
|
log.WithFields("file", location.RealPath, "statement", use).Trace("unable to parse github action usage statement")
|
||||||
return nil
|
return nil, fmt.Errorf("unable to parse github action usage statement")
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(name, ".github/workflows/") {
|
if strings.Contains(name, ".github/workflows/") {
|
||||||
return newGithubActionWorkflowPackageUsage(name, version, location)
|
return newGithubActionWorkflowPackageUsage(name, version, location), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return newGithubActionPackageUsage(name, version, location)
|
return newGithubActionPackageUsage(name, version, location), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newGithubActionWorkflowPackageUsage(name, version string, workflowLocation file.Location) *pkg.Package {
|
func newGithubActionWorkflowPackageUsage(name, version string, workflowLocation file.Location) *pkg.Package {
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/internal/unknown"
|
||||||
"github.com/anchore/syft/syft/artifact"
|
"github.com/anchore/syft/syft/artifact"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
@ -24,14 +25,14 @@ type compositeActionRunsDef struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func parseCompositeActionForActionUsage(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
func parseCompositeActionForActionUsage(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||||
contents, err := io.ReadAll(reader)
|
contents, errs := io.ReadAll(reader)
|
||||||
if err != nil {
|
if errs != nil {
|
||||||
return nil, nil, fmt.Errorf("unable to read yaml composite action file: %w", err)
|
return nil, nil, fmt.Errorf("unable to read yaml composite action file: %w", errs)
|
||||||
}
|
}
|
||||||
|
|
||||||
var ca compositeActionDef
|
var ca compositeActionDef
|
||||||
if err = yaml.Unmarshal(contents, &ca); err != nil {
|
if errs = yaml.Unmarshal(contents, &ca); errs != nil {
|
||||||
return nil, nil, fmt.Errorf("unable to parse yaml composite action file: %w", err)
|
return nil, nil, fmt.Errorf("unable to parse yaml composite action file: %w", errs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// we use a collection to help with deduplication before raising to higher level processing
|
// we use a collection to help with deduplication before raising to higher level processing
|
||||||
@ -42,11 +43,14 @@ func parseCompositeActionForActionUsage(_ context.Context, _ file.Resolver, _ *g
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
p := newPackageFromUsageStatement(step.Uses, reader.Location)
|
p, err := newPackageFromUsageStatement(step.Uses, reader.Location)
|
||||||
|
if err != nil {
|
||||||
|
errs = unknown.Append(errs, reader, err)
|
||||||
|
}
|
||||||
if p != nil {
|
if p != nil {
|
||||||
pkgs.Add(*p)
|
pkgs.Add(*p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return pkgs.Sorted(), nil, nil
|
return pkgs.Sorted(), nil, errs
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,3 +33,10 @@ func Test_parseCompositeActionForActionUsage(t *testing.T) {
|
|||||||
var expectedRelationships []artifact.Relationship
|
var expectedRelationships []artifact.Relationship
|
||||||
pkgtest.TestFileParser(t, fixture, parseCompositeActionForActionUsage, expected, expectedRelationships)
|
pkgtest.TestFileParser(t, fixture, parseCompositeActionForActionUsage, expected, expectedRelationships)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_corruptCompositeAction(t *testing.T) {
|
||||||
|
pkgtest.NewCatalogTester().
|
||||||
|
FromFile(t, "test-fixtures/corrupt/composite-action.yaml").
|
||||||
|
WithError().
|
||||||
|
TestParser(t, parseCompositeActionForActionUsage)
|
||||||
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/internal/unknown"
|
||||||
"github.com/anchore/syft/syft/artifact"
|
"github.com/anchore/syft/syft/artifact"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
@ -37,14 +38,14 @@ type stepDef struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func parseWorkflowForWorkflowUsage(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
func parseWorkflowForWorkflowUsage(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||||
contents, err := io.ReadAll(reader)
|
contents, errs := io.ReadAll(reader)
|
||||||
if err != nil {
|
if errs != nil {
|
||||||
return nil, nil, fmt.Errorf("unable to read yaml workflow file: %w", err)
|
return nil, nil, fmt.Errorf("unable to read yaml workflow file: %w", errs)
|
||||||
}
|
}
|
||||||
|
|
||||||
var wf workflowDef
|
var wf workflowDef
|
||||||
if err = yaml.Unmarshal(contents, &wf); err != nil {
|
if errs = yaml.Unmarshal(contents, &wf); errs != nil {
|
||||||
return nil, nil, fmt.Errorf("unable to parse yaml workflow file: %w", err)
|
return nil, nil, fmt.Errorf("unable to parse yaml workflow file: %w", errs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// we use a collection to help with deduplication before raising to higher level processing
|
// we use a collection to help with deduplication before raising to higher level processing
|
||||||
@ -52,25 +53,28 @@ func parseWorkflowForWorkflowUsage(_ context.Context, _ file.Resolver, _ *generi
|
|||||||
|
|
||||||
for _, job := range wf.Jobs {
|
for _, job := range wf.Jobs {
|
||||||
if job.Uses != "" {
|
if job.Uses != "" {
|
||||||
p := newPackageFromUsageStatement(job.Uses, reader.Location)
|
p, err := newPackageFromUsageStatement(job.Uses, reader.Location)
|
||||||
|
if err != nil {
|
||||||
|
errs = unknown.Append(errs, reader, err)
|
||||||
|
}
|
||||||
if p != nil {
|
if p != nil {
|
||||||
pkgs.Add(*p)
|
pkgs.Add(*p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return pkgs.Sorted(), nil, nil
|
return pkgs.Sorted(), nil, errs
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseWorkflowForActionUsage(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
func parseWorkflowForActionUsage(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||||
contents, err := io.ReadAll(reader)
|
contents, errs := io.ReadAll(reader)
|
||||||
if err != nil {
|
if errs != nil {
|
||||||
return nil, nil, fmt.Errorf("unable to read yaml workflow file: %w", err)
|
return nil, nil, fmt.Errorf("unable to read yaml workflow file: %w", errs)
|
||||||
}
|
}
|
||||||
|
|
||||||
var wf workflowDef
|
var wf workflowDef
|
||||||
if err = yaml.Unmarshal(contents, &wf); err != nil {
|
if errs = yaml.Unmarshal(contents, &wf); errs != nil {
|
||||||
return nil, nil, fmt.Errorf("unable to parse yaml workflow file: %w", err)
|
return nil, nil, fmt.Errorf("unable to parse yaml workflow file: %w", errs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// we use a collection to help with deduplication before raising to higher level processing
|
// we use a collection to help with deduplication before raising to higher level processing
|
||||||
@ -81,12 +85,15 @@ func parseWorkflowForActionUsage(_ context.Context, _ file.Resolver, _ *generic.
|
|||||||
if step.Uses == "" {
|
if step.Uses == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
p := newPackageFromUsageStatement(step.Uses, reader.Location)
|
p, err := newPackageFromUsageStatement(step.Uses, reader.Location)
|
||||||
|
if err != nil {
|
||||||
|
errs = unknown.Append(errs, reader, err)
|
||||||
|
}
|
||||||
if p != nil {
|
if p != nil {
|
||||||
pkgs.Add(*p)
|
pkgs.Add(*p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return pkgs.Sorted(), nil, nil
|
return pkgs.Sorted(), nil, errs
|
||||||
}
|
}
|
||||||
|
|||||||
@ -86,3 +86,17 @@ func Test_parseWorkflowForWorkflowUsage(t *testing.T) {
|
|||||||
var expectedRelationships []artifact.Relationship
|
var expectedRelationships []artifact.Relationship
|
||||||
pkgtest.TestFileParser(t, fixture, parseWorkflowForWorkflowUsage, expected, expectedRelationships)
|
pkgtest.TestFileParser(t, fixture, parseWorkflowForWorkflowUsage, expected, expectedRelationships)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_corruptActionWorkflow(t *testing.T) {
|
||||||
|
pkgtest.NewCatalogTester().
|
||||||
|
FromFile(t, "test-fixtures/corrupt/workflow-multi-job.yaml").
|
||||||
|
WithError().
|
||||||
|
TestParser(t, parseWorkflowForActionUsage)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_corruptWorkflowWorkflow(t *testing.T) {
|
||||||
|
pkgtest.NewCatalogTester().
|
||||||
|
FromFile(t, "test-fixtures/corrupt/workflow-multi-job.yaml").
|
||||||
|
WithError().
|
||||||
|
TestParser(t, parseWorkflowForWorkflowUsage)
|
||||||
|
}
|
||||||
|
|||||||
@ -0,0 +1,13 @@
|
|||||||
|
name: "Bootstrap"
|
||||||
|
description: "Bootstrap all tools and dependencies"
|
||||||
|
ints:
|
||||||
|
go-version:
|
||||||
|
descrapt-packages:
|
||||||
|
description: "Space delimited list of tools to install via apt"
|
||||||
|
default: "libxml2-utils"
|
||||||
|
|
||||||
|
rns:
|
||||||
|
us all cache fingerprints
|
||||||
|
shell: bash
|
||||||
|
run: make fingerprints
|
||||||
|
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
name: "Validations"
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jbs
|
||||||
|
|
||||||
|
Statnapshot
|
||||||
|
key: snapshot-build-${{ github.run_id }}
|
||||||
|
|
||||||
|
- name: Run CLI Tests (Linux)
|
||||||
|
run: make cli
|
||||||
@ -66,9 +66,9 @@ func (c *goBinaryCataloger) parseGoBinary(_ context.Context, resolver file.Resol
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
defer internal.CloseAndLogError(reader.ReadCloser, reader.RealPath)
|
||||||
|
|
||||||
mods := scanFile(unionReader, reader.RealPath)
|
mods, errs := scanFile(reader.Location, unionReader)
|
||||||
internal.CloseAndLogError(reader.ReadCloser, reader.RealPath)
|
|
||||||
|
|
||||||
var rels []artifact.Relationship
|
var rels []artifact.Relationship
|
||||||
for _, mod := range mods {
|
for _, mod := range mods {
|
||||||
@ -81,7 +81,7 @@ func (c *goBinaryCataloger) parseGoBinary(_ context.Context, resolver file.Resol
|
|||||||
pkgs = append(pkgs, depPkgs...)
|
pkgs = append(pkgs, depPkgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return pkgs, rels, nil
|
return pkgs, rels, errs
|
||||||
}
|
}
|
||||||
|
|
||||||
func createModuleRelationships(main pkg.Package, deps []pkg.Package) []artifact.Relationship {
|
func createModuleRelationships(main pkg.Package, deps []pkg.Package) []artifact.Relationship {
|
||||||
|
|||||||
@ -157,3 +157,11 @@ func Test_GoSumHashes(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_corruptGoMod(t *testing.T) {
|
||||||
|
c := NewGoModuleFileCataloger(DefaultCatalogerConfig().WithSearchRemoteLicenses(false))
|
||||||
|
pkgtest.NewCatalogTester().
|
||||||
|
FromDirectory(t, "test-fixtures/corrupt").
|
||||||
|
WithError().
|
||||||
|
TestCataloger(t, c)
|
||||||
|
}
|
||||||
|
|||||||
@ -9,6 +9,8 @@ import (
|
|||||||
"github.com/kastenhq/goversion/version"
|
"github.com/kastenhq/goversion/version"
|
||||||
|
|
||||||
"github.com/anchore/syft/internal/log"
|
"github.com/anchore/syft/internal/log"
|
||||||
|
"github.com/anchore/syft/internal/unknown"
|
||||||
|
"github.com/anchore/syft/syft/file"
|
||||||
"github.com/anchore/syft/syft/internal/unionreader"
|
"github.com/anchore/syft/syft/internal/unionreader"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -19,20 +21,21 @@ type extendedBuildInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// scanFile scans file to try to report the Go and module versions.
|
// scanFile scans file to try to report the Go and module versions.
|
||||||
func scanFile(reader unionreader.UnionReader, filename string) []*extendedBuildInfo {
|
func scanFile(location file.Location, reader unionreader.UnionReader) ([]*extendedBuildInfo, error) {
|
||||||
// NOTE: multiple readers are returned to cover universal binaries, which are files
|
// NOTE: multiple readers are returned to cover universal binaries, which are files
|
||||||
// with more than one binary
|
// with more than one binary
|
||||||
readers, err := unionreader.GetReaders(reader)
|
readers, errs := unionreader.GetReaders(reader)
|
||||||
if err != nil {
|
if errs != nil {
|
||||||
log.WithFields("error", err).Warnf("failed to open a golang binary")
|
log.WithFields("error", errs).Warnf("failed to open a golang binary")
|
||||||
return nil
|
return nil, fmt.Errorf("failed to open a golang binary: %w", errs)
|
||||||
}
|
}
|
||||||
|
|
||||||
var builds []*extendedBuildInfo
|
var builds []*extendedBuildInfo
|
||||||
for _, r := range readers {
|
for _, r := range readers {
|
||||||
bi, err := getBuildInfo(r)
|
bi, err := getBuildInfo(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields("file", filename, "error", err).Trace("unable to read golang buildinfo")
|
log.WithFields("file", location.RealPath, "error", err).Trace("unable to read golang buildinfo")
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// it's possible the reader just isn't a go binary, in which case just skip it
|
// it's possible the reader just isn't a go binary, in which case just skip it
|
||||||
@ -42,23 +45,25 @@ func scanFile(reader unionreader.UnionReader, filename string) []*extendedBuildI
|
|||||||
|
|
||||||
v, err := getCryptoInformation(r)
|
v, err := getCryptoInformation(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields("file", filename, "error", err).Trace("unable to read golang version info")
|
log.WithFields("file", location.RealPath, "error", err).Trace("unable to read golang version info")
|
||||||
// don't skip this build info.
|
// don't skip this build info.
|
||||||
// we can still catalog packages, even if we can't get the crypto information
|
// we can still catalog packages, even if we can't get the crypto information
|
||||||
|
errs = unknown.Appendf(errs, location, "unable to read golang version info: %w", err)
|
||||||
}
|
}
|
||||||
arch := getGOARCH(bi.Settings)
|
arch := getGOARCH(bi.Settings)
|
||||||
if arch == "" {
|
if arch == "" {
|
||||||
arch, err = getGOARCHFromBin(r)
|
arch, err = getGOARCHFromBin(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields("file", filename, "error", err).Trace("unable to read golang arch info")
|
log.WithFields("file", location.RealPath, "error", err).Trace("unable to read golang arch info")
|
||||||
// don't skip this build info.
|
// don't skip this build info.
|
||||||
// we can still catalog packages, even if we can't get the arch information
|
// we can still catalog packages, even if we can't get the arch information
|
||||||
|
errs = unknown.Appendf(errs, location, "unable to read golang arch info: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
builds = append(builds, &extendedBuildInfo{BuildInfo: bi, cryptoSettings: v, arch: arch})
|
builds = append(builds, &extendedBuildInfo{BuildInfo: bi, cryptoSettings: v, arch: arch})
|
||||||
}
|
}
|
||||||
return builds
|
return builds, errs
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCryptoInformation(reader io.ReaderAt) ([]string, error) {
|
func getCryptoInformation(reader io.ReaderAt) ([]string, error) {
|
||||||
|
|||||||
11
syft/pkg/cataloger/golang/test-fixtures/corrupt/go.mod
Normal file
11
syft/pkg/cataloger/golang/test-fixtures/corrupt/go.mod
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
module github.com/anchore/syft
|
||||||
|
|
||||||
|
go 1.18
|
||||||
|
|
||||||
|
ruire (
|
||||||
|
github.com/CycloneDX/cyclonedx-go v0.7.0
|
||||||
|
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
|
||||||
|
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
replace github.com/CycloneDX/cyclonedx-go => github.com/CycloneDX/cyclonedx-go v0.6.0
|
||||||
4
syft/pkg/cataloger/golang/test-fixtures/corrupt/go.sum
Normal file
4
syft/pkg/cataloger/golang/test-fixtures/corrupt/go.sum
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
github.com/CycloneDX/cyclonedx-go v0.6.0/go.mod h1:nQCiF4Tvrg5Ieu8qPhYMvzPGMu5I7fANZkrSsJjl5mg=
|
||||||
|
github.com/Cycpansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
|
||||||
|
github6IF
|
||||||
|
github.com/stretchr/test4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
@ -9,6 +9,7 @@ import (
|
|||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
"github.com/anchore/syft/internal/log"
|
"github.com/anchore/syft/internal/log"
|
||||||
|
"github.com/anchore/syft/internal/unknown"
|
||||||
"github.com/anchore/syft/syft/artifact"
|
"github.com/anchore/syft/syft/artifact"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
@ -50,7 +51,7 @@ func parseStackLock(_ context.Context, _ file.Resolver, _ *generic.Environment,
|
|||||||
|
|
||||||
if err := yaml.Unmarshal(bytes, &lockFile); err != nil {
|
if err := yaml.Unmarshal(bytes, &lockFile); err != nil {
|
||||||
log.WithFields("error", err).Tracef("failed to parse stack.yaml.lock file %q", reader.RealPath)
|
log.WithFields("error", err).Tracef("failed to parse stack.yaml.lock file %q", reader.RealPath)
|
||||||
return nil, nil, nil
|
return nil, nil, fmt.Errorf("failed to parse stack.yaml.lock file")
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -82,7 +83,7 @@ func parseStackLock(_ context.Context, _ file.Resolver, _ *generic.Environment,
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return pkgs, nil, nil
|
return pkgs, nil, unknown.IfEmptyf(pkgs, "unable to determine packages")
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseStackPackageEncoding(pkgEncoding string) (name, version, hash string) {
|
func parseStackPackageEncoding(pkgEncoding string) (name, version, hash string) {
|
||||||
|
|||||||
@ -130,3 +130,10 @@ func TestParseStackLock(t *testing.T) {
|
|||||||
|
|
||||||
pkgtest.TestFileParser(t, fixture, parseStackLock, expectedPkgs, expectedRelationships)
|
pkgtest.TestFileParser(t, fixture, parseStackLock, expectedPkgs, expectedRelationships)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_corruptStackLock(t *testing.T) {
|
||||||
|
pkgtest.NewCatalogTester().
|
||||||
|
FromFile(t, "test-fixtures/corrupt/stack.yaml.lock").
|
||||||
|
WithError().
|
||||||
|
TestParser(t, parseStackLock)
|
||||||
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import (
|
|||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
"github.com/anchore/syft/internal/log"
|
"github.com/anchore/syft/internal/log"
|
||||||
|
"github.com/anchore/syft/internal/unknown"
|
||||||
"github.com/anchore/syft/syft/artifact"
|
"github.com/anchore/syft/syft/artifact"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
@ -31,7 +32,7 @@ func parseStackYaml(_ context.Context, _ file.Resolver, _ *generic.Environment,
|
|||||||
|
|
||||||
if err := yaml.Unmarshal(bytes, &stackFile); err != nil {
|
if err := yaml.Unmarshal(bytes, &stackFile); err != nil {
|
||||||
log.WithFields("error", err).Tracef("failed to parse stack.yaml file %q", reader.RealPath)
|
log.WithFields("error", err).Tracef("failed to parse stack.yaml file %q", reader.RealPath)
|
||||||
return nil, nil, nil
|
return nil, nil, fmt.Errorf("failed to parse stack.yaml file")
|
||||||
}
|
}
|
||||||
|
|
||||||
var pkgs []pkg.Package
|
var pkgs []pkg.Package
|
||||||
@ -50,5 +51,5 @@ func parseStackYaml(_ context.Context, _ file.Resolver, _ *generic.Environment,
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return pkgs, nil, nil
|
return pkgs, nil, unknown.IfEmptyf(pkgs, "unable to determine packages")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -110,3 +110,10 @@ func TestParseStackYaml(t *testing.T) {
|
|||||||
pkgtest.TestFileParser(t, fixture, parseStackYaml, expectedPkgs, expectedRelationships)
|
pkgtest.TestFileParser(t, fixture, parseStackYaml, expectedPkgs, expectedRelationships)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_corruptStackYaml(t *testing.T) {
|
||||||
|
pkgtest.NewCatalogTester().
|
||||||
|
FromFile(t, "test-fixtures/corrupt/stack.yaml").
|
||||||
|
WithError().
|
||||||
|
TestParser(t, parseStackYaml)
|
||||||
|
}
|
||||||
|
|||||||
@ -0,0 +1,6 @@
|
|||||||
|
flag
|
||||||
|
extra-package-dbs: []
|
||||||
|
packa@sha256:fbcf49ecfc3d4da53e797fd0275264cba776ffa324ee223e2a3f4ec2d2c9c4a6,2165
|
||||||
|
- stm-2.5.0.2@sha256:e4dc6473faaa75fbd7eccab4e3ee1d651d75bb0e49946ef0b8b751ccde771a55,2314
|
||||||
|
ghc-options:
|
||||||
|
"$everything": -haddock
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
packages
|
||||||
|
-comp
|
||||||
|
al:
|
||||||
|
commit: a5847301404583e16d55cd4d051b8e605d704fbc
|
||||||
|
git: https://github.com/runtimeverification/haskell-backend.git
|
||||||
|
subdir: kore
|
||||||
|
snapshots
|
||||||
|
- complete//raw.github
|
||||||
@ -48,7 +48,6 @@ type CatalogTester struct {
|
|||||||
|
|
||||||
func NewCatalogTester() *CatalogTester {
|
func NewCatalogTester() *CatalogTester {
|
||||||
return &CatalogTester{
|
return &CatalogTester{
|
||||||
wantErr: require.NoError,
|
|
||||||
locationComparer: cmptest.DefaultLocationComparer,
|
locationComparer: cmptest.DefaultLocationComparer,
|
||||||
licenseComparer: cmptest.DefaultLicenseComparer,
|
licenseComparer: cmptest.DefaultLicenseComparer,
|
||||||
packageStringer: stringPackage,
|
packageStringer: stringPackage,
|
||||||
@ -113,7 +112,6 @@ func (p *CatalogTester) WithEnv(env *generic.Environment) *CatalogTester {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *CatalogTester) WithError() *CatalogTester {
|
func (p *CatalogTester) WithError() *CatalogTester {
|
||||||
p.assertResultExpectations = true
|
|
||||||
p.wantErr = require.Error
|
p.wantErr = require.Error
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
@ -226,7 +224,10 @@ func (p *CatalogTester) IgnoreUnfulfilledPathResponses(paths ...string) *Catalog
|
|||||||
func (p *CatalogTester) TestParser(t *testing.T, parser generic.Parser) {
|
func (p *CatalogTester) TestParser(t *testing.T, parser generic.Parser) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
pkgs, relationships, err := parser(context.Background(), p.resolver, p.env, p.reader)
|
pkgs, relationships, err := parser(context.Background(), p.resolver, p.env, p.reader)
|
||||||
|
// only test for errors if explicitly requested
|
||||||
|
if p.wantErr != nil {
|
||||||
p.wantErr(t, err)
|
p.wantErr(t, err)
|
||||||
|
}
|
||||||
p.assertPkgs(t, pkgs, relationships)
|
p.assertPkgs(t, pkgs, relationships)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,8 +248,12 @@ func (p *CatalogTester) TestCataloger(t *testing.T, cataloger pkg.Cataloger) {
|
|||||||
assert.ElementsMatchf(t, p.expectedContentQueries, resolver.AllContentQueries(), "unexpected content queries observed: diff %s", cmp.Diff(p.expectedContentQueries, resolver.AllContentQueries()))
|
assert.ElementsMatchf(t, p.expectedContentQueries, resolver.AllContentQueries(), "unexpected content queries observed: diff %s", cmp.Diff(p.expectedContentQueries, resolver.AllContentQueries()))
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.assertResultExpectations {
|
// only test for errors if explicitly requested
|
||||||
|
if p.wantErr != nil {
|
||||||
p.wantErr(t, err)
|
p.wantErr(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.assertResultExpectations {
|
||||||
p.assertPkgs(t, pkgs, relationships)
|
p.assertPkgs(t, pkgs, relationships)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,7 +261,7 @@ func (p *CatalogTester) TestCataloger(t *testing.T, cataloger pkg.Cataloger) {
|
|||||||
a(t, pkgs, relationships)
|
a(t, pkgs, relationships)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !p.assertResultExpectations && len(p.customAssertions) == 0 {
|
if !p.assertResultExpectations && len(p.customAssertions) == 0 && p.wantErr == nil {
|
||||||
resolver.PruneUnfulfilledPathResponses(p.ignoreUnfulfilledPathResponses, p.ignoreAnyUnfulfilledPaths...)
|
resolver.PruneUnfulfilledPathResponses(p.ignoreUnfulfilledPathResponses, p.ignoreAnyUnfulfilledPaths...)
|
||||||
|
|
||||||
// if we aren't testing the results, we should focus on what was searched for (for glob-centric tests)
|
// if we aren't testing the results, we should focus on what was searched for (for glob-centric tests)
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import (
|
|||||||
intFile "github.com/anchore/syft/internal/file"
|
intFile "github.com/anchore/syft/internal/file"
|
||||||
"github.com/anchore/syft/internal/licenses"
|
"github.com/anchore/syft/internal/licenses"
|
||||||
"github.com/anchore/syft/internal/log"
|
"github.com/anchore/syft/internal/log"
|
||||||
|
"github.com/anchore/syft/internal/unknown"
|
||||||
"github.com/anchore/syft/syft/artifact"
|
"github.com/anchore/syft/syft/artifact"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
@ -133,14 +134,22 @@ func (j *archiveParser) parse(ctx context.Context) ([]pkg.Package, []artifact.Re
|
|||||||
}
|
}
|
||||||
pkgs = append(pkgs, auxPkgs...)
|
pkgs = append(pkgs, auxPkgs...)
|
||||||
|
|
||||||
|
var errs error
|
||||||
if j.detectNested {
|
if j.detectNested {
|
||||||
// find nested java archive packages
|
// find nested java archive packages
|
||||||
nestedPkgs, nestedRelationships, err := j.discoverPkgsFromNestedArchives(ctx, parentPkg)
|
nestedPkgs, nestedRelationships, err := j.discoverPkgsFromNestedArchives(ctx, parentPkg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
errs = unknown.Append(errs, j.location, err)
|
||||||
}
|
}
|
||||||
pkgs = append(pkgs, nestedPkgs...)
|
pkgs = append(pkgs, nestedPkgs...)
|
||||||
relationships = append(relationships, nestedRelationships...)
|
relationships = append(relationships, nestedRelationships...)
|
||||||
|
} else {
|
||||||
|
// .jar and .war files are present in archives, are others? or generally just consider them top-level?
|
||||||
|
nestedArchives := j.fileManifest.GlobMatch(true, "*.jar", "*.war")
|
||||||
|
if len(nestedArchives) > 0 {
|
||||||
|
slices.Sort(nestedArchives)
|
||||||
|
errs = unknown.Appendf(errs, j.location, "nested archives not cataloged: %v", strings.Join(nestedArchives, ", "))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// lastly, add the parent package to the list (assuming the parent exists)
|
// lastly, add the parent package to the list (assuming the parent exists)
|
||||||
@ -166,7 +175,11 @@ func (j *archiveParser) parse(ctx context.Context) ([]pkg.Package, []artifact.Re
|
|||||||
p.SetID()
|
p.SetID()
|
||||||
}
|
}
|
||||||
|
|
||||||
return pkgs, relationships, nil
|
if len(pkgs) == 0 {
|
||||||
|
errs = unknown.Appendf(errs, j.location, "no package identified in archive")
|
||||||
|
}
|
||||||
|
|
||||||
|
return pkgs, relationships, errs
|
||||||
}
|
}
|
||||||
|
|
||||||
// discoverMainPackage parses the root Java manifest used as the parent package to all discovered nested packages.
|
// discoverMainPackage parses the root Java manifest used as the parent package to all discovered nested packages.
|
||||||
@ -283,14 +296,14 @@ func (j *archiveParser) findLicenseFromJavaMetadata(ctx context.Context, groupID
|
|||||||
if parsedPom != nil {
|
if parsedPom != nil {
|
||||||
pomLicenses, err = j.maven.resolveLicenses(ctx, parsedPom.project)
|
pomLicenses, err = j.maven.resolveLicenses(ctx, parsedPom.project)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields("error", err, "mavenID", j.maven.getMavenID(ctx, parsedPom.project)).Debug("error attempting to resolve pom licenses")
|
log.WithFields("error", err, "mavenID", j.maven.getMavenID(ctx, parsedPom.project)).Trace("error attempting to resolve pom licenses")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == nil && len(pomLicenses) == 0 {
|
if err == nil && len(pomLicenses) == 0 {
|
||||||
pomLicenses, err = j.maven.findLicenses(ctx, groupID, artifactID, version)
|
pomLicenses, err = j.maven.findLicenses(ctx, groupID, artifactID, version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields("error", err, "mavenID", mavenID{groupID, artifactID, version}).Debug("error attempting to find licenses")
|
log.WithFields("error", err, "mavenID", mavenID{groupID, artifactID, version}).Trace("error attempting to find licenses")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -300,7 +313,7 @@ func (j *archiveParser) findLicenseFromJavaMetadata(ctx context.Context, groupID
|
|||||||
groupID = strings.Join(packages[:len(packages)-1], ".")
|
groupID = strings.Join(packages[:len(packages)-1], ".")
|
||||||
pomLicenses, err = j.maven.findLicenses(ctx, groupID, artifactID, version)
|
pomLicenses, err = j.maven.findLicenses(ctx, groupID, artifactID, version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields("error", err, "mavenID", mavenID{groupID, artifactID, version}).Debug("error attempting to find sub-group licenses")
|
log.WithFields("error", err, "mavenID", mavenID{groupID, artifactID, version}).Trace("error attempting to find sub-group licenses")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -630,7 +643,7 @@ func newPackageFromMavenData(ctx context.Context, r *mavenResolver, pomPropertie
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields("error", err, "mavenID", mavenID{pomProperties.GroupID, pomProperties.ArtifactID, pomProperties.Version}).Debug("error attempting to resolve licenses")
|
log.WithFields("error", err, "mavenID", mavenID{pomProperties.GroupID, pomProperties.ArtifactID, pomProperties.Version}).Trace("error attempting to resolve licenses")
|
||||||
}
|
}
|
||||||
|
|
||||||
licenses := make([]pkg.License, 0)
|
licenses := make([]pkg.License, 0)
|
||||||
|
|||||||
@ -91,10 +91,12 @@ func TestParseJar(t *testing.T) {
|
|||||||
fixture string
|
fixture string
|
||||||
expected map[string]pkg.Package
|
expected map[string]pkg.Package
|
||||||
ignoreExtras []string
|
ignoreExtras []string
|
||||||
|
wantErr require.ErrorAssertionFunc
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "example-jenkins-plugin",
|
name: "example-jenkins-plugin",
|
||||||
fixture: "test-fixtures/java-builds/packages/example-jenkins-plugin.hpi",
|
fixture: "test-fixtures/java-builds/packages/example-jenkins-plugin.hpi",
|
||||||
|
wantErr: require.Error, // there are nested jars, which are not scanned and result in unknown errors
|
||||||
ignoreExtras: []string{
|
ignoreExtras: []string{
|
||||||
"Plugin-Version", // has dynamic date
|
"Plugin-Version", // has dynamic date
|
||||||
"Built-By", // podman returns the real UID
|
"Built-By", // podman returns the real UID
|
||||||
@ -153,6 +155,7 @@ func TestParseJar(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "example-java-app-gradle",
|
name: "example-java-app-gradle",
|
||||||
fixture: "test-fixtures/java-builds/packages/example-java-app-gradle-0.1.0.jar",
|
fixture: "test-fixtures/java-builds/packages/example-java-app-gradle-0.1.0.jar",
|
||||||
|
wantErr: require.NoError, // no nested jars
|
||||||
expected: map[string]pkg.Package{
|
expected: map[string]pkg.Package{
|
||||||
"example-java-app-gradle": {
|
"example-java-app-gradle": {
|
||||||
Name: "example-java-app-gradle",
|
Name: "example-java-app-gradle",
|
||||||
@ -226,6 +229,7 @@ func TestParseJar(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "example-java-app-maven",
|
name: "example-java-app-maven",
|
||||||
fixture: "test-fixtures/java-builds/packages/example-java-app-maven-0.1.0.jar",
|
fixture: "test-fixtures/java-builds/packages/example-java-app-maven-0.1.0.jar",
|
||||||
|
wantErr: require.NoError, // no nested jars
|
||||||
ignoreExtras: []string{
|
ignoreExtras: []string{
|
||||||
"Build-Jdk", // can't guarantee the JDK used at build time
|
"Build-Jdk", // can't guarantee the JDK used at build time
|
||||||
"Built-By", // podman returns the real UID
|
"Built-By", // podman returns the real UID
|
||||||
@ -351,13 +355,15 @@ func TestParseJar(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
actual, _, err := parser.parse(context.Background())
|
actual, _, err := parser.parse(context.Background())
|
||||||
require.NoError(t, err)
|
if test.wantErr != nil {
|
||||||
|
test.wantErr(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
if len(actual) != len(test.expected) {
|
if len(actual) != len(test.expected) {
|
||||||
for _, a := range actual {
|
for _, a := range actual {
|
||||||
t.Log(" ", a)
|
t.Log(" ", a)
|
||||||
}
|
}
|
||||||
t.Fatalf("unexpected package count: %d!=%d", len(actual), len(test.expected))
|
t.Fatalf("unexpected package count; expected: %d got: %d", len(test.expected), len(actual))
|
||||||
}
|
}
|
||||||
|
|
||||||
var parent *pkg.Package
|
var parent *pkg.Package
|
||||||
@ -1488,3 +1494,11 @@ func run(t testing.TB, cmd *exec.Cmd) {
|
|||||||
func ptr[T any](value T) *T {
|
func ptr[T any](value T) *T {
|
||||||
return &value
|
return &value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_corruptJarArchive(t *testing.T) {
|
||||||
|
ap := newGenericArchiveParserAdapter(DefaultArchiveCatalogerConfig())
|
||||||
|
pkgtest.NewCatalogTester().
|
||||||
|
FromFile(t, "test-fixtures/corrupt/example.jar").
|
||||||
|
WithError().
|
||||||
|
TestParser(t, ap.parseJavaArchive)
|
||||||
|
}
|
||||||
|
|||||||
@ -79,7 +79,7 @@ func (r *mavenResolver) resolvePropertyValue(ctx context.Context, propertyValue
|
|||||||
}
|
}
|
||||||
resolved, err := r.resolveExpression(ctx, resolutionContext, *propertyValue, resolvingProperties)
|
resolved, err := r.resolveExpression(ctx, resolutionContext, *propertyValue, resolvingProperties)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields("error", err, "propertyValue", *propertyValue).Debug("error resolving maven property")
|
log.WithFields("error", err, "propertyValue", *propertyValue).Trace("error resolving maven property")
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return resolved
|
return resolved
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import (
|
|||||||
|
|
||||||
"github.com/anchore/syft/internal"
|
"github.com/anchore/syft/internal"
|
||||||
"github.com/anchore/syft/internal/log"
|
"github.com/anchore/syft/internal/log"
|
||||||
|
"github.com/anchore/syft/internal/unknown"
|
||||||
"github.com/anchore/syft/syft/artifact"
|
"github.com/anchore/syft/syft/artifact"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
@ -41,11 +42,13 @@ func (p pomXMLCataloger) Catalog(ctx context.Context, fileResolver file.Resolver
|
|||||||
|
|
||||||
r := newMavenResolver(fileResolver, p.cfg)
|
r := newMavenResolver(fileResolver, p.cfg)
|
||||||
|
|
||||||
|
var errs error
|
||||||
var poms []*gopom.Project
|
var poms []*gopom.Project
|
||||||
for _, pomLocation := range locations {
|
for _, pomLocation := range locations {
|
||||||
pom, err := readPomFromLocation(fileResolver, pomLocation)
|
pom, err := readPomFromLocation(fileResolver, pomLocation)
|
||||||
if err != nil || pom == nil {
|
if err != nil || pom == nil {
|
||||||
log.WithFields("error", err, "pomLocation", pomLocation).Debug("error while reading pom")
|
log.WithFields("error", err, "pomLocation", pomLocation).Debug("error while reading pom")
|
||||||
|
errs = unknown.Appendf(errs, pomLocation, "error reading pom.xml: %w", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,7 +63,7 @@ func (p pomXMLCataloger) Catalog(ctx context.Context, fileResolver file.Resolver
|
|||||||
for _, pom := range poms {
|
for _, pom := range poms {
|
||||||
pkgs = append(pkgs, processPomXML(ctx, r, pom, r.pomLocations[pom])...)
|
pkgs = append(pkgs, processPomXML(ctx, r, pom, r.pomLocations[pom])...)
|
||||||
}
|
}
|
||||||
return pkgs, nil, nil
|
return pkgs, nil, errs
|
||||||
}
|
}
|
||||||
|
|
||||||
func readPomFromLocation(fileResolver file.Resolver, pomLocation file.Location) (*gopom.Project, error) {
|
func readPomFromLocation(fileResolver file.Resolver, pomLocation file.Location) (*gopom.Project, error) {
|
||||||
|
|||||||
@ -705,3 +705,11 @@ func getCommonsTextExpectedPackages() []pkg.Package {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_corruptPomXml(t *testing.T) {
|
||||||
|
c := NewPomCataloger(DefaultArchiveCatalogerConfig())
|
||||||
|
pkgtest.NewCatalogTester().
|
||||||
|
FromDirectory(t, "test-fixtures/corrupt").
|
||||||
|
WithError().
|
||||||
|
TestCataloger(t, c)
|
||||||
|
}
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
|
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_parseTarWrappedJavaArchive(t *testing.T) {
|
func Test_parseTarWrappedJavaArchive(t *testing.T) {
|
||||||
@ -57,3 +58,11 @@ func Test_parseTarWrappedJavaArchive(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_corruptTarArchive(t *testing.T) {
|
||||||
|
ap := newGenericTarWrappedJavaArchiveParser(DefaultArchiveCatalogerConfig())
|
||||||
|
pkgtest.NewCatalogTester().
|
||||||
|
FromFile(t, "test-fixtures/corrupt/example.tar").
|
||||||
|
WithError().
|
||||||
|
TestParser(t, ap.parseTarWrappedJavaArchive)
|
||||||
|
}
|
||||||
|
|||||||
@ -0,0 +1 @@
|
|||||||
|
example archive
|
||||||
@ -0,0 +1 @@
|
|||||||
|
example archive
|
||||||
1
syft/pkg/cataloger/java/test-fixtures/corrupt/pom.xml
Normal file
1
syft/pkg/cataloger/java/test-fixtures/corrupt/pom.xml
Normal file
@ -0,0 +1 @@
|
|||||||
|
<this is not a valid pom xml>
|
||||||
@ -11,7 +11,6 @@ import (
|
|||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
|
|
||||||
"github.com/anchore/syft/internal"
|
"github.com/anchore/syft/internal"
|
||||||
"github.com/anchore/syft/internal/log"
|
|
||||||
"github.com/anchore/syft/syft/artifact"
|
"github.com/anchore/syft/syft/artifact"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
@ -64,11 +63,8 @@ func parsePackageJSON(_ context.Context, _ file.Resolver, _ *generic.Environment
|
|||||||
return nil, nil, fmt.Errorf("failed to parse package.json file: %w", err)
|
return nil, nil, fmt.Errorf("failed to parse package.json file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !p.hasNameAndVersionValues() {
|
// always create a package, regardless of having a valid name and/or version,
|
||||||
log.Debugf("encountered package.json file without a name and/or version field, ignoring (path=%q)", reader.Path())
|
// a compliance filter later will remove these packages based on compliance rules
|
||||||
return nil, nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
pkgs = append(
|
pkgs = append(
|
||||||
pkgs,
|
pkgs,
|
||||||
newPackageJSONPackage(p, reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
|
newPackageJSONPackage(p, reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
|
||||||
@ -203,10 +199,6 @@ func licensesFromJSON(b []byte) ([]npmPackageLicense, error) {
|
|||||||
return nil, errors.New("unmarshal failed")
|
return nil, errors.New("unmarshal failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p packageJSON) hasNameAndVersionValues() bool {
|
|
||||||
return p.Name != "" && p.Version != ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// this supports both windows and unix paths
|
// this supports both windows and unix paths
|
||||||
var filepathSeparator = regexp.MustCompile(`[\\/]`)
|
var filepathSeparator = regexp.MustCompile(`[\\/]`)
|
||||||
|
|
||||||
|
|||||||
@ -210,10 +210,28 @@ func TestParsePackageJSON(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_corruptPackageJSON(t *testing.T) {
|
||||||
|
pkgtest.NewCatalogTester().
|
||||||
|
FromFile(t, "test-fixtures/corrupt/package.json").
|
||||||
|
WithError().
|
||||||
|
TestParser(t, parsePackageJSON)
|
||||||
|
}
|
||||||
|
|
||||||
func TestParsePackageJSON_Partial(t *testing.T) { // see https://github.com/anchore/syft/issues/311
|
func TestParsePackageJSON_Partial(t *testing.T) { // see https://github.com/anchore/syft/issues/311
|
||||||
const fixtureFile = "test-fixtures/pkg-json/package-partial.json"
|
const fixtureFile = "test-fixtures/pkg-json/package-partial.json"
|
||||||
|
|
||||||
pkgtest.TestFileParser(t, fixtureFile, parsePackageJSON, nil, nil)
|
// raise package.json files as packages with any information we find, these will be filtered out
|
||||||
|
// according to compliance rules later
|
||||||
|
expectedPkgs := []pkg.Package{
|
||||||
|
{
|
||||||
|
Language: pkg.JavaScript,
|
||||||
|
Type: pkg.NpmPkg,
|
||||||
|
PURL: packageURL("", ""),
|
||||||
|
Metadata: pkg.NpmPackage{},
|
||||||
|
Locations: file.NewLocationSet(file.NewLocation(fixtureFile)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
pkgtest.TestFileParser(t, fixtureFile, parsePackageJSON, expectedPkgs, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_pathContainsNodeModulesDirectory(t *testing.T) {
|
func Test_pathContainsNodeModulesDirectory(t *testing.T) {
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/anchore/syft/internal/log"
|
"github.com/anchore/syft/internal/log"
|
||||||
|
"github.com/anchore/syft/internal/unknown"
|
||||||
"github.com/anchore/syft/syft/artifact"
|
"github.com/anchore/syft/syft/artifact"
|
||||||
"github.com/anchore/syft/syft/file"
|
"github.com/anchore/syft/syft/file"
|
||||||
"github.com/anchore/syft/syft/pkg"
|
"github.com/anchore/syft/syft/pkg"
|
||||||
@ -100,7 +101,7 @@ func (a genericPackageLockAdapter) parsePackageLock(_ context.Context, resolver
|
|||||||
|
|
||||||
pkg.Sort(pkgs)
|
pkg.Sort(pkgs)
|
||||||
|
|
||||||
return pkgs, nil, nil
|
return pkgs, nil, unknown.IfEmptyf(pkgs, "unable to determine packages")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (licenses *packageLockLicense) UnmarshalJSON(data []byte) (err error) {
|
func (licenses *packageLockLicense) UnmarshalJSON(data []byte) (err error) {
|
||||||
|
|||||||
@ -333,3 +333,11 @@ func TestParsePackageLockLicenseWithArray(t *testing.T) {
|
|||||||
adapter := newGenericPackageLockAdapter(CatalogerConfig{})
|
adapter := newGenericPackageLockAdapter(CatalogerConfig{})
|
||||||
pkgtest.TestFileParser(t, fixture, adapter.parsePackageLock, expectedPkgs, expectedRelationships)
|
pkgtest.TestFileParser(t, fixture, adapter.parsePackageLock, expectedPkgs, expectedRelationships)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_corruptPackageLock(t *testing.T) {
|
||||||
|
gap := newGenericPackageLockAdapter(DefaultCatalogerConfig())
|
||||||
|
pkgtest.NewCatalogTester().
|
||||||
|
FromFile(t, "test-fixtures/corrupt/package-lock.json").
|
||||||
|
WithError().
|
||||||
|
TestParser(t, gap.parsePackageLock)
|
||||||
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user