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