Skip to content
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,28 @@ exclude: []
# same as --platform; SYFT_PLATFORM env var
platform: ""

# set the list of package catalogers to use when generating the SBOM
# default = empty (cataloger set determined automatically by the source type [image or file/directory])
# catalogers:
# - "ruby-gemfile"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: do these have to be in quotes or can we just remove that part?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so, I'll update 👍

# - "ruby-gemspec"
# - "python-index"
# - "python-package"
# - "javascript-lock"
# - "javascript-package"
# - "php-composer-installed"
# - "php-composer-lock"
# - "dpkgdb"
# - "rpmdb"
# - "java"
# - "apkdb"
# - "go-module-binary"
# - "go-mod-file"
# - "dartlang-lock"
# - "rust"
# - "dotnet-deps"
catalogers:

# cataloging packages is exposed through the packages and power-user subcommands
package:

Expand Down
2 changes: 1 addition & 1 deletion cmd/syft/cli/eventloop/tasks.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func generateCatalogPackagesTask(app *config.Application) (Task, error) {
}

task := func(results *sbom.Artifacts, src *source.Source) ([]artifact.Relationship, error) {
packageCatalog, relationships, theDistro, err := syft.CatalogPackages(src, app.Package.ToConfig())
packageCatalog, relationships, theDistro, err := syft.CatalogPackages(src, app.ToCatalogerConfig())
if err != nil {
return nil, err
}
Expand Down
32 changes: 20 additions & 12 deletions cmd/syft/cli/options/packages.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,45 +24,49 @@ type PackagesOptions struct {
Exclude []string
OverwriteExistingImage bool
ImportTimeout uint
Catalogers []string
}

var _ Interface = (*PackagesOptions)(nil)

func (o *PackagesOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error {
cmd.PersistentFlags().StringVarP(&o.Scope, "scope", "s", cataloger.DefaultSearchConfig().Scope.String(),
cmd.Flags().StringVarP(&o.Scope, "scope", "s", cataloger.DefaultSearchConfig().Scope.String(),
Comment thread
wagoodman marked this conversation as resolved.
fmt.Sprintf("selection of layers to catalog, options=%v", source.AllScopes))

cmd.PersistentFlags().StringArrayVarP(&o.Output, "output", "o", FormatAliases(table.ID),
cmd.Flags().StringArrayVarP(&o.Output, "output", "o", FormatAliases(table.ID),
fmt.Sprintf("report output format, options=%v", FormatAliases(syft.FormatIDs()...)))

cmd.PersistentFlags().StringVarP(&o.File, "file", "", "",
cmd.Flags().StringVarP(&o.File, "file", "", "",
"file to write the default report output to (default is STDOUT)")

cmd.PersistentFlags().StringVarP(&o.Platform, "platform", "", "",
cmd.Flags().StringVarP(&o.Platform, "platform", "", "",
"an optional platform specifier for container image sources (e.g. 'linux/arm64', 'linux/arm64/v8', 'arm64', 'linux')")

cmd.PersistentFlags().StringVarP(&o.Host, "host", "H", "",
cmd.Flags().StringVarP(&o.Host, "host", "H", "",
"the hostname or URL of the Anchore Enterprise instance to upload to")

cmd.PersistentFlags().StringVarP(&o.Username, "username", "u", "",
cmd.Flags().StringVarP(&o.Username, "username", "u", "",
"the username to authenticate against Anchore Enterprise")

cmd.PersistentFlags().StringVarP(&o.Password, "password", "p", "",
cmd.Flags().StringVarP(&o.Password, "password", "p", "",
"the password to authenticate against Anchore Enterprise")

cmd.PersistentFlags().StringVarP(&o.Dockerfile, "dockerfile", "d", "",
cmd.Flags().StringVarP(&o.Dockerfile, "dockerfile", "d", "",
"include dockerfile for upload to Anchore Enterprise")

cmd.PersistentFlags().StringArrayVarP(&o.Exclude, "exclude", "", nil,
cmd.Flags().StringArrayVarP(&o.Exclude, "exclude", "", nil,
"exclude paths from being scanned using a glob expression")

cmd.PersistentFlags().BoolVarP(&o.OverwriteExistingImage, "overwrite-existing-image", "", false,
cmd.Flags().StringArrayVarP(&o.Catalogers, "catalogers", "", nil,
"enable one or more package catalogers")

cmd.Flags().BoolVarP(&o.OverwriteExistingImage, "overwrite-existing-image", "", false,
"overwrite an existing image during the upload to Anchore Enterprise")

cmd.PersistentFlags().UintVarP(&o.ImportTimeout, "import-timeout", "", 30,
cmd.Flags().UintVarP(&o.ImportTimeout, "import-timeout", "", 30,
"set a timeout duration (in seconds) for the upload to Anchore Enterprise")

return bindPackageConfigOptions(cmd.PersistentFlags(), v)
return bindPackageConfigOptions(cmd.Flags(), v)
}

func bindPackageConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error {
Expand All @@ -80,6 +84,10 @@ func bindPackageConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error {
return err
}

if err := v.BindPFlag("catalogers", flags.Lookup("catalogers")); err != nil {
return err
}

if err := v.BindPFlag("output", flags.Lookup("output")); err != nil {
return err
}
Expand Down
26 changes: 26 additions & 0 deletions internal/config/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ import (
"fmt"
"path"
"reflect"
"sort"
"strings"

"github.com/anchore/syft/syft/pkg/cataloger"

"github.com/sirupsen/logrus"

"github.com/adrg/xdg"
Expand Down Expand Up @@ -43,6 +46,7 @@ type Application struct {
Anchore anchore `yaml:"anchore" json:"anchore" mapstructure:"anchore"` // options for interacting with Anchore Engine/Enterprise
Dev development `yaml:"dev" json:"dev" mapstructure:"dev"`
Log logging `yaml:"log" json:"log" mapstructure:"log"` // all logging-related options
Catalogers []string `yaml:"catalogers" json:"catalogers" mapstructure:"catalogers"`
Package pkg `yaml:"package" json:"package" mapstructure:"package"`
FileMetadata FileMetadata `yaml:"file-metadata" json:"file-metadata" mapstructure:"file-metadata"`
FileClassification fileClassification `yaml:"file-classification" json:"file-classification" mapstructure:"file-classification"`
Expand All @@ -54,6 +58,17 @@ type Application struct {
Platform string `yaml:"platform" json:"platform" mapstructure:"platform"`
}

func (cfg Application) ToCatalogerConfig() cataloger.Config {
return cataloger.Config{
Search: cataloger.SearchConfig{
IncludeIndexedArchives: cfg.Package.SearchIndexedArchives,
IncludeUnindexedArchives: cfg.Package.SearchUnindexedArchives,
Scope: cfg.Package.Cataloger.ScopeOpt,
},
Catalogers: cfg.Catalogers,
}
}

func (cfg *Application) LoadAllValues(v *viper.Viper, configPath string) error {
// priority order: viper.Set, flag, env, config, kv, defaults
// flags have already been loaded into viper by command construction
Expand Down Expand Up @@ -85,6 +100,16 @@ func (cfg *Application) LoadAllValues(v *viper.Viper, configPath string) error {
}

func (cfg *Application) parseConfigValues() error {
// parse options on this struct
var catalogers []string
for _, c := range cfg.Catalogers {
for _, f := range strings.Split(c, ",") {
catalogers = append(catalogers, strings.TrimSpace(f))
}
}
sort.Strings(catalogers)
cfg.Catalogers = catalogers

// parse application config options
for _, optionFn := range []func() error{
cfg.parseUploadOptions,
Expand Down Expand Up @@ -172,6 +197,7 @@ func loadDefaultValues(v *viper.Viper) {
// set the default values for primitive fields in this struct
v.SetDefault("quiet", false)
v.SetDefault("check-for-app-update", true)
v.SetDefault("catalogers", nil)

// for each field in the configuration struct, see if the field implements the defaultValueLoader interface and invoke it if it does
value := reflect.ValueOf(Application{})
Expand Down
10 changes: 0 additions & 10 deletions internal/config/pkg.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,3 @@ func (cfg pkg) loadDefaultValues(v *viper.Viper) {
func (cfg *pkg) parseConfigValues() error {
return cfg.Cataloger.parseConfigValues()
}

func (cfg pkg) ToConfig() cataloger.Config {
return cataloger.Config{
Search: cataloger.SearchConfig{
IncludeIndexedArchives: cfg.SearchIndexedArchives,
IncludeUnindexedArchives: cfg.SearchUnindexedArchives,
Scope: cfg.Cataloger.ScopeOpt,
},
}
}
43 changes: 38 additions & 5 deletions syft/pkg/cataloger/cataloger.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ catalogers defined in child packages as well as the interface definition to impl
package cataloger

import (
"strings"

"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/apkdb"
Expand Down Expand Up @@ -35,7 +38,7 @@ type Cataloger interface {

// ImageCatalogers returns a slice of locally implemented catalogers that are fit for detecting installations of packages.
func ImageCatalogers(cfg Config) []Cataloger {
return []Cataloger{
return filterCatalogers([]Cataloger{
ruby.NewGemSpecCataloger(),
python.NewPythonPackageCataloger(),
php.NewPHPComposerInstalledCataloger(),
Expand All @@ -46,12 +49,12 @@ func ImageCatalogers(cfg Config) []Cataloger {
apkdb.NewApkdbCataloger(),
golang.NewGoModuleBinaryCataloger(),
dotnet.NewDotnetDepsCataloger(),
}
}, cfg.Catalogers)
}

// DirectoryCatalogers returns a slice of locally implemented catalogers that are fit for detecting packages from index files (and select installations)
func DirectoryCatalogers(cfg Config) []Cataloger {
return []Cataloger{
return filterCatalogers([]Cataloger{
ruby.NewGemFileLockCataloger(),
python.NewPythonIndexCataloger(),
python.NewPythonPackageCataloger(),
Expand All @@ -66,12 +69,12 @@ func DirectoryCatalogers(cfg Config) []Cataloger {
rust.NewCargoLockCataloger(),
dart.NewPubspecLockCataloger(),
dotnet.NewDotnetDepsCataloger(),
}
}, cfg.Catalogers)
}

// AllCatalogers returns all implemented catalogers
func AllCatalogers(cfg Config) []Cataloger {
return []Cataloger{
return filterCatalogers([]Cataloger{
ruby.NewGemFileLockCataloger(),
ruby.NewGemSpecCataloger(),
python.NewPythonIndexCataloger(),
Expand All @@ -87,5 +90,35 @@ func AllCatalogers(cfg Config) []Cataloger {
rust.NewCargoLockCataloger(),
dart.NewPubspecLockCataloger(),
dotnet.NewDotnetDepsCataloger(),
}, cfg.Catalogers)
}

func filterCatalogers(catalogers []Cataloger, enabledCatalogerPatterns []string) []Cataloger {
// if cataloger is not set, all applicable catalogers are enabled by default
if len(enabledCatalogerPatterns) == 0 {
return catalogers
}
var keepCatalogers []Cataloger
for _, cataloger := range catalogers {
if contains(enabledCatalogerPatterns, cataloger.Name()) {
keepCatalogers = append(keepCatalogers, cataloger)
continue
}
log.Infof("skipping cataloger %q", cataloger.Name())
}
return keepCatalogers
}

func contains(enabledPartial []string, catalogerName string) bool {
catalogerName = strings.TrimSuffix(catalogerName, "-cataloger")
for _, partial := range enabledPartial {
partial = strings.TrimSuffix(partial, "-cataloger")
if partial == "" {
continue
}
if strings.Contains(catalogerName, partial) {
return true
}
}
return false
}
Loading