Compare commits

...
13 Commits
Author SHA1 Message Date
Alex Goodman b06a2d4f27 add task id to constructor
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2022-06-07 17:44:24 -04:00
Alex Goodman a5dd485672 add configurable task collection backend
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2022-06-07 10:57:44 -04:00
Alex Goodman 078dbedfb6 separate CPE definitions from capabilities
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2022-04-02 00:13:21 -04:00
Alex Goodman 9f60d32369 migrate source.FileResolver to the file package
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2022-03-22 20:50:05 -04:00
Alex Goodman a3dc0fa97d rename location.VirtualPath to AccessPath
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2022-03-22 18:24:50 -04:00
Alex Goodman f999604a29 migrate location and file metadata to the file package
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2022-03-22 18:21:35 -04:00
Alex Goodman a49c7e3c53 migrate filename glob helpers to internal
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2022-03-22 17:41:02 -04:00
Alex Goodman b3ca75646c keep file catalogers separate from file-related definitions
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2022-03-22 17:23:13 -04:00
Alex Goodman 3308079158 export event monitor structs but not behavior
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2022-03-22 16:05:45 -04:00
Alex Goodman 1279bd0b08 add additional package catalger append option
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2022-03-22 10:48:16 -04:00
Alex Goodman f89f58b825 change package cataloger option name
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2022-03-14 12:54:37 -04:00
Alex Goodman a70cfa6dd7 migrate cataloging config to separate source file
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2022-03-14 12:30:08 -04:00
Alex Goodman 3323ce2b6b [wip] api refactor
Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
2022-03-11 21:26:33 -05:00
404 changed files with 3328 additions and 2565 deletions
+2 -2
View File
@@ -211,13 +211,13 @@ integration-fingerprint:
.PHONY: java-packages-fingerprint .PHONY: java-packages-fingerprint
java-packages-fingerprint: java-packages-fingerprint:
$(call title,Java test fixture fingerprint) $(call title,Java test fixture fingerprint)
cd syft/pkg/cataloger/java/test-fixtures/java-builds && \ cd syft/cataloger/packages/java/test-fixtures/java-builds && \
make packages.fingerprint make packages.fingerprint
.PHONY: fixtures .PHONY: fixtures
fixtures: fixtures:
$(call title,Generating test fixtures) $(call title,Generating test fixtures)
cd syft/pkg/cataloger/java/test-fixtures/java-builds && make cd syft/cataloger/packages/java/test-fixtures/java-builds && make
.PHONY: generate-json-schema .PHONY: generate-json-schema
generate-json-schema: ## Generate a new json schema generate-json-schema: ## Generate a new json schema
+19 -8
View File
@@ -136,12 +136,12 @@ func attestExec(ctx context.Context, _ *cobra.Command, args []string) error {
return fmt.Errorf("could not generate source input for attest command: %w", err) return fmt.Errorf("could not generate source input for attest command: %w", err)
} }
switch si.Scheme { switch si.Type {
case source.ImageScheme, source.UnknownScheme: case source.ImageType, source.UnknownType:
// at this point we know that it cannot be dir: or file: schemes, so we will assume that the unknown scheme could represent an image // at this point we know that it cannot be dir: or file: schemes, so we will assume that the unknown scheme could represent an image
si.Scheme = source.ImageScheme si.Type = source.ImageType
default: default:
return fmt.Errorf("attest command can only be used with image sources but discovered %q when given %q", si.Scheme, userInput) return fmt.Errorf("attest command can only be used with image sources but discovered %q when given %q", si.Type, userInput)
} }
// if the original detection was from a local daemon we want to short circuit // if the original detection was from a local daemon we want to short circuit
@@ -188,26 +188,37 @@ func attestExec(ctx context.Context, _ *cobra.Command, args []string) error {
) )
} }
func attestationExecWorker(sourceInput source.Input, format sbom.Format, predicateType string, sv *sign.SignerVerifier) <-chan error { func attestationExecWorker(si source.Input, format sbom.Format, predicateType string, sv *sign.SignerVerifier) <-chan error {
errs := make(chan error) errs := make(chan error)
go func() { go func() {
defer close(errs) defer close(errs)
src, cleanup, err := source.NewFromRegistry(sourceInput, appConfig.Registry.ToOptions(), appConfig.Exclusions) catalogingOpts, err := appConfig.ToCatalogingOptions()
if err != nil {
errs <- err
return
}
src, cleanup, err := source.NewFromRegistry(si, appConfig.Registry.ToOptions(), appConfig.Exclusions)
if cleanup != nil { if cleanup != nil {
defer cleanup() defer cleanup()
} }
if err != nil { if err != nil {
errs <- fmt.Errorf("failed to construct source from user input %q: %w", sourceInput.UserInput, err) errs <- fmt.Errorf("failed to construct source from user input %q: %w", si.UserInput, err)
return return
} }
s, err := generateSBOM(src, errs) s, err := syft.Catalog(src, catalogingOpts...)
if err != nil { if err != nil {
errs <- err errs <- err
return return
} }
if s == nil {
errs <- fmt.Errorf("no SBOM produced for %q", si.UserInput)
return
}
sbomBytes, err := syft.Encode(*s, format) sbomBytes, err := syft.Encode(*s, format)
if err != nil { if err != nil {
errs <- err errs <- err
+21 -48
View File
@@ -13,11 +13,8 @@ import (
"github.com/anchore/syft/internal/formats/table" "github.com/anchore/syft/internal/formats/table"
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/ui" "github.com/anchore/syft/internal/ui"
"github.com/anchore/syft/internal/version"
"github.com/anchore/syft/syft" "github.com/anchore/syft/syft"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/event" "github.com/anchore/syft/syft/event"
"github.com/anchore/syft/syft/pkg/cataloger"
"github.com/anchore/syft/syft/sbom" "github.com/anchore/syft/syft/sbom"
"github.com/anchore/syft/syft/source" "github.com/anchore/syft/syft/source"
"github.com/pkg/profile" "github.com/pkg/profile"
@@ -97,7 +94,7 @@ func init() {
func setPackageFlags(flags *pflag.FlagSet) { func setPackageFlags(flags *pflag.FlagSet) {
// Formatting & Input options ////////////////////////////////////////////// // Formatting & Input options //////////////////////////////////////////////
flags.StringP( flags.StringP(
"scope", "s", cataloger.DefaultSearchConfig().Scope.String(), "scope", "s", syft.DefaultCatalogingConfig().DefaultScope.String(),
fmt.Sprintf("selection of layers to catalog, options=%v", source.AllScopes)) fmt.Sprintf("selection of layers to catalog, options=%v", source.AllScopes))
flags.StringArrayP( flags.StringArrayP(
@@ -115,6 +112,12 @@ func setPackageFlags(flags *pflag.FlagSet) {
"an optional platform specifier for container image sources (e.g. 'linux/arm64', 'linux/arm64/v8', 'arm64', 'linux')", "an optional platform specifier for container image sources (e.g. 'linux/arm64', 'linux/arm64/v8', 'arm64', 'linux')",
) )
flags.StringArrayP(
"cataloger", "", nil,
// TODO: have a nice way for a user to get the set of catalogers that exist
"set which catalogers to use when generating an SBOM",
)
// Upload options ////////////////////////////////////////////////////////// // Upload options //////////////////////////////////////////////////////////
flags.StringP( flags.StringP(
"host", "H", "", "host", "H", "",
@@ -168,7 +171,11 @@ func bindExclusivePackagesConfigOptions(flags *pflag.FlagSet) error {
// note: output is not included since this configuration option is shared between multiple subcommands // note: output is not included since this configuration option is shared between multiple subcommands
if err := viper.BindPFlag("package.cataloger.scope", flags.Lookup("scope")); err != nil { if err := viper.BindPFlag("scope", flags.Lookup("scope")); err != nil {
return err
}
if err := viper.BindPFlag("catalogers", flags.Lookup("cataloger")); err != nil {
return err return err
} }
@@ -260,42 +267,17 @@ func isVerbose() (result bool) {
return appConfig.CliOptions.Verbosity > 0 || isPipedInput return appConfig.CliOptions.Verbosity > 0 || isPipedInput
} }
func generateSBOM(src *source.Source, errs chan error) (*sbom.SBOM, error) {
tasks, err := tasks()
if err != nil {
return nil, err
}
s := sbom.SBOM{
Source: src.Metadata,
Descriptor: sbom.Descriptor{
Name: internal.ApplicationName,
Version: version.FromBuild().Version,
Configuration: appConfig,
},
}
buildRelationships(&s, src, tasks, errs)
return &s, nil
}
func buildRelationships(s *sbom.SBOM, src *source.Source, tasks []task, errs chan error) {
var relationships []<-chan artifact.Relationship
for _, task := range tasks {
c := make(chan artifact.Relationship)
relationships = append(relationships, c)
go runTask(task, &s.Artifacts, src, c, errs)
}
s.Relationships = append(s.Relationships, mergeRelationships(relationships...)...)
}
func packagesExecWorker(si source.Input, writer sbom.Writer) <-chan error { func packagesExecWorker(si source.Input, writer sbom.Writer) <-chan error {
errs := make(chan error) errs := make(chan error)
go func() { go func() {
defer close(errs) defer close(errs)
catalogingOpts, err := appConfig.ToCatalogingOptions()
if err != nil {
errs <- err
return
}
src, cleanup, err := source.New(si, appConfig.Registry.ToOptions(), appConfig.Exclusions) src, cleanup, err := source.New(si, appConfig.Registry.ToOptions(), appConfig.Exclusions)
if cleanup != nil { if cleanup != nil {
defer cleanup() defer cleanup()
@@ -305,7 +287,7 @@ func packagesExecWorker(si source.Input, writer sbom.Writer) <-chan error {
return return
} }
s, err := generateSBOM(src, errs) s, err := syft.Catalog(src, catalogingOpts...)
if err != nil { if err != nil {
errs <- err errs <- err
return return
@@ -313,6 +295,7 @@ func packagesExecWorker(si source.Input, writer sbom.Writer) <-chan error {
if s == nil { if s == nil {
errs <- fmt.Errorf("no SBOM produced for %q", si.UserInput) errs <- fmt.Errorf("no SBOM produced for %q", si.UserInput)
return
} }
if appConfig.Anchore.Host != "" { if appConfig.Anchore.Host != "" {
@@ -330,20 +313,10 @@ func packagesExecWorker(si source.Input, writer sbom.Writer) <-chan error {
return errs return errs
} }
func mergeRelationships(cs ...<-chan artifact.Relationship) (relationships []artifact.Relationship) {
for _, c := range cs {
for n := range c {
relationships = append(relationships, n)
}
}
return relationships
}
func runPackageSbomUpload(src *source.Source, s sbom.SBOM) error { func runPackageSbomUpload(src *source.Source, s sbom.SBOM) error {
log.Infof("uploading results to %s", appConfig.Anchore.Host) log.Infof("uploading results to %s", appConfig.Anchore.Host)
if src.Metadata.Scheme != source.ImageScheme { if src.Metadata.Scheme != source.ImageType {
return fmt.Errorf("unable to upload results: only images are supported") return fmt.Errorf("unable to upload results: only images are supported")
} }
+16 -23
View File
@@ -2,6 +2,7 @@ package cmd
import ( import (
"fmt" "fmt"
"github.com/anchore/syft/syft"
"os" "os"
"github.com/anchore/stereoscope" "github.com/anchore/stereoscope"
@@ -10,8 +11,6 @@ import (
"github.com/anchore/syft/internal/formats/syftjson" "github.com/anchore/syft/internal/formats/syftjson"
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/ui" "github.com/anchore/syft/internal/ui"
"github.com/anchore/syft/internal/version"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/event" "github.com/anchore/syft/syft/event"
"github.com/anchore/syft/syft/sbom" "github.com/anchore/syft/syft/sbom"
"github.com/anchore/syft/syft/source" "github.com/anchore/syft/syft/source"
@@ -106,11 +105,13 @@ func powerUserExecWorker(userInput string, writer sbom.Writer) <-chan error {
go func() { go func() {
defer close(errs) defer close(errs)
appConfig.Secrets.Cataloger.Enabled = true // TODO: replace
appConfig.FileMetadata.Cataloger.Enabled = true //appConfig.Secrets.Cataloger.Enabled = true
appConfig.FileContents.Cataloger.Enabled = true //appConfig.FileMetadata.Cataloger.Enabled = true
appConfig.FileClassification.Cataloger.Enabled = true //appConfig.FileContents.Cataloger.Enabled = true
tasks, err := tasks() //appConfig.FileClassification.Cataloger.Enabled = true
catalogingOpts, err := appConfig.ToCatalogingOptions()
if err != nil { if err != nil {
errs <- err errs <- err
return return
@@ -131,28 +132,20 @@ func powerUserExecWorker(userInput string, writer sbom.Writer) <-chan error {
defer cleanup() defer cleanup()
} }
s := sbom.SBOM{ s, err := syft.Catalog(src, catalogingOpts...)
Source: src.Metadata, if err != nil {
Descriptor: sbom.Descriptor{ errs <- err
Name: internal.ApplicationName, return
Version: version.FromBuild().Version,
Configuration: appConfig,
},
} }
var relationships []<-chan artifact.Relationship if s == nil {
for _, task := range tasks { errs <- fmt.Errorf("no SBOM produced for %q", si.UserInput)
c := make(chan artifact.Relationship) return
relationships = append(relationships, c)
go runTask(task, &s.Artifacts, src, c, errs)
} }
s.Relationships = append(s.Relationships, mergeRelationships(relationships...)...)
bus.Publish(partybus.Event{ bus.Publish(partybus.Event{
Type: event.Exit, Type: event.Exit,
Value: func() error { return writer.Write(s) }, Value: func() error { return writer.Write(*s) },
}) })
}() }()
-231
View File
@@ -1,231 +0,0 @@
package cmd
import (
"crypto"
"fmt"
"github.com/anchore/syft/syft"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/sbom"
"github.com/anchore/syft/syft/source"
)
type task func(*sbom.Artifacts, *source.Source) ([]artifact.Relationship, error)
func tasks() ([]task, error) {
var tasks []task
generators := []func() (task, error){
generateCatalogPackagesTask,
generateCatalogFileMetadataTask,
generateCatalogFileDigestsTask,
generateCatalogSecretsTask,
generateCatalogFileClassificationsTask,
generateCatalogContentsTask,
}
for _, generator := range generators {
task, err := generator()
if err != nil {
return nil, err
}
if task != nil {
tasks = append(tasks, task)
}
}
return tasks, nil
}
func generateCatalogPackagesTask() (task, error) {
if !appConfig.Package.Cataloger.Enabled {
return nil, nil
}
task := func(results *sbom.Artifacts, src *source.Source) ([]artifact.Relationship, error) {
packageCatalog, relationships, theDistro, err := syft.CatalogPackages(src, appConfig.Package.ToConfig())
if err != nil {
return nil, err
}
results.PackageCatalog = packageCatalog
results.LinuxDistribution = theDistro
return relationships, nil
}
return task, nil
}
func generateCatalogFileMetadataTask() (task, error) {
if !appConfig.FileMetadata.Cataloger.Enabled {
return nil, nil
}
metadataCataloger := file.NewMetadataCataloger()
task := func(results *sbom.Artifacts, src *source.Source) ([]artifact.Relationship, error) {
resolver, err := src.FileResolver(appConfig.FileMetadata.Cataloger.ScopeOpt)
if err != nil {
return nil, err
}
result, err := metadataCataloger.Catalog(resolver)
if err != nil {
return nil, err
}
results.FileMetadata = result
return nil, nil
}
return task, nil
}
func generateCatalogFileDigestsTask() (task, error) {
if !appConfig.FileMetadata.Cataloger.Enabled {
return nil, nil
}
supportedHashAlgorithms := make(map[string]crypto.Hash)
for _, h := range []crypto.Hash{
crypto.MD5,
crypto.SHA1,
crypto.SHA256,
} {
supportedHashAlgorithms[file.DigestAlgorithmName(h)] = h
}
var hashes []crypto.Hash
for _, hashStr := range appConfig.FileMetadata.Digests {
name := file.CleanDigestAlgorithmName(hashStr)
hashObj, ok := supportedHashAlgorithms[name]
if !ok {
return nil, fmt.Errorf("unsupported hash algorithm: %s", hashStr)
}
hashes = append(hashes, hashObj)
}
digestsCataloger, err := file.NewDigestsCataloger(hashes)
if err != nil {
return nil, err
}
task := func(results *sbom.Artifacts, src *source.Source) ([]artifact.Relationship, error) {
resolver, err := src.FileResolver(appConfig.FileMetadata.Cataloger.ScopeOpt)
if err != nil {
return nil, err
}
result, err := digestsCataloger.Catalog(resolver)
if err != nil {
return nil, err
}
results.FileDigests = result
return nil, nil
}
return task, nil
}
func generateCatalogSecretsTask() (task, error) {
if !appConfig.Secrets.Cataloger.Enabled {
return nil, nil
}
patterns, err := file.GenerateSearchPatterns(file.DefaultSecretsPatterns, appConfig.Secrets.AdditionalPatterns, appConfig.Secrets.ExcludePatternNames)
if err != nil {
return nil, err
}
secretsCataloger, err := file.NewSecretsCataloger(patterns, appConfig.Secrets.RevealValues, appConfig.Secrets.SkipFilesAboveSize)
if err != nil {
return nil, err
}
task := func(results *sbom.Artifacts, src *source.Source) ([]artifact.Relationship, error) {
resolver, err := src.FileResolver(appConfig.Secrets.Cataloger.ScopeOpt)
if err != nil {
return nil, err
}
result, err := secretsCataloger.Catalog(resolver)
if err != nil {
return nil, err
}
results.Secrets = result
return nil, nil
}
return task, nil
}
func generateCatalogFileClassificationsTask() (task, error) {
if !appConfig.FileClassification.Cataloger.Enabled {
return nil, nil
}
// TODO: in the future we could expose out the classifiers via configuration
classifierCataloger, err := file.NewClassificationCataloger(file.DefaultClassifiers)
if err != nil {
return nil, err
}
task := func(results *sbom.Artifacts, src *source.Source) ([]artifact.Relationship, error) {
resolver, err := src.FileResolver(appConfig.FileClassification.Cataloger.ScopeOpt)
if err != nil {
return nil, err
}
result, err := classifierCataloger.Catalog(resolver)
if err != nil {
return nil, err
}
results.FileClassifications = result
return nil, nil
}
return task, nil
}
func generateCatalogContentsTask() (task, error) {
if !appConfig.FileContents.Cataloger.Enabled {
return nil, nil
}
contentsCataloger, err := file.NewContentsCataloger(appConfig.FileContents.Globs, appConfig.FileContents.SkipFilesAboveSize)
if err != nil {
return nil, err
}
task := func(results *sbom.Artifacts, src *source.Source) ([]artifact.Relationship, error) {
resolver, err := src.FileResolver(appConfig.FileContents.Cataloger.ScopeOpt)
if err != nil {
return nil, err
}
result, err := contentsCataloger.Catalog(resolver)
if err != nil {
return nil, err
}
results.FileContents = result
return nil, nil
}
return task, nil
}
func runTask(t task, a *sbom.Artifacts, src *source.Source, c chan<- artifact.Relationship, errs chan<- error) {
defer close(c)
relationships, err := t(a, src)
if err != nil {
errs <- err
return
}
for _, relationship := range relationships {
c <- relationship
}
}
+5 -4
View File
@@ -6,14 +6,15 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/anchore/client-go/pkg/external"
"github.com/anchore/stereoscope/pkg/image"
"github.com/anchore/syft/internal/bus" "github.com/anchore/syft/internal/bus"
"github.com/anchore/syft/syft/event" "github.com/anchore/syft/syft/event"
"github.com/anchore/syft/syft/sbom"
"github.com/antihax/optional"
"github.com/wagoodman/go-partybus" "github.com/wagoodman/go-partybus"
"github.com/wagoodman/go-progress" "github.com/wagoodman/go-progress"
"github.com/anchore/client-go/pkg/external"
"github.com/anchore/stereoscope/pkg/image"
"github.com/anchore/syft/syft/sbom"
"github.com/antihax/optional"
) )
type ImportConfig struct { type ImportConfig struct {
+11 -9
View File
@@ -4,6 +4,8 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/file"
"net/http" "net/http"
"strings" "strings"
"testing" "testing"
@@ -22,7 +24,7 @@ import (
"github.com/wagoodman/go-progress" "github.com/wagoodman/go-progress"
) )
func must(c pkg.CPE, e error) pkg.CPE { func must(c cpe.CPE, e error) cpe.CPE {
if e != nil { if e != nil {
panic(e) panic(e)
} }
@@ -52,13 +54,13 @@ func (m *mockPackageSBOMImportAPI) ImportImagePackages(ctx context.Context, sess
func sbomFixture() sbom.SBOM { func sbomFixture() sbom.SBOM {
return sbom.SBOM{ return sbom.SBOM{
Artifacts: sbom.Artifacts{ Artifacts: sbom.Artifacts{
PackageCatalog: pkg.NewCatalog(pkg.Package{ Packages: pkg.NewCollection(pkg.Package{
Name: "name", Name: "name",
Version: "version", Version: "version",
FoundBy: "foundBy", FoundBy: "foundBy",
Locations: []source.Location{ Locations: []file.Location{
{ {
Coordinates: source.Coordinates{ Coordinates: file.Coordinates{
RealPath: "path", RealPath: "path",
FileSystemID: "layerID", FileSystemID: "layerID",
}, },
@@ -67,8 +69,8 @@ func sbomFixture() sbom.SBOM {
Licenses: []string{"license"}, Licenses: []string{"license"},
Language: pkg.Python, Language: pkg.Python,
Type: pkg.PythonPkg, Type: pkg.PythonPkg,
CPEs: []pkg.CPE{ CPEs: []cpe.CPE{
must(pkg.NewCPE("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*")), must(cpe.New("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*")),
}, },
PURL: "purl", PURL: "purl",
MetadataType: pkg.PythonPackageMetadataType, MetadataType: pkg.PythonPackageMetadataType,
@@ -102,13 +104,13 @@ func sbomFixture() sbom.SBOM {
}, },
Relationships: []artifact.Relationship{ Relationships: []artifact.Relationship{
{ {
From: source.NewLocation("/place1"), From: file.NewLocation("/place1"),
To: source.NewLocation("/place2"), To: file.NewLocation("/place2"),
Type: artifact.ContainsRelationship, Type: artifact.ContainsRelationship,
}, },
}, },
Source: source.Metadata{ Source: source.Metadata{
Scheme: source.ImageScheme, Scheme: source.ImageType,
ImageMetadata: source.ImageMetadata{ ImageMetadata: source.ImageMetadata{
UserInput: "user-in", UserInput: "user-in",
Layers: nil, Layers: nil,
@@ -1,12 +1,14 @@
package file package archive
import ( import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"github.com/anchore/syft/syft/file"
) )
const perFileReadLimit = 2 * GB const perFileReadLimit = 2 * file.GB
// safeCopy limits the copy from the reader. This is useful when extracting files from archives to // safeCopy limits the copy from the reader. This is useful when extracting files from archives to
// protect against decompression bomb attacks. // protect against decompression bomb attacks.
@@ -1,38 +1,39 @@
package file package archive
import ( import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"path/filepath" "path/filepath"
"github.com/anchore/syft/syft/file"
"github.com/bmatcuk/doublestar/v4" "github.com/bmatcuk/doublestar/v4"
"github.com/mholt/archiver/v3" "github.com/mholt/archiver/v3"
) )
// ExtractGlobsFromTarToUniqueTempFile extracts paths matching the given globs within the given archive to a temporary directory, returning file openers for each file extracted. // ExtractGlobsFromTarToUniqueTempFile extracts paths matching the given globs within the given archive to a temporary directory, returning file openers for each file extracted.
func ExtractGlobsFromTarToUniqueTempFile(archivePath, dir string, globs ...string) (map[string]Opener, error) { func ExtractGlobsFromTarToUniqueTempFile(archivePath, dir string, globs ...string) (map[string]file.Opener, error) {
results := make(map[string]Opener) results := make(map[string]file.Opener)
// don't allow for full traversal, only select traversal from given paths // don't allow for full traversal, only select traversal from given paths
if len(globs) == 0 { if len(globs) == 0 {
return results, nil return results, nil
} }
visitor := func(file archiver.File) error { visitor := func(f archiver.File) error {
defer file.Close() defer f.Close()
// ignore directories // ignore directories
if file.FileInfo.IsDir() { if f.FileInfo.IsDir() {
return nil return nil
} }
// ignore any filename that doesn't match the given globs... // ignore any filename that doesn't match the given globs...
if !matchesAnyGlob(file.Name(), globs...) { if !matchesAnyGlob(f.Name(), globs...) {
return nil return nil
} }
// we have a file we want to extract.... // we have a file we want to extract....
tempfilePrefix := filepath.Base(filepath.Clean(file.Name())) + "-" tempfilePrefix := filepath.Base(filepath.Clean(f.Name())) + "-"
tempFile, err := ioutil.TempFile(dir, tempfilePrefix) tempFile, err := ioutil.TempFile(dir, tempfilePrefix)
if err != nil { if err != nil {
return fmt.Errorf("unable to create temp file: %w", err) return fmt.Errorf("unable to create temp file: %w", err)
@@ -42,11 +43,11 @@ func ExtractGlobsFromTarToUniqueTempFile(archivePath, dir string, globs ...strin
// provides a ReadCloser. It is up to the caller to handle closing the file explicitly. // provides a ReadCloser. It is up to the caller to handle closing the file explicitly.
defer tempFile.Close() defer tempFile.Close()
if err := safeCopy(tempFile, file.ReadCloser); err != nil { if err := safeCopy(tempFile, f.ReadCloser); err != nil {
return fmt.Errorf("unable to copy source=%q for tar=%q: %w", file.Name(), archivePath, err) return fmt.Errorf("unable to copy source=%q for tar=%q: %w", f.Name(), archivePath, err)
} }
results[file.Name()] = Opener{path: tempFile.Name()} results[f.Name()] = file.NewOpener(tempFile.Name())
return nil return nil
} }
@@ -1,4 +1,4 @@
package file package archive
import ( import (
"io/ioutil" "io/ioutil"
@@ -1,4 +1,4 @@
package file package archive
import ( import (
"fmt" "fmt"
@@ -48,7 +48,7 @@ func (z ZipFileManifest) GlobMatch(patterns ...string) []string {
// so that glob logic is consistent inside and outside of ZIP archives // so that glob logic is consistent inside and outside of ZIP archives
normalizedEntry := normalizeZipEntryName(entry) normalizedEntry := normalizeZipEntryName(entry)
if GlobMatch(pattern, normalizedEntry) { if internal.FileNameGlobMatch(pattern, normalizedEntry) {
uniqueMatches.Add(entry) uniqueMatches.Add(entry)
} }
} }
@@ -1,7 +1,7 @@
//go:build !windows //go:build !windows
// +build !windows // +build !windows
package file package archive
import ( import (
"encoding/json" "encoding/json"
@@ -1,4 +1,4 @@
package file package archive
import ( import (
"archive/zip" "archive/zip"
@@ -9,15 +9,9 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/file"
)
const ( "github.com/anchore/syft/internal/log"
// represents the order of bytes
_ = iota
KB = 1 << (10 * iota)
MB
GB
) )
type errZipSlipDetected struct { type errZipSlipDetected struct {
@@ -71,16 +65,16 @@ func TraverseFilesInZip(archivePath string, visitor func(*zip.File) error, paths
} }
// ExtractFromZipToUniqueTempFile extracts select paths for the given archive to a temporary directory, returning file openers for each file extracted. // ExtractFromZipToUniqueTempFile extracts select paths for the given archive to a temporary directory, returning file openers for each file extracted.
func ExtractFromZipToUniqueTempFile(archivePath, dir string, paths ...string) (map[string]Opener, error) { func ExtractFromZipToUniqueTempFile(archivePath, dir string, paths ...string) (map[string]file.Opener, error) {
results := make(map[string]Opener) results := make(map[string]file.Opener)
// don't allow for full traversal, only select traversal from given paths // don't allow for full traversal, only select traversal from given paths
if len(paths) == 0 { if len(paths) == 0 {
return results, nil return results, nil
} }
visitor := func(file *zip.File) error { visitor := func(f *zip.File) error {
tempfilePrefix := filepath.Base(filepath.Clean(file.Name)) + "-" tempfilePrefix := filepath.Base(filepath.Clean(f.Name)) + "-"
tempFile, err := ioutil.TempFile(dir, tempfilePrefix) tempFile, err := ioutil.TempFile(dir, tempfilePrefix)
if err != nil { if err != nil {
@@ -91,26 +85,26 @@ func ExtractFromZipToUniqueTempFile(archivePath, dir string, paths ...string) (m
// provides a ReadCloser. It is up to the caller to handle closing the file explicitly. // provides a ReadCloser. It is up to the caller to handle closing the file explicitly.
defer tempFile.Close() defer tempFile.Close()
zippedFile, err := file.Open() zippedFile, err := f.Open()
if err != nil { if err != nil {
return fmt.Errorf("unable to read file=%q from zip=%q: %w", file.Name, archivePath, err) return fmt.Errorf("unable to read file=%q from zip=%q: %w", f.Name, archivePath, err)
} }
defer func() { defer func() {
err := zippedFile.Close() err := zippedFile.Close()
if err != nil { if err != nil {
log.Errorf("unable to close source file=%q from zip=%q: %+v", file.Name, archivePath, err) log.Errorf("unable to close source file=%q from zip=%q: %+v", f.Name, archivePath, err)
} }
}() }()
if file.FileInfo().IsDir() { if f.FileInfo().IsDir() {
return fmt.Errorf("unable to extract directories, only files: %s", file.Name) return fmt.Errorf("unable to extract directories, only files: %s", f.Name)
} }
if err := safeCopy(tempFile, zippedFile); err != nil { if err := safeCopy(tempFile, zippedFile); err != nil {
return fmt.Errorf("unable to copy source=%q for zip=%q: %w", file.Name, archivePath, err) return fmt.Errorf("unable to copy source=%q for zip=%q: %w", f.Name, archivePath, err)
} }
results[file.Name] = Opener{path: tempFile.Name()} results[f.Name] = file.NewOpener(tempFile.Name())
return nil return nil
} }
@@ -1,7 +1,7 @@
//go:build !windows //go:build !windows
// +build !windows // +build !windows
package file package archive
import ( import (
"crypto/sha256" "crypto/sha256"
@@ -1,4 +1,4 @@
package file package archive
import ( import (
"archive/zip" "archive/zip"
@@ -1,7 +1,7 @@
//go:build !windows //go:build !windows
// +build !windows // +build !windows
package file package archive
import ( import (
"os" "os"
+74 -18
View File
@@ -3,12 +3,16 @@ package config
import ( import (
"errors" "errors"
"fmt" "fmt"
"github.com/anchore/syft/syft/source"
"path" "path"
"reflect" "reflect"
"strings" "strings"
"github.com/adrg/xdg" "github.com/adrg/xdg"
"github.com/anchore/syft/internal" "github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/version"
"github.com/anchore/syft/syft"
"github.com/anchore/syft/syft/file"
"github.com/mitchellh/go-homedir" "github.com/mitchellh/go-homedir"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/spf13/viper" "github.com/spf13/viper"
@@ -17,6 +21,7 @@ import (
var ErrApplicationConfigNotFound = fmt.Errorf("application config not found") var ErrApplicationConfigNotFound = fmt.Errorf("application config not found")
// TODO: set all catalogers when this is set
var catalogerEnabledDefault = false var catalogerEnabledDefault = false
type defaultValueLoader interface { type defaultValueLoader interface {
@@ -29,24 +34,66 @@ type parser interface {
// Application is the main syft application configuration. // Application is the main syft application configuration.
type Application struct { type Application struct {
ConfigPath string `yaml:",omitempty" json:"configPath"` // the location where the application config was read from (either from -c or discovered while loading) ConfigPath string `yaml:",omitempty" json:"configPath"` // the location where the application config was read from (either from -c or discovered while loading)
Outputs []string `yaml:"output" json:"output" mapstructure:"output"` // -o, the format to use for output Outputs []string `yaml:"output" json:"output" mapstructure:"output"` // -o, the format to use for output
File string `yaml:"file" json:"file" mapstructure:"file"` // --file, the file to write report output to File string `yaml:"file" json:"file" mapstructure:"file"` // --file, the file to write report output to
Quiet bool `yaml:"quiet" json:"quiet" mapstructure:"quiet"` // -q, indicates to not show any status output to stderr (ETUI or logging UI) Quiet bool `yaml:"quiet" json:"quiet" mapstructure:"quiet"` // -q, indicates to not show any status output to stderr (ETUI or logging UI)
CheckForAppUpdate bool `yaml:"check-for-app-update" json:"check-for-app-update" mapstructure:"check-for-app-update"` // whether to check for an application update on start up or not CheckForAppUpdate bool `yaml:"check-for-app-update" json:"check-for-app-update" mapstructure:"check-for-app-update"` // whether to check for an application update on start up or not
Anchore anchore `yaml:"anchore" json:"anchore" mapstructure:"anchore"` // options for interacting with Anchore Engine/Enterprise Anchore anchore `yaml:"anchore" json:"anchore" mapstructure:"anchore"` // options for interacting with Anchore Engine/Enterprise
CliOptions CliOnlyOptions `yaml:"-" json:"-"` // all options only available through the CLI (not via env vars or config) CliOptions CliOnlyOptions `yaml:"-" json:"-"` // all options only available through the CLI (not via env vars or config)
Dev development `yaml:"dev" json:"dev" mapstructure:"dev"` Catalogers []string `yaml:"catalogers" json:"catalogers" mapstructure:"catalogers"`
Log logging `yaml:"log" json:"log" mapstructure:"log"` // all logging-related options Scope string `yaml:"scope" json:"scope" mapstructure:"scope"`
Package pkg `yaml:"package" json:"package" mapstructure:"package"` Dev development `yaml:"dev" json:"dev" mapstructure:"dev"`
FileMetadata FileMetadata `yaml:"file-metadata" json:"file-metadata" mapstructure:"file-metadata"` Log logging `yaml:"log" json:"log" mapstructure:"log"` // all logging-related options
FileClassification fileClassification `yaml:"file-classification" json:"file-classification" mapstructure:"file-classification"` Package pkg `yaml:"package" json:"package" mapstructure:"package"`
FileContents fileContents `yaml:"file-contents" json:"file-contents" mapstructure:"file-contents"` FileMetadata fileMetadata `yaml:"file-metadata" json:"file-metadata" mapstructure:"file-metadata"`
Secrets secrets `yaml:"secrets" json:"secrets" mapstructure:"secrets"` FileContents fileContents `yaml:"file-contents" json:"file-contents" mapstructure:"file-contents"`
Registry registry `yaml:"registry" json:"registry" mapstructure:"registry"` Secrets secretsCfg `yaml:"secrets" json:"secrets" mapstructure:"secrets"`
Exclusions []string `yaml:"exclude" json:"exclude" mapstructure:"exclude"` Registry registry `yaml:"registry" json:"registry" mapstructure:"registry"`
Attest attest `yaml:"attest" json:"attest" mapstructure:"attest"` Exclusions []string `yaml:"exclude" json:"exclude" mapstructure:"exclude"`
Platform string `yaml:"platform" json:"platform" mapstructure:"platform"` Attest attest `yaml:"attest" json:"attest" mapstructure:"attest"`
Platform string `yaml:"platform" json:"platform" mapstructure:"platform"`
}
func (cfg Application) ToCatalogingOptions() ([]syft.CatalogingOption, error) {
digests, err := file.DigestHashesByName(cfg.FileMetadata.Digests...)
if err != nil {
return nil, fmt.Errorf("unable to parse config item 'file-metadata.digests': %w", err)
}
scopeOption := source.ParseScope(cfg.Scope)
if scopeOption == source.UnknownScope {
return nil, fmt.Errorf("bad scope value %q", cfg.Scope)
}
secretsConfig, err := cfg.Secrets.ToConfig()
if err != nil {
return nil, err
}
secretsScopeOption := source.ParseScope(cfg.Secrets.Scope)
if secretsScopeOption == source.UnknownScope {
return nil, fmt.Errorf("bad scope value %q", cfg.Secrets.Scope)
}
c := syft.DefaultCatalogingConfig()
c.ToolVersion = version.FromBuild().Version
c.ToolConfiguration = &c
c.DefaultScope = scopeOption
c.ProcessTasksInSerial = false
c.DigestHashes = digests
c.SecretsSearch = *secretsConfig
c.SecretsScope = secretsScopeOption
c.ContentsSearch = cfg.FileContents.ToConfig()
opts := []syft.CatalogingOption{
syft.WithConfig(c),
syft.WithCatalogers(cfg.Catalogers...),
}
return opts, nil
} }
// PowerUserCatalogerEnabledDefault switches all catalogers to be enabled when running power-user command // PowerUserCatalogerEnabledDefault switches all catalogers to be enabled when running power-user command
@@ -100,6 +147,15 @@ func (cfg Application) loadDefaultValues(v *viper.Viper) {
} }
func (cfg *Application) parseConfigValues() error { func (cfg *Application) parseConfigValues() error {
// parse fields on application directyy
var catalogers []string
for _, c := range cfg.Catalogers {
for _, f := range strings.Split(c, ",") {
catalogers = append(catalogers, strings.TrimSpace(f))
}
}
cfg.Catalogers = catalogers
// parse application config options // parse application config options
for _, optionFn := range []func() error{ for _, optionFn := range []func() error{
cfg.parseUploadOptions, cfg.parseUploadOptions,
-29
View File
@@ -1,29 +0,0 @@
package config
import (
"fmt"
"github.com/spf13/viper"
"github.com/anchore/syft/syft/source"
)
type catalogerOptions struct {
Enabled bool `yaml:"enabled" json:"enabled" mapstructure:"enabled"`
Scope string `yaml:"scope" json:"scope" mapstructure:"scope"`
ScopeOpt source.Scope `yaml:"-" json:"-"`
}
func (cfg catalogerOptions) loadDefaultValues(v *viper.Viper) {
v.SetDefault("package.cataloger.enabled", true)
}
func (cfg *catalogerOptions) parseConfigValues() error {
scopeOption := source.ParseScope(cfg.Scope)
if scopeOption == source.UnknownScope {
return fmt.Errorf("bad scope value %q", cfg.Scope)
}
cfg.ScopeOpt = scopeOption
return nil
}
-19
View File
@@ -1,19 +0,0 @@
package config
import (
"github.com/anchore/syft/syft/source"
"github.com/spf13/viper"
)
type fileClassification struct {
Cataloger catalogerOptions `yaml:"cataloger" json:"cataloger" mapstructure:"cataloger"`
}
func (cfg fileClassification) loadDefaultValues(v *viper.Viper) {
v.SetDefault("file-classification.cataloger.enabled", catalogerEnabledDefault)
v.SetDefault("file-classification.cataloger.scope", source.SquashedScope)
}
func (cfg *fileClassification) parseConfigValues() error {
return cfg.Cataloger.parseConfigValues()
}
+9 -9
View File
@@ -1,24 +1,24 @@
package config package config
import ( import (
"github.com/anchore/syft/internal/file" "github.com/anchore/syft/syft/cataloger/files/filecontents"
"github.com/anchore/syft/syft/source" "github.com/anchore/syft/syft/file"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
type fileContents struct { type fileContents struct {
Cataloger catalogerOptions `yaml:"cataloger" json:"cataloger" mapstructure:"cataloger"` SkipFilesAboveSize int64 `yaml:"skip-files-above-size" json:"skip-files-above-size" mapstructure:"skip-files-above-size"`
SkipFilesAboveSize int64 `yaml:"skip-files-above-size" json:"skip-files-above-size" mapstructure:"skip-files-above-size"` Globs []string `yaml:"globs" json:"globs" mapstructure:"globs"`
Globs []string `yaml:"globs" json:"globs" mapstructure:"globs"`
} }
func (cfg fileContents) loadDefaultValues(v *viper.Viper) { func (cfg fileContents) loadDefaultValues(v *viper.Viper) {
v.SetDefault("file-contents.cataloger.enabled", catalogerEnabledDefault)
v.SetDefault("file-contents.cataloger.scope", source.SquashedScope)
v.SetDefault("file-contents.skip-files-above-size", 1*file.MB) v.SetDefault("file-contents.skip-files-above-size", 1*file.MB)
v.SetDefault("file-contents.globs", []string{}) v.SetDefault("file-contents.globs", []string{})
} }
func (cfg *fileContents) parseConfigValues() error { func (cfg fileContents) ToConfig() filecontents.Config {
return cfg.Cataloger.parseConfigValues() return filecontents.Config{
Globs: cfg.Globs,
SkipFilesAboveSizeInBytes: cfg.SkipFilesAboveSize,
}
} }
+5 -9
View File
@@ -1,21 +1,17 @@
package config package config
import ( import (
"github.com/anchore/syft/syft/source"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
type FileMetadata struct { type fileMetadata struct {
Cataloger catalogerOptions `yaml:"cataloger" json:"cataloger" mapstructure:"cataloger"` Digests []string `yaml:"digests" json:"digests" mapstructure:"digests"`
Digests []string `yaml:"digests" json:"digests" mapstructure:"digests"`
} }
func (cfg FileMetadata) loadDefaultValues(v *viper.Viper) { func (cfg fileMetadata) loadDefaultValues(v *viper.Viper) {
v.SetDefault("file-metadata.cataloger.enabled", catalogerEnabledDefault)
v.SetDefault("file-metadata.cataloger.scope", source.SquashedScope)
v.SetDefault("file-metadata.digests", []string{"sha256"}) v.SetDefault("file-metadata.digests", []string{"sha256"})
} }
func (cfg *FileMetadata) parseConfigValues() error { func (cfg *fileMetadata) parseConfigValues() error {
return cfg.Cataloger.parseConfigValues() return nil
} }
+8 -17
View File
@@ -1,33 +1,24 @@
package config package config
import ( import (
"github.com/anchore/syft/syft/pkg/cataloger" "github.com/anchore/syft/syft/cataloger/packages"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
type pkg struct { type pkg struct {
Cataloger catalogerOptions `yaml:"cataloger" json:"cataloger" mapstructure:"cataloger"` SearchUnindexedArchives bool `yaml:"search-unindexed-archives" json:"search-unindexed-archives" mapstructure:"search-unindexed-archives"`
SearchUnindexedArchives bool `yaml:"search-unindexed-archives" json:"search-unindexed-archives" mapstructure:"search-unindexed-archives"` SearchIndexedArchives bool `yaml:"search-indexed-archives" json:"search-indexed-archives" mapstructure:"search-indexed-archives"`
SearchIndexedArchives bool `yaml:"search-indexed-archives" json:"search-indexed-archives" mapstructure:"search-indexed-archives"`
} }
func (cfg pkg) loadDefaultValues(v *viper.Viper) { func (cfg pkg) loadDefaultValues(v *viper.Viper) {
cfg.Cataloger.loadDefaultValues(v) c := packages.DefaultSearchConfig()
c := cataloger.DefaultSearchConfig()
v.SetDefault("package.search-unindexed-archives", c.IncludeUnindexedArchives) v.SetDefault("package.search-unindexed-archives", c.IncludeUnindexedArchives)
v.SetDefault("package.search-indexed-archives", c.IncludeIndexedArchives) v.SetDefault("package.search-indexed-archives", c.IncludeIndexedArchives)
} }
func (cfg *pkg) parseConfigValues() error { func (cfg pkg) ToConfig() packages.SearchConfig {
return cfg.Cataloger.parseConfigValues() return packages.SearchConfig{
} IncludeIndexedArchives: cfg.SearchIndexedArchives,
IncludeUnindexedArchives: cfg.SearchUnindexedArchives,
func (cfg pkg) ToConfig() cataloger.Config {
return cataloger.Config{
Search: cataloger.SearchConfig{
IncludeIndexedArchives: cfg.SearchIndexedArchives,
IncludeUnindexedArchives: cfg.SearchUnindexedArchives,
Scope: cfg.Cataloger.ScopeOpt,
},
} }
} }
+18 -8
View File
@@ -1,28 +1,38 @@
package config package config
import ( import (
"github.com/anchore/syft/internal/file" "fmt"
"github.com/anchore/syft/syft/cataloger/files/secrets"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/source" "github.com/anchore/syft/syft/source"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
type secrets struct { type secretsCfg struct {
Cataloger catalogerOptions `yaml:"cataloger" json:"cataloger" mapstructure:"cataloger"`
AdditionalPatterns map[string]string `yaml:"additional-patterns" json:"additional-patterns" mapstructure:"additional-patterns"` AdditionalPatterns map[string]string `yaml:"additional-patterns" json:"additional-patterns" mapstructure:"additional-patterns"`
ExcludePatternNames []string `yaml:"exclude-pattern-names" json:"exclude-pattern-names" mapstructure:"exclude-pattern-names"` ExcludePatternNames []string `yaml:"exclude-pattern-names" json:"exclude-pattern-names" mapstructure:"exclude-pattern-names"`
RevealValues bool `yaml:"reveal-values" json:"reveal-values" mapstructure:"reveal-values"` RevealValues bool `yaml:"reveal-values" json:"reveal-values" mapstructure:"reveal-values"`
SkipFilesAboveSize int64 `yaml:"skip-files-above-size" json:"skip-files-above-size" mapstructure:"skip-files-above-size"` SkipFilesAboveSize int64 `yaml:"skip-files-above-size" json:"skip-files-above-size" mapstructure:"skip-files-above-size"`
Scope string `yaml:"scope" json:"scope" mapstructure:"scope"`
} }
func (cfg secrets) loadDefaultValues(v *viper.Viper) { func (cfg secretsCfg) loadDefaultValues(v *viper.Viper) {
v.SetDefault("secrets.cataloger.enabled", catalogerEnabledDefault) v.SetDefault("secrets.scope", source.AllLayersScope)
v.SetDefault("secrets.cataloger.scope", source.AllLayersScope)
v.SetDefault("secrets.reveal-values", false) v.SetDefault("secrets.reveal-values", false)
v.SetDefault("secrets.skip-files-above-size", 1*file.MB) v.SetDefault("secrets.skip-files-above-size", 1*file.MB)
v.SetDefault("secrets.additional-patterns", map[string]string{}) v.SetDefault("secrets.additional-patterns", map[string]string{})
v.SetDefault("secrets.exclude-pattern-names", []string{}) v.SetDefault("secrets.exclude-pattern-names", []string{})
} }
func (cfg *secrets) parseConfigValues() error { func (cfg secretsCfg) ToConfig() (*secrets.Config, error) {
return cfg.Cataloger.parseConfigValues() patterns, err := file.GenerateSearchPatterns(secrets.DefaultSecretsPatterns, cfg.AdditionalPatterns, cfg.ExcludePatternNames)
if err != nil {
return nil, fmt.Errorf("unable to process secrets config patterns: %w", err)
}
return &secrets.Config{
Patterns: patterns,
RevealValues: cfg.RevealValues,
MaxFileSize: cfg.SkipFilesAboveSize,
}, nil
} }
@@ -1,8 +1,8 @@
package file package internal
// GlobMatch evaluates the given glob pattern against the given "name" string, indicating if there is a match or not. // FileNameGlobMatch evaluates the given glob pattern against the given "name" string, indicating if there is a match or not.
// Source: https://research.swtch.com/glob.go // Source: https://research.swtch.com/glob.go
func GlobMatch(pattern, name string) bool { func FileNameGlobMatch(pattern, name string) bool {
px := 0 px := 0
nx := 0 nx := 0
nextPx := 0 nextPx := 0
@@ -1,4 +1,4 @@
package file package internal
import ( import (
"strings" "strings"
@@ -32,7 +32,7 @@ func TestGlobMatch(t *testing.T) {
} }
for _, test := range tests { for _, test := range tests {
if GlobMatch(test.pattern, test.data) != test.ok { if FileNameGlobMatch(test.pattern, test.data) != test.ok {
t.Errorf("failed glob='%s' data='%s'", test.pattern, test.data) t.Errorf("failed glob='%s' data='%s'", test.pattern, test.data)
} }
} }
@@ -3,11 +3,12 @@ package cyclonedxhelpers
import ( import (
"reflect" "reflect"
"github.com/anchore/syft/syft/file"
"github.com/CycloneDX/cyclonedx-go" "github.com/CycloneDX/cyclonedx-go"
"github.com/anchore/syft/internal/formats/common" "github.com/anchore/syft/internal/formats/common"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
) )
func encodeComponent(p pkg.Package) cyclonedx.Component { func encodeComponent(p pkg.Package) cyclonedx.Component {
@@ -71,9 +72,9 @@ func decodeComponent(c *cyclonedx.Component) *pkg.Package {
return p return p
} }
func decodeLocations(vals map[string]string) []source.Location { func decodeLocations(vals map[string]string) []file.Location {
v := common.Decode(reflect.TypeOf([]source.Location{}), vals, "syft:location", CycloneDXFields) v := common.Decode(reflect.TypeOf([]file.Location{}), vals, "syft:location", CycloneDXFields)
out, _ := v.([]source.Location) out, _ := v.([]file.Location)
return out return out
} }
@@ -1,13 +1,13 @@
package cyclonedxhelpers package cyclonedxhelpers
import ( import (
"github.com/anchore/syft/syft/file"
"testing" "testing"
"github.com/CycloneDX/cyclonedx-go" "github.com/CycloneDX/cyclonedx-go"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
) )
func Test_encodeComponentProperties(t *testing.T) { func Test_encodeComponentProperties(t *testing.T) {
@@ -26,8 +26,8 @@ func Test_encodeComponentProperties(t *testing.T) {
name: "from apk", name: "from apk",
input: pkg.Package{ input: pkg.Package{
FoundBy: "cataloger", FoundBy: "cataloger",
Locations: []source.Location{ Locations: []file.Location{
{Coordinates: source.Coordinates{RealPath: "test"}}, {Coordinates: file.Coordinates{RealPath: "test"}},
}, },
Metadata: pkg.ApkMetadata{ Metadata: pkg.ApkMetadata{
Package: "libc-utils", Package: "libc-utils",
@@ -2,6 +2,7 @@ package cyclonedxhelpers
import ( import (
"github.com/CycloneDX/cyclonedx-go" "github.com/CycloneDX/cyclonedx-go"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
@@ -11,7 +12,7 @@ func encodeSingleCPE(p pkg.Package) string {
// Since the CPEs in a package are sorted by specificity // Since the CPEs in a package are sorted by specificity
// we can extract the first CPE as the one to output in cyclonedx // we can extract the first CPE as the one to output in cyclonedx
if len(p.CPEs) > 0 { if len(p.CPEs) > 0 {
return pkg.CPEString(p.CPEs[0]) return cpe.String(p.CPEs[0])
} }
return "" return ""
} }
@@ -24,15 +25,15 @@ func encodeCPEs(p pkg.Package) (out []cyclonedx.Property) {
} }
out = append(out, cyclonedx.Property{ out = append(out, cyclonedx.Property{
Name: "syft:cpe23", Name: "syft:cpe23",
Value: pkg.CPEString(c), Value: cpe.String(c),
}) })
} }
return return
} }
func decodeCPEs(c *cyclonedx.Component) (out []pkg.CPE) { func decodeCPEs(c *cyclonedx.Component) (out []cpe.CPE) {
if c.CPE != "" { if c.CPE != "" {
cp, err := pkg.NewCPE(c.CPE) cp, err := cpe.New(c.CPE)
if err != nil { if err != nil {
log.Warnf("invalid CPE: %s", c.CPE) log.Warnf("invalid CPE: %s", c.CPE)
} else { } else {
@@ -43,7 +44,7 @@ func decodeCPEs(c *cyclonedx.Component) (out []pkg.CPE) {
if c.Properties != nil { if c.Properties != nil {
for _, p := range *c.Properties { for _, p := range *c.Properties {
if p.Name == "syft:cpe23" { if p.Name == "syft:cpe23" {
cp, err := pkg.NewCPE(p.Value) cp, err := cpe.New(p.Value)
if err != nil { if err != nil {
log.Warnf("invalid CPE: %s", p.Value) log.Warnf("invalid CPE: %s", p.Value)
} else { } else {
@@ -1,6 +1,7 @@
package cyclonedxhelpers package cyclonedxhelpers
import ( import (
"github.com/anchore/syft/syft/cpe"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -9,8 +10,8 @@ import (
) )
func Test_encodeCPE(t *testing.T) { func Test_encodeCPE(t *testing.T) {
testCPE := pkg.MustCPE("cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*") testCPE := cpe.Must("cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*")
testCPE2 := pkg.MustCPE("cpe:2.3:a:name:name2:3.2:*:*:*:*:*:*:*") testCPE2 := cpe.Must("cpe:2.3:a:name:name2:3.2:*:*:*:*:*:*:*")
tests := []struct { tests := []struct {
name string name string
input pkg.Package input pkg.Package
@@ -20,14 +21,14 @@ func Test_encodeCPE(t *testing.T) {
// note: since this is an optional field, no value is preferred over NONE or NOASSERTION // note: since this is an optional field, no value is preferred over NONE or NOASSERTION
name: "no metadata", name: "no metadata",
input: pkg.Package{ input: pkg.Package{
CPEs: []pkg.CPE{}, CPEs: []cpe.CPE{},
}, },
expected: "", expected: "",
}, },
{ {
name: "single CPE", name: "single CPE",
input: pkg.Package{ input: pkg.Package{
CPEs: []pkg.CPE{ CPEs: []cpe.CPE{
testCPE, testCPE,
}, },
}, },
@@ -36,7 +37,7 @@ func Test_encodeCPE(t *testing.T) {
{ {
name: "multiple CPEs", name: "multiple CPEs",
input: pkg.Package{ input: pkg.Package{
CPEs: []pkg.CPE{ CPEs: []cpe.CPE{
testCPE2, testCPE2,
testCPE, testCPE,
}, },
@@ -49,7 +49,7 @@ func toSyftModel(bom *cyclonedx.BOM) (*sbom.SBOM, error) {
} }
s := &sbom.SBOM{ s := &sbom.SBOM{
Artifacts: sbom.Artifacts{ Artifacts: sbom.Artifacts{
PackageCatalog: pkg.NewCatalog(), Packages: pkg.NewCollection(),
LinuxDistribution: linuxReleaseFromComponents(*bom.Components), LinuxDistribution: linuxReleaseFromComponents(*bom.Components),
}, },
Source: meta, Source: meta,
@@ -86,7 +86,7 @@ func collectPackages(component *cyclonedx.Component, s *sbom.SBOM, idMap map[str
idMap[component.BOMRef] = p idMap[component.BOMRef] = p
// TODO there must be a better way than needing to call this manually: // TODO there must be a better way than needing to call this manually:
p.SetID() p.SetID()
s.Artifacts.PackageCatalog.Add(*p) s.Artifacts.Packages.Add(*p)
} }
if component.Components != nil { if component.Components != nil {
@@ -199,7 +199,7 @@ func decodeMetadata(component *cyclonedx.Component) source.Metadata {
switch component.Type { switch component.Type {
case cyclonedx.ComponentTypeContainer: case cyclonedx.ComponentTypeContainer:
return source.Metadata{ return source.Metadata{
Scheme: source.ImageScheme, Scheme: source.ImageType,
ImageMetadata: source.ImageMetadata{ ImageMetadata: source.ImageMetadata{
UserInput: component.Name, UserInput: component.Name,
ID: component.BOMRef, ID: component.BOMRef,
@@ -208,7 +208,7 @@ func decodeMetadata(component *cyclonedx.Component) source.Metadata {
} }
case cyclonedx.ComponentTypeFile: case cyclonedx.ComponentTypeFile:
return source.Metadata{ return source.Metadata{
Scheme: source.FileScheme, // or source.DirectoryScheme Scheme: source.FileType, // or source.DirectoryType
Path: component.Name, Path: component.Name,
ImageMetadata: source.ImageMetadata{ ImageMetadata: source.ImageMetadata{
UserInput: component.Name, UserInput: component.Name,
@@ -212,7 +212,7 @@ func Test_decode(t *testing.T) {
assert.Equal(t, e.ver, sbom.Artifacts.LinuxDistribution.VersionID) assert.Equal(t, e.ver, sbom.Artifacts.LinuxDistribution.VersionID)
} }
if e.pkg != "" { if e.pkg != "" {
for p := range sbom.Artifacts.PackageCatalog.Enumerate() { for p := range sbom.Artifacts.Packages.Enumerate() {
if e.pkg != p.Name { if e.pkg != p.Name {
continue continue
} }
@@ -240,7 +240,7 @@ func Test_decode(t *testing.T) {
if e.relation != "" { if e.relation != "" {
foundRelation := false foundRelation := false
for _, r := range sbom.Relationships { for _, r := range sbom.Relationships {
p := sbom.Artifacts.PackageCatalog.Package(r.To.ID()) p := sbom.Artifacts.Packages.Package(r.To.ID())
if e.relation == p.Name { if e.relation == p.Name {
foundRelation = true foundRelation = true
break break
@@ -25,7 +25,7 @@ func ToFormatModel(s sbom.SBOM) *cyclonedx.BOM {
cdxBOM.SerialNumber = uuid.New().URN() cdxBOM.SerialNumber = uuid.New().URN()
cdxBOM.Metadata = toBomDescriptor(internal.ApplicationName, versionInfo.Version, s.Source) cdxBOM.Metadata = toBomDescriptor(internal.ApplicationName, versionInfo.Version, s.Source)
packages := s.Artifacts.PackageCatalog.Sorted() packages := s.Artifacts.Packages.Sorted()
components := make([]cyclonedx.Component, len(packages)) components := make([]cyclonedx.Component, len(packages))
for i, p := range packages { for i, p := range packages {
components[i] = encodeComponent(p) components[i] = encodeComponent(p)
@@ -155,7 +155,7 @@ func toDependencies(relationships []artifact.Relationship) []cyclonedx.Dependenc
func toBomDescriptorComponent(srcMetadata source.Metadata) *cyclonedx.Component { func toBomDescriptorComponent(srcMetadata source.Metadata) *cyclonedx.Component {
switch srcMetadata.Scheme { switch srcMetadata.Scheme {
case source.ImageScheme: case source.ImageType:
bomRef, err := artifact.IDByHash(srcMetadata.ImageMetadata.ID) bomRef, err := artifact.IDByHash(srcMetadata.ImageMetadata.ID)
if err != nil { if err != nil {
log.Warnf("unable to get fingerprint of image metadata=%s: %+v", srcMetadata.ImageMetadata.ID, err) log.Warnf("unable to get fingerprint of image metadata=%s: %+v", srcMetadata.ImageMetadata.ID, err)
@@ -166,7 +166,7 @@ func toBomDescriptorComponent(srcMetadata source.Metadata) *cyclonedx.Component
Name: srcMetadata.ImageMetadata.UserInput, Name: srcMetadata.ImageMetadata.UserInput,
Version: srcMetadata.ImageMetadata.ManifestDigest, Version: srcMetadata.ImageMetadata.ManifestDigest,
} }
case source.DirectoryScheme, source.FileScheme: case source.DirectoryType, source.FileType:
bomRef, err := artifact.IDByHash(srcMetadata.Path) bomRef, err := artifact.IDByHash(srcMetadata.Path)
if err != nil { if err != nil {
log.Warnf("unable to get fingerprint of source metadata path=%s: %+v", srcMetadata.Path, err) log.Warnf("unable to get fingerprint of source metadata path=%s: %+v", srcMetadata.Path, err)
@@ -10,9 +10,9 @@ import (
func DocumentName(srcMetadata source.Metadata) (string, error) { func DocumentName(srcMetadata source.Metadata) (string, error) {
switch srcMetadata.Scheme { switch srcMetadata.Scheme {
case source.ImageScheme: case source.ImageType:
return cleanName(srcMetadata.ImageMetadata.UserInput), nil return cleanName(srcMetadata.ImageMetadata.UserInput), nil
case source.DirectoryScheme, source.FileScheme: case source.DirectoryType, source.FileType:
return cleanName(srcMetadata.Path), nil return cleanName(srcMetadata.Path), nil
} }
@@ -14,7 +14,7 @@ import (
func Test_DocumentName(t *testing.T) { func Test_DocumentName(t *testing.T) {
allSchemes := strset.New() allSchemes := strset.New()
for _, s := range source.AllSchemes { for _, s := range source.AllTypes {
allSchemes.Add(string(s)) allSchemes.Add(string(s))
} }
testedSchemes := strset.New() testedSchemes := strset.New()
@@ -29,7 +29,7 @@ func Test_DocumentName(t *testing.T) {
name: "image", name: "image",
inputName: "my-name", inputName: "my-name",
srcMetadata: source.Metadata{ srcMetadata: source.Metadata{
Scheme: source.ImageScheme, Scheme: source.ImageType,
ImageMetadata: source.ImageMetadata{ ImageMetadata: source.ImageMetadata{
UserInput: "image-repo/name:tag", UserInput: "image-repo/name:tag",
ID: "id", ID: "id",
@@ -42,7 +42,7 @@ func Test_DocumentName(t *testing.T) {
name: "directory", name: "directory",
inputName: "my-name", inputName: "my-name",
srcMetadata: source.Metadata{ srcMetadata: source.Metadata{
Scheme: source.DirectoryScheme, Scheme: source.DirectoryType,
Path: "some/path/to/place", Path: "some/path/to/place",
}, },
expected: "some/path/to/place", expected: "some/path/to/place",
@@ -51,7 +51,7 @@ func Test_DocumentName(t *testing.T) {
name: "file", name: "file",
inputName: "my-name", inputName: "my-name",
srcMetadata: source.Metadata{ srcMetadata: source.Metadata{
Scheme: source.FileScheme, Scheme: source.FileType,
Path: "some/path/to/place", Path: "some/path/to/place",
}, },
expected: "some/path/to/place", expected: "some/path/to/place",
@@ -21,11 +21,11 @@ func DocumentNameAndNamespace(srcMetadata source.Metadata) (string, string, erro
func DocumentNamespace(name string, srcMetadata source.Metadata) string { func DocumentNamespace(name string, srcMetadata source.Metadata) string {
input := "unknown-source-type" input := "unknown-source-type"
switch srcMetadata.Scheme { switch srcMetadata.Scheme {
case source.ImageScheme: case source.ImageType:
input = "image" input = "image"
case source.DirectoryScheme: case source.DirectoryType:
input = "dir" input = "dir"
case source.FileScheme: case source.FileType:
input = "file" input = "file"
} }
@@ -12,7 +12,7 @@ import (
func Test_documentNamespace(t *testing.T) { func Test_documentNamespace(t *testing.T) {
allSchemes := strset.New() allSchemes := strset.New()
for _, s := range source.AllSchemes { for _, s := range source.AllTypes {
allSchemes.Add(string(s)) allSchemes.Add(string(s))
} }
testedSchemes := strset.New() testedSchemes := strset.New()
@@ -27,7 +27,7 @@ func Test_documentNamespace(t *testing.T) {
name: "image", name: "image",
inputName: "my-name", inputName: "my-name",
srcMetadata: source.Metadata{ srcMetadata: source.Metadata{
Scheme: source.ImageScheme, Scheme: source.ImageType,
ImageMetadata: source.ImageMetadata{ ImageMetadata: source.ImageMetadata{
UserInput: "image-repo/name:tag", UserInput: "image-repo/name:tag",
ID: "id", ID: "id",
@@ -40,7 +40,7 @@ func Test_documentNamespace(t *testing.T) {
name: "directory", name: "directory",
inputName: "my-name", inputName: "my-name",
srcMetadata: source.Metadata{ srcMetadata: source.Metadata{
Scheme: source.DirectoryScheme, Scheme: source.DirectoryType,
Path: "some/path/to/place", Path: "some/path/to/place",
}, },
expected: "https://anchore.com/syft/dir/my-name-", expected: "https://anchore.com/syft/dir/my-name-",
@@ -49,7 +49,7 @@ func Test_documentNamespace(t *testing.T) {
name: "file", name: "file",
inputName: "my-name", inputName: "my-name",
srcMetadata: source.Metadata{ srcMetadata: source.Metadata{
Scheme: source.FileScheme, Scheme: source.FileType,
Path: "some/path/to/place", Path: "some/path/to/place",
}, },
expected: "https://anchore.com/syft/file/my-name-", expected: "https://anchore.com/syft/file/my-name-",
@@ -1,6 +1,7 @@
package spdxhelpers package spdxhelpers
import ( import (
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
) )
@@ -10,7 +11,7 @@ func ExternalRefs(p pkg.Package) (externalRefs []ExternalRef) {
for _, c := range p.CPEs { for _, c := range p.CPEs {
externalRefs = append(externalRefs, ExternalRef{ externalRefs = append(externalRefs, ExternalRef{
ReferenceCategory: SecurityReferenceCategory, ReferenceCategory: SecurityReferenceCategory,
ReferenceLocator: pkg.CPEString(c), ReferenceLocator: cpe.String(c),
ReferenceType: Cpe23ExternalRefType, ReferenceType: Cpe23ExternalRefType,
}) })
} }
@@ -1,6 +1,7 @@
package spdxhelpers package spdxhelpers
import ( import (
"github.com/anchore/syft/syft/cpe"
"testing" "testing"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
@@ -8,7 +9,7 @@ import (
) )
func Test_ExternalRefs(t *testing.T) { func Test_ExternalRefs(t *testing.T) {
testCPE := pkg.MustCPE("cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*") testCPE := cpe.Must("cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*")
tests := []struct { tests := []struct {
name string name string
input pkg.Package input pkg.Package
@@ -17,7 +18,7 @@ func Test_ExternalRefs(t *testing.T) {
{ {
name: "cpe + purl", name: "cpe + purl",
input: pkg.Package{ input: pkg.Package{
CPEs: []pkg.CPE{ CPEs: []cpe.CPE{
testCPE, testCPE,
}, },
PURL: "a-purl", PURL: "a-purl",
@@ -25,7 +26,7 @@ func Test_ExternalRefs(t *testing.T) {
expected: []ExternalRef{ expected: []ExternalRef{
{ {
ReferenceCategory: SecurityReferenceCategory, ReferenceCategory: SecurityReferenceCategory,
ReferenceLocator: pkg.CPEString(testCPE), ReferenceLocator: cpe.String(testCPE),
ReferenceType: Cpe23ExternalRefType, ReferenceType: Cpe23ExternalRefType,
}, },
{ {
@@ -1,10 +1,10 @@
package spdxhelpers package spdxhelpers
import ( import (
"github.com/anchore/syft/syft/file"
"testing" "testing"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -18,9 +18,9 @@ func Test_SourceInfo(t *testing.T) {
name: "locations are captured", name: "locations are captured",
input: pkg.Package{ input: pkg.Package{
// note: no type given // note: no type given
Locations: []source.Location{ Locations: []file.Location{
source.NewVirtualLocation("/a-place", "/b-place"), file.NewVirtualLocation("/a-place", "/b-place"),
source.NewVirtualLocation("/c-place", "/d-place"), file.NewVirtualLocation("/c-place", "/d-place"),
}, },
}, },
expected: []string{ expected: []string{
@@ -1,6 +1,7 @@
package spdxhelpers package spdxhelpers
import ( import (
"github.com/anchore/syft/syft/cpe"
"strconv" "strconv"
"strings" "strings"
@@ -13,7 +14,6 @@ import (
"github.com/anchore/syft/syft/linux" "github.com/anchore/syft/syft/linux"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/sbom" "github.com/anchore/syft/syft/sbom"
"github.com/anchore/syft/syft/source"
) )
func ToSyftModel(doc *spdx.Document2_2) (*sbom.SBOM, error) { func ToSyftModel(doc *spdx.Document2_2) (*sbom.SBOM, error) {
@@ -21,9 +21,9 @@ func ToSyftModel(doc *spdx.Document2_2) (*sbom.SBOM, error) {
s := &sbom.SBOM{ s := &sbom.SBOM{
Artifacts: sbom.Artifacts{ Artifacts: sbom.Artifacts{
PackageCatalog: pkg.NewCatalog(), Packages: pkg.NewCollection(),
FileMetadata: map[source.Coordinates]source.FileMetadata{}, FileMetadata: map[file.Coordinates]file.Metadata{},
FileDigests: map[source.Coordinates][]file.Digest{}, FileDigests: map[file.Coordinates][]file.Digest{},
LinuxDistribution: findLinuxReleaseByPURL(doc), LinuxDistribution: findLinuxReleaseByPURL(doc),
}, },
} }
@@ -74,7 +74,7 @@ func collectSyftPackages(s *sbom.SBOM, spdxIDMap map[string]interface{}, doc *sp
for _, p := range doc.Packages { for _, p := range doc.Packages {
syftPkg := toSyftPackage(p) syftPkg := toSyftPackage(p)
spdxIDMap[string(p.PackageSPDXIdentifier)] = syftPkg spdxIDMap[string(p.PackageSPDXIdentifier)] = syftPkg
s.Artifacts.PackageCatalog.Add(*syftPkg) s.Artifacts.Packages.Add(*syftPkg)
} }
} }
@@ -98,7 +98,7 @@ func toFileDigests(f *spdx.File2_2) (digests []file.Digest) {
return digests return digests
} }
func toFileMetadata(f *spdx.File2_2) (meta source.FileMetadata) { func toFileMetadata(f *spdx.File2_2) (meta file.Metadata) {
// FIXME Syft is currently lossy due to the SPDX 2.2.1 spec not supporting arbitrary mimetypes // FIXME Syft is currently lossy due to the SPDX 2.2.1 spec not supporting arbitrary mimetypes
for _, typ := range f.FileType { for _, typ := range f.FileType {
switch FileType(typ) { switch FileType(typ) {
@@ -132,7 +132,7 @@ func toSyftRelationships(spdxIDMap map[string]interface{}, doc *spdx.Document2_2
b := spdxIDMap[string(r.RefB.ElementRefID)] b := spdxIDMap[string(r.RefB.ElementRefID)]
from, fromOk := a.(*pkg.Package) from, fromOk := a.(*pkg.Package)
toPackage, toPackageOk := b.(*pkg.Package) toPackage, toPackageOk := b.(*pkg.Package)
toLocation, toLocationOk := b.(*source.Location) toLocation, toLocationOk := b.(*file.Location)
if !fromOk || !(toPackageOk || toLocationOk) { if !fromOk || !(toPackageOk || toLocationOk) {
log.Debugf("unable to find valid relationship mapping from SPDX 2.2 JSON, ignoring: (from: %+v) (to: %+v)", a, b) log.Debugf("unable to find valid relationship mapping from SPDX 2.2 JSON, ignoring: (from: %+v) (to: %+v)", a, b)
continue continue
@@ -174,7 +174,7 @@ func toSyftRelationships(spdxIDMap map[string]interface{}, doc *spdx.Document2_2
return out return out
} }
func toSyftCoordinates(f *spdx.File2_2) source.Coordinates { func toSyftCoordinates(f *spdx.File2_2) file.Coordinates {
const layerIDPrefix = "layerID: " const layerIDPrefix = "layerID: "
var fileSystemID string var fileSystemID string
if strings.Index(f.FileComment, layerIDPrefix) == 0 { if strings.Index(f.FileComment, layerIDPrefix) == 0 {
@@ -183,16 +183,16 @@ func toSyftCoordinates(f *spdx.File2_2) source.Coordinates {
if strings.Index(string(f.FileSPDXIdentifier), layerIDPrefix) == 0 { if strings.Index(string(f.FileSPDXIdentifier), layerIDPrefix) == 0 {
fileSystemID = strings.TrimPrefix(string(f.FileSPDXIdentifier), layerIDPrefix) fileSystemID = strings.TrimPrefix(string(f.FileSPDXIdentifier), layerIDPrefix)
} }
return source.Coordinates{ return file.Coordinates{
RealPath: f.FileName, RealPath: f.FileName,
FileSystemID: fileSystemID, FileSystemID: fileSystemID,
} }
} }
func toSyftLocation(f *spdx.File2_2) *source.Location { func toSyftLocation(f *spdx.File2_2) *file.Location {
return &source.Location{ return &file.Location{
Coordinates: toSyftCoordinates(f), Coordinates: toSyftCoordinates(f),
VirtualPath: f.FileName, AccessPath: f.FileName,
} }
} }
@@ -317,10 +317,10 @@ func findPURLValue(p *spdx.Package2_2) string {
return "" return ""
} }
func extractCPEs(p *spdx.Package2_2) (cpes []pkg.CPE) { func extractCPEs(p *spdx.Package2_2) (cpes []cpe.CPE) {
for _, r := range p.PackageExternalReferences { for _, r := range p.PackageExternalReferences {
if r.RefType == string(Cpe23ExternalRefType) { if r.RefType == string(Cpe23ExternalRefType) {
cpe, err := pkg.NewCPE(r.Locator) cpe, err := cpe.New(r.Locator)
if err != nil { if err != nil {
log.Warnf("unable to extract SPDX CPE=%q: %+v", r.Locator, err) log.Warnf("unable to extract SPDX CPE=%q: %+v", r.Locator, err)
continue continue
@@ -94,7 +94,7 @@ func TestToSyftModel(t *testing.T) {
assert.NotNil(t, sbom) assert.NotNil(t, sbom)
pkgs := sbom.Artifacts.PackageCatalog.Sorted() pkgs := sbom.Artifacts.Packages.Sorted()
assert.Len(t, pkgs, 2) assert.Len(t, pkgs, 2)
+25 -22
View File
@@ -2,9 +2,12 @@ package testutils
import ( import (
"bytes" "bytes"
"github.com/anchore/syft/syft/cpe"
"strings" "strings"
"testing" "testing"
"github.com/anchore/syft/syft/file"
"github.com/anchore/go-testutils" "github.com/anchore/go-testutils"
"github.com/anchore/stereoscope/pkg/filetree" "github.com/anchore/stereoscope/pkg/filetree"
"github.com/anchore/stereoscope/pkg/image" "github.com/anchore/stereoscope/pkg/image"
@@ -97,7 +100,7 @@ func AssertEncoderAgainstGoldenSnapshot(t *testing.T, format sbom.Format, sbom s
func ImageInput(t testing.TB, testImage string, options ...ImageOption) sbom.SBOM { func ImageInput(t testing.TB, testImage string, options ...ImageOption) sbom.SBOM {
t.Helper() t.Helper()
catalog := pkg.NewCatalog() catalog := pkg.NewCollection()
var cfg imageCfg var cfg imageCfg
var img *image.Image var img *image.Image
for _, opt := range options { for _, opt := range options {
@@ -121,7 +124,7 @@ func ImageInput(t testing.TB, testImage string, options ...ImageOption) sbom.SBO
return sbom.SBOM{ return sbom.SBOM{
Artifacts: sbom.Artifacts{ Artifacts: sbom.Artifacts{
PackageCatalog: catalog, Packages: catalog,
LinuxDistribution: &linux.Release{ LinuxDistribution: &linux.Release{
PrettyName: "debian", PrettyName: "debian",
Name: "debian", Name: "debian",
@@ -149,7 +152,7 @@ func carriageRedactor(s []byte) []byte {
return []byte(msg) return []byte(msg)
} }
func populateImageCatalog(catalog *pkg.Catalog, img *image.Image) { func populateImageCatalog(catalog pkg.Collection, img *image.Image) {
_, ref1, _ := img.SquashedTree().File("/somefile-1.txt", filetree.FollowBasenameLinks) _, ref1, _ := img.SquashedTree().File("/somefile-1.txt", filetree.FollowBasenameLinks)
_, ref2, _ := img.SquashedTree().File("/somefile-2.txt", filetree.FollowBasenameLinks) _, ref2, _ := img.SquashedTree().File("/somefile-2.txt", filetree.FollowBasenameLinks)
@@ -157,8 +160,8 @@ func populateImageCatalog(catalog *pkg.Catalog, img *image.Image) {
catalog.Add(pkg.Package{ catalog.Add(pkg.Package{
Name: "package-1", Name: "package-1",
Version: "1.0.1", Version: "1.0.1",
Locations: []source.Location{ Locations: []file.Location{
source.NewLocationFromImage(string(ref1.RealPath), *ref1, img), file.NewLocationFromImage(string(ref1.RealPath), *ref1, img),
}, },
Type: pkg.PythonPkg, Type: pkg.PythonPkg,
FoundBy: "the-cataloger-1", FoundBy: "the-cataloger-1",
@@ -170,15 +173,15 @@ func populateImageCatalog(catalog *pkg.Catalog, img *image.Image) {
Version: "1.0.1", Version: "1.0.1",
}, },
PURL: "a-purl-1", PURL: "a-purl-1",
CPEs: []pkg.CPE{ CPEs: []cpe.CPE{
pkg.MustCPE("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"), cpe.Must("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"),
}, },
}) })
catalog.Add(pkg.Package{ catalog.Add(pkg.Package{
Name: "package-2", Name: "package-2",
Version: "2.0.1", Version: "2.0.1",
Locations: []source.Location{ Locations: []file.Location{
source.NewLocationFromImage(string(ref2.RealPath), *ref2, img), file.NewLocationFromImage(string(ref2.RealPath), *ref2, img),
}, },
Type: pkg.DebPkg, Type: pkg.DebPkg,
FoundBy: "the-cataloger-2", FoundBy: "the-cataloger-2",
@@ -188,8 +191,8 @@ func populateImageCatalog(catalog *pkg.Catalog, img *image.Image) {
Version: "2.0.1", Version: "2.0.1",
}, },
PURL: "a-purl-2", PURL: "a-purl-2",
CPEs: []pkg.CPE{ CPEs: []cpe.CPE{
pkg.MustCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"), cpe.Must("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
}, },
}) })
} }
@@ -202,7 +205,7 @@ func DirectoryInput(t testing.TB) sbom.SBOM {
return sbom.SBOM{ return sbom.SBOM{
Artifacts: sbom.Artifacts{ Artifacts: sbom.Artifacts{
PackageCatalog: catalog, Packages: catalog,
LinuxDistribution: &linux.Release{ LinuxDistribution: &linux.Release{
PrettyName: "debian", PrettyName: "debian",
Name: "debian", Name: "debian",
@@ -225,8 +228,8 @@ func DirectoryInput(t testing.TB) sbom.SBOM {
} }
} }
func newDirectoryCatalog() *pkg.Catalog { func newDirectoryCatalog() pkg.Collection {
catalog := pkg.NewCatalog() catalog := pkg.NewCollection()
// populate catalog with test data // populate catalog with test data
catalog.Add(pkg.Package{ catalog.Add(pkg.Package{
@@ -234,8 +237,8 @@ func newDirectoryCatalog() *pkg.Catalog {
Version: "1.0.1", Version: "1.0.1",
Type: pkg.PythonPkg, Type: pkg.PythonPkg,
FoundBy: "the-cataloger-1", FoundBy: "the-cataloger-1",
Locations: []source.Location{ Locations: []file.Location{
source.NewLocation("/some/path/pkg1"), file.NewLocation("/some/path/pkg1"),
}, },
Language: pkg.Python, Language: pkg.Python,
MetadataType: pkg.PythonPackageMetadataType, MetadataType: pkg.PythonPackageMetadataType,
@@ -250,8 +253,8 @@ func newDirectoryCatalog() *pkg.Catalog {
}, },
}, },
PURL: "a-purl-2", PURL: "a-purl-2",
CPEs: []pkg.CPE{ CPEs: []cpe.CPE{
pkg.MustCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"), cpe.Must("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
}, },
}) })
catalog.Add(pkg.Package{ catalog.Add(pkg.Package{
@@ -259,8 +262,8 @@ func newDirectoryCatalog() *pkg.Catalog {
Version: "2.0.1", Version: "2.0.1",
Type: pkg.DebPkg, Type: pkg.DebPkg,
FoundBy: "the-cataloger-2", FoundBy: "the-cataloger-2",
Locations: []source.Location{ Locations: []file.Location{
source.NewLocation("/some/path/pkg1"), file.NewLocation("/some/path/pkg1"),
}, },
MetadataType: pkg.DpkgMetadataType, MetadataType: pkg.DpkgMetadataType,
Metadata: pkg.DpkgMetadata{ Metadata: pkg.DpkgMetadata{
@@ -268,8 +271,8 @@ func newDirectoryCatalog() *pkg.Catalog {
Version: "2.0.1", Version: "2.0.1",
}, },
PURL: "a-purl-2", PURL: "a-purl-2",
CPEs: []pkg.CPE{ CPEs: []cpe.CPE{
pkg.MustCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"), cpe.Must("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
}, },
}) })
@@ -57,7 +57,7 @@ func Test_decodeJSON(t *testing.T) {
split = strings.SplitN(pkg, ":", 2) split = strings.SplitN(pkg, ":", 2)
name = split[0] name = split[0]
version = split[1] version = split[1]
for p := range bom.Artifacts.PackageCatalog.Enumerate() { for p := range bom.Artifacts.Packages.Enumerate() {
if p.Name == name { if p.Name == name {
assert.Equal(t, version, p.Version) assert.Equal(t, version, p.Version)
continue pkgs continue pkgs
@@ -57,7 +57,7 @@ func Test_decodeXML(t *testing.T) {
split = strings.SplitN(pkg, ":", 2) split = strings.SplitN(pkg, ":", 2)
name = split[0] name = split[0]
version = split[1] version = split[1]
for p := range bom.Artifacts.PackageCatalog.Enumerate() { for p := range bom.Artifacts.Packages.Enumerate() {
if p.Name == name { if p.Name == name {
assert.Equal(t, version, p.Version) assert.Equal(t, version, p.Version)
continue pkgs continue pkgs
+2 -2
View File
@@ -71,11 +71,11 @@ func TestSPDXJSONDecoder(t *testing.T) {
} }
if test.packages != nil { if test.packages != nil {
assert.Equal(t, sbom.Artifacts.PackageCatalog.PackageCount(), len(test.packages)) assert.Equal(t, sbom.Artifacts.Packages.Size(), len(test.packages))
packages: packages:
for _, pkgName := range test.packages { for _, pkgName := range test.packages {
for _, p := range sbom.Artifacts.PackageCatalog.Sorted() { for _, p := range sbom.Artifacts.Packages.Sorted() {
if p.Name == pkgName { if p.Name == pkgName {
continue packages continue packages
} }
@@ -16,7 +16,6 @@ import (
"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/sbom" "github.com/anchore/syft/syft/sbom"
"github.com/anchore/syft/syft/source"
) )
// toFormatModel creates and populates a new JSON document struct that follows the SPDX 2.2 spec from the given cataloging results. // toFormatModel creates and populates a new JSON document struct that follows the SPDX 2.2 spec from the given cataloging results.
@@ -43,13 +42,13 @@ func toFormatModel(s sbom.SBOM) (*model.Document, error) {
}, },
DataLicense: "CC0-1.0", DataLicense: "CC0-1.0",
DocumentNamespace: namespace, DocumentNamespace: namespace,
Packages: toPackages(s.Artifacts.PackageCatalog, s.Relationships), Packages: toPackages(s.Artifacts.Packages, s.Relationships),
Files: toFiles(s), Files: toFiles(s),
Relationships: toRelationships(s.Relationships), Relationships: toRelationships(s.Relationships),
}, nil }, nil
} }
func toPackages(catalog *pkg.Catalog, relationships []artifact.Relationship) []model.Package { func toPackages(catalog pkg.Collection, relationships []artifact.Relationship) []model.Package {
packages := make([]model.Package, 0) packages := make([]model.Package, 0)
for _, p := range catalog.Sorted() { for _, p := range catalog.Sorted() {
@@ -94,7 +93,7 @@ func fileIDsForPackage(packageSpdxID string, relationships []artifact.Relationsh
continue continue
} }
if _, ok := relationship.To.(source.Coordinates); !ok { if _, ok := relationship.To.(file.Coordinates); !ok {
continue continue
} }
@@ -110,7 +109,7 @@ func toFiles(s sbom.SBOM) []model.File {
artifacts := s.Artifacts artifacts := s.Artifacts
for _, coordinates := range sbom.AllCoordinates(s) { for _, coordinates := range sbom.AllCoordinates(s) {
var metadata *source.FileMetadata var metadata *file.Metadata
if metadataForLocation, exists := artifacts.FileMetadata[coordinates]; exists { if metadataForLocation, exists := artifacts.FileMetadata[coordinates]; exists {
metadata = &metadataForLocation metadata = &metadataForLocation
} }
@@ -165,7 +164,7 @@ func toChecksumAlgorithm(algorithm string) string {
return strings.ToUpper(algorithm) return strings.ToUpper(algorithm)
} }
func toFileTypes(metadata *source.FileMetadata) (ty []string) { func toFileTypes(metadata *file.Metadata) (ty []string) {
if metadata == nil { if metadata == nil {
return nil return nil
} }
@@ -11,7 +11,6 @@ import (
"github.com/anchore/syft/internal/formats/common/spdxhelpers" "github.com/anchore/syft/internal/formats/common/spdxhelpers"
"github.com/anchore/syft/internal/formats/spdx22json/model" "github.com/anchore/syft/internal/formats/spdx22json/model"
"github.com/anchore/syft/syft/source"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -19,12 +18,12 @@ func Test_toFileTypes(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
metadata source.FileMetadata metadata file.Metadata
expected []string expected []string
}{ }{
{ {
name: "application", name: "application",
metadata: source.FileMetadata{ metadata: file.Metadata{
MIMEType: "application/vnd.unknown", MIMEType: "application/vnd.unknown",
}, },
expected: []string{ expected: []string{
@@ -33,7 +32,7 @@ func Test_toFileTypes(t *testing.T) {
}, },
{ {
name: "archive", name: "archive",
metadata: source.FileMetadata{ metadata: file.Metadata{
MIMEType: "application/zip", MIMEType: "application/zip",
}, },
expected: []string{ expected: []string{
@@ -43,7 +42,7 @@ func Test_toFileTypes(t *testing.T) {
}, },
{ {
name: "audio", name: "audio",
metadata: source.FileMetadata{ metadata: file.Metadata{
MIMEType: "audio/ogg", MIMEType: "audio/ogg",
}, },
expected: []string{ expected: []string{
@@ -52,7 +51,7 @@ func Test_toFileTypes(t *testing.T) {
}, },
{ {
name: "video", name: "video",
metadata: source.FileMetadata{ metadata: file.Metadata{
MIMEType: "video/3gpp", MIMEType: "video/3gpp",
}, },
expected: []string{ expected: []string{
@@ -61,7 +60,7 @@ func Test_toFileTypes(t *testing.T) {
}, },
{ {
name: "text", name: "text",
metadata: source.FileMetadata{ metadata: file.Metadata{
MIMEType: "text/html", MIMEType: "text/html",
}, },
expected: []string{ expected: []string{
@@ -70,7 +69,7 @@ func Test_toFileTypes(t *testing.T) {
}, },
{ {
name: "image", name: "image",
metadata: source.FileMetadata{ metadata: file.Metadata{
MIMEType: "image/png", MIMEType: "image/png",
}, },
expected: []string{ expected: []string{
@@ -79,7 +78,7 @@ func Test_toFileTypes(t *testing.T) {
}, },
{ {
name: "binary", name: "binary",
metadata: source.FileMetadata{ metadata: file.Metadata{
MIMEType: "application/x-sharedlib", MIMEType: "application/x-sharedlib",
}, },
expected: []string{ expected: []string{
@@ -175,7 +174,7 @@ func Test_fileIDsForPackage(t *testing.T) {
Name: "bogus", Name: "bogus",
} }
c := source.Coordinates{ c := file.Coordinates{
RealPath: "/path", RealPath: "/path",
FileSystemID: "nowhere", FileSystemID: "nowhere",
} }
@@ -85,13 +85,13 @@ func toFormatModel(s sbom.SBOM) (*spdx.Document2_2, error) {
// Cardinality: optional, one // Cardinality: optional, one
DocumentComment: "", DocumentComment: "",
}, },
Packages: toFormatPackages(s.Artifacts.PackageCatalog), Packages: toFormatPackages(s.Artifacts.Packages),
}, nil }, nil
} }
// packages populates all Package Information from the package Catalog (see https://spdx.github.io/spdx-spec/3-package-information/) // packages populates all Package Information from the package Collection (see https://spdx.github.io/spdx-spec/3-package-information/)
// nolint: funlen // nolint: funlen
func toFormatPackages(catalog *pkg.Catalog) map[spdx.ElementID]*spdx.Package2_2 { func toFormatPackages(catalog pkg.Collection) map[spdx.ElementID]*spdx.Package2_2 {
results := make(map[spdx.ElementID]*spdx.Package2_2) results := make(map[spdx.ElementID]*spdx.Package2_2)
for _, p := range catalog.Sorted() { for _, p := range catalog.Sorted() {
+2 -2
View File
@@ -28,8 +28,8 @@ func TestEncodeDecodeCycle(t *testing.T) {
t.Errorf("metadata difference: %+v", d) t.Errorf("metadata difference: %+v", d)
} }
actualPackages := actualSBOM.Artifacts.PackageCatalog.Sorted() actualPackages := actualSBOM.Artifacts.Packages.Sorted()
for idx, p := range originalSBOM.Artifacts.PackageCatalog.Sorted() { for idx, p := range originalSBOM.Artifacts.Packages.Sorted() {
if !assert.Equal(t, p.Name, actualPackages[idx].Name) { if !assert.Equal(t, p.Name, actualPackages[idx].Name) {
t.Errorf("different package at idx=%d: %s vs %s", idx, p.Name, actualPackages[idx].Name) t.Errorf("different package at idx=%d: %s vs %s", idx, p.Name, actualPackages[idx].Name)
continue continue
+22 -21
View File
@@ -2,6 +2,7 @@ package syftjson
import ( import (
"flag" "flag"
"github.com/anchore/syft/syft/cpe"
"testing" "testing"
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
@@ -37,14 +38,14 @@ func TestImageEncoder(t *testing.T) {
} }
func TestEncodeFullJSONDocument(t *testing.T) { func TestEncodeFullJSONDocument(t *testing.T) {
catalog := pkg.NewCatalog() catalog := pkg.NewCollection()
p1 := pkg.Package{ p1 := pkg.Package{
Name: "package-1", Name: "package-1",
Version: "1.0.1", Version: "1.0.1",
Locations: []source.Location{ Locations: []file.Location{
{ {
Coordinates: source.Coordinates{ Coordinates: file.Coordinates{
RealPath: "/a/place/a", RealPath: "/a/place/a",
}, },
}, },
@@ -60,17 +61,17 @@ func TestEncodeFullJSONDocument(t *testing.T) {
Files: []pkg.PythonFileRecord{}, Files: []pkg.PythonFileRecord{},
}, },
PURL: "a-purl-1", PURL: "a-purl-1",
CPEs: []pkg.CPE{ CPEs: []cpe.CPE{
pkg.MustCPE("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"), cpe.Must("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"),
}, },
} }
p2 := pkg.Package{ p2 := pkg.Package{
Name: "package-2", Name: "package-2",
Version: "2.0.1", Version: "2.0.1",
Locations: []source.Location{ Locations: []file.Location{
{ {
Coordinates: source.Coordinates{ Coordinates: file.Coordinates{
RealPath: "/b/place/b", RealPath: "/b/place/b",
}, },
}, },
@@ -84,8 +85,8 @@ func TestEncodeFullJSONDocument(t *testing.T) {
Files: []pkg.DpkgFileRecord{}, Files: []pkg.DpkgFileRecord{},
}, },
PURL: "a-purl-2", PURL: "a-purl-2",
CPEs: []pkg.CPE{ CPEs: []cpe.CPE{
pkg.MustCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"), cpe.Must("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
}, },
} }
@@ -94,50 +95,50 @@ func TestEncodeFullJSONDocument(t *testing.T) {
s := sbom.SBOM{ s := sbom.SBOM{
Artifacts: sbom.Artifacts{ Artifacts: sbom.Artifacts{
PackageCatalog: catalog, Packages: catalog,
FileMetadata: map[source.Coordinates]source.FileMetadata{ FileMetadata: map[file.Coordinates]file.Metadata{
source.NewLocation("/a/place").Coordinates: { file.NewLocation("/a/place").Coordinates: {
Mode: 0775, Mode: 0775,
Type: "directory", Type: "directory",
UserID: 0, UserID: 0,
GroupID: 0, GroupID: 0,
}, },
source.NewLocation("/a/place/a").Coordinates: { file.NewLocation("/a/place/a").Coordinates: {
Mode: 0775, Mode: 0775,
Type: "regularFile", Type: "regularFile",
UserID: 0, UserID: 0,
GroupID: 0, GroupID: 0,
}, },
source.NewLocation("/b").Coordinates: { file.NewLocation("/b").Coordinates: {
Mode: 0775, Mode: 0775,
Type: "symbolicLink", Type: "symbolicLink",
LinkDestination: "/c", LinkDestination: "/c",
UserID: 0, UserID: 0,
GroupID: 0, GroupID: 0,
}, },
source.NewLocation("/b/place/b").Coordinates: { file.NewLocation("/b/place/b").Coordinates: {
Mode: 0644, Mode: 0644,
Type: "regularFile", Type: "regularFile",
UserID: 1, UserID: 1,
GroupID: 2, GroupID: 2,
}, },
}, },
FileDigests: map[source.Coordinates][]file.Digest{ FileDigests: map[file.Coordinates][]file.Digest{
source.NewLocation("/a/place/a").Coordinates: { file.NewLocation("/a/place/a").Coordinates: {
{ {
Algorithm: "sha256", Algorithm: "sha256",
Value: "366a3f5653e34673b875891b021647440d0127c2ef041e3b1a22da2a7d4f3703", Value: "366a3f5653e34673b875891b021647440d0127c2ef041e3b1a22da2a7d4f3703",
}, },
}, },
source.NewLocation("/b/place/b").Coordinates: { file.NewLocation("/b/place/b").Coordinates: {
{ {
Algorithm: "sha256", Algorithm: "sha256",
Value: "1b3722da2a7d90d033b87581a2a3f12021647445653e34666ef041e3b4f3707c", Value: "1b3722da2a7d90d033b87581a2a3f12021647445653e34666ef041e3b4f3707c",
}, },
}, },
}, },
FileContents: map[source.Coordinates]string{ FileContents: map[file.Coordinates]string{
source.NewLocation("/a/place/a").Coordinates: "the-contents", file.NewLocation("/a/place/a").Coordinates: "the-contents",
}, },
LinuxDistribution: &linux.Release{ LinuxDistribution: &linux.Release{
ID: "redhat", ID: "redhat",
@@ -159,7 +160,7 @@ func TestEncodeFullJSONDocument(t *testing.T) {
}, },
}, },
Source: source.Metadata{ Source: source.Metadata{
Scheme: source.ImageScheme, Scheme: source.ImageType,
ImageMetadata: source.ImageMetadata{ ImageMetadata: source.ImageMetadata{
UserInput: "user-image-input", UserInput: "user-image-input",
ID: "sha256:c2b46b4eb06296933b7cf0722683964e9ecbd93265b9ef6ae9642e3952afbba0", ID: "sha256:c2b46b4eb06296933b7cf0722683964e9ecbd93265b9ef6ae9642e3952afbba0",
+7 -9
View File
@@ -2,13 +2,11 @@ package model
import ( import (
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/source"
) )
type File struct { type File struct {
ID string `json:"id"` ID string `json:"id"`
Location source.Coordinates `json:"location"` Location file.Coordinates `json:"location"`
Metadata *FileMetadataEntry `json:"metadata,omitempty"` Metadata *FileMetadataEntry `json:"metadata,omitempty"`
Contents string `json:"contents,omitempty"` Contents string `json:"contents,omitempty"`
Digests []file.Digest `json:"digests,omitempty"` Digests []file.Digest `json:"digests,omitempty"`
@@ -16,10 +14,10 @@ type File struct {
} }
type FileMetadataEntry struct { type FileMetadataEntry struct {
Mode int `json:"mode"` Mode int `json:"mode"`
Type source.FileType `json:"type"` Type file.Type `json:"type"`
LinkDestination string `json:"linkDestination,omitempty"` LinkDestination string `json:"linkDestination,omitempty"`
UserID int `json:"userID"` UserID int `json:"userID"`
GroupID int `json:"groupID"` GroupID int `json:"groupID"`
MIMEType string `json:"mimeType"` MIMEType string `json:"mimeType"`
} }
+11 -11
View File
@@ -4,7 +4,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/anchore/syft/syft/source" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
@@ -18,16 +18,16 @@ type Package struct {
// PackageBasicData contains non-ambiguous values (type-wise) from pkg.Package. // PackageBasicData contains non-ambiguous values (type-wise) from pkg.Package.
type PackageBasicData struct { type PackageBasicData struct {
ID string `json:"id"` ID string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Version string `json:"version"` Version string `json:"version"`
Type pkg.Type `json:"type"` Type pkg.Type `json:"type"`
FoundBy string `json:"foundBy"` FoundBy string `json:"foundBy"`
Locations []source.Coordinates `json:"locations"` Locations []file.Coordinates `json:"locations"`
Licenses []string `json:"licenses"` Licenses []string `json:"licenses"`
Language pkg.Language `json:"language"` Language pkg.Language `json:"language"`
CPEs []string `json:"cpes"` CPEs []string `json:"cpes"`
PURL string `json:"purl"` PURL string `json:"purl"`
} }
// PackageCustomData contains ambiguous values (type-wise) from pkg.Package. // PackageCustomData contains ambiguous values (type-wise) from pkg.Package.
+1 -2
View File
@@ -2,10 +2,9 @@ package model
import ( import (
"github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/source"
) )
type Secrets struct { type Secrets struct {
Location source.Coordinates `json:"location"` Location file.Coordinates `json:"location"`
Secrets []file.SearchResult `json:"secrets"` Secrets []file.SearchResult `json:"secrets"`
} }
+11 -10
View File
@@ -2,6 +2,7 @@ package syftjson
import ( import (
"fmt" "fmt"
"github.com/anchore/syft/syft/cpe"
"sort" "sort"
"strconv" "strconv"
@@ -30,7 +31,7 @@ func ToFormatModel(s sbom.SBOM) model.Document {
} }
return model.Document{ return model.Document{
Artifacts: toPackageModels(s.Artifacts.PackageCatalog), Artifacts: toPackageModels(s.Artifacts.Packages),
ArtifactRelationships: toRelationshipModel(s.Relationships), ArtifactRelationships: toRelationshipModel(s.Relationships),
Files: toFile(s), Files: toFile(s),
Secrets: toSecrets(s.Artifacts.Secrets), Secrets: toSecrets(s.Artifacts.Secrets),
@@ -73,7 +74,7 @@ func toDescriptor(d sbom.Descriptor) model.Descriptor {
} }
} }
func toSecrets(data map[source.Coordinates][]file.SearchResult) []model.Secrets { func toSecrets(data map[file.Coordinates][]file.SearchResult) []model.Secrets {
results := make([]model.Secrets, 0) results := make([]model.Secrets, 0)
for coordinates, secrets := range data { for coordinates, secrets := range data {
results = append(results, model.Secrets{ results = append(results, model.Secrets{
@@ -94,7 +95,7 @@ func toFile(s sbom.SBOM) []model.File {
artifacts := s.Artifacts artifacts := s.Artifacts
for _, coordinates := range sbom.AllCoordinates(s) { for _, coordinates := range sbom.AllCoordinates(s) {
var metadata *source.FileMetadata var metadata *file.Metadata
if metadataForLocation, exists := artifacts.FileMetadata[coordinates]; exists { if metadataForLocation, exists := artifacts.FileMetadata[coordinates]; exists {
metadata = &metadataForLocation metadata = &metadataForLocation
} }
@@ -131,7 +132,7 @@ func toFile(s sbom.SBOM) []model.File {
return results return results
} }
func toFileMetadataEntry(coordinates source.Coordinates, metadata *source.FileMetadata) *model.FileMetadataEntry { func toFileMetadataEntry(coordinates file.Coordinates, metadata *file.Metadata) *model.FileMetadataEntry {
if metadata == nil { if metadata == nil {
return nil return nil
} }
@@ -152,7 +153,7 @@ func toFileMetadataEntry(coordinates source.Coordinates, metadata *source.FileMe
} }
} }
func toPackageModels(catalog *pkg.Catalog) []model.Package { func toPackageModels(catalog pkg.Collection) []model.Package {
artifacts := make([]model.Package, 0) artifacts := make([]model.Package, 0)
if catalog == nil { if catalog == nil {
return artifacts return artifacts
@@ -167,7 +168,7 @@ func toPackageModels(catalog *pkg.Catalog) []model.Package {
func toPackageModel(p pkg.Package) model.Package { func toPackageModel(p pkg.Package) model.Package {
var cpes = make([]string, len(p.CPEs)) var cpes = make([]string, len(p.CPEs))
for i, c := range p.CPEs { for i, c := range p.CPEs {
cpes[i] = pkg.CPEString(c) cpes[i] = cpe.String(c)
} }
var licenses = make([]string, 0) var licenses = make([]string, 0)
@@ -175,7 +176,7 @@ func toPackageModel(p pkg.Package) model.Package {
licenses = p.Licenses licenses = p.Licenses
} }
var coordinates = make([]source.Coordinates, len(p.Locations)) var coordinates = make([]file.Coordinates, len(p.Locations))
for i, l := range p.Locations { for i, l := range p.Locations {
coordinates[i] = l.Coordinates coordinates[i] = l.Coordinates
} }
@@ -216,7 +217,7 @@ func toRelationshipModel(relationships []artifact.Relationship) []model.Relation
// toSourceModel creates a new source object to be represented into JSON. // toSourceModel creates a new source object to be represented into JSON.
func toSourceModel(src source.Metadata) (model.Source, error) { func toSourceModel(src source.Metadata) (model.Source, error) {
switch src.Scheme { switch src.Scheme {
case source.ImageScheme: case source.ImageType:
metadata := src.ImageMetadata metadata := src.ImageMetadata
// ensure that empty collections are not shown as null // ensure that empty collections are not shown as null
if metadata.RepoDigests == nil { if metadata.RepoDigests == nil {
@@ -229,12 +230,12 @@ func toSourceModel(src source.Metadata) (model.Source, error) {
Type: "image", Type: "image",
Target: metadata, Target: metadata,
}, nil }, nil
case source.DirectoryScheme: case source.DirectoryType:
return model.Source{ return model.Source{
Type: "directory", Type: "directory",
Target: src.Path, Target: src.Path,
}, nil }, nil
case source.FileScheme: case source.FileType:
return model.Source{ return model.Source{
Type: "file", Type: "file",
Target: src.Path, Target: src.Path,
@@ -13,7 +13,7 @@ import (
func Test_toSourceModel(t *testing.T) { func Test_toSourceModel(t *testing.T) {
allSchemes := strset.New() allSchemes := strset.New()
for _, s := range source.AllSchemes { for _, s := range source.AllTypes {
allSchemes.Add(string(s)) allSchemes.Add(string(s))
} }
testedSchemes := strset.New() testedSchemes := strset.New()
@@ -26,7 +26,7 @@ func Test_toSourceModel(t *testing.T) {
{ {
name: "directory", name: "directory",
src: source.Metadata{ src: source.Metadata{
Scheme: source.DirectoryScheme, Scheme: source.DirectoryType,
Path: "some/path", Path: "some/path",
}, },
expected: model.Source{ expected: model.Source{
@@ -37,7 +37,7 @@ func Test_toSourceModel(t *testing.T) {
{ {
name: "file", name: "file",
src: source.Metadata{ src: source.Metadata{
Scheme: source.FileScheme, Scheme: source.FileType,
Path: "some/path", Path: "some/path",
}, },
expected: model.Source{ expected: model.Source{
@@ -48,7 +48,7 @@ func Test_toSourceModel(t *testing.T) {
{ {
name: "image", name: "image",
src: source.Metadata{ src: source.Metadata{
Scheme: source.ImageScheme, Scheme: source.ImageType,
ImageMetadata: source.ImageMetadata{ ImageMetadata: source.ImageMetadata{
UserInput: "user-input", UserInput: "user-input",
ID: "id...", ID: "id...",
+13 -11
View File
@@ -4,6 +4,8 @@ import (
"github.com/anchore/syft/internal/formats/syftjson/model" "github.com/anchore/syft/internal/formats/syftjson/model"
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/linux" "github.com/anchore/syft/syft/linux"
"github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/sbom" "github.com/anchore/syft/syft/sbom"
@@ -16,7 +18,7 @@ func toSyftModel(doc model.Document) (*sbom.SBOM, error) {
return &sbom.SBOM{ return &sbom.SBOM{
Artifacts: sbom.Artifacts{ Artifacts: sbom.Artifacts{
PackageCatalog: catalog, Packages: catalog,
LinuxDistribution: toSyftLinuxRelease(doc.Distro), LinuxDistribution: toSyftLinuxRelease(doc.Distro),
}, },
Source: *toSyftSourceData(doc.Source), Source: *toSyftSourceData(doc.Source),
@@ -46,7 +48,7 @@ func toSyftLinuxRelease(d model.LinuxRelease) *linux.Release {
} }
} }
func toSyftRelationships(doc *model.Document, catalog *pkg.Catalog, relationships []model.Relationship) []artifact.Relationship { func toSyftRelationships(doc *model.Document, catalog pkg.Collection, relationships []model.Relationship) []artifact.Relationship {
idMap := make(map[string]interface{}) idMap := make(map[string]interface{})
for _, p := range catalog.Sorted() { for _, p := range catalog.Sorted() {
@@ -111,25 +113,25 @@ func toSyftSourceData(s model.Source) *source.Metadata {
switch s.Type { switch s.Type {
case "directory": case "directory":
return &source.Metadata{ return &source.Metadata{
Scheme: source.DirectoryScheme, Scheme: source.DirectoryType,
Path: s.Target.(string), Path: s.Target.(string),
} }
case "file": case "file":
return &source.Metadata{ return &source.Metadata{
Scheme: source.FileScheme, Scheme: source.FileType,
Path: s.Target.(string), Path: s.Target.(string),
} }
case "image": case "image":
return &source.Metadata{ return &source.Metadata{
Scheme: source.ImageScheme, Scheme: source.ImageType,
ImageMetadata: s.Target.(source.ImageMetadata), ImageMetadata: s.Target.(source.ImageMetadata),
} }
} }
return nil return nil
} }
func toSyftCatalog(pkgs []model.Package) *pkg.Catalog { func toSyftCatalog(pkgs []model.Package) pkg.Collection {
catalog := pkg.NewCatalog() catalog := pkg.NewCollection()
for _, p := range pkgs { for _, p := range pkgs {
catalog.Add(toSyftPackage(p)) catalog.Add(toSyftPackage(p))
} }
@@ -137,9 +139,9 @@ func toSyftCatalog(pkgs []model.Package) *pkg.Catalog {
} }
func toSyftPackage(p model.Package) pkg.Package { func toSyftPackage(p model.Package) pkg.Package {
var cpes []pkg.CPE var cpes []cpe.CPE
for _, c := range p.CPEs { for _, c := range p.CPEs {
value, err := pkg.NewCPE(c) value, err := cpe.New(c)
if err != nil { if err != nil {
log.Warnf("excluding invalid CPE %q: %v", c, err) log.Warnf("excluding invalid CPE %q: %v", c, err)
continue continue
@@ -148,9 +150,9 @@ func toSyftPackage(p model.Package) pkg.Package {
cpes = append(cpes, value) cpes = append(cpes, value)
} }
var locations = make([]source.Location, len(p.Locations)) var locations = make([]file.Location, len(p.Locations))
for i, c := range p.Locations { for i, c := range p.Locations {
locations[i] = source.NewLocationFromCoordinates(c) locations[i] = file.NewLocationFromCoordinates(c)
} }
return pkg.Package{ return pkg.Package{
@@ -11,7 +11,7 @@ import (
func Test_toSyftSourceData(t *testing.T) { func Test_toSyftSourceData(t *testing.T) {
allSchemes := strset.New() allSchemes := strset.New()
for _, s := range source.AllSchemes { for _, s := range source.AllTypes {
allSchemes.Add(string(s)) allSchemes.Add(string(s))
} }
testedSchemes := strset.New() testedSchemes := strset.New()
@@ -24,7 +24,7 @@ func Test_toSyftSourceData(t *testing.T) {
{ {
name: "directory", name: "directory",
expected: source.Metadata{ expected: source.Metadata{
Scheme: source.DirectoryScheme, Scheme: source.DirectoryType,
Path: "some/path", Path: "some/path",
}, },
src: model.Source{ src: model.Source{
@@ -35,7 +35,7 @@ func Test_toSyftSourceData(t *testing.T) {
{ {
name: "file", name: "file",
expected: source.Metadata{ expected: source.Metadata{
Scheme: source.FileScheme, Scheme: source.FileType,
Path: "some/path", Path: "some/path",
}, },
src: model.Source{ src: model.Source{
@@ -46,7 +46,7 @@ func Test_toSyftSourceData(t *testing.T) {
{ {
name: "image", name: "image",
expected: source.Metadata{ expected: source.Metadata{
Scheme: source.ImageScheme, Scheme: source.ImageType,
ImageMetadata: source.ImageMetadata{ ImageMetadata: source.ImageMetadata{
UserInput: "user-input", UserInput: "user-input",
ID: "id...", ID: "id...",
+1 -1
View File
@@ -15,7 +15,7 @@ func encoder(output io.Writer, s sbom.SBOM) error {
var rows [][]string var rows [][]string
columns := []string{"Name", "Version", "Type"} columns := []string{"Name", "Version", "Type"}
for _, p := range s.Artifacts.PackageCatalog.Sorted() { for _, p := range s.Artifacts.Packages.Sorted() {
row := []string{ row := []string{
p.Name, p.Name,
p.Version, p.Version,
+3 -3
View File
@@ -16,9 +16,9 @@ func encoder(output io.Writer, s sbom.SBOM) error {
w.Init(output, 0, 8, 0, '\t', tabwriter.AlignRight) w.Init(output, 0, 8, 0, '\t', tabwriter.AlignRight)
switch s.Source.Scheme { switch s.Source.Scheme {
case source.DirectoryScheme, source.FileScheme: case source.DirectoryType, source.FileType:
fmt.Fprintf(w, "[Path: %s]\n", s.Source.Path) fmt.Fprintf(w, "[Path: %s]\n", s.Source.Path)
case source.ImageScheme: case source.ImageType:
fmt.Fprintln(w, "[Image]") fmt.Fprintln(w, "[Image]")
for idx, l := range s.Source.ImageMetadata.Layers { for idx, l := range s.Source.ImageMetadata.Layers {
@@ -35,7 +35,7 @@ func encoder(output io.Writer, s sbom.SBOM) error {
// populate artifacts... // populate artifacts...
rows := 0 rows := 0
for _, p := range s.Artifacts.PackageCatalog.Sorted() { for _, p := range s.Artifacts.Packages.Sorted() {
fmt.Fprintf(w, "[%s]\n", p.Name) fmt.Fprintf(w, "[%s]\n", p.Name)
fmt.Fprintln(w, " Version:\t", p.Version) fmt.Fprintln(w, " Version:\t", p.Version)
fmt.Fprintln(w, " Type:\t", string(p.Type)) fmt.Fprintln(w, " Type:\t", string(p.Type))
+47
View File
@@ -0,0 +1,47 @@
package version
import (
"runtime/debug"
"strings"
"github.com/anchore/syft/internal/log"
)
func Guess() string {
v := FromBuild().Version
if strings.HasPrefix(v, "v") {
return v
}
buildInfo, ok := debug.ReadBuildInfo()
if !ok {
log.Warn("syft version could not be determined: unable to find the buildinfo section of the binary")
return v
}
var found bool
for _, d := range buildInfo.Deps {
if d.Path == "github.com/anchore/syft" {
v = d.Version
found = true
break
}
}
if !found {
// look for probable forks
for _, d := range buildInfo.Deps {
if strings.HasSuffix(d.Path, "/syft") {
v = d.Version
found = true
break
}
}
}
if !found {
log.Warn("syft version could not be determined: unable to find syft within the buildinfo section of the binary")
}
return v
}
+172
View File
@@ -0,0 +1,172 @@
package syft
import (
"fmt"
"github.com/anchore/syft/internal/bus"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/event"
"github.com/anchore/syft/syft/event/monitor"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/sbom"
"github.com/anchore/syft/syft/source"
"github.com/hashicorp/go-multierror"
"github.com/wagoodman/go-partybus"
"github.com/wagoodman/go-progress"
)
type monitorableCollection struct {
pkg.Collection
monitor *progress.Manual
}
func (m *monitorableCollection) Add(p pkg.Package) {
m.monitor.N++
m.Collection.Add(p)
}
func Catalog(src *source.Source, options ...CatalogingOption) (*sbom.SBOM, error) {
var config = DefaultCatalogingConfig()
for _, optFn := range options {
if err := optFn(src, &config); err != nil {
return nil, fmt.Errorf("unable to apply cataloging option: %w", err)
}
}
if config.availableTasks == nil {
var err error
config.availableTasks, err = newTaskCollection()
if err != nil {
return nil, err
}
}
if len(config.EnabledCatalogers) == 0 {
return nil, fmt.Errorf("no cataloging tasks configured to run")
}
catalogingTasks, err := config.availableTasks.tasks(config, config.EnabledCatalogers...)
if err != nil {
return nil, err
}
if len(catalogingTasks) == 0 {
return nil, fmt.Errorf("no cataloging tasks found to run")
}
// special case: we need to identify the linux distro for downstream processing
identifyLinuxDistroTask, err := newIdentifyDistroTask(config)
if err != nil {
return nil, fmt.Errorf("unable to create linux distro identification task: %+v", err)
}
synthesizePackageRelationshipsTask, err := newSynthesizePackageRelationshipsTasks(config)
if err != nil {
return nil, fmt.Errorf("unable to create task to synthesize package relationships: %+v", err)
}
taskGroups := [][]task{
{
identifyLinuxDistroTask,
},
catalogingTasks,
{
synthesizePackageRelationshipsTask,
},
}
files, pkgs := newCatalogerMonitor()
defer func() {
files.SetCompleted() // TODO: files monitor is unused... should we remove?
pkgs.SetCompleted()
}()
s := sbom.SBOM{
Source: src.Metadata,
Descriptor: sbom.Descriptor{
Name: config.ToolName,
Version: config.ToolVersion,
Configuration: config.ToolConfiguration,
},
Artifacts: sbom.Artifacts{
Packages: &monitorableCollection{
Collection: pkg.NewCollection(),
monitor: pkgs,
},
},
}
for _, tasks := range taskGroups {
if err := runTasks(&s, src, config.ProcessTasksInSerial, tasks...); err != nil {
return &s, err
}
}
return &s, nil
}
// newCatalogerMonitor creates a new CatalogingMonitor object and publishes the object on the bus as a CatalogingStarted event.
func newCatalogerMonitor() (*progress.Manual, *progress.Manual) {
filesProcessed := progress.Manual{}
packagesDiscovered := progress.Manual{}
bus.Publish(partybus.Event{
Type: event.CatalogingStarted,
Value: monitor.CatalogingMonitor{
FilesProcessed: progress.Monitorable(&filesProcessed),
PackagesDiscovered: progress.Monitorable(&packagesDiscovered),
},
})
return &filesProcessed, &packagesDiscovered
}
func runTasks(s *sbom.SBOM, src *source.Source, serial bool, tasks ...task) error {
var relationships []<-chan artifact.Relationship
var errs = make(chan error)
for _, t := range tasks {
r := make(chan artifact.Relationship)
relationships = append(relationships, r)
if serial {
runTask(t, &s.Artifacts, src, r, errs)
} else {
go runTask(t, &s.Artifacts, src, r, errs)
}
}
s.Relationships = append(s.Relationships, mergeRelationships(relationships...)...)
close(errs)
return mergeErrors(errs)
}
func mergeRelationships(cs ...<-chan artifact.Relationship) (relationships []artifact.Relationship) {
for _, c := range cs {
for n := range c {
relationships = append(relationships, n)
}
}
return relationships
}
func mergeErrors(errs <-chan error) (allErrs error) {
for err := range errs {
if err != nil {
allErrs = multierror.Append(allErrs, err)
}
}
return allErrs
}
func runTask(t task, a *sbom.Artifacts, src *source.Source, r chan<- artifact.Relationship, errs chan<- error) {
defer close(r)
relationships, err := t.Run(a, src)
if err != nil {
errs <- err
return
}
for _, relationship := range relationships {
r <- relationship
}
}
@@ -1,25 +1,26 @@
package file package fileclassifier
import ( import (
"github.com/anchore/syft/internal/log" "github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/source" "github.com/anchore/syft/syft/source"
) )
type ClassificationCataloger struct { type Cataloger struct {
classifiers []Classifier classifiers []Classifier
} }
func NewClassificationCataloger(classifiers []Classifier) (*ClassificationCataloger, error) { func NewCataloger(classifiers []Classifier) (*Cataloger, error) {
return &ClassificationCataloger{ return &Cataloger{
classifiers: classifiers, classifiers: classifiers,
}, nil }, nil
} }
func (i *ClassificationCataloger) Catalog(resolver source.FileResolver) (map[source.Coordinates][]Classification, error) { func (i *Cataloger) Catalog(resolver file.Resolver) (map[file.Coordinates][]file.Classification, error) {
results := make(map[source.Coordinates][]Classification) results := make(map[file.Coordinates][]file.Classification)
numResults := 0 numResults := 0
for _, location := range allRegularFiles(resolver) { for _, location := range source.AllRegularFiles(resolver) {
for _, classifier := range i.classifiers { for _, classifier := range i.classifiers {
result, err := classifier.Classify(resolver, location) result, err := classifier.Classify(resolver, location)
if err != nil { if err != nil {
@@ -1,9 +1,10 @@
package file package fileclassifier
import ( import (
"github.com/anchore/stereoscope/pkg/imagetest"
"testing" "testing"
"github.com/anchore/stereoscope/pkg/imagetest"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/source" "github.com/anchore/syft/syft/source"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -13,14 +14,14 @@ func TestClassifierCataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
name string name string
fixtureDir string fixtureDir string
location string location string
expected []Classification expected []file.Classification
expectedErr func(assert.TestingT, error, ...interface{}) bool expectedErr func(assert.TestingT, error, ...interface{}) bool
}{ }{
{ {
name: "positive-libpython3.7.so", name: "positive-libpython3.7.so",
fixtureDir: "test-fixtures/classifiers/positive", fixtureDir: "test-fixtures/classifiers/positive",
location: "libpython3.7.so", location: "libpython3.7.so",
expected: []Classification{ expected: []file.Classification{
{ {
Class: "python-binary", Class: "python-binary",
Metadata: map[string]string{ Metadata: map[string]string{
@@ -34,7 +35,7 @@ func TestClassifierCataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
name: "positive-python3.6", name: "positive-python3.6",
fixtureDir: "test-fixtures/classifiers/positive", fixtureDir: "test-fixtures/classifiers/positive",
location: "python3.6", location: "python3.6",
expected: []Classification{ expected: []file.Classification{
{ {
Class: "python-binary", Class: "python-binary",
Metadata: map[string]string{ Metadata: map[string]string{
@@ -48,7 +49,7 @@ func TestClassifierCataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
name: "positive-patchlevel.h", name: "positive-patchlevel.h",
fixtureDir: "test-fixtures/classifiers/positive", fixtureDir: "test-fixtures/classifiers/positive",
location: "patchlevel.h", location: "patchlevel.h",
expected: []Classification{ expected: []file.Classification{
{ {
Class: "cpython-source", Class: "cpython-source",
Metadata: map[string]string{ Metadata: map[string]string{
@@ -62,7 +63,7 @@ func TestClassifierCataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
name: "positive-go", name: "positive-go",
fixtureDir: "test-fixtures/classifiers/positive", fixtureDir: "test-fixtures/classifiers/positive",
location: "go", location: "go",
expected: []Classification{ expected: []file.Classification{
{ {
Class: "go-binary", Class: "go-binary",
Metadata: map[string]string{ Metadata: map[string]string{
@@ -76,7 +77,7 @@ func TestClassifierCataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
name: "positive-go-hint", name: "positive-go-hint",
fixtureDir: "test-fixtures/classifiers/positive", fixtureDir: "test-fixtures/classifiers/positive",
location: "VERSION", location: "VERSION",
expected: []Classification{ expected: []file.Classification{
{ {
Class: "go-binary-hint", Class: "go-binary-hint",
Metadata: map[string]string{ Metadata: map[string]string{
@@ -90,7 +91,7 @@ func TestClassifierCataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
name: "positive-busybox", name: "positive-busybox",
fixtureDir: "test-fixtures/classifiers/positive", fixtureDir: "test-fixtures/classifiers/positive",
location: "[", // note: busybox is a link to [ location: "[", // note: busybox is a link to [
expected: []Classification{ expected: []file.Classification{
{ {
Class: "busybox-binary", Class: "busybox-binary",
Metadata: map[string]string{ Metadata: map[string]string{
@@ -105,7 +106,7 @@ func TestClassifierCataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
c, err := NewClassificationCataloger(DefaultClassifiers) c, err := NewCataloger(DefaultClassifiers())
test.expectedErr(t, err) test.expectedErr(t, err)
src, err := source.NewFromDirectory(test.fixtureDir) src, err := source.NewFromDirectory(test.fixtureDir)
@@ -138,14 +139,14 @@ func TestClassifierCataloger_DefaultClassifiers_PositiveCases_Image(t *testing.T
name string name string
fixtureImage string fixtureImage string
location string location string
expected []Classification expected []file.Classification
expectedErr func(assert.TestingT, error, ...interface{}) bool expectedErr func(assert.TestingT, error, ...interface{}) bool
}{ }{
{ {
name: "busybox-regression", name: "busybox-regression",
fixtureImage: "image-busybox", fixtureImage: "image-busybox",
location: "/bin/[", location: "/bin/[",
expected: []Classification{ expected: []file.Classification{
{ {
Class: "busybox-binary", Class: "busybox-binary",
Metadata: map[string]string{ Metadata: map[string]string{
@@ -160,7 +161,7 @@ func TestClassifierCataloger_DefaultClassifiers_PositiveCases_Image(t *testing.T
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
c, err := NewClassificationCataloger(DefaultClassifiers) c, err := NewCataloger(DefaultClassifiers())
test.expectedErr(t, err) test.expectedErr(t, err)
img := imagetest.GetFixtureImage(t, "docker-archive", test.fixtureImage) img := imagetest.GetFixtureImage(t, "docker-archive", test.fixtureImage)
@@ -191,7 +192,7 @@ func TestClassifierCataloger_DefaultClassifiers_PositiveCases_Image(t *testing.T
func TestClassifierCataloger_DefaultClassifiers_NegativeCases(t *testing.T) { func TestClassifierCataloger_DefaultClassifiers_NegativeCases(t *testing.T) {
c, err := NewClassificationCataloger(DefaultClassifiers) c, err := NewCataloger(DefaultClassifiers())
assert.NoError(t, err) assert.NoError(t, err)
src, err := source.NewFromDirectory("test-fixtures/classifiers/negative") src, err := source.NewFromDirectory("test-fixtures/classifiers/negative")
@@ -1,4 +1,4 @@
package file package fileclassifier
import ( import (
"bytes" "bytes"
@@ -7,58 +7,10 @@ import (
"regexp" "regexp"
"text/template" "text/template"
"github.com/anchore/syft/internal" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/source"
)
var DefaultClassifiers = []Classifier{ "github.com/anchore/syft/internal"
{ )
Class: "python-binary",
FilepathPatterns: []*regexp.Regexp{
regexp.MustCompile(`(.*/|^)python(?P<version>[0-9]+\.[0-9]+)$`),
regexp.MustCompile(`(.*/|^)libpython(?P<version>[0-9]+\.[0-9]+).so.*$`),
},
EvidencePatternTemplates: []string{
`(?m)(?P<version>{{ .version }}\.[0-9]+[-_a-zA-Z0-9]*)`,
},
},
{
Class: "cpython-source",
FilepathPatterns: []*regexp.Regexp{
regexp.MustCompile(`(.*/|^)patchlevel.h$`),
},
EvidencePatternTemplates: []string{
`(?m)#define\s+PY_VERSION\s+"?(?P<version>[0-9\.\-_a-zA-Z]+)"?`,
},
},
{
Class: "go-binary",
FilepathPatterns: []*regexp.Regexp{
regexp.MustCompile(`(.*/|^)go$`),
},
EvidencePatternTemplates: []string{
`(?m)go(?P<version>[0-9]+\.[0-9]+(\.[0-9]+|beta[0-9]+|alpha[0-9]+|rc[0-9]+)?)`,
},
},
{
Class: "go-binary-hint",
FilepathPatterns: []*regexp.Regexp{
regexp.MustCompile(`(.*/|^)VERSION$`),
},
EvidencePatternTemplates: []string{
`(?m)go(?P<version>[0-9]+\.[0-9]+(\.[0-9]+|beta[0-9]+|alpha[0-9]+|rc[0-9]+)?)`,
},
},
{
Class: "busybox-binary",
FilepathPatterns: []*regexp.Regexp{
regexp.MustCompile(`(.*/|^)busybox$`),
},
EvidencePatternTemplates: []string{
`(?m)BusyBox\s+v(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`,
},
},
}
type Classifier struct { type Classifier struct {
Class string Class string
@@ -66,12 +18,58 @@ type Classifier struct {
EvidencePatternTemplates []string EvidencePatternTemplates []string
} }
type Classification struct { func DefaultClassifiers() []Classifier {
Class string `json:"class"` return []Classifier{
Metadata map[string]string `json:"metadata"` {
Class: "python-binary",
FilepathPatterns: []*regexp.Regexp{
regexp.MustCompile(`(.*/|^)python(?P<version>[0-9]+\.[0-9]+)$`),
regexp.MustCompile(`(.*/|^)libpython(?P<version>[0-9]+\.[0-9]+).so.*$`),
},
EvidencePatternTemplates: []string{
`(?m)(?P<version>{{ .version }}\.[0-9]+[-_a-zA-Z0-9]*)`,
},
},
{
Class: "cpython-source",
FilepathPatterns: []*regexp.Regexp{
regexp.MustCompile(`(.*/|^)patchlevel.h$`),
},
EvidencePatternTemplates: []string{
`(?m)#define\s+PY_VERSION\s+"?(?P<version>[0-9\.\-_a-zA-Z]+)"?`,
},
},
{
Class: "go-binary",
FilepathPatterns: []*regexp.Regexp{
regexp.MustCompile(`(.*/|^)go$`),
},
EvidencePatternTemplates: []string{
`(?m)go(?P<version>[0-9]+\.[0-9]+(\.[0-9]+|beta[0-9]+|alpha[0-9]+|rc[0-9]+)?)`,
},
},
{
Class: "go-binary-hint",
FilepathPatterns: []*regexp.Regexp{
regexp.MustCompile(`(.*/|^)VERSION$`),
},
EvidencePatternTemplates: []string{
`(?m)go(?P<version>[0-9]+\.[0-9]+(\.[0-9]+|beta[0-9]+|alpha[0-9]+|rc[0-9]+)?)`,
},
},
{
Class: "busybox-binary",
FilepathPatterns: []*regexp.Regexp{
regexp.MustCompile(`(.*/|^)busybox$`),
},
EvidencePatternTemplates: []string{
`(?m)BusyBox\s+v(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`,
},
},
}
} }
func (c Classifier) Classify(resolver source.FileResolver, location source.Location) (*Classification, error) { func (c Classifier) Classify(resolver file.Resolver, location file.Location) (*file.Classification, error) {
doesFilepathMatch, filepathNamedGroupValues := filepathMatches(c.FilepathPatterns, location) doesFilepathMatch, filepathNamedGroupValues := filepathMatches(c.FilepathPatterns, location)
if !doesFilepathMatch { if !doesFilepathMatch {
return nil, nil return nil, nil
@@ -81,7 +79,7 @@ func (c Classifier) Classify(resolver source.FileResolver, location source.Locat
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer internal.CloseAndLogError(contentReader, location.VirtualPath) defer internal.CloseAndLogError(contentReader, location.AccessPath)
// TODO: there is room for improvement here, as this may use an excessive amount of memory. Alternate approach is to leverage a RuneReader. // TODO: there is room for improvement here, as this may use an excessive amount of memory. Alternate approach is to leverage a RuneReader.
contents, err := ioutil.ReadAll(contentReader) contents, err := ioutil.ReadAll(contentReader)
@@ -89,7 +87,7 @@ func (c Classifier) Classify(resolver source.FileResolver, location source.Locat
return nil, err return nil, err
} }
var result *Classification var result *file.Classification
for _, patternTemplate := range c.EvidencePatternTemplates { for _, patternTemplate := range c.EvidencePatternTemplates {
tmpl, err := template.New("").Parse(patternTemplate) tmpl, err := template.New("").Parse(patternTemplate)
if err != nil { if err != nil {
@@ -113,7 +111,7 @@ func (c Classifier) Classify(resolver source.FileResolver, location source.Locat
matchMetadata := internal.MatchNamedCaptureGroups(pattern, string(contents)) matchMetadata := internal.MatchNamedCaptureGroups(pattern, string(contents))
if result == nil { if result == nil {
result = &Classification{ result = &file.Classification{
Class: c.Class, Class: c.Class,
Metadata: matchMetadata, Metadata: matchMetadata,
} }
@@ -126,8 +124,8 @@ func (c Classifier) Classify(resolver source.FileResolver, location source.Locat
return result, nil return result, nil
} }
func filepathMatches(patterns []*regexp.Regexp, location source.Location) (bool, map[string]string) { func filepathMatches(patterns []*regexp.Regexp, location file.Location) (bool, map[string]string) {
for _, path := range []string{location.RealPath, location.VirtualPath} { for _, path := range []string{location.RealPath, location.AccessPath} {
if path == "" { if path == "" {
continue continue
} }
@@ -1,25 +1,25 @@
package file package fileclassifier
import ( import (
"github.com/anchore/syft/syft/file"
"regexp" "regexp"
"testing" "testing"
"github.com/anchore/syft/syft/source"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestFilepathMatches(t *testing.T) { func TestFilepathMatches(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
location source.Location location file.Location
patterns []string patterns []string
expectedMatches bool expectedMatches bool
expectedNamedGroups map[string]string expectedNamedGroups map[string]string
}{ }{
{ {
name: "simple-filename-match", name: "simple-filename-match",
location: source.Location{ location: file.Location{
Coordinates: source.Coordinates{ Coordinates: file.Coordinates{
RealPath: "python2.7", RealPath: "python2.7",
}, },
}, },
@@ -30,8 +30,8 @@ func TestFilepathMatches(t *testing.T) {
}, },
{ {
name: "filepath-match", name: "filepath-match",
location: source.Location{ location: file.Location{
Coordinates: source.Coordinates{ Coordinates: file.Coordinates{
RealPath: "/usr/bin/python2.7", RealPath: "/usr/bin/python2.7",
}, },
}, },
@@ -42,8 +42,8 @@ func TestFilepathMatches(t *testing.T) {
}, },
{ {
name: "virtual-filepath-match", name: "virtual-filepath-match",
location: source.Location{ location: file.Location{
VirtualPath: "/usr/bin/python2.7", AccessPath: "/usr/bin/python2.7",
}, },
patterns: []string{ patterns: []string{
`python([0-9]+\.[0-9]+)$`, `python([0-9]+\.[0-9]+)$`,
@@ -52,8 +52,8 @@ func TestFilepathMatches(t *testing.T) {
}, },
{ {
name: "full-filepath-match", name: "full-filepath-match",
location: source.Location{ location: file.Location{
VirtualPath: "/usr/bin/python2.7", AccessPath: "/usr/bin/python2.7",
}, },
patterns: []string{ patterns: []string{
`.*/bin/python([0-9]+\.[0-9]+)$`, `.*/bin/python([0-9]+\.[0-9]+)$`,
@@ -62,8 +62,8 @@ func TestFilepathMatches(t *testing.T) {
}, },
{ {
name: "anchored-filename-match-FAILS", name: "anchored-filename-match-FAILS",
location: source.Location{ location: file.Location{
Coordinates: source.Coordinates{ Coordinates: file.Coordinates{
RealPath: "/usr/bin/python2.7", RealPath: "/usr/bin/python2.7",
}, },
}, },
@@ -74,7 +74,7 @@ func TestFilepathMatches(t *testing.T) {
}, },
{ {
name: "empty-filename-match-FAILS", name: "empty-filename-match-FAILS",
location: source.Location{}, location: file.Location{},
patterns: []string{ patterns: []string{
`^python([0-9]+\.[0-9]+)$`, `^python([0-9]+\.[0-9]+)$`,
}, },
@@ -1,4 +1,4 @@
package file package filecontents
import ( import (
"bytes" "bytes"
@@ -7,28 +7,37 @@ import (
"io" "io"
"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/syft/source" "github.com/anchore/syft/syft/file"
) )
type ContentsCataloger struct { type Config struct {
globs []string Globs []string
skipFilesAboveSizeInBytes int64 SkipFilesAboveSizeInBytes int64
} }
func NewContentsCataloger(globs []string, skipFilesAboveSize int64) (*ContentsCataloger, error) { type Cataloger struct {
return &ContentsCataloger{ config Config
globs: globs, }
skipFilesAboveSizeInBytes: skipFilesAboveSize,
func DefaultConfig() Config {
return Config{
Globs: nil,
SkipFilesAboveSizeInBytes: 1 * file.MB,
}
}
func NewCataloger(config Config) (*Cataloger, error) {
return &Cataloger{
config: config,
}, nil }, nil
} }
func (i *ContentsCataloger) Catalog(resolver source.FileResolver) (map[source.Coordinates]string, error) { func (i *Cataloger) Catalog(resolver file.Resolver) (map[file.Coordinates]string, error) {
results := make(map[source.Coordinates]string) results := make(map[file.Coordinates]string)
var locations []source.Location var locations []file.Location
locations, err := resolver.FilesByGlob(i.globs...) locations, err := resolver.FilesByGlob(i.config.Globs...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -38,7 +47,7 @@ func (i *ContentsCataloger) Catalog(resolver source.FileResolver) (map[source.Co
return nil, err return nil, err
} }
if i.skipFilesAboveSizeInBytes > 0 && metadata.Size > i.skipFilesAboveSizeInBytes { if i.config.SkipFilesAboveSizeInBytes > 0 && metadata.Size > i.config.SkipFilesAboveSizeInBytes {
continue continue
} }
@@ -57,12 +66,12 @@ func (i *ContentsCataloger) Catalog(resolver source.FileResolver) (map[source.Co
return results, nil return results, nil
} }
func (i *ContentsCataloger) catalogLocation(resolver source.FileResolver, location source.Location) (string, error) { func (i *Cataloger) catalogLocation(resolver file.Resolver, location file.Location) (string, error) {
contentReader, err := resolver.FileContentsByLocation(location) contentReader, err := resolver.FileContentsByLocation(location)
if err != nil { if err != nil {
return "", err return "", err
} }
defer internal.CloseAndLogError(contentReader, location.VirtualPath) defer internal.CloseAndLogError(contentReader, location.AccessPath)
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
encoder := base64.NewEncoder(base64.StdEncoding, buf) encoder := base64.NewEncoder(base64.StdEncoding, buf)
@@ -0,0 +1,82 @@
package filecontents
import (
"github.com/anchore/syft/syft/file"
"testing"
"github.com/stretchr/testify/assert"
)
func TestContentsCataloger(t *testing.T) {
allFiles := []string{"test-fixtures/last/path.txt", "test-fixtures/another-path.txt", "test-fixtures/a-path.txt"}
tests := []struct {
name string
globs []string
maxSize int64
files []string
expected map[file.Coordinates]string
}{
{
name: "multi-pattern",
globs: []string{"test-fixtures/last/*.txt", "test-fixtures/*.txt"},
files: allFiles,
expected: map[file.Coordinates]string{
file.NewLocation("test-fixtures/last/path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9sYXN0L3BhdGgudHh0IGZpbGUgY29udGVudHMh",
file.NewLocation("test-fixtures/another-path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9hbm90aGVyLXBhdGgudHh0IGZpbGUgY29udGVudHMh",
file.NewLocation("test-fixtures/a-path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9hLXBhdGgudHh0IGZpbGUgY29udGVudHMh",
},
},
{
name: "no-patterns",
globs: []string{},
files: []string{"test-fixtures/last/path.txt", "test-fixtures/another-path.txt", "test-fixtures/a-path.txt"},
expected: map[file.Coordinates]string{},
},
{
name: "all-txt",
globs: []string{"**/*.txt"},
files: allFiles,
expected: map[file.Coordinates]string{
file.NewLocation("test-fixtures/last/path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9sYXN0L3BhdGgudHh0IGZpbGUgY29udGVudHMh",
file.NewLocation("test-fixtures/another-path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9hbm90aGVyLXBhdGgudHh0IGZpbGUgY29udGVudHMh",
file.NewLocation("test-fixtures/a-path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9hLXBhdGgudHh0IGZpbGUgY29udGVudHMh",
},
},
{
name: "subpath",
globs: []string{"test-fixtures/*.txt"},
files: allFiles,
expected: map[file.Coordinates]string{
file.NewLocation("test-fixtures/another-path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9hbm90aGVyLXBhdGgudHh0IGZpbGUgY29udGVudHMh",
file.NewLocation("test-fixtures/a-path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9hLXBhdGgudHh0IGZpbGUgY29udGVudHMh",
},
},
{
name: "size-filter",
maxSize: 42,
globs: []string{"**/*.txt"},
files: allFiles,
expected: map[file.Coordinates]string{
file.NewLocation("test-fixtures/last/path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9sYXN0L3BhdGgudHh0IGZpbGUgY29udGVudHMh",
file.NewLocation("test-fixtures/a-path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9hLXBhdGgudHh0IGZpbGUgY29udGVudHMh",
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
c, err := NewCataloger(Config{
Globs: test.globs,
SkipFilesAboveSizeInBytes: test.maxSize,
})
assert.NoError(t, err)
resolver := file.NewMockResolverForPaths(test.files...)
actual, err := c.Catalog(resolver)
assert.NoError(t, err)
assert.Equal(t, test.expected, actual, "mismatched contents")
})
}
}
@@ -1,4 +1,4 @@
package file package filedigests
import ( import (
"crypto" "crypto"
@@ -6,35 +6,32 @@ import (
"fmt" "fmt"
"hash" "hash"
"io" "io"
"strings"
"github.com/anchore/syft/internal" "github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/bus" "github.com/anchore/syft/internal/bus"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/event" "github.com/anchore/syft/syft/event"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/source"
"github.com/wagoodman/go-partybus" "github.com/wagoodman/go-partybus"
"github.com/wagoodman/go-progress" "github.com/wagoodman/go-progress"
"github.com/anchore/syft/syft/source"
) )
var errUndigestableFile = errors.New("undigestable file") var errUndigestableFile = errors.New("undigestable file")
type DigestsCataloger struct { type Cataloger struct {
hashes []crypto.Hash hashes []crypto.Hash
} }
func NewDigestsCataloger(hashes []crypto.Hash) (*DigestsCataloger, error) { func NewCataloger(hashes []crypto.Hash) (*Cataloger, error) {
return &DigestsCataloger{ return &Cataloger{
hashes: hashes, hashes: hashes,
}, nil }, nil
} }
func (i *DigestsCataloger) Catalog(resolver source.FileResolver) (map[source.Coordinates][]Digest, error) { func (i *Cataloger) Catalog(resolver file.Resolver) (map[file.Coordinates][]file.Digest, error) {
results := make(map[source.Coordinates][]Digest) results := make(map[file.Coordinates][]file.Digest)
locations := allRegularFiles(resolver) locations := source.AllRegularFiles(resolver)
stage, prog := digestsCatalogingProgress(int64(len(locations))) stage, prog := digestsCatalogingProgress(int64(len(locations)))
for _, location := range locations { for _, location := range locations {
stage.Current = location.RealPath stage.Current = location.RealPath
@@ -60,14 +57,14 @@ func (i *DigestsCataloger) Catalog(resolver source.FileResolver) (map[source.Coo
return results, nil return results, nil
} }
func (i *DigestsCataloger) catalogLocation(resolver source.FileResolver, location source.Location) ([]Digest, error) { func (i *Cataloger) catalogLocation(resolver file.Resolver, location file.Location) ([]file.Digest, error) {
meta, err := resolver.FileMetadataByLocation(location) meta, err := resolver.FileMetadataByLocation(location)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// we should only attempt to report digests for files that are regular files (don't attempt to resolve links) // we should only attempt to report digests for files that are regular files (don't attempt to resolve links)
if meta.Type != source.RegularFile { if meta.Type != file.RegularFile {
return nil, errUndigestableFile return nil, errUndigestableFile
} }
@@ -75,7 +72,7 @@ func (i *DigestsCataloger) catalogLocation(resolver source.FileResolver, locatio
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer internal.CloseAndLogError(contentReader, location.VirtualPath) defer internal.CloseAndLogError(contentReader, location.AccessPath)
// create a set of hasher objects tied together with a single writer to feed content into // create a set of hasher objects tied together with a single writer to feed content into
hashers := make([]hash.Hash, len(i.hashes)) hashers := make([]hash.Hash, len(i.hashes))
@@ -91,16 +88,16 @@ func (i *DigestsCataloger) catalogLocation(resolver source.FileResolver, locatio
} }
if size == 0 { if size == 0 {
return make([]Digest, 0), nil return make([]file.Digest, 0), nil
} }
result := make([]Digest, len(i.hashes)) result := make([]file.Digest, len(i.hashes))
// only capture digests when there is content. It is important to do this based on SIZE and not // only capture digests when there is content. It is important to do this based on SIZE and not
// FILE TYPE. The reasoning is that it is possible for a tar to be crafted with a header-only // FILE TYPE. The reasoning is that it is possible for a tar to be crafted with a header-only
// file type but a body is still allowed. // file type but a body is still allowed.
for idx, hasher := range hashers { for idx, hasher := range hashers {
result[idx] = Digest{ result[idx] = file.Digest{
Algorithm: DigestAlgorithmName(i.hashes[idx]), Algorithm: file.DigestAlgorithmName(i.hashes[idx]),
Value: fmt.Sprintf("%+x", hasher.Sum(nil)), Value: fmt.Sprintf("%+x", hasher.Sum(nil)),
} }
} }
@@ -108,15 +105,6 @@ func (i *DigestsCataloger) catalogLocation(resolver source.FileResolver, locatio
return result, nil return result, nil
} }
func DigestAlgorithmName(hash crypto.Hash) string {
return CleanDigestAlgorithmName(hash.String())
}
func CleanDigestAlgorithmName(name string) string {
lower := strings.ToLower(name)
return strings.ReplaceAll(lower, "-", "")
}
func digestsCatalogingProgress(locations int64) (*progress.Stage, *progress.Manual) { func digestsCatalogingProgress(locations int64) (*progress.Stage, *progress.Manual) {
stage := &progress.Stage{} stage := &progress.Stage{}
prog := &progress.Manual{ prog := &progress.Manual{
@@ -1,25 +1,23 @@
package file package filedigests
import ( import (
"crypto" "crypto"
"fmt" "fmt"
"github.com/stretchr/testify/require"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
"github.com/anchore/stereoscope/pkg/file" stereoscopeFile "github.com/anchore/stereoscope/pkg/file"
"github.com/anchore/stereoscope/pkg/imagetest" "github.com/anchore/stereoscope/pkg/imagetest"
"github.com/anchore/syft/syft/file"
"github.com/stretchr/testify/assert"
"github.com/anchore/syft/syft/source" "github.com/anchore/syft/syft/source"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func testDigests(t testing.TB, root string, files []string, hashes ...crypto.Hash) map[source.Coordinates][]Digest { func testDigests(t testing.TB, root string, files []string, hashes ...crypto.Hash) map[file.Coordinates][]file.Digest {
digests := make(map[source.Coordinates][]Digest) digests := make(map[file.Coordinates][]file.Digest)
for _, f := range files { for _, f := range files {
fh, err := os.Open(filepath.Join(root, f)) fh, err := os.Open(filepath.Join(root, f))
@@ -33,15 +31,15 @@ func testDigests(t testing.TB, root string, files []string, hashes ...crypto.Has
if len(b) == 0 { if len(b) == 0 {
// we don't keep digests for empty files // we don't keep digests for empty files
digests[source.NewLocation(f).Coordinates] = []Digest{} digests[file.NewLocation(f).Coordinates] = []file.Digest{}
continue continue
} }
for _, hash := range hashes { for _, hash := range hashes {
h := hash.New() h := hash.New()
h.Write(b) h.Write(b)
digests[source.NewLocation(f).Coordinates] = append(digests[source.NewLocation(f).Coordinates], Digest{ digests[file.NewLocation(f).Coordinates] = append(digests[file.NewLocation(f).Coordinates], file.Digest{
Algorithm: CleanDigestAlgorithmName(hash.String()), Algorithm: file.CleanDigestAlgorithmName(hash.String()),
Value: fmt.Sprintf("%x", h.Sum(nil)), Value: fmt.Sprintf("%x", h.Sum(nil)),
}) })
} }
@@ -56,7 +54,7 @@ func TestDigestsCataloger(t *testing.T) {
name string name string
digests []crypto.Hash digests []crypto.Hash
files []string files []string
expected map[source.Coordinates][]Digest expected map[file.Coordinates][]file.Digest
}{ }{
{ {
name: "md5", name: "md5",
@@ -74,7 +72,7 @@ func TestDigestsCataloger(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
c, err := NewDigestsCataloger(test.digests) c, err := NewCataloger(test.digests)
require.NoError(t, err) require.NoError(t, err)
src, err := source.NewFromDirectory("test-fixtures/last/") src, err := source.NewFromDirectory("test-fixtures/last/")
@@ -92,14 +90,7 @@ func TestDigestsCataloger(t *testing.T) {
} }
func TestDigestsCataloger_MixFileTypes(t *testing.T) { func TestDigestsCataloger_MixFileTypes(t *testing.T) {
testImage := "image-file-type-mix" img := imagetest.GetFixtureImage(t, "docker-archive", "image-file-type-mix")
if *updateImageGoldenFiles {
imagetest.UpdateGoldenFixtureImage(t, testImage)
}
img := imagetest.GetGoldenFixtureImage(t, testImage)
src, err := source.NewFromImage(img, "---") src, err := source.NewFromImage(img, "---")
if err != nil { if err != nil {
t.Fatalf("could not create source: %+v", err) t.Fatalf("could not create source: %+v", err)
@@ -140,7 +131,7 @@ func TestDigestsCataloger_MixFileTypes(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(test.path, func(t *testing.T) { t.Run(test.path, func(t *testing.T) {
c, err := NewDigestsCataloger([]crypto.Hash{crypto.MD5}) c, err := NewCataloger([]crypto.Hash{crypto.MD5})
if err != nil { if err != nil {
t.Fatalf("unable to get cataloger: %+v", err) t.Fatalf("unable to get cataloger: %+v", err)
} }
@@ -150,11 +141,11 @@ func TestDigestsCataloger_MixFileTypes(t *testing.T) {
t.Fatalf("could not catalog: %+v", err) t.Fatalf("could not catalog: %+v", err)
} }
_, ref, err := img.SquashedTree().File(file.Path(test.path)) _, ref, err := img.SquashedTree().File(stereoscopeFile.Path(test.path))
if err != nil { if err != nil {
t.Fatalf("unable to get file=%q : %+v", test.path, err) t.Fatalf("unable to get file=%q : %+v", test.path, err)
} }
l := source.NewLocationFromImage(test.path, *ref, img) l := file.NewLocationFromImage(test.path, *ref, img)
if len(actual[l.Coordinates]) == 0 { if len(actual[l.Coordinates]) == 0 {
if test.expected != "" { if test.expected != "" {
@@ -4,7 +4,6 @@ ADD file-1.txt .
RUN chmod 644 file-1.txt RUN chmod 644 file-1.txt
RUN chown 1:2 file-1.txt RUN chown 1:2 file-1.txt
RUN ln -s file-1.txt symlink-1 RUN ln -s file-1.txt symlink-1
# note: hard links may behave inconsistently, this should be a golden image
RUN ln file-1.txt hardlink-1 RUN ln file-1.txt hardlink-1
RUN mknod char-device-1 c 89 1 RUN mknod char-device-1 c 89 1
RUN mknod block-device-1 b 0 1 RUN mknod block-device-1 b 0 1
@@ -1,24 +1,24 @@
package file package filemetadata
import ( 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/syft/event" "github.com/anchore/syft/syft/event"
"github.com/anchore/syft/syft/source" "github.com/anchore/syft/syft/file"
"github.com/wagoodman/go-partybus" "github.com/wagoodman/go-partybus"
"github.com/wagoodman/go-progress" "github.com/wagoodman/go-progress"
) )
type MetadataCataloger struct { type Cataloger struct {
} }
func NewMetadataCataloger() *MetadataCataloger { func NewCataloger() *Cataloger {
return &MetadataCataloger{} return &Cataloger{}
} }
func (i *MetadataCataloger) Catalog(resolver source.FileResolver) (map[source.Coordinates]source.FileMetadata, error) { func (i *Cataloger) Catalog(resolver file.Resolver) (map[file.Coordinates]file.Metadata, error) {
results := make(map[source.Coordinates]source.FileMetadata) results := make(map[file.Coordinates]file.Metadata)
var locations []source.Location var locations []file.Location
for location := range resolver.AllLocations() { for location := range resolver.AllLocations() {
locations = append(locations, location) locations = append(locations, location)
} }
@@ -1,28 +1,20 @@
package file package filemetadata
import ( import (
"flag"
"os" "os"
"testing" "testing"
"github.com/anchore/stereoscope/pkg/file" stereoscopeFile "github.com/anchore/stereoscope/pkg/file"
"github.com/anchore/stereoscope/pkg/imagetest" "github.com/anchore/stereoscope/pkg/imagetest"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/source" "github.com/anchore/syft/syft/source"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
var updateImageGoldenFiles = flag.Bool("update-image", false, "update the golden fixture images used for testing")
func TestFileMetadataCataloger(t *testing.T) { func TestFileMetadataCataloger(t *testing.T) {
testImage := "image-file-type-mix" img := imagetest.GetFixtureImage(t, "docker-archive", "image-file-type-mix")
if *updateImageGoldenFiles { c := NewCataloger()
imagetest.UpdateGoldenFixtureImage(t, testImage)
}
img := imagetest.GetGoldenFixtureImage(t, testImage)
c := NewMetadataCataloger()
src, err := source.NewFromImage(img, "---") src, err := source.NewFromImage(img, "---")
if err != nil { if err != nil {
@@ -42,13 +34,13 @@ func TestFileMetadataCataloger(t *testing.T) {
tests := []struct { tests := []struct {
path string path string
exists bool exists bool
expected source.FileMetadata expected file.Metadata
err bool err bool
}{ }{
{ {
path: "/file-1.txt", path: "/file-1.txt",
exists: true, exists: true,
expected: source.FileMetadata{ expected: file.Metadata{
Mode: 0644, Mode: 0644,
Type: "RegularFile", Type: "RegularFile",
UserID: 1, UserID: 1,
@@ -60,7 +52,7 @@ func TestFileMetadataCataloger(t *testing.T) {
{ {
path: "/hardlink-1", path: "/hardlink-1",
exists: true, exists: true,
expected: source.FileMetadata{ expected: file.Metadata{
Mode: 0644, Mode: 0644,
Type: "HardLink", Type: "HardLink",
LinkDestination: "file-1.txt", LinkDestination: "file-1.txt",
@@ -72,7 +64,7 @@ func TestFileMetadataCataloger(t *testing.T) {
{ {
path: "/symlink-1", path: "/symlink-1",
exists: true, exists: true,
expected: source.FileMetadata{ expected: file.Metadata{
Mode: 0777 | os.ModeSymlink, Mode: 0777 | os.ModeSymlink,
Type: "SymbolicLink", Type: "SymbolicLink",
LinkDestination: "file-1.txt", LinkDestination: "file-1.txt",
@@ -84,7 +76,7 @@ func TestFileMetadataCataloger(t *testing.T) {
{ {
path: "/char-device-1", path: "/char-device-1",
exists: true, exists: true,
expected: source.FileMetadata{ expected: file.Metadata{
Mode: 0644 | os.ModeDevice | os.ModeCharDevice, Mode: 0644 | os.ModeDevice | os.ModeCharDevice,
Type: "CharacterDevice", Type: "CharacterDevice",
UserID: 0, UserID: 0,
@@ -95,7 +87,7 @@ func TestFileMetadataCataloger(t *testing.T) {
{ {
path: "/block-device-1", path: "/block-device-1",
exists: true, exists: true,
expected: source.FileMetadata{ expected: file.Metadata{
Mode: 0644 | os.ModeDevice, Mode: 0644 | os.ModeDevice,
Type: "BlockDevice", Type: "BlockDevice",
UserID: 0, UserID: 0,
@@ -106,7 +98,7 @@ func TestFileMetadataCataloger(t *testing.T) {
{ {
path: "/fifo-1", path: "/fifo-1",
exists: true, exists: true,
expected: source.FileMetadata{ expected: file.Metadata{
Mode: 0644 | os.ModeNamedPipe, Mode: 0644 | os.ModeNamedPipe,
Type: "FIFONode", Type: "FIFONode",
UserID: 0, UserID: 0,
@@ -117,7 +109,7 @@ func TestFileMetadataCataloger(t *testing.T) {
{ {
path: "/bin", path: "/bin",
exists: true, exists: true,
expected: source.FileMetadata{ expected: file.Metadata{
Mode: 0755 | os.ModeDir, Mode: 0755 | os.ModeDir,
Type: "Directory", Type: "Directory",
UserID: 0, UserID: 0,
@@ -129,12 +121,12 @@ func TestFileMetadataCataloger(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(test.path, func(t *testing.T) { t.Run(test.path, func(t *testing.T) {
_, ref, err := img.SquashedTree().File(file.Path(test.path)) _, ref, err := img.SquashedTree().File(stereoscopeFile.Path(test.path))
if err != nil { if err != nil {
t.Fatalf("unable to get file: %+v", err) t.Fatalf("unable to get file: %+v", err)
} }
l := source.NewLocationFromImage(test.path, *ref, img) l := file.NewLocationFromImage(test.path, *ref, img)
assert.Equal(t, test.expected, actual[l.Coordinates], "mismatched metadata") assert.Equal(t, test.expected, actual[l.Coordinates], "mismatched metadata")
@@ -0,0 +1,12 @@
FROM busybox:latest
ADD file-1.txt .
RUN chmod 644 file-1.txt
RUN chown 1:2 file-1.txt
RUN ln -s file-1.txt symlink-1
RUN ln file-1.txt hardlink-1
RUN mknod char-device-1 c 89 1
RUN mknod block-device-1 b 0 1
RUN mknod fifo-1 p
RUN mkdir /dir
RUN rm -rf home etc/group etc/localtime etc/mtab etc/network etc/passwd etc/shadow var usr bin/*
@@ -1,4 +1,4 @@
package file package secrets
import ( import (
"bytes" "bytes"
@@ -9,10 +9,11 @@ import (
"sort" "sort"
"github.com/anchore/syft/internal" "github.com/anchore/syft/internal"
"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/syft/event" "github.com/anchore/syft/syft/event"
"github.com/anchore/syft/syft/event/monitor"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/source" "github.com/anchore/syft/syft/source"
"github.com/wagoodman/go-partybus" "github.com/wagoodman/go-partybus"
"github.com/wagoodman/go-progress" "github.com/wagoodman/go-progress"
@@ -26,24 +27,39 @@ var DefaultSecretsPatterns = map[string]string{
"generic-api-key": `(?i)api(-|_)?key["'=:\s]*?(?P<value>[A-Z0-9]{20,60})["']?(\s|$)`, "generic-api-key": `(?i)api(-|_)?key["'=:\s]*?(?P<value>[A-Z0-9]{20,60})["']?(\s|$)`,
} }
type SecretsCataloger struct { type Config struct {
patterns map[string]*regexp.Regexp Patterns map[string]*regexp.Regexp
revealValues bool RevealValues bool
skipFilesAboveSize int64 MaxFileSize int64
} }
func NewSecretsCataloger(patterns map[string]*regexp.Regexp, revealValues bool, maxFileSize int64) (*SecretsCataloger, error) { type Cataloger struct {
return &SecretsCataloger{ config Config
patterns: patterns, }
revealValues: revealValues,
skipFilesAboveSize: maxFileSize, func DefaultConfig() Config {
patterns, err := file.GenerateSearchPatterns(DefaultSecretsPatterns, nil, nil)
if err != nil {
patterns = make(map[string]*regexp.Regexp)
log.Errorf("unable to create default secrets config: %w", err)
}
return Config{
Patterns: patterns,
RevealValues: false,
MaxFileSize: 1 * file.MB,
}
}
func NewCataloger(config Config) (*Cataloger, error) {
return &Cataloger{
config: config,
}, nil }, nil
} }
func (i *SecretsCataloger) Catalog(resolver source.FileResolver) (map[source.Coordinates][]SearchResult, error) { func (i *Cataloger) Catalog(resolver file.Resolver) (map[file.Coordinates][]file.SearchResult, error) {
results := make(map[source.Coordinates][]SearchResult) results := make(map[file.Coordinates][]file.SearchResult)
locations := allRegularFiles(resolver) locations := source.AllRegularFiles(resolver)
stage, prog, secretsDiscovered := secretsCatalogingProgress(int64(len(locations))) stage, prog, secretsDiscovered := newSecretsCatalogerMonitor(int64(len(locations)))
for _, location := range locations { for _, location := range locations {
stage.Current = location.RealPath stage.Current = location.RealPath
result, err := i.catalogLocation(resolver, location) result, err := i.catalogLocation(resolver, location)
@@ -66,7 +82,7 @@ func (i *SecretsCataloger) Catalog(resolver source.FileResolver) (map[source.Coo
return results, nil return results, nil
} }
func (i *SecretsCataloger) catalogLocation(resolver source.FileResolver, location source.Location) ([]SearchResult, error) { func (i *Cataloger) catalogLocation(resolver file.Resolver, location file.Location) ([]file.SearchResult, error) {
metadata, err := resolver.FileMetadataByLocation(location) metadata, err := resolver.FileMetadataByLocation(location)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -76,17 +92,17 @@ func (i *SecretsCataloger) catalogLocation(resolver source.FileResolver, locatio
return nil, nil return nil, nil
} }
if i.skipFilesAboveSize > 0 && metadata.Size > i.skipFilesAboveSize { if i.config.MaxFileSize > 0 && metadata.Size > i.config.MaxFileSize {
return nil, nil return nil, nil
} }
// TODO: in the future we can swap out search strategies here // TODO: in the future we can swap out search strategies here
secrets, err := catalogLocationByLine(resolver, location, i.patterns) secrets, err := catalogLocationByLine(resolver, location, i.config.Patterns)
if err != nil { if err != nil {
return nil, internal.ErrPath{Context: "secrets-cataloger", Path: location.RealPath, Err: err} return nil, internal.ErrPath{Context: "secrets-cataloger", Path: location.RealPath, Err: err}
} }
if i.revealValues { if i.config.RevealValues {
for idx, secret := range secrets { for idx, secret := range secrets {
value, err := extractValue(resolver, location, secret.SeekPosition, secret.Length) value, err := extractValue(resolver, location, secret.SeekPosition, secret.Length)
if err != nil { if err != nil {
@@ -104,12 +120,12 @@ func (i *SecretsCataloger) catalogLocation(resolver source.FileResolver, locatio
return secrets, nil return secrets, nil
} }
func extractValue(resolver source.FileResolver, location source.Location, start, length int64) (string, error) { func extractValue(resolver file.Resolver, location file.Location, start, length int64) (string, error) {
readCloser, err := resolver.FileContentsByLocation(location) readCloser, err := resolver.FileContentsByLocation(location)
if err != nil { if err != nil {
return "", fmt.Errorf("unable to fetch reader for location=%q : %w", location, err) return "", fmt.Errorf("unable to fetch reader for location=%q : %w", location, err)
} }
defer internal.CloseAndLogError(readCloser, location.VirtualPath) defer internal.CloseAndLogError(readCloser, location.AccessPath)
n, err := io.CopyN(ioutil.Discard, readCloser, start) n, err := io.CopyN(ioutil.Discard, readCloser, start)
if err != nil { if err != nil {
@@ -131,13 +147,7 @@ func extractValue(resolver source.FileResolver, location source.Location, start,
return buf.String(), nil return buf.String(), nil
} }
type SecretsMonitor struct { func newSecretsCatalogerMonitor(locations int64) (*progress.Stage, *progress.Manual, *progress.Manual) {
progress.Stager
SecretsDiscovered progress.Monitorable
progress.Progressable
}
func secretsCatalogingProgress(locations int64) (*progress.Stage, *progress.Manual, *progress.Manual) {
stage := &progress.Stage{} stage := &progress.Stage{}
secretsDiscovered := &progress.Manual{} secretsDiscovered := &progress.Manual{}
prog := &progress.Manual{ prog := &progress.Manual{
@@ -147,7 +157,7 @@ func secretsCatalogingProgress(locations int64) (*progress.Stage, *progress.Manu
bus.Publish(partybus.Event{ bus.Publish(partybus.Event{
Type: event.SecretsCatalogerStarted, Type: event.SecretsCatalogerStarted,
Source: secretsDiscovered, Source: secretsDiscovered,
Value: SecretsMonitor{ Value: monitor.SecretsCatalogerMonitor{
Stager: progress.Stager(stage), Stager: progress.Stager(stage),
SecretsDiscovered: secretsDiscovered, SecretsDiscovered: secretsDiscovered,
Progressable: prog, Progressable: prog,
@@ -1,13 +1,10 @@
package file package secrets
import ( import (
"regexp" "regexp"
"testing" "testing"
"github.com/anchore/syft/internal/file" "github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/source"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -18,7 +15,7 @@ func TestSecretsCataloger(t *testing.T) {
reveal bool reveal bool
maxSize int64 maxSize int64
patterns map[string]string patterns map[string]string
expected []SearchResult expected []file.SearchResult
constructorErr bool constructorErr bool
catalogErr bool catalogErr bool
}{ }{
@@ -29,7 +26,7 @@ func TestSecretsCataloger(t *testing.T) {
patterns: map[string]string{ patterns: map[string]string{
"simple-secret-key": `^secret_key=.*`, "simple-secret-key": `^secret_key=.*`,
}, },
expected: []SearchResult{ expected: []file.SearchResult{
{ {
Classification: "simple-secret-key", Classification: "simple-secret-key",
LineNumber: 2, LineNumber: 2,
@@ -47,7 +44,7 @@ func TestSecretsCataloger(t *testing.T) {
patterns: map[string]string{ patterns: map[string]string{
"simple-secret-key": `^secret_key=.*`, "simple-secret-key": `^secret_key=.*`,
}, },
expected: []SearchResult{ expected: []file.SearchResult{
{ {
Classification: "simple-secret-key", Classification: "simple-secret-key",
LineNumber: 2, LineNumber: 2,
@@ -65,7 +62,7 @@ func TestSecretsCataloger(t *testing.T) {
patterns: map[string]string{ patterns: map[string]string{
"simple-secret-key": `^secret_key=(?P<value>.*)`, "simple-secret-key": `^secret_key=(?P<value>.*)`,
}, },
expected: []SearchResult{ expected: []file.SearchResult{
{ {
Classification: "simple-secret-key", Classification: "simple-secret-key",
LineNumber: 2, LineNumber: 2,
@@ -83,7 +80,7 @@ func TestSecretsCataloger(t *testing.T) {
patterns: map[string]string{ patterns: map[string]string{
"simple-secret-key": `secret_key=.*`, "simple-secret-key": `secret_key=.*`,
}, },
expected: []SearchResult{ expected: []file.SearchResult{
{ {
Classification: "simple-secret-key", Classification: "simple-secret-key",
LineNumber: 1, LineNumber: 1,
@@ -126,7 +123,7 @@ func TestSecretsCataloger(t *testing.T) {
patterns: map[string]string{ patterns: map[string]string{
"simple-secret-key": `secret_key=(?P<value>.*)`, "simple-secret-key": `secret_key=(?P<value>.*)`,
}, },
expected: []SearchResult{ expected: []file.SearchResult{
{ {
Classification: "simple-secret-key", Classification: "simple-secret-key",
LineNumber: 1, LineNumber: 1,
@@ -177,7 +174,11 @@ func TestSecretsCataloger(t *testing.T) {
regexObjs[name] = obj regexObjs[name] = obj
} }
c, err := NewSecretsCataloger(regexObjs, test.reveal, test.maxSize) c, err := NewCataloger(Config{
Patterns: regexObjs,
RevealValues: test.reveal,
MaxFileSize: test.maxSize,
})
if err != nil && !test.constructorErr { if err != nil && !test.constructorErr {
t.Fatalf("could not create cataloger (but should have been able to): %+v", err) t.Fatalf("could not create cataloger (but should have been able to): %+v", err)
} else if err == nil && test.constructorErr { } else if err == nil && test.constructorErr {
@@ -186,7 +187,7 @@ func TestSecretsCataloger(t *testing.T) {
return return
} }
resolver := source.NewMockResolverForPaths(test.fixture) resolver := file.NewMockResolverForPaths(test.fixture)
actualResults, err := c.Catalog(resolver) actualResults, err := c.Catalog(resolver)
if err != nil && !test.catalogErr { if err != nil && !test.catalogErr {
@@ -197,7 +198,7 @@ func TestSecretsCataloger(t *testing.T) {
return return
} }
loc := source.NewLocation(test.fixture) loc := file.NewLocation(test.fixture)
if _, exists := actualResults[loc.Coordinates]; !exists { if _, exists := actualResults[loc.Coordinates]; !exists {
t.Fatalf("could not find location=%q in results", loc) t.Fatalf("could not find location=%q in results", loc)
} }
@@ -208,18 +209,18 @@ func TestSecretsCataloger(t *testing.T) {
} }
func TestSecretsCataloger_DefaultSecrets(t *testing.T) { func TestSecretsCataloger_DefaultSecrets(t *testing.T) {
regexObjs, err := GenerateSearchPatterns(DefaultSecretsPatterns, nil, nil) regexObjs, err := file.GenerateSearchPatterns(DefaultSecretsPatterns, nil, nil)
if err != nil { if err != nil {
t.Fatalf("unable to get patterns: %+v", err) t.Fatalf("unable to get patterns: %+v", err)
} }
tests := []struct { tests := []struct {
fixture string fixture string
expected []SearchResult expected []file.SearchResult
}{ }{
{ {
fixture: "test-fixtures/secrets/default/aws.env", fixture: "test-fixtures/secrets/default/aws.env",
expected: []SearchResult{ expected: []file.SearchResult{
{ {
Classification: "aws-access-key", Classification: "aws-access-key",
LineNumber: 2, LineNumber: 2,
@@ -240,7 +241,7 @@ func TestSecretsCataloger_DefaultSecrets(t *testing.T) {
}, },
{ {
fixture: "test-fixtures/secrets/default/aws.ini", fixture: "test-fixtures/secrets/default/aws.ini",
expected: []SearchResult{ expected: []file.SearchResult{
{ {
Classification: "aws-access-key", Classification: "aws-access-key",
LineNumber: 3, LineNumber: 3,
@@ -261,7 +262,7 @@ func TestSecretsCataloger_DefaultSecrets(t *testing.T) {
}, },
{ {
fixture: "test-fixtures/secrets/default/private-key.pem", fixture: "test-fixtures/secrets/default/private-key.pem",
expected: []SearchResult{ expected: []file.SearchResult{
{ {
Classification: "pem-private-key", Classification: "pem-private-key",
LineNumber: 2, LineNumber: 2,
@@ -281,7 +282,7 @@ z3P668YfhUbKdRF6S42Cg6zn
}, },
{ {
fixture: "test-fixtures/secrets/default/private-key-openssl.pem", fixture: "test-fixtures/secrets/default/private-key-openssl.pem",
expected: []SearchResult{ expected: []file.SearchResult{
{ {
Classification: "pem-private-key", Classification: "pem-private-key",
LineNumber: 2, LineNumber: 2,
@@ -303,7 +304,7 @@ z3P668YfhUbKdRF6S42Cg6zn
// note: this test proves that the PEM regex matches the smallest possible match // note: this test proves that the PEM regex matches the smallest possible match
// since the test catches two adjacent secrets // since the test catches two adjacent secrets
fixture: "test-fixtures/secrets/default/private-keys.pem", fixture: "test-fixtures/secrets/default/private-keys.pem",
expected: []SearchResult{ expected: []file.SearchResult{
{ {
Classification: "pem-private-key", Classification: "pem-private-key",
LineNumber: 1, LineNumber: 1,
@@ -346,7 +347,7 @@ j4f668YfhUbKdRF6S6734856
// 2. a named capture group with the correct line number and line offset case // 2. a named capture group with the correct line number and line offset case
// 3. the named capture group is in a different line than the match start, and both the match start and the capture group have different line offsets // 3. the named capture group is in a different line than the match start, and both the match start and the capture group have different line offsets
fixture: "test-fixtures/secrets/default/docker-config.json", fixture: "test-fixtures/secrets/default/docker-config.json",
expected: []SearchResult{ expected: []file.SearchResult{
{ {
Classification: "docker-config-auth", Classification: "docker-config-auth",
LineNumber: 5, LineNumber: 5,
@@ -363,7 +364,7 @@ j4f668YfhUbKdRF6S6734856
}, },
{ {
fixture: "test-fixtures/secrets/default/api-key.txt", fixture: "test-fixtures/secrets/default/api-key.txt",
expected: []SearchResult{ expected: []file.SearchResult{
{ {
Classification: "generic-api-key", Classification: "generic-api-key",
LineNumber: 2, LineNumber: 2,
@@ -419,19 +420,23 @@ j4f668YfhUbKdRF6S6734856
for _, test := range tests { for _, test := range tests {
t.Run(test.fixture, func(t *testing.T) { t.Run(test.fixture, func(t *testing.T) {
c, err := NewSecretsCataloger(regexObjs, true, 10*file.MB) c, err := NewCataloger(Config{
Patterns: regexObjs,
RevealValues: true,
MaxFileSize: 10 * file.MB,
})
if err != nil { if err != nil {
t.Fatalf("could not create cataloger: %+v", err) t.Fatalf("could not create cataloger: %+v", err)
} }
resolver := source.NewMockResolverForPaths(test.fixture) resolver := file.NewMockResolverForPaths(test.fixture)
actualResults, err := c.Catalog(resolver) actualResults, err := c.Catalog(resolver)
if err != nil { if err != nil {
t.Fatalf("could not catalog: %+v", err) t.Fatalf("could not catalog: %+v", err)
} }
loc := source.NewLocation(test.fixture) loc := file.NewLocation(test.fixture)
if _, exists := actualResults[loc.Coordinates]; !exists && test.expected != nil { if _, exists := actualResults[loc.Coordinates]; !exists && test.expected != nil {
t.Fatalf("could not find location=%q in results", loc) t.Fatalf("could not find location=%q in results", loc)
} else if !exists && test.expected == nil { } else if !exists && test.expected == nil {

Some files were not shown because too many files have changed in this diff Show More