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
418 changed files with 3342 additions and 4265 deletions
+2 -2
View File
@@ -211,13 +211,13 @@ integration-fingerprint:
.PHONY: java-packages-fingerprint
java-packages-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
.PHONY: fixtures
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
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)
}
switch si.Scheme {
case source.ImageScheme, source.UnknownScheme:
switch si.Type {
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
si.Scheme = source.ImageScheme
si.Type = source.ImageType
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
@@ -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)
go func() {
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 {
defer cleanup()
}
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
}
s, err := generateSBOM(src, errs)
s, err := syft.Catalog(src, catalogingOpts...)
if err != nil {
errs <- err
return
}
if s == nil {
errs <- fmt.Errorf("no SBOM produced for %q", si.UserInput)
return
}
sbomBytes, err := syft.Encode(*s, format)
if err != nil {
errs <- err
-2
View File
@@ -22,8 +22,6 @@ func formatAliases(ids ...sbom.FormatID) (aliases []string) {
aliases = append(aliases, "cyclonedx-xml")
case syft.CycloneDxJSONFormatID:
aliases = append(aliases, "cyclonedx-json")
case syft.GitHubID:
aliases = append(aliases, "github", "github-json")
default:
aliases = append(aliases, string(id))
}
+21 -48
View File
@@ -13,11 +13,8 @@ import (
"github.com/anchore/syft/internal/formats/table"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/ui"
"github.com/anchore/syft/internal/version"
"github.com/anchore/syft/syft"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/event"
"github.com/anchore/syft/syft/pkg/cataloger"
"github.com/anchore/syft/syft/sbom"
"github.com/anchore/syft/syft/source"
"github.com/pkg/profile"
@@ -97,7 +94,7 @@ func init() {
func setPackageFlags(flags *pflag.FlagSet) {
// Formatting & Input options //////////////////////////////////////////////
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))
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')",
)
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 //////////////////////////////////////////////////////////
flags.StringP(
"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
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
}
@@ -260,42 +267,17 @@ func isVerbose() (result bool) {
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 {
errs := make(chan error)
go func() {
defer close(errs)
catalogingOpts, err := appConfig.ToCatalogingOptions()
if err != nil {
errs <- err
return
}
src, cleanup, err := source.New(si, appConfig.Registry.ToOptions(), appConfig.Exclusions)
if cleanup != nil {
defer cleanup()
@@ -305,7 +287,7 @@ func packagesExecWorker(si source.Input, writer sbom.Writer) <-chan error {
return
}
s, err := generateSBOM(src, errs)
s, err := syft.Catalog(src, catalogingOpts...)
if err != nil {
errs <- err
return
@@ -313,6 +295,7 @@ func packagesExecWorker(si source.Input, writer sbom.Writer) <-chan error {
if s == nil {
errs <- fmt.Errorf("no SBOM produced for %q", si.UserInput)
return
}
if appConfig.Anchore.Host != "" {
@@ -330,20 +313,10 @@ func packagesExecWorker(si source.Input, writer sbom.Writer) <-chan error {
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 {
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")
}
+16 -23
View File
@@ -2,6 +2,7 @@ package cmd
import (
"fmt"
"github.com/anchore/syft/syft"
"os"
"github.com/anchore/stereoscope"
@@ -10,8 +11,6 @@ import (
"github.com/anchore/syft/internal/formats/syftjson"
"github.com/anchore/syft/internal/log"
"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/sbom"
"github.com/anchore/syft/syft/source"
@@ -106,11 +105,13 @@ func powerUserExecWorker(userInput string, writer sbom.Writer) <-chan error {
go func() {
defer close(errs)
appConfig.Secrets.Cataloger.Enabled = true
appConfig.FileMetadata.Cataloger.Enabled = true
appConfig.FileContents.Cataloger.Enabled = true
appConfig.FileClassification.Cataloger.Enabled = true
tasks, err := tasks()
// TODO: replace
//appConfig.Secrets.Cataloger.Enabled = true
//appConfig.FileMetadata.Cataloger.Enabled = true
//appConfig.FileContents.Cataloger.Enabled = true
//appConfig.FileClassification.Cataloger.Enabled = true
catalogingOpts, err := appConfig.ToCatalogingOptions()
if err != nil {
errs <- err
return
@@ -131,28 +132,20 @@ func powerUserExecWorker(userInput string, writer sbom.Writer) <-chan error {
defer cleanup()
}
s := sbom.SBOM{
Source: src.Metadata,
Descriptor: sbom.Descriptor{
Name: internal.ApplicationName,
Version: version.FromBuild().Version,
Configuration: appConfig,
},
s, err := syft.Catalog(src, catalogingOpts...)
if err != nil {
errs <- err
return
}
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)
if s == nil {
errs <- fmt.Errorf("no SBOM produced for %q", si.UserInput)
return
}
s.Relationships = append(s.Relationships, mergeRelationships(relationships...)...)
bus.Publish(partybus.Event{
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
}
}
+1 -1
View File
@@ -171,7 +171,7 @@ require (
github.com/hashicorp/go-uuid v1.0.2 // indirect
github.com/hashicorp/go-version v1.4.0 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/hashicorp/vault/api v1.4.1 // indirect
github.com/hashicorp/vault/api v1.3.1 // indirect
github.com/hashicorp/vault/sdk v0.4.1 // indirect
github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 // indirect
github.com/imdario/mergo v0.3.12 // indirect
+1 -2
View File
@@ -1349,9 +1349,8 @@ github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/J
github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
github.com/hashicorp/vault/api v1.3.0/go.mod h1:EabNQLI0VWbWoGlA+oBLC8PXmR9D60aUVgQGvangFWQ=
github.com/hashicorp/vault/api v1.3.1 h1:pkDkcgTh47PRjY1NEFeofqR4W/HkNUi9qIakESO2aRM=
github.com/hashicorp/vault/api v1.3.1/go.mod h1:QeJoWxMFt+MsuWcYhmwRLwKEXrjwAFFywzhptMsTIUw=
github.com/hashicorp/vault/api v1.4.1 h1:mWLfPT0RhxBitjKr6swieCEP2v5pp/M//t70S3kMLRo=
github.com/hashicorp/vault/api v1.4.1/go.mod h1:LkMdrZnWNrFaQyYYazWVn7KshilfDidgVBq6YiTq/bM=
github.com/hashicorp/vault/sdk v0.3.0/go.mod h1:aZ3fNuL5VNydQk8GcLJ2TV8YCRVvyaakYkhZRoVuhj0=
github.com/hashicorp/vault/sdk v0.4.1 h1:3SaHOJY687jY1fnB61PtL0cOkKItphrbLmux7T92HBo=
github.com/hashicorp/vault/sdk v0.4.1/go.mod h1:aZ3fNuL5VNydQk8GcLJ2TV8YCRVvyaakYkhZRoVuhj0=
+5 -4
View File
@@ -6,14 +6,15 @@ import (
"fmt"
"time"
"github.com/anchore/client-go/pkg/external"
"github.com/anchore/stereoscope/pkg/image"
"github.com/anchore/syft/internal/bus"
"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-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 {
+11 -9
View File
@@ -4,6 +4,8 @@ import (
"context"
"encoding/json"
"fmt"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/file"
"net/http"
"strings"
"testing"
@@ -22,7 +24,7 @@ import (
"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 {
panic(e)
}
@@ -52,13 +54,13 @@ func (m *mockPackageSBOMImportAPI) ImportImagePackages(ctx context.Context, sess
func sbomFixture() sbom.SBOM {
return sbom.SBOM{
Artifacts: sbom.Artifacts{
PackageCatalog: pkg.NewCatalog(pkg.Package{
Packages: pkg.NewCollection(pkg.Package{
Name: "name",
Version: "version",
FoundBy: "foundBy",
Locations: []source.Location{
Locations: []file.Location{
{
Coordinates: source.Coordinates{
Coordinates: file.Coordinates{
RealPath: "path",
FileSystemID: "layerID",
},
@@ -67,8 +69,8 @@ func sbomFixture() sbom.SBOM {
Licenses: []string{"license"},
Language: pkg.Python,
Type: pkg.PythonPkg,
CPEs: []pkg.CPE{
must(pkg.NewCPE("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*")),
CPEs: []cpe.CPE{
must(cpe.New("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*")),
},
PURL: "purl",
MetadataType: pkg.PythonPackageMetadataType,
@@ -102,13 +104,13 @@ func sbomFixture() sbom.SBOM {
},
Relationships: []artifact.Relationship{
{
From: source.NewLocation("/place1"),
To: source.NewLocation("/place2"),
From: file.NewLocation("/place1"),
To: file.NewLocation("/place2"),
Type: artifact.ContainsRelationship,
},
},
Source: source.Metadata{
Scheme: source.ImageScheme,
Scheme: source.ImageType,
ImageMetadata: source.ImageMetadata{
UserInput: "user-in",
Layers: nil,
@@ -1,12 +1,14 @@
package file
package archive
import (
"errors"
"fmt"
"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
// protect against decompression bomb attacks.
@@ -1,38 +1,39 @@
package file
package archive
import (
"fmt"
"io/ioutil"
"path/filepath"
"github.com/anchore/syft/syft/file"
"github.com/bmatcuk/doublestar/v4"
"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.
func ExtractGlobsFromTarToUniqueTempFile(archivePath, dir string, globs ...string) (map[string]Opener, error) {
results := make(map[string]Opener)
func ExtractGlobsFromTarToUniqueTempFile(archivePath, dir string, globs ...string) (map[string]file.Opener, error) {
results := make(map[string]file.Opener)
// don't allow for full traversal, only select traversal from given paths
if len(globs) == 0 {
return results, nil
}
visitor := func(file archiver.File) error {
defer file.Close()
visitor := func(f archiver.File) error {
defer f.Close()
// ignore directories
if file.FileInfo.IsDir() {
if f.FileInfo.IsDir() {
return nil
}
// ignore any filename that doesn't match the given globs...
if !matchesAnyGlob(file.Name(), globs...) {
if !matchesAnyGlob(f.Name(), globs...) {
return nil
}
// 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)
if err != nil {
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.
defer tempFile.Close()
if err := safeCopy(tempFile, file.ReadCloser); err != nil {
return fmt.Errorf("unable to copy source=%q for tar=%q: %w", file.Name(), archivePath, err)
if err := safeCopy(tempFile, f.ReadCloser); err != nil {
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
}
@@ -1,4 +1,4 @@
package file
package archive
import (
"io/ioutil"
@@ -1,4 +1,4 @@
package file
package archive
import (
"fmt"
@@ -48,7 +48,7 @@ func (z ZipFileManifest) GlobMatch(patterns ...string) []string {
// so that glob logic is consistent inside and outside of ZIP archives
normalizedEntry := normalizeZipEntryName(entry)
if GlobMatch(pattern, normalizedEntry) {
if internal.FileNameGlobMatch(pattern, normalizedEntry) {
uniqueMatches.Add(entry)
}
}
@@ -1,7 +1,7 @@
//go:build !windows
// +build !windows
package file
package archive
import (
"encoding/json"
@@ -1,4 +1,4 @@
package file
package archive
import (
"archive/zip"
@@ -9,15 +9,9 @@ import (
"path/filepath"
"strings"
"github.com/anchore/syft/internal/log"
)
"github.com/anchore/syft/syft/file"
const (
// represents the order of bytes
_ = iota
KB = 1 << (10 * iota)
MB
GB
"github.com/anchore/syft/internal/log"
)
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.
func ExtractFromZipToUniqueTempFile(archivePath, dir string, paths ...string) (map[string]Opener, error) {
results := make(map[string]Opener)
func ExtractFromZipToUniqueTempFile(archivePath, dir string, paths ...string) (map[string]file.Opener, error) {
results := make(map[string]file.Opener)
// don't allow for full traversal, only select traversal from given paths
if len(paths) == 0 {
return results, nil
}
visitor := func(file *zip.File) error {
tempfilePrefix := filepath.Base(filepath.Clean(file.Name)) + "-"
visitor := func(f *zip.File) error {
tempfilePrefix := filepath.Base(filepath.Clean(f.Name)) + "-"
tempFile, err := ioutil.TempFile(dir, tempfilePrefix)
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.
defer tempFile.Close()
zippedFile, err := file.Open()
zippedFile, err := f.Open()
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() {
err := zippedFile.Close()
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() {
return fmt.Errorf("unable to extract directories, only files: %s", file.Name)
if f.FileInfo().IsDir() {
return fmt.Errorf("unable to extract directories, only files: %s", f.Name)
}
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
}
@@ -1,7 +1,7 @@
//go:build !windows
// +build !windows
package file
package archive
import (
"crypto/sha256"
@@ -1,4 +1,4 @@
package file
package archive
import (
"archive/zip"
@@ -1,7 +1,7 @@
//go:build !windows
// +build !windows
package file
package archive
import (
"os"
+74 -18
View File
@@ -3,12 +3,16 @@ package config
import (
"errors"
"fmt"
"github.com/anchore/syft/syft/source"
"path"
"reflect"
"strings"
"github.com/adrg/xdg"
"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/sirupsen/logrus"
"github.com/spf13/viper"
@@ -17,6 +21,7 @@ import (
var ErrApplicationConfigNotFound = fmt.Errorf("application config not found")
// TODO: set all catalogers when this is set
var catalogerEnabledDefault = false
type defaultValueLoader interface {
@@ -29,24 +34,66 @@ type parser interface {
// Application is the main syft application configuration.
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)
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
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
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)
Dev development `yaml:"dev" json:"dev" mapstructure:"dev"`
Log logging `yaml:"log" json:"log" mapstructure:"log"` // all logging-related options
Package pkg `yaml:"package" json:"package" mapstructure:"package"`
FileMetadata FileMetadata `yaml:"file-metadata" json:"file-metadata" mapstructure:"file-metadata"`
FileClassification fileClassification `yaml:"file-classification" json:"file-classification" mapstructure:"file-classification"`
FileContents fileContents `yaml:"file-contents" json:"file-contents" mapstructure:"file-contents"`
Secrets secrets `yaml:"secrets" json:"secrets" mapstructure:"secrets"`
Registry registry `yaml:"registry" json:"registry" mapstructure:"registry"`
Exclusions []string `yaml:"exclude" json:"exclude" mapstructure:"exclude"`
Attest attest `yaml:"attest" json:"attest" mapstructure:"attest"`
Platform string `yaml:"platform" json:"platform" mapstructure:"platform"`
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
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)
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
CliOptions CliOnlyOptions `yaml:"-" json:"-"` // all options only available through the CLI (not via env vars or config)
Catalogers []string `yaml:"catalogers" json:"catalogers" mapstructure:"catalogers"`
Scope string `yaml:"scope" json:"scope" mapstructure:"scope"`
Dev development `yaml:"dev" json:"dev" mapstructure:"dev"`
Log logging `yaml:"log" json:"log" mapstructure:"log"` // all logging-related options
Package pkg `yaml:"package" json:"package" mapstructure:"package"`
FileMetadata fileMetadata `yaml:"file-metadata" json:"file-metadata" mapstructure:"file-metadata"`
FileContents fileContents `yaml:"file-contents" json:"file-contents" mapstructure:"file-contents"`
Secrets secretsCfg `yaml:"secrets" json:"secrets" mapstructure:"secrets"`
Registry registry `yaml:"registry" json:"registry" mapstructure:"registry"`
Exclusions []string `yaml:"exclude" json:"exclude" mapstructure:"exclude"`
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
@@ -100,6 +147,15 @@ func (cfg Application) loadDefaultValues(v *viper.Viper) {
}
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
for _, optionFn := range []func() error{
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
import (
"github.com/anchore/syft/internal/file"
"github.com/anchore/syft/syft/source"
"github.com/anchore/syft/syft/cataloger/files/filecontents"
"github.com/anchore/syft/syft/file"
"github.com/spf13/viper"
)
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"`
Globs []string `yaml:"globs" json:"globs" mapstructure:"globs"`
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"`
}
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.globs", []string{})
}
func (cfg *fileContents) parseConfigValues() error {
return cfg.Cataloger.parseConfigValues()
func (cfg fileContents) ToConfig() filecontents.Config {
return filecontents.Config{
Globs: cfg.Globs,
SkipFilesAboveSizeInBytes: cfg.SkipFilesAboveSize,
}
}
+5 -9
View File
@@ -1,21 +1,17 @@
package config
import (
"github.com/anchore/syft/syft/source"
"github.com/spf13/viper"
)
type FileMetadata struct {
Cataloger catalogerOptions `yaml:"cataloger" json:"cataloger" mapstructure:"cataloger"`
Digests []string `yaml:"digests" json:"digests" mapstructure:"digests"`
type fileMetadata struct {
Digests []string `yaml:"digests" json:"digests" mapstructure:"digests"`
}
func (cfg FileMetadata) loadDefaultValues(v *viper.Viper) {
v.SetDefault("file-metadata.cataloger.enabled", catalogerEnabledDefault)
v.SetDefault("file-metadata.cataloger.scope", source.SquashedScope)
func (cfg fileMetadata) loadDefaultValues(v *viper.Viper) {
v.SetDefault("file-metadata.digests", []string{"sha256"})
}
func (cfg *FileMetadata) parseConfigValues() error {
return cfg.Cataloger.parseConfigValues()
func (cfg *fileMetadata) parseConfigValues() error {
return nil
}
+8 -17
View File
@@ -1,33 +1,24 @@
package config
import (
"github.com/anchore/syft/syft/pkg/cataloger"
"github.com/anchore/syft/syft/cataloger/packages"
"github.com/spf13/viper"
)
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"`
SearchIndexedArchives bool `yaml:"search-indexed-archives" json:"search-indexed-archives" mapstructure:"search-indexed-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"`
}
func (cfg pkg) loadDefaultValues(v *viper.Viper) {
cfg.Cataloger.loadDefaultValues(v)
c := cataloger.DefaultSearchConfig()
c := packages.DefaultSearchConfig()
v.SetDefault("package.search-unindexed-archives", c.IncludeUnindexedArchives)
v.SetDefault("package.search-indexed-archives", c.IncludeIndexedArchives)
}
func (cfg *pkg) parseConfigValues() error {
return cfg.Cataloger.parseConfigValues()
}
func (cfg pkg) ToConfig() cataloger.Config {
return cataloger.Config{
Search: cataloger.SearchConfig{
IncludeIndexedArchives: cfg.SearchIndexedArchives,
IncludeUnindexedArchives: cfg.SearchUnindexedArchives,
Scope: cfg.Cataloger.ScopeOpt,
},
func (cfg pkg) ToConfig() packages.SearchConfig {
return packages.SearchConfig{
IncludeIndexedArchives: cfg.SearchIndexedArchives,
IncludeUnindexedArchives: cfg.SearchUnindexedArchives,
}
}
+18 -8
View File
@@ -1,28 +1,38 @@
package config
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/spf13/viper"
)
type secrets struct {
Cataloger catalogerOptions `yaml:"cataloger" json:"cataloger" mapstructure:"cataloger"`
type secretsCfg struct {
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"`
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"`
Scope string `yaml:"scope" json:"scope" mapstructure:"scope"`
}
func (cfg secrets) loadDefaultValues(v *viper.Viper) {
v.SetDefault("secrets.cataloger.enabled", catalogerEnabledDefault)
v.SetDefault("secrets.cataloger.scope", source.AllLayersScope)
func (cfg secretsCfg) loadDefaultValues(v *viper.Viper) {
v.SetDefault("secrets.scope", source.AllLayersScope)
v.SetDefault("secrets.reveal-values", false)
v.SetDefault("secrets.skip-files-above-size", 1*file.MB)
v.SetDefault("secrets.additional-patterns", map[string]string{})
v.SetDefault("secrets.exclude-pattern-names", []string{})
}
func (cfg *secrets) parseConfigValues() error {
return cfg.Cataloger.parseConfigValues()
func (cfg secretsCfg) ToConfig() (*secrets.Config, error) {
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 -1
View File
@@ -6,5 +6,5 @@ const (
// JSONSchemaVersion is the current schema version output by the JSON encoder
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
JSONSchemaVersion = "3.1.0"
JSONSchemaVersion = "3.0.1"
)
@@ -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
func GlobMatch(pattern, name string) bool {
func FileNameGlobMatch(pattern, name string) bool {
px := 0
nx := 0
nextPx := 0
@@ -1,4 +1,4 @@
package file
package internal
import (
"strings"
@@ -32,7 +32,7 @@ func TestGlobMatch(t *testing.T) {
}
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)
}
}
@@ -3,11 +3,12 @@ package cyclonedxhelpers
import (
"reflect"
"github.com/anchore/syft/syft/file"
"github.com/CycloneDX/cyclonedx-go"
"github.com/anchore/syft/internal/formats/common"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
)
func encodeComponent(p pkg.Package) cyclonedx.Component {
@@ -71,9 +72,9 @@ func decodeComponent(c *cyclonedx.Component) *pkg.Package {
return p
}
func decodeLocations(vals map[string]string) []source.Location {
v := common.Decode(reflect.TypeOf([]source.Location{}), vals, "syft:location", CycloneDXFields)
out, _ := v.([]source.Location)
func decodeLocations(vals map[string]string) []file.Location {
v := common.Decode(reflect.TypeOf([]file.Location{}), vals, "syft:location", CycloneDXFields)
out, _ := v.([]file.Location)
return out
}
@@ -1,13 +1,13 @@
package cyclonedxhelpers
import (
"github.com/anchore/syft/syft/file"
"testing"
"github.com/CycloneDX/cyclonedx-go"
"github.com/stretchr/testify/assert"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
)
func Test_encodeComponentProperties(t *testing.T) {
@@ -26,8 +26,8 @@ func Test_encodeComponentProperties(t *testing.T) {
name: "from apk",
input: pkg.Package{
FoundBy: "cataloger",
Locations: []source.Location{
{Coordinates: source.Coordinates{RealPath: "test"}},
Locations: []file.Location{
{Coordinates: file.Coordinates{RealPath: "test"}},
},
Metadata: pkg.ApkMetadata{
Package: "libc-utils",
@@ -2,6 +2,7 @@ package cyclonedxhelpers
import (
"github.com/CycloneDX/cyclonedx-go"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/internal/log"
"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
// we can extract the first CPE as the one to output in cyclonedx
if len(p.CPEs) > 0 {
return pkg.CPEString(p.CPEs[0])
return cpe.String(p.CPEs[0])
}
return ""
}
@@ -24,15 +25,15 @@ func encodeCPEs(p pkg.Package) (out []cyclonedx.Property) {
}
out = append(out, cyclonedx.Property{
Name: "syft:cpe23",
Value: pkg.CPEString(c),
Value: cpe.String(c),
})
}
return
}
func decodeCPEs(c *cyclonedx.Component) (out []pkg.CPE) {
func decodeCPEs(c *cyclonedx.Component) (out []cpe.CPE) {
if c.CPE != "" {
cp, err := pkg.NewCPE(c.CPE)
cp, err := cpe.New(c.CPE)
if err != nil {
log.Warnf("invalid CPE: %s", c.CPE)
} else {
@@ -43,7 +44,7 @@ func decodeCPEs(c *cyclonedx.Component) (out []pkg.CPE) {
if c.Properties != nil {
for _, p := range *c.Properties {
if p.Name == "syft:cpe23" {
cp, err := pkg.NewCPE(p.Value)
cp, err := cpe.New(p.Value)
if err != nil {
log.Warnf("invalid CPE: %s", p.Value)
} else {
@@ -1,6 +1,7 @@
package cyclonedxhelpers
import (
"github.com/anchore/syft/syft/cpe"
"testing"
"github.com/stretchr/testify/assert"
@@ -9,8 +10,8 @@ import (
)
func Test_encodeCPE(t *testing.T) {
testCPE := pkg.MustCPE("cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*")
testCPE2 := pkg.MustCPE("cpe:2.3:a:name:name2:3.2:*:*:*:*:*:*:*")
testCPE := cpe.Must("cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*")
testCPE2 := cpe.Must("cpe:2.3:a:name:name2:3.2:*:*:*:*:*:*:*")
tests := []struct {
name string
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
name: "no metadata",
input: pkg.Package{
CPEs: []pkg.CPE{},
CPEs: []cpe.CPE{},
},
expected: "",
},
{
name: "single CPE",
input: pkg.Package{
CPEs: []pkg.CPE{
CPEs: []cpe.CPE{
testCPE,
},
},
@@ -36,7 +37,7 @@ func Test_encodeCPE(t *testing.T) {
{
name: "multiple CPEs",
input: pkg.Package{
CPEs: []pkg.CPE{
CPEs: []cpe.CPE{
testCPE2,
testCPE,
},
@@ -49,7 +49,7 @@ func toSyftModel(bom *cyclonedx.BOM) (*sbom.SBOM, error) {
}
s := &sbom.SBOM{
Artifacts: sbom.Artifacts{
PackageCatalog: pkg.NewCatalog(),
Packages: pkg.NewCollection(),
LinuxDistribution: linuxReleaseFromComponents(*bom.Components),
},
Source: meta,
@@ -86,7 +86,7 @@ func collectPackages(component *cyclonedx.Component, s *sbom.SBOM, idMap map[str
idMap[component.BOMRef] = p
// TODO there must be a better way than needing to call this manually:
p.SetID()
s.Artifacts.PackageCatalog.Add(*p)
s.Artifacts.Packages.Add(*p)
}
if component.Components != nil {
@@ -199,7 +199,7 @@ func decodeMetadata(component *cyclonedx.Component) source.Metadata {
switch component.Type {
case cyclonedx.ComponentTypeContainer:
return source.Metadata{
Scheme: source.ImageScheme,
Scheme: source.ImageType,
ImageMetadata: source.ImageMetadata{
UserInput: component.Name,
ID: component.BOMRef,
@@ -208,7 +208,7 @@ func decodeMetadata(component *cyclonedx.Component) source.Metadata {
}
case cyclonedx.ComponentTypeFile:
return source.Metadata{
Scheme: source.FileScheme, // or source.DirectoryScheme
Scheme: source.FileType, // or source.DirectoryType
Path: component.Name,
ImageMetadata: source.ImageMetadata{
UserInput: component.Name,
@@ -212,7 +212,7 @@ func Test_decode(t *testing.T) {
assert.Equal(t, e.ver, sbom.Artifacts.LinuxDistribution.VersionID)
}
if e.pkg != "" {
for p := range sbom.Artifacts.PackageCatalog.Enumerate() {
for p := range sbom.Artifacts.Packages.Enumerate() {
if e.pkg != p.Name {
continue
}
@@ -240,7 +240,7 @@ func Test_decode(t *testing.T) {
if e.relation != "" {
foundRelation := false
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 {
foundRelation = true
break
@@ -25,7 +25,7 @@ func ToFormatModel(s sbom.SBOM) *cyclonedx.BOM {
cdxBOM.SerialNumber = uuid.New().URN()
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))
for i, p := range packages {
components[i] = encodeComponent(p)
@@ -155,7 +155,7 @@ func toDependencies(relationships []artifact.Relationship) []cyclonedx.Dependenc
func toBomDescriptorComponent(srcMetadata source.Metadata) *cyclonedx.Component {
switch srcMetadata.Scheme {
case source.ImageScheme:
case source.ImageType:
bomRef, err := artifact.IDByHash(srcMetadata.ImageMetadata.ID)
if err != nil {
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,
Version: srcMetadata.ImageMetadata.ManifestDigest,
}
case source.DirectoryScheme, source.FileScheme:
case source.DirectoryType, source.FileType:
bomRef, err := artifact.IDByHash(srcMetadata.Path)
if err != nil {
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) {
switch srcMetadata.Scheme {
case source.ImageScheme:
case source.ImageType:
return cleanName(srcMetadata.ImageMetadata.UserInput), nil
case source.DirectoryScheme, source.FileScheme:
case source.DirectoryType, source.FileType:
return cleanName(srcMetadata.Path), nil
}
@@ -14,7 +14,7 @@ import (
func Test_DocumentName(t *testing.T) {
allSchemes := strset.New()
for _, s := range source.AllSchemes {
for _, s := range source.AllTypes {
allSchemes.Add(string(s))
}
testedSchemes := strset.New()
@@ -29,7 +29,7 @@ func Test_DocumentName(t *testing.T) {
name: "image",
inputName: "my-name",
srcMetadata: source.Metadata{
Scheme: source.ImageScheme,
Scheme: source.ImageType,
ImageMetadata: source.ImageMetadata{
UserInput: "image-repo/name:tag",
ID: "id",
@@ -42,7 +42,7 @@ func Test_DocumentName(t *testing.T) {
name: "directory",
inputName: "my-name",
srcMetadata: source.Metadata{
Scheme: source.DirectoryScheme,
Scheme: source.DirectoryType,
Path: "some/path/to/place",
},
expected: "some/path/to/place",
@@ -51,7 +51,7 @@ func Test_DocumentName(t *testing.T) {
name: "file",
inputName: "my-name",
srcMetadata: source.Metadata{
Scheme: source.FileScheme,
Scheme: source.FileType,
Path: "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 {
input := "unknown-source-type"
switch srcMetadata.Scheme {
case source.ImageScheme:
case source.ImageType:
input = "image"
case source.DirectoryScheme:
case source.DirectoryType:
input = "dir"
case source.FileScheme:
case source.FileType:
input = "file"
}
@@ -12,7 +12,7 @@ import (
func Test_documentNamespace(t *testing.T) {
allSchemes := strset.New()
for _, s := range source.AllSchemes {
for _, s := range source.AllTypes {
allSchemes.Add(string(s))
}
testedSchemes := strset.New()
@@ -27,7 +27,7 @@ func Test_documentNamespace(t *testing.T) {
name: "image",
inputName: "my-name",
srcMetadata: source.Metadata{
Scheme: source.ImageScheme,
Scheme: source.ImageType,
ImageMetadata: source.ImageMetadata{
UserInput: "image-repo/name:tag",
ID: "id",
@@ -40,7 +40,7 @@ func Test_documentNamespace(t *testing.T) {
name: "directory",
inputName: "my-name",
srcMetadata: source.Metadata{
Scheme: source.DirectoryScheme,
Scheme: source.DirectoryType,
Path: "some/path/to/place",
},
expected: "https://anchore.com/syft/dir/my-name-",
@@ -49,7 +49,7 @@ func Test_documentNamespace(t *testing.T) {
name: "file",
inputName: "my-name",
srcMetadata: source.Metadata{
Scheme: source.FileScheme,
Scheme: source.FileType,
Path: "some/path/to/place",
},
expected: "https://anchore.com/syft/file/my-name-",
@@ -1,6 +1,7 @@
package spdxhelpers
import (
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/pkg"
)
@@ -10,7 +11,7 @@ func ExternalRefs(p pkg.Package) (externalRefs []ExternalRef) {
for _, c := range p.CPEs {
externalRefs = append(externalRefs, ExternalRef{
ReferenceCategory: SecurityReferenceCategory,
ReferenceLocator: pkg.CPEString(c),
ReferenceLocator: cpe.String(c),
ReferenceType: Cpe23ExternalRefType,
})
}
@@ -1,6 +1,7 @@
package spdxhelpers
import (
"github.com/anchore/syft/syft/cpe"
"testing"
"github.com/anchore/syft/syft/pkg"
@@ -8,7 +9,7 @@ import (
)
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 {
name string
input pkg.Package
@@ -17,7 +18,7 @@ func Test_ExternalRefs(t *testing.T) {
{
name: "cpe + purl",
input: pkg.Package{
CPEs: []pkg.CPE{
CPEs: []cpe.CPE{
testCPE,
},
PURL: "a-purl",
@@ -25,7 +26,7 @@ func Test_ExternalRefs(t *testing.T) {
expected: []ExternalRef{
{
ReferenceCategory: SecurityReferenceCategory,
ReferenceLocator: pkg.CPEString(testCPE),
ReferenceLocator: cpe.String(testCPE),
ReferenceType: Cpe23ExternalRefType,
},
{
@@ -1,10 +1,10 @@
package spdxhelpers
import (
"github.com/anchore/syft/syft/file"
"testing"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
"github.com/stretchr/testify/assert"
)
@@ -18,9 +18,9 @@ func Test_SourceInfo(t *testing.T) {
name: "locations are captured",
input: pkg.Package{
// note: no type given
Locations: []source.Location{
source.NewVirtualLocation("/a-place", "/b-place"),
source.NewVirtualLocation("/c-place", "/d-place"),
Locations: []file.Location{
file.NewVirtualLocation("/a-place", "/b-place"),
file.NewVirtualLocation("/c-place", "/d-place"),
},
},
expected: []string{
@@ -1,6 +1,7 @@
package spdxhelpers
import (
"github.com/anchore/syft/syft/cpe"
"strconv"
"strings"
@@ -13,7 +14,6 @@ import (
"github.com/anchore/syft/syft/linux"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/sbom"
"github.com/anchore/syft/syft/source"
)
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{
Artifacts: sbom.Artifacts{
PackageCatalog: pkg.NewCatalog(),
FileMetadata: map[source.Coordinates]source.FileMetadata{},
FileDigests: map[source.Coordinates][]file.Digest{},
Packages: pkg.NewCollection(),
FileMetadata: map[file.Coordinates]file.Metadata{},
FileDigests: map[file.Coordinates][]file.Digest{},
LinuxDistribution: findLinuxReleaseByPURL(doc),
},
}
@@ -74,7 +74,7 @@ func collectSyftPackages(s *sbom.SBOM, spdxIDMap map[string]interface{}, doc *sp
for _, p := range doc.Packages {
syftPkg := toSyftPackage(p)
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
}
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
for _, typ := range f.FileType {
switch FileType(typ) {
@@ -132,7 +132,7 @@ func toSyftRelationships(spdxIDMap map[string]interface{}, doc *spdx.Document2_2
b := spdxIDMap[string(r.RefB.ElementRefID)]
from, fromOk := a.(*pkg.Package)
toPackage, toPackageOk := b.(*pkg.Package)
toLocation, toLocationOk := b.(*source.Location)
toLocation, toLocationOk := b.(*file.Location)
if !fromOk || !(toPackageOk || toLocationOk) {
log.Debugf("unable to find valid relationship mapping from SPDX 2.2 JSON, ignoring: (from: %+v) (to: %+v)", a, b)
continue
@@ -174,7 +174,7 @@ func toSyftRelationships(spdxIDMap map[string]interface{}, doc *spdx.Document2_2
return out
}
func toSyftCoordinates(f *spdx.File2_2) source.Coordinates {
func toSyftCoordinates(f *spdx.File2_2) file.Coordinates {
const layerIDPrefix = "layerID: "
var fileSystemID string
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 {
fileSystemID = strings.TrimPrefix(string(f.FileSPDXIdentifier), layerIDPrefix)
}
return source.Coordinates{
return file.Coordinates{
RealPath: f.FileName,
FileSystemID: fileSystemID,
}
}
func toSyftLocation(f *spdx.File2_2) *source.Location {
return &source.Location{
func toSyftLocation(f *spdx.File2_2) *file.Location {
return &file.Location{
Coordinates: toSyftCoordinates(f),
VirtualPath: f.FileName,
AccessPath: f.FileName,
}
}
@@ -317,10 +317,10 @@ func findPURLValue(p *spdx.Package2_2) string {
return ""
}
func extractCPEs(p *spdx.Package2_2) (cpes []pkg.CPE) {
func extractCPEs(p *spdx.Package2_2) (cpes []cpe.CPE) {
for _, r := range p.PackageExternalReferences {
if r.RefType == string(Cpe23ExternalRefType) {
cpe, err := pkg.NewCPE(r.Locator)
cpe, err := cpe.New(r.Locator)
if err != nil {
log.Warnf("unable to extract SPDX CPE=%q: %+v", r.Locator, err)
continue
@@ -94,7 +94,7 @@ func TestToSyftModel(t *testing.T) {
assert.NotNil(t, sbom)
pkgs := sbom.Artifacts.PackageCatalog.Sorted()
pkgs := sbom.Artifacts.Packages.Sorted()
assert.Len(t, pkgs, 2)
+25 -22
View File
@@ -2,9 +2,12 @@ package testutils
import (
"bytes"
"github.com/anchore/syft/syft/cpe"
"strings"
"testing"
"github.com/anchore/syft/syft/file"
"github.com/anchore/go-testutils"
"github.com/anchore/stereoscope/pkg/filetree"
"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 {
t.Helper()
catalog := pkg.NewCatalog()
catalog := pkg.NewCollection()
var cfg imageCfg
var img *image.Image
for _, opt := range options {
@@ -121,7 +124,7 @@ func ImageInput(t testing.TB, testImage string, options ...ImageOption) sbom.SBO
return sbom.SBOM{
Artifacts: sbom.Artifacts{
PackageCatalog: catalog,
Packages: catalog,
LinuxDistribution: &linux.Release{
PrettyName: "debian",
Name: "debian",
@@ -149,7 +152,7 @@ func carriageRedactor(s []byte) []byte {
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)
_, 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{
Name: "package-1",
Version: "1.0.1",
Locations: []source.Location{
source.NewLocationFromImage(string(ref1.RealPath), *ref1, img),
Locations: []file.Location{
file.NewLocationFromImage(string(ref1.RealPath), *ref1, img),
},
Type: pkg.PythonPkg,
FoundBy: "the-cataloger-1",
@@ -170,15 +173,15 @@ func populateImageCatalog(catalog *pkg.Catalog, img *image.Image) {
Version: "1.0.1",
},
PURL: "a-purl-1",
CPEs: []pkg.CPE{
pkg.MustCPE("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"),
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"),
},
})
catalog.Add(pkg.Package{
Name: "package-2",
Version: "2.0.1",
Locations: []source.Location{
source.NewLocationFromImage(string(ref2.RealPath), *ref2, img),
Locations: []file.Location{
file.NewLocationFromImage(string(ref2.RealPath), *ref2, img),
},
Type: pkg.DebPkg,
FoundBy: "the-cataloger-2",
@@ -188,8 +191,8 @@ func populateImageCatalog(catalog *pkg.Catalog, img *image.Image) {
Version: "2.0.1",
},
PURL: "a-purl-2",
CPEs: []pkg.CPE{
pkg.MustCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
},
})
}
@@ -202,7 +205,7 @@ func DirectoryInput(t testing.TB) sbom.SBOM {
return sbom.SBOM{
Artifacts: sbom.Artifacts{
PackageCatalog: catalog,
Packages: catalog,
LinuxDistribution: &linux.Release{
PrettyName: "debian",
Name: "debian",
@@ -225,8 +228,8 @@ func DirectoryInput(t testing.TB) sbom.SBOM {
}
}
func newDirectoryCatalog() *pkg.Catalog {
catalog := pkg.NewCatalog()
func newDirectoryCatalog() pkg.Collection {
catalog := pkg.NewCollection()
// populate catalog with test data
catalog.Add(pkg.Package{
@@ -234,8 +237,8 @@ func newDirectoryCatalog() *pkg.Catalog {
Version: "1.0.1",
Type: pkg.PythonPkg,
FoundBy: "the-cataloger-1",
Locations: []source.Location{
source.NewLocation("/some/path/pkg1"),
Locations: []file.Location{
file.NewLocation("/some/path/pkg1"),
},
Language: pkg.Python,
MetadataType: pkg.PythonPackageMetadataType,
@@ -250,8 +253,8 @@ func newDirectoryCatalog() *pkg.Catalog {
},
},
PURL: "a-purl-2",
CPEs: []pkg.CPE{
pkg.MustCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
},
})
catalog.Add(pkg.Package{
@@ -259,8 +262,8 @@ func newDirectoryCatalog() *pkg.Catalog {
Version: "2.0.1",
Type: pkg.DebPkg,
FoundBy: "the-cataloger-2",
Locations: []source.Location{
source.NewLocation("/some/path/pkg1"),
Locations: []file.Location{
file.NewLocation("/some/path/pkg1"),
},
MetadataType: pkg.DpkgMetadataType,
Metadata: pkg.DpkgMetadata{
@@ -268,8 +271,8 @@ func newDirectoryCatalog() *pkg.Catalog {
Version: "2.0.1",
},
PURL: "a-purl-2",
CPEs: []pkg.CPE{
pkg.MustCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
},
})
@@ -57,7 +57,7 @@ func Test_decodeJSON(t *testing.T) {
split = strings.SplitN(pkg, ":", 2)
name = split[0]
version = split[1]
for p := range bom.Artifacts.PackageCatalog.Enumerate() {
for p := range bom.Artifacts.Packages.Enumerate() {
if p.Name == name {
assert.Equal(t, version, p.Version)
continue pkgs
@@ -57,7 +57,7 @@ func Test_decodeXML(t *testing.T) {
split = strings.SplitN(pkg, ":", 2)
name = split[0]
version = split[1]
for p := range bom.Artifacts.PackageCatalog.Enumerate() {
for p := range bom.Artifacts.Packages.Enumerate() {
if p.Name == name {
assert.Equal(t, version, p.Version)
continue pkgs
-183
View File
@@ -1,183 +0,0 @@
package github
import (
"fmt"
"strings"
"time"
"github.com/mholt/archiver/v3"
"github.com/anchore/packageurl-go"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/version"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/sbom"
"github.com/anchore/syft/syft/source"
)
// toGithubModel converts the provided SBOM to a GitHub dependency model
func toGithubModel(s *sbom.SBOM) DependencySnapshot {
scanTime := time.Now().Format(time.RFC3339) // TODO is there a record of this somewhere?
v := version.FromBuild().Version
if v == "[not provided]" {
v = "0.0.0-dev"
}
return DependencySnapshot{
Version: 0,
// TODO allow property input to specify the Job, Sha, and Ref
Detector: DetectorMetadata{
Name: internal.ApplicationName,
URL: "https://github.com/anchore/syft",
Version: v,
},
Metadata: toSnapshotMetadata(s),
Manifests: toGithubManifests(s),
Scanned: scanTime,
}
}
// toSnapshotMetadata captures the linux distribution information and other metadata
func toSnapshotMetadata(s *sbom.SBOM) Metadata {
out := Metadata{}
if s.Artifacts.LinuxDistribution != nil {
d := s.Artifacts.LinuxDistribution
qualifiers := packageurl.Qualifiers{}
if len(d.IDLike) > 0 {
qualifiers = append(qualifiers, packageurl.Qualifier{
Key: "like",
Value: strings.Join(d.IDLike, ","),
})
}
purl := packageurl.NewPackageURL("generic", "", d.ID, d.VersionID, qualifiers, "")
out["syft:distro"] = purl.ToString()
}
return out
}
func filesystem(p pkg.Package) string {
if len(p.Locations) > 0 {
return p.Locations[0].FileSystemID
}
return ""
}
// isArchive returns true if the path appears to be an archive
func isArchive(path string) bool {
_, err := archiver.ByExtension(path)
return err == nil
}
// toPath Generates a string representation of the package location, optionally including the layer hash
func toPath(s source.Metadata, p pkg.Package) string {
inputPath := strings.TrimPrefix(s.Path, "./")
if inputPath == "." {
inputPath = ""
}
if len(p.Locations) > 0 {
location := p.Locations[0]
packagePath := location.RealPath
if location.VirtualPath != "" {
packagePath = location.VirtualPath
}
packagePath = strings.TrimPrefix(packagePath, "/")
switch s.Scheme {
case source.ImageScheme:
image := strings.ReplaceAll(s.ImageMetadata.UserInput, ":/", "//")
return fmt.Sprintf("%s:/%s", image, packagePath)
case source.FileScheme:
if isArchive(inputPath) {
return fmt.Sprintf("%s:/%s", inputPath, packagePath)
}
return inputPath
case source.DirectoryScheme:
if inputPath != "" {
return fmt.Sprintf("%s/%s", inputPath, packagePath)
}
return packagePath
}
}
return fmt.Sprintf("%s%s", inputPath, s.ImageMetadata.UserInput)
}
// toGithubManifests manifests, each of which represents a specific location that has dependencies
func toGithubManifests(s *sbom.SBOM) Manifests {
manifests := map[string]*Manifest{}
for _, p := range s.Artifacts.PackageCatalog.Sorted() {
path := toPath(s.Source, p)
manifest, ok := manifests[path]
if !ok {
manifest = &Manifest{
Name: path,
File: FileInfo{
SourceLocation: path,
},
Resolved: DependencyGraph{},
}
fs := filesystem(p)
if fs != "" {
manifest.Metadata = Metadata{
"syft:filesystem": fs,
}
}
manifests[path] = manifest
}
name := dependencyName(p)
manifest.Resolved[name] = DependencyNode{
Purl: p.PURL,
Metadata: toDependencyMetadata(p),
Relationship: toDependencyRelationshipType(p),
Scope: toDependencyScope(p),
Dependencies: toDependencies(s, p),
}
}
out := Manifests{}
for k, v := range manifests {
out[k] = *v
}
return out
}
// dependencyName to make things a little nicer to read; this might end up being lossy
func dependencyName(p pkg.Package) string {
purl, err := packageurl.FromString(p.PURL)
if err != nil {
log.Warnf("Invalid PURL for package: '%s' PURL: '%s' (%w)", p.Name, p.PURL, err)
return ""
}
// don't use qualifiers for this
purl.Qualifiers = nil
return purl.ToString()
}
func toDependencyScope(_ pkg.Package) DependencyScope {
return DependencyScopeRuntime
}
func toDependencyRelationshipType(_ pkg.Package) DependencyRelationship {
return DependencyRelationshipDirect
}
func toDependencyMetadata(_ pkg.Package) Metadata {
// We have limited properties: up to 8 with reasonably small values
// For now, we are encoding the location as part of the key, we are encoding PURLs with most
// of the other information Grype might need; and the distro information at the top level
// so we don't need anything here yet
return Metadata{}
}
func toDependencies(s *sbom.SBOM, p pkg.Package) (out []string) {
for _, r := range s.Relationships {
if r.From.ID() == p.ID() {
if p, ok := r.To.(pkg.Package); ok {
out = append(out, dependencyName(p))
}
}
}
return
}
-161
View File
@@ -1,161 +0,0 @@
package github
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"github.com/anchore/packageurl-go"
"github.com/anchore/syft/syft/linux"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/sbom"
"github.com/anchore/syft/syft/source"
)
func Test_toGithubModel(t *testing.T) {
s := sbom.SBOM{
Source: source.Metadata{
Scheme: source.ImageScheme,
ImageMetadata: source.ImageMetadata{
UserInput: "ubuntu:18.04",
Architecture: "amd64",
},
},
Artifacts: sbom.Artifacts{
LinuxDistribution: &linux.Release{
ID: "ubuntu",
VersionID: "18.04",
IDLike: []string{"debian"},
},
PackageCatalog: pkg.NewCatalog(),
},
}
for _, p := range []pkg.Package{
{
Name: "pkg-1",
Version: "1.0.1",
Locations: []source.Location{{
Coordinates: source.Coordinates{
RealPath: "/usr/lib",
FileSystemID: "fsid-1",
},
}},
},
{
Name: "pkg-2",
Version: "2.0.2",
Locations: []source.Location{{
Coordinates: source.Coordinates{
RealPath: "/usr/lib",
FileSystemID: "fsid-1",
},
}},
},
{
Name: "pkg-3",
Version: "3.0.3",
Locations: []source.Location{{
Coordinates: source.Coordinates{
RealPath: "/etc",
FileSystemID: "fsid-1",
},
}},
},
} {
p.PURL = packageurl.NewPackageURL(
"generic",
"",
p.Name,
p.Version,
nil,
"",
).ToString()
s.Artifacts.PackageCatalog.Add(p)
}
actual := toGithubModel(&s)
expected := DependencySnapshot{
Version: 0,
Detector: DetectorMetadata{
Name: "syft",
Version: "0.0.0-dev",
URL: "https://github.com/anchore/syft",
},
Metadata: Metadata{
"syft:distro": "pkg:generic/ubuntu@18.04?like=debian",
},
Scanned: actual.Scanned,
Manifests: Manifests{
"ubuntu:18.04:/usr/lib": Manifest{
Name: "ubuntu:18.04:/usr/lib",
File: FileInfo{
SourceLocation: "ubuntu:18.04:/usr/lib",
},
Metadata: Metadata{
"syft:filesystem": "fsid-1",
},
Resolved: DependencyGraph{
"pkg:generic/pkg-1@1.0.1": DependencyNode{
Purl: "pkg:generic/pkg-1@1.0.1",
Scope: DependencyScopeRuntime,
Relationship: DependencyRelationshipDirect,
},
"pkg:generic/pkg-2@2.0.2": DependencyNode{
Purl: "pkg:generic/pkg-2@2.0.2",
Scope: DependencyScopeRuntime,
Relationship: DependencyRelationshipDirect,
},
},
},
"ubuntu:18.04:/etc": Manifest{
Name: "ubuntu:18.04:/etc",
File: FileInfo{
SourceLocation: "ubuntu:18.04:/etc",
},
Metadata: Metadata{
"syft:filesystem": "fsid-1",
},
Resolved: DependencyGraph{
"pkg:generic/pkg-3@3.0.3": DependencyNode{
Purl: "pkg:generic/pkg-3@3.0.3",
Scope: DependencyScopeRuntime,
Relationship: DependencyRelationshipDirect,
},
},
},
},
}
// just using JSONEq because it gives a comprehensible diff
s1, _ := json.Marshal(expected)
s2, _ := json.Marshal(actual)
assert.JSONEq(t, string(s1), string(s2))
// Just test the other schemes:
s.Source.Path = "."
s.Source.Scheme = source.DirectoryScheme
actual = toGithubModel(&s)
assert.Equal(t, "etc", actual.Manifests["etc"].Name)
s.Source.Path = "./artifacts"
s.Source.Scheme = source.DirectoryScheme
actual = toGithubModel(&s)
assert.Equal(t, "artifacts/etc", actual.Manifests["artifacts/etc"].Name)
s.Source.Path = "/artifacts"
s.Source.Scheme = source.DirectoryScheme
actual = toGithubModel(&s)
assert.Equal(t, "/artifacts/etc", actual.Manifests["/artifacts/etc"].Name)
s.Source.Path = "./executable"
s.Source.Scheme = source.FileScheme
actual = toGithubModel(&s)
assert.Equal(t, "executable", actual.Manifests["executable"].Name)
s.Source.Path = "./archive.tar.gz"
s.Source.Scheme = source.FileScheme
actual = toGithubModel(&s)
assert.Equal(t, "archive.tar.gz:/etc", actual.Manifests["archive.tar.gz:/etc"].Name)
}
-29
View File
@@ -1,29 +0,0 @@
package github
import (
"encoding/json"
"io"
"github.com/anchore/syft/syft/sbom"
)
const ID sbom.FormatID = "github-0-json"
func Format() sbom.Format {
return sbom.NewFormat(
ID,
func(writer io.Writer, sbom sbom.SBOM) error {
bom := toGithubModel(&sbom)
bytes, err := json.MarshalIndent(bom, "", " ")
if err != nil {
return err
}
_, err = writer.Write(bytes)
return err
},
nil,
nil,
)
}
@@ -1,78 +0,0 @@
package github
// Derived from: https://gist.github.com/reiddraper/fdab2883db0f372c146d1a750fc1c43f
type DependencySnapshot struct {
Version int `json:"version"`
Job Job `json:"job,omitempty"` // !omitempty
Sha string `json:"sha,omitempty"` // !omitempty sha of the Git commit
Ref string `json:"ref,omitempty"` // !omitempty ref of the Git commit example "refs/heads/main"
Detector DetectorMetadata `json:"detector,omitempty"`
Metadata Metadata `json:"metadata,omitempty"`
Manifests Manifests `json:"manifests,omitempty"`
Scanned ISO8601Date `json:"scanned,omitempty"`
}
type Job struct {
Name string `json:"name,omitempty"` // !omitempty
ID string `json:"id,omitempty"` // !omitempty
HTMLURL string `json:"html_url,omitempty"`
}
type DetectorMetadata struct {
Name string `json:"name,omitempty"`
URL string `json:"url,omitempty"`
Version string `json:"version,omitempty"`
}
type Manifests map[string]Manifest
// Manifest A collection of related dependencies, either declared in a file,
// or representing a logical group of dependencies.
type Manifest struct {
Name string `json:"name"`
File FileInfo `json:"file"`
Metadata Metadata `json:"metadata,omitempty"`
Resolved DependencyGraph `json:"resolved,omitempty"`
}
type FileInfo struct {
SourceLocation string `json:"source_location,omitempty"`
}
// DependencyRelationship A notation of whether a dependency is requested directly
// by this manifest, or is a dependency of another dependency.
type DependencyRelationship string
const (
DependencyRelationshipDirect DependencyRelationship = "direct"
DependencyRelationshipIndirect DependencyRelationship = "indirect"
)
// DependencyScope A notation of whether the dependency is required for the primary
// build artifact (runtime), or is only used for development.
// Future versions of this specification may allow for more granular
// scopes, like `runtimeserver`, `runtimeshipped`,
// `developmenttest`, `developmentbenchmark`.
type DependencyScope string
const (
DependencyScopeRuntime DependencyScope = "runtime"
DependencyScopeDevelopment DependencyScope = "development"
)
type DependencyNode struct {
Purl string `json:"purl,omitempty"`
Metadata Metadata `json:"metadata,omitempty"`
Relationship DependencyRelationship `json:"relationship,omitempty"`
Scope DependencyScope `json:"scope,omitempty"`
Dependencies []string `json:"dependencies,omitempty"`
}
type DependencyGraph map[string]DependencyNode
type ISO8601Date = string
type Scalar interface{} // should be: null | boolean | string | number
type Metadata map[string]Scalar
+2 -2
View File
@@ -71,11 +71,11 @@ func TestSPDXJSONDecoder(t *testing.T) {
}
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:
for _, pkgName := range test.packages {
for _, p := range sbom.Artifacts.PackageCatalog.Sorted() {
for _, p := range sbom.Artifacts.Packages.Sorted() {
if p.Name == pkgName {
continue packages
}
@@ -16,7 +16,6 @@ import (
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
"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.
@@ -43,13 +42,13 @@ func toFormatModel(s sbom.SBOM) (*model.Document, error) {
},
DataLicense: "CC0-1.0",
DocumentNamespace: namespace,
Packages: toPackages(s.Artifacts.PackageCatalog, s.Relationships),
Packages: toPackages(s.Artifacts.Packages, s.Relationships),
Files: toFiles(s),
Relationships: toRelationships(s.Relationships),
}, 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)
for _, p := range catalog.Sorted() {
@@ -94,7 +93,7 @@ func fileIDsForPackage(packageSpdxID string, relationships []artifact.Relationsh
continue
}
if _, ok := relationship.To.(source.Coordinates); !ok {
if _, ok := relationship.To.(file.Coordinates); !ok {
continue
}
@@ -110,7 +109,7 @@ func toFiles(s sbom.SBOM) []model.File {
artifacts := s.Artifacts
for _, coordinates := range sbom.AllCoordinates(s) {
var metadata *source.FileMetadata
var metadata *file.Metadata
if metadataForLocation, exists := artifacts.FileMetadata[coordinates]; exists {
metadata = &metadataForLocation
}
@@ -165,7 +164,7 @@ func toChecksumAlgorithm(algorithm string) string {
return strings.ToUpper(algorithm)
}
func toFileTypes(metadata *source.FileMetadata) (ty []string) {
func toFileTypes(metadata *file.Metadata) (ty []string) {
if metadata == nil {
return nil
}
@@ -11,7 +11,6 @@ import (
"github.com/anchore/syft/internal/formats/common/spdxhelpers"
"github.com/anchore/syft/internal/formats/spdx22json/model"
"github.com/anchore/syft/syft/source"
"github.com/stretchr/testify/assert"
)
@@ -19,12 +18,12 @@ func Test_toFileTypes(t *testing.T) {
tests := []struct {
name string
metadata source.FileMetadata
metadata file.Metadata
expected []string
}{
{
name: "application",
metadata: source.FileMetadata{
metadata: file.Metadata{
MIMEType: "application/vnd.unknown",
},
expected: []string{
@@ -33,7 +32,7 @@ func Test_toFileTypes(t *testing.T) {
},
{
name: "archive",
metadata: source.FileMetadata{
metadata: file.Metadata{
MIMEType: "application/zip",
},
expected: []string{
@@ -43,7 +42,7 @@ func Test_toFileTypes(t *testing.T) {
},
{
name: "audio",
metadata: source.FileMetadata{
metadata: file.Metadata{
MIMEType: "audio/ogg",
},
expected: []string{
@@ -52,7 +51,7 @@ func Test_toFileTypes(t *testing.T) {
},
{
name: "video",
metadata: source.FileMetadata{
metadata: file.Metadata{
MIMEType: "video/3gpp",
},
expected: []string{
@@ -61,7 +60,7 @@ func Test_toFileTypes(t *testing.T) {
},
{
name: "text",
metadata: source.FileMetadata{
metadata: file.Metadata{
MIMEType: "text/html",
},
expected: []string{
@@ -70,7 +69,7 @@ func Test_toFileTypes(t *testing.T) {
},
{
name: "image",
metadata: source.FileMetadata{
metadata: file.Metadata{
MIMEType: "image/png",
},
expected: []string{
@@ -79,7 +78,7 @@ func Test_toFileTypes(t *testing.T) {
},
{
name: "binary",
metadata: source.FileMetadata{
metadata: file.Metadata{
MIMEType: "application/x-sharedlib",
},
expected: []string{
@@ -175,7 +174,7 @@ func Test_fileIDsForPackage(t *testing.T) {
Name: "bogus",
}
c := source.Coordinates{
c := file.Coordinates{
RealPath: "/path",
FileSystemID: "nowhere",
}
@@ -85,13 +85,13 @@ func toFormatModel(s sbom.SBOM) (*spdx.Document2_2, error) {
// Cardinality: optional, one
DocumentComment: "",
},
Packages: toFormatPackages(s.Artifacts.PackageCatalog),
Packages: toFormatPackages(s.Artifacts.Packages),
}, 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
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)
for _, p := range catalog.Sorted() {
+2 -2
View File
@@ -28,8 +28,8 @@ func TestEncodeDecodeCycle(t *testing.T) {
t.Errorf("metadata difference: %+v", d)
}
actualPackages := actualSBOM.Artifacts.PackageCatalog.Sorted()
for idx, p := range originalSBOM.Artifacts.PackageCatalog.Sorted() {
actualPackages := actualSBOM.Artifacts.Packages.Sorted()
for idx, p := range originalSBOM.Artifacts.Packages.Sorted() {
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)
continue
+22 -21
View File
@@ -2,6 +2,7 @@ package syftjson
import (
"flag"
"github.com/anchore/syft/syft/cpe"
"testing"
"github.com/anchore/syft/syft/file"
@@ -37,14 +38,14 @@ func TestImageEncoder(t *testing.T) {
}
func TestEncodeFullJSONDocument(t *testing.T) {
catalog := pkg.NewCatalog()
catalog := pkg.NewCollection()
p1 := pkg.Package{
Name: "package-1",
Version: "1.0.1",
Locations: []source.Location{
Locations: []file.Location{
{
Coordinates: source.Coordinates{
Coordinates: file.Coordinates{
RealPath: "/a/place/a",
},
},
@@ -60,17 +61,17 @@ func TestEncodeFullJSONDocument(t *testing.T) {
Files: []pkg.PythonFileRecord{},
},
PURL: "a-purl-1",
CPEs: []pkg.CPE{
pkg.MustCPE("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"),
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"),
},
}
p2 := pkg.Package{
Name: "package-2",
Version: "2.0.1",
Locations: []source.Location{
Locations: []file.Location{
{
Coordinates: source.Coordinates{
Coordinates: file.Coordinates{
RealPath: "/b/place/b",
},
},
@@ -84,8 +85,8 @@ func TestEncodeFullJSONDocument(t *testing.T) {
Files: []pkg.DpkgFileRecord{},
},
PURL: "a-purl-2",
CPEs: []pkg.CPE{
pkg.MustCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
CPEs: []cpe.CPE{
cpe.Must("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
},
}
@@ -94,50 +95,50 @@ func TestEncodeFullJSONDocument(t *testing.T) {
s := sbom.SBOM{
Artifacts: sbom.Artifacts{
PackageCatalog: catalog,
FileMetadata: map[source.Coordinates]source.FileMetadata{
source.NewLocation("/a/place").Coordinates: {
Packages: catalog,
FileMetadata: map[file.Coordinates]file.Metadata{
file.NewLocation("/a/place").Coordinates: {
Mode: 0775,
Type: "directory",
UserID: 0,
GroupID: 0,
},
source.NewLocation("/a/place/a").Coordinates: {
file.NewLocation("/a/place/a").Coordinates: {
Mode: 0775,
Type: "regularFile",
UserID: 0,
GroupID: 0,
},
source.NewLocation("/b").Coordinates: {
file.NewLocation("/b").Coordinates: {
Mode: 0775,
Type: "symbolicLink",
LinkDestination: "/c",
UserID: 0,
GroupID: 0,
},
source.NewLocation("/b/place/b").Coordinates: {
file.NewLocation("/b/place/b").Coordinates: {
Mode: 0644,
Type: "regularFile",
UserID: 1,
GroupID: 2,
},
},
FileDigests: map[source.Coordinates][]file.Digest{
source.NewLocation("/a/place/a").Coordinates: {
FileDigests: map[file.Coordinates][]file.Digest{
file.NewLocation("/a/place/a").Coordinates: {
{
Algorithm: "sha256",
Value: "366a3f5653e34673b875891b021647440d0127c2ef041e3b1a22da2a7d4f3703",
},
},
source.NewLocation("/b/place/b").Coordinates: {
file.NewLocation("/b/place/b").Coordinates: {
{
Algorithm: "sha256",
Value: "1b3722da2a7d90d033b87581a2a3f12021647445653e34666ef041e3b4f3707c",
},
},
},
FileContents: map[source.Coordinates]string{
source.NewLocation("/a/place/a").Coordinates: "the-contents",
FileContents: map[file.Coordinates]string{
file.NewLocation("/a/place/a").Coordinates: "the-contents",
},
LinuxDistribution: &linux.Release{
ID: "redhat",
@@ -159,7 +160,7 @@ func TestEncodeFullJSONDocument(t *testing.T) {
},
},
Source: source.Metadata{
Scheme: source.ImageScheme,
Scheme: source.ImageType,
ImageMetadata: source.ImageMetadata{
UserInput: "user-image-input",
ID: "sha256:c2b46b4eb06296933b7cf0722683964e9ecbd93265b9ef6ae9642e3952afbba0",
+7 -9
View File
@@ -2,13 +2,11 @@ package model
import (
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/source"
)
type File struct {
ID string `json:"id"`
Location source.Coordinates `json:"location"`
Location file.Coordinates `json:"location"`
Metadata *FileMetadataEntry `json:"metadata,omitempty"`
Contents string `json:"contents,omitempty"`
Digests []file.Digest `json:"digests,omitempty"`
@@ -16,10 +14,10 @@ type File struct {
}
type FileMetadataEntry struct {
Mode int `json:"mode"`
Type source.FileType `json:"type"`
LinkDestination string `json:"linkDestination,omitempty"`
UserID int `json:"userID"`
GroupID int `json:"groupID"`
MIMEType string `json:"mimeType"`
Mode int `json:"mode"`
Type file.Type `json:"type"`
LinkDestination string `json:"linkDestination,omitempty"`
UserID int `json:"userID"`
GroupID int `json:"groupID"`
MIMEType string `json:"mimeType"`
}
+11 -11
View File
@@ -4,7 +4,7 @@ import (
"encoding/json"
"fmt"
"github.com/anchore/syft/syft/source"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/pkg"
@@ -18,16 +18,16 @@ type Package struct {
// PackageBasicData contains non-ambiguous values (type-wise) from pkg.Package.
type PackageBasicData struct {
ID string `json:"id"`
Name string `json:"name"`
Version string `json:"version"`
Type pkg.Type `json:"type"`
FoundBy string `json:"foundBy"`
Locations []source.Coordinates `json:"locations"`
Licenses []string `json:"licenses"`
Language pkg.Language `json:"language"`
CPEs []string `json:"cpes"`
PURL string `json:"purl"`
ID string `json:"id"`
Name string `json:"name"`
Version string `json:"version"`
Type pkg.Type `json:"type"`
FoundBy string `json:"foundBy"`
Locations []file.Coordinates `json:"locations"`
Licenses []string `json:"licenses"`
Language pkg.Language `json:"language"`
CPEs []string `json:"cpes"`
PURL string `json:"purl"`
}
// PackageCustomData contains ambiguous values (type-wise) from pkg.Package.
+1 -2
View File
@@ -2,10 +2,9 @@ package model
import (
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/source"
)
type Secrets struct {
Location source.Coordinates `json:"location"`
Location file.Coordinates `json:"location"`
Secrets []file.SearchResult `json:"secrets"`
}
@@ -88,7 +88,7 @@
}
},
"schema": {
"version": "3.1.0",
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-3.1.0.json"
"version": "3.0.1",
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-3.0.1.json"
}
}
@@ -184,7 +184,7 @@
}
},
"schema": {
"version": "3.1.0",
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-3.1.0.json"
"version": "3.0.1",
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-3.0.1.json"
}
}
@@ -111,7 +111,7 @@
}
},
"schema": {
"version": "3.1.0",
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-3.1.0.json"
"version": "3.0.1",
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-3.0.1.json"
}
}
+11 -10
View File
@@ -2,6 +2,7 @@ package syftjson
import (
"fmt"
"github.com/anchore/syft/syft/cpe"
"sort"
"strconv"
@@ -30,7 +31,7 @@ func ToFormatModel(s sbom.SBOM) model.Document {
}
return model.Document{
Artifacts: toPackageModels(s.Artifacts.PackageCatalog),
Artifacts: toPackageModels(s.Artifacts.Packages),
ArtifactRelationships: toRelationshipModel(s.Relationships),
Files: toFile(s),
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)
for coordinates, secrets := range data {
results = append(results, model.Secrets{
@@ -94,7 +95,7 @@ func toFile(s sbom.SBOM) []model.File {
artifacts := s.Artifacts
for _, coordinates := range sbom.AllCoordinates(s) {
var metadata *source.FileMetadata
var metadata *file.Metadata
if metadataForLocation, exists := artifacts.FileMetadata[coordinates]; exists {
metadata = &metadataForLocation
}
@@ -131,7 +132,7 @@ func toFile(s sbom.SBOM) []model.File {
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 {
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)
if catalog == nil {
return artifacts
@@ -167,7 +168,7 @@ func toPackageModels(catalog *pkg.Catalog) []model.Package {
func toPackageModel(p pkg.Package) model.Package {
var cpes = make([]string, len(p.CPEs))
for i, c := range p.CPEs {
cpes[i] = pkg.CPEString(c)
cpes[i] = cpe.String(c)
}
var licenses = make([]string, 0)
@@ -175,7 +176,7 @@ func toPackageModel(p pkg.Package) model.Package {
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 {
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.
func toSourceModel(src source.Metadata) (model.Source, error) {
switch src.Scheme {
case source.ImageScheme:
case source.ImageType:
metadata := src.ImageMetadata
// ensure that empty collections are not shown as null
if metadata.RepoDigests == nil {
@@ -229,12 +230,12 @@ func toSourceModel(src source.Metadata) (model.Source, error) {
Type: "image",
Target: metadata,
}, nil
case source.DirectoryScheme:
case source.DirectoryType:
return model.Source{
Type: "directory",
Target: src.Path,
}, nil
case source.FileScheme:
case source.FileType:
return model.Source{
Type: "file",
Target: src.Path,
@@ -13,7 +13,7 @@ import (
func Test_toSourceModel(t *testing.T) {
allSchemes := strset.New()
for _, s := range source.AllSchemes {
for _, s := range source.AllTypes {
allSchemes.Add(string(s))
}
testedSchemes := strset.New()
@@ -26,7 +26,7 @@ func Test_toSourceModel(t *testing.T) {
{
name: "directory",
src: source.Metadata{
Scheme: source.DirectoryScheme,
Scheme: source.DirectoryType,
Path: "some/path",
},
expected: model.Source{
@@ -37,7 +37,7 @@ func Test_toSourceModel(t *testing.T) {
{
name: "file",
src: source.Metadata{
Scheme: source.FileScheme,
Scheme: source.FileType,
Path: "some/path",
},
expected: model.Source{
@@ -48,7 +48,7 @@ func Test_toSourceModel(t *testing.T) {
{
name: "image",
src: source.Metadata{
Scheme: source.ImageScheme,
Scheme: source.ImageType,
ImageMetadata: source.ImageMetadata{
UserInput: "user-input",
ID: "id...",
+13 -11
View File
@@ -4,6 +4,8 @@ import (
"github.com/anchore/syft/internal/formats/syftjson/model"
"github.com/anchore/syft/internal/log"
"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/pkg"
"github.com/anchore/syft/syft/sbom"
@@ -16,7 +18,7 @@ func toSyftModel(doc model.Document) (*sbom.SBOM, error) {
return &sbom.SBOM{
Artifacts: sbom.Artifacts{
PackageCatalog: catalog,
Packages: catalog,
LinuxDistribution: toSyftLinuxRelease(doc.Distro),
},
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{})
for _, p := range catalog.Sorted() {
@@ -111,25 +113,25 @@ func toSyftSourceData(s model.Source) *source.Metadata {
switch s.Type {
case "directory":
return &source.Metadata{
Scheme: source.DirectoryScheme,
Scheme: source.DirectoryType,
Path: s.Target.(string),
}
case "file":
return &source.Metadata{
Scheme: source.FileScheme,
Scheme: source.FileType,
Path: s.Target.(string),
}
case "image":
return &source.Metadata{
Scheme: source.ImageScheme,
Scheme: source.ImageType,
ImageMetadata: s.Target.(source.ImageMetadata),
}
}
return nil
}
func toSyftCatalog(pkgs []model.Package) *pkg.Catalog {
catalog := pkg.NewCatalog()
func toSyftCatalog(pkgs []model.Package) pkg.Collection {
catalog := pkg.NewCollection()
for _, p := range pkgs {
catalog.Add(toSyftPackage(p))
}
@@ -137,9 +139,9 @@ func toSyftCatalog(pkgs []model.Package) *pkg.Catalog {
}
func toSyftPackage(p model.Package) pkg.Package {
var cpes []pkg.CPE
var cpes []cpe.CPE
for _, c := range p.CPEs {
value, err := pkg.NewCPE(c)
value, err := cpe.New(c)
if err != nil {
log.Warnf("excluding invalid CPE %q: %v", c, err)
continue
@@ -148,9 +150,9 @@ func toSyftPackage(p model.Package) pkg.Package {
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 {
locations[i] = source.NewLocationFromCoordinates(c)
locations[i] = file.NewLocationFromCoordinates(c)
}
return pkg.Package{
@@ -11,7 +11,7 @@ import (
func Test_toSyftSourceData(t *testing.T) {
allSchemes := strset.New()
for _, s := range source.AllSchemes {
for _, s := range source.AllTypes {
allSchemes.Add(string(s))
}
testedSchemes := strset.New()
@@ -24,7 +24,7 @@ func Test_toSyftSourceData(t *testing.T) {
{
name: "directory",
expected: source.Metadata{
Scheme: source.DirectoryScheme,
Scheme: source.DirectoryType,
Path: "some/path",
},
src: model.Source{
@@ -35,7 +35,7 @@ func Test_toSyftSourceData(t *testing.T) {
{
name: "file",
expected: source.Metadata{
Scheme: source.FileScheme,
Scheme: source.FileType,
Path: "some/path",
},
src: model.Source{
@@ -46,7 +46,7 @@ func Test_toSyftSourceData(t *testing.T) {
{
name: "image",
expected: source.Metadata{
Scheme: source.ImageScheme,
Scheme: source.ImageType,
ImageMetadata: source.ImageMetadata{
UserInput: "user-input",
ID: "id...",
+1 -1
View File
@@ -15,7 +15,7 @@ func encoder(output io.Writer, s sbom.SBOM) error {
var rows [][]string
columns := []string{"Name", "Version", "Type"}
for _, p := range s.Artifacts.PackageCatalog.Sorted() {
for _, p := range s.Artifacts.Packages.Sorted() {
row := []string{
p.Name,
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)
switch s.Source.Scheme {
case source.DirectoryScheme, source.FileScheme:
case source.DirectoryType, source.FileType:
fmt.Fprintf(w, "[Path: %s]\n", s.Source.Path)
case source.ImageScheme:
case source.ImageType:
fmt.Fprintln(w, "[Image]")
for idx, l := range s.Source.ImageMetadata.Layers {
@@ -35,7 +35,7 @@ func encoder(output io.Writer, s sbom.SBOM) error {
// populate artifacts...
rows := 0
for _, p := range s.Artifacts.PackageCatalog.Sorted() {
for _, p := range s.Artifacts.Packages.Sorted() {
fmt.Fprintf(w, "[%s]\n", p.Name)
fmt.Fprintln(w, " Version:\t", p.Version)
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
}
File diff suppressed because it is too large Load Diff
+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 (
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/source"
)
type ClassificationCataloger struct {
type Cataloger struct {
classifiers []Classifier
}
func NewClassificationCataloger(classifiers []Classifier) (*ClassificationCataloger, error) {
return &ClassificationCataloger{
func NewCataloger(classifiers []Classifier) (*Cataloger, error) {
return &Cataloger{
classifiers: classifiers,
}, nil
}
func (i *ClassificationCataloger) Catalog(resolver source.FileResolver) (map[source.Coordinates][]Classification, error) {
results := make(map[source.Coordinates][]Classification)
func (i *Cataloger) Catalog(resolver file.Resolver) (map[file.Coordinates][]file.Classification, error) {
results := make(map[file.Coordinates][]file.Classification)
numResults := 0
for _, location := range allRegularFiles(resolver) {
for _, location := range source.AllRegularFiles(resolver) {
for _, classifier := range i.classifiers {
result, err := classifier.Classify(resolver, location)
if err != nil {
@@ -1,9 +1,10 @@
package file
package fileclassifier
import (
"github.com/anchore/stereoscope/pkg/imagetest"
"testing"
"github.com/anchore/stereoscope/pkg/imagetest"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/source"
"github.com/stretchr/testify/assert"
)
@@ -13,14 +14,14 @@ func TestClassifierCataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
name string
fixtureDir string
location string
expected []Classification
expected []file.Classification
expectedErr func(assert.TestingT, error, ...interface{}) bool
}{
{
name: "positive-libpython3.7.so",
fixtureDir: "test-fixtures/classifiers/positive",
location: "libpython3.7.so",
expected: []Classification{
expected: []file.Classification{
{
Class: "python-binary",
Metadata: map[string]string{
@@ -34,7 +35,7 @@ func TestClassifierCataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
name: "positive-python3.6",
fixtureDir: "test-fixtures/classifiers/positive",
location: "python3.6",
expected: []Classification{
expected: []file.Classification{
{
Class: "python-binary",
Metadata: map[string]string{
@@ -48,7 +49,7 @@ func TestClassifierCataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
name: "positive-patchlevel.h",
fixtureDir: "test-fixtures/classifiers/positive",
location: "patchlevel.h",
expected: []Classification{
expected: []file.Classification{
{
Class: "cpython-source",
Metadata: map[string]string{
@@ -62,7 +63,7 @@ func TestClassifierCataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
name: "positive-go",
fixtureDir: "test-fixtures/classifiers/positive",
location: "go",
expected: []Classification{
expected: []file.Classification{
{
Class: "go-binary",
Metadata: map[string]string{
@@ -76,7 +77,7 @@ func TestClassifierCataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
name: "positive-go-hint",
fixtureDir: "test-fixtures/classifiers/positive",
location: "VERSION",
expected: []Classification{
expected: []file.Classification{
{
Class: "go-binary-hint",
Metadata: map[string]string{
@@ -90,7 +91,7 @@ func TestClassifierCataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
name: "positive-busybox",
fixtureDir: "test-fixtures/classifiers/positive",
location: "[", // note: busybox is a link to [
expected: []Classification{
expected: []file.Classification{
{
Class: "busybox-binary",
Metadata: map[string]string{
@@ -105,7 +106,7 @@ func TestClassifierCataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
c, err := NewClassificationCataloger(DefaultClassifiers)
c, err := NewCataloger(DefaultClassifiers())
test.expectedErr(t, err)
src, err := source.NewFromDirectory(test.fixtureDir)
@@ -138,14 +139,14 @@ func TestClassifierCataloger_DefaultClassifiers_PositiveCases_Image(t *testing.T
name string
fixtureImage string
location string
expected []Classification
expected []file.Classification
expectedErr func(assert.TestingT, error, ...interface{}) bool
}{
{
name: "busybox-regression",
fixtureImage: "image-busybox",
location: "/bin/[",
expected: []Classification{
expected: []file.Classification{
{
Class: "busybox-binary",
Metadata: map[string]string{
@@ -160,7 +161,7 @@ func TestClassifierCataloger_DefaultClassifiers_PositiveCases_Image(t *testing.T
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
c, err := NewClassificationCataloger(DefaultClassifiers)
c, err := NewCataloger(DefaultClassifiers())
test.expectedErr(t, err)
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) {
c, err := NewClassificationCataloger(DefaultClassifiers)
c, err := NewCataloger(DefaultClassifiers())
assert.NoError(t, err)
src, err := source.NewFromDirectory("test-fixtures/classifiers/negative")
@@ -1,4 +1,4 @@
package file
package fileclassifier
import (
"bytes"
@@ -7,58 +7,10 @@ import (
"regexp"
"text/template"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/syft/source"
)
"github.com/anchore/syft/syft/file"
var DefaultClassifiers = []Classifier{
{
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]+)`,
},
},
}
"github.com/anchore/syft/internal"
)
type Classifier struct {
Class string
@@ -66,12 +18,58 @@ type Classifier struct {
EvidencePatternTemplates []string
}
type Classification struct {
Class string `json:"class"`
Metadata map[string]string `json:"metadata"`
func DefaultClassifiers() []Classifier {
return []Classifier{
{
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)
if !doesFilepathMatch {
return nil, nil
@@ -81,7 +79,7 @@ func (c Classifier) Classify(resolver source.FileResolver, location source.Locat
if err != nil {
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.
contents, err := ioutil.ReadAll(contentReader)
@@ -89,7 +87,7 @@ func (c Classifier) Classify(resolver source.FileResolver, location source.Locat
return nil, err
}
var result *Classification
var result *file.Classification
for _, patternTemplate := range c.EvidencePatternTemplates {
tmpl, err := template.New("").Parse(patternTemplate)
if err != nil {
@@ -113,7 +111,7 @@ func (c Classifier) Classify(resolver source.FileResolver, location source.Locat
matchMetadata := internal.MatchNamedCaptureGroups(pattern, string(contents))
if result == nil {
result = &Classification{
result = &file.Classification{
Class: c.Class,
Metadata: matchMetadata,
}
@@ -126,8 +124,8 @@ func (c Classifier) Classify(resolver source.FileResolver, location source.Locat
return result, nil
}
func filepathMatches(patterns []*regexp.Regexp, location source.Location) (bool, map[string]string) {
for _, path := range []string{location.RealPath, location.VirtualPath} {
func filepathMatches(patterns []*regexp.Regexp, location file.Location) (bool, map[string]string) {
for _, path := range []string{location.RealPath, location.AccessPath} {
if path == "" {
continue
}
@@ -1,25 +1,25 @@
package file
package fileclassifier
import (
"github.com/anchore/syft/syft/file"
"regexp"
"testing"
"github.com/anchore/syft/syft/source"
"github.com/stretchr/testify/assert"
)
func TestFilepathMatches(t *testing.T) {
tests := []struct {
name string
location source.Location
location file.Location
patterns []string
expectedMatches bool
expectedNamedGroups map[string]string
}{
{
name: "simple-filename-match",
location: source.Location{
Coordinates: source.Coordinates{
location: file.Location{
Coordinates: file.Coordinates{
RealPath: "python2.7",
},
},
@@ -30,8 +30,8 @@ func TestFilepathMatches(t *testing.T) {
},
{
name: "filepath-match",
location: source.Location{
Coordinates: source.Coordinates{
location: file.Location{
Coordinates: file.Coordinates{
RealPath: "/usr/bin/python2.7",
},
},
@@ -42,8 +42,8 @@ func TestFilepathMatches(t *testing.T) {
},
{
name: "virtual-filepath-match",
location: source.Location{
VirtualPath: "/usr/bin/python2.7",
location: file.Location{
AccessPath: "/usr/bin/python2.7",
},
patterns: []string{
`python([0-9]+\.[0-9]+)$`,
@@ -52,8 +52,8 @@ func TestFilepathMatches(t *testing.T) {
},
{
name: "full-filepath-match",
location: source.Location{
VirtualPath: "/usr/bin/python2.7",
location: file.Location{
AccessPath: "/usr/bin/python2.7",
},
patterns: []string{
`.*/bin/python([0-9]+\.[0-9]+)$`,
@@ -62,8 +62,8 @@ func TestFilepathMatches(t *testing.T) {
},
{
name: "anchored-filename-match-FAILS",
location: source.Location{
Coordinates: source.Coordinates{
location: file.Location{
Coordinates: file.Coordinates{
RealPath: "/usr/bin/python2.7",
},
},
@@ -74,7 +74,7 @@ func TestFilepathMatches(t *testing.T) {
},
{
name: "empty-filename-match-FAILS",
location: source.Location{},
location: file.Location{},
patterns: []string{
`^python([0-9]+\.[0-9]+)$`,
},
@@ -1,4 +1,4 @@
package file
package filecontents
import (
"bytes"
@@ -7,28 +7,37 @@ import (
"io"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/source"
"github.com/anchore/syft/syft/file"
)
type ContentsCataloger struct {
globs []string
skipFilesAboveSizeInBytes int64
type Config struct {
Globs []string
SkipFilesAboveSizeInBytes int64
}
func NewContentsCataloger(globs []string, skipFilesAboveSize int64) (*ContentsCataloger, error) {
return &ContentsCataloger{
globs: globs,
skipFilesAboveSizeInBytes: skipFilesAboveSize,
type Cataloger struct {
config Config
}
func DefaultConfig() Config {
return Config{
Globs: nil,
SkipFilesAboveSizeInBytes: 1 * file.MB,
}
}
func NewCataloger(config Config) (*Cataloger, error) {
return &Cataloger{
config: config,
}, nil
}
func (i *ContentsCataloger) Catalog(resolver source.FileResolver) (map[source.Coordinates]string, error) {
results := make(map[source.Coordinates]string)
var locations []source.Location
func (i *Cataloger) Catalog(resolver file.Resolver) (map[file.Coordinates]string, error) {
results := make(map[file.Coordinates]string)
var locations []file.Location
locations, err := resolver.FilesByGlob(i.globs...)
locations, err := resolver.FilesByGlob(i.config.Globs...)
if err != nil {
return nil, err
}
@@ -38,7 +47,7 @@ func (i *ContentsCataloger) Catalog(resolver source.FileResolver) (map[source.Co
return nil, err
}
if i.skipFilesAboveSizeInBytes > 0 && metadata.Size > i.skipFilesAboveSizeInBytes {
if i.config.SkipFilesAboveSizeInBytes > 0 && metadata.Size > i.config.SkipFilesAboveSizeInBytes {
continue
}
@@ -57,12 +66,12 @@ func (i *ContentsCataloger) Catalog(resolver source.FileResolver) (map[source.Co
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)
if err != nil {
return "", err
}
defer internal.CloseAndLogError(contentReader, location.VirtualPath)
defer internal.CloseAndLogError(contentReader, location.AccessPath)
buf := &bytes.Buffer{}
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")
})
}
}

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