diff --git a/README.md b/README.md index 280d16b8fc3..4091f1a5ffc 100644 --- a/README.md +++ b/README.md @@ -314,6 +314,29 @@ package: # same as -s ; SYFT_PACKAGE_CATALOGER_SCOPE env var scope: "squashed" + # Cataloger group select + # When Empty default select for each scheme - Dir:Index, Image:installed + # Options: [index install all]. + # cataloger-group: "" + + # enable specific language or ecosystem cataloger + # default: select catalogers out of group + # catalogers: + # - "ruby-gemfile-cataloger" + # - "ruby-gemspec-cataloger" + # - "python-index-cataloger" + # - "python-package-cataloger" + # - "javascript-lock-cataloger" + # - "javascript-package-cataloger" + # - "php-composer-installed-cataloger" + # - "php-composer-lock-cataloger" + # - "dpkgdb-cataloger" + # - "rpmdb-cataloger" + # - "java-cataloger" + # - "apkdb-cataloger" + # - "go-module-binary-cataloger" + catalogers: + # cataloging file classifications is exposed through the power-user subcommand file-classification: cataloger: diff --git a/cmd/packages.go b/cmd/packages.go index bb90a94fdf8..540bd022d4b 100644 --- a/cmd/packages.go +++ b/cmd/packages.go @@ -137,18 +137,23 @@ func setPackageFlags(flags *pflag.FlagSet) { ) flags.StringArrayP( - "exclude", "", nil, - "exclude paths from being scanned using a glob expression", + "exclude", "", nil, "exclude paths from being scanned using a glob expression", + ) + + flags.StringArrayP( + "cataloger", "C", nil, "enable specific language or ecosystem cataloger", + ) + + flags.StringP( + "cataloger-group", "", "", fmt.Sprintf("selection cataloger group, options=%v", cataloger.AllGroups), ) flags.Bool( - "overwrite-existing-image", false, - "overwrite an existing image during the upload to Anchore Enterprise", + "overwrite-existing-image", false, "overwrite an existing image during the upload to Anchore Enterprise", ) flags.Uint( - "import-timeout", 30, - "set a timeout duration (in seconds) for the upload to Anchore Enterprise", + "import-timeout", 30, "set a timeout duration (in seconds) for the upload to Anchore Enterprise", ) } @@ -180,6 +185,14 @@ func bindExclusivePackagesConfigOptions(flags *pflag.FlagSet) error { return err } + if err := viper.BindPFlag("package.catalogers", flags.Lookup("cataloger")); err != nil { + return err + } + + if err := viper.BindPFlag("package.cataloger-group", flags.Lookup("cataloger-group")); err != nil { + return err + } + // Upload options ////////////////////////////////////////////////////////// if err := viper.BindPFlag("anchore.host", flags.Lookup("host")); err != nil { diff --git a/internal/config/pkg.go b/internal/config/pkg.go index 2e695a995c3..bf78ec1935a 100644 --- a/internal/config/pkg.go +++ b/internal/config/pkg.go @@ -9,6 +9,8 @@ type pkg struct { Cataloger catalogerOptions `yaml:"cataloger" json:"cataloger" mapstructure:"cataloger"` SearchUnindexedArchives bool `yaml:"search-unindexed-archives" json:"search-unindexed-archives" mapstructure:"search-unindexed-archives"` SearchIndexedArchives bool `yaml:"search-indexed-archives" json:"search-indexed-archives" mapstructure:"search-indexed-archives"` + Catalogers []string `yaml:"catalogers" json:"catalogers" mapstructure:"catalogers"` + CatalogerGroup cataloger.Group `yaml:"cataloger-group" json:"cataloger-group" mapstructure:"cataloger-group"` } func (cfg pkg) loadDefaultValues(v *viper.Viper) { @@ -29,5 +31,7 @@ func (cfg pkg) ToConfig() cataloger.Config { IncludeUnindexedArchives: cfg.SearchUnindexedArchives, Scope: cfg.Cataloger.ScopeOpt, }, + Catalogers: cfg.Catalogers, + CatalogerGroup: cfg.CatalogerGroup, } } diff --git a/syft/lib.go b/syft/lib.go index a712e510abf..fbeb09bd98f 100644 --- a/syft/lib.go +++ b/syft/lib.go @@ -48,23 +48,27 @@ func CatalogPackages(src *source.Source, cfg cataloger.Config) (*pkg.Catalog, [] log.Info("could not identify distro") } - // conditionally use the correct set of loggers based on the input type (container image or directory) - var catalogers []cataloger.Cataloger - switch src.Metadata.Scheme { - case source.ImageScheme: - log.Info("cataloging image") - catalogers = cataloger.ImageCatalogers(cfg) - case source.FileScheme: - log.Info("cataloging file") - catalogers = cataloger.AllCatalogers(cfg) - case source.DirectoryScheme: - log.Info("cataloging directory") - catalogers = cataloger.DirectoryCatalogers(cfg) - default: - return nil, nil, nil, fmt.Errorf("unable to determine cataloger set from scheme=%+v", src.Metadata.Scheme) + // conditionally use the correct set of catalogers based on the scheme (container image or directory) + if cfg.CatalogerGroup == "" { + switch src.Metadata.Scheme { + case source.ImageScheme: + cfg.CatalogerGroup = cataloger.InstallationGroup + case source.FileScheme: + cfg.CatalogerGroup = cataloger.AllGroup + case source.DirectoryScheme: + cfg.CatalogerGroup = cataloger.IndexGroup + default: + return nil, nil, nil, fmt.Errorf("unable to determine cataloger set from scheme=%+v", src.Metadata.Scheme) + } } - catalog, relationships, err := cataloger.Catalog(resolver, release, catalogers...) + groupCatalogers, err := cataloger.SelectGroup(cfg) + if err != nil { + return nil, nil, nil, err + } + enabledCatalogers := cataloger.FilterCatalogers(cfg, groupCatalogers) + + catalog, relationships, err := cataloger.Catalog(resolver, release, enabledCatalogers...) if err != nil { return nil, nil, nil, err } diff --git a/syft/pkg/cataloger/cataloger.go b/syft/pkg/cataloger/cataloger.go index 8e10bf5d626..0d4bbe32552 100644 --- a/syft/pkg/cataloger/cataloger.go +++ b/syft/pkg/cataloger/cataloger.go @@ -6,6 +6,9 @@ catalogers defined in child packages as well as the interface definition to impl package cataloger import ( + "fmt" + + "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" @@ -22,6 +25,20 @@ import ( "github.com/anchore/syft/syft/source" ) +type Group string + +const ( + IndexGroup Group = "index" + InstallationGroup Group = "install" + AllGroup Group = "all" +) + +var AllGroups = []Group{ + IndexGroup, + InstallationGroup, + AllGroup, +} + // Cataloger describes behavior for an object to participate in parsing container image or file system // contents for the purpose of discovering Packages. Each concrete implementation should focus on discovering Packages // for a specific Package Type or ecosystem. @@ -32,8 +49,8 @@ type Cataloger interface { Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) } -// ImageCatalogers returns a slice of locally implemented catalogers that are fit for detecting installations of packages. -func ImageCatalogers(cfg Config) []Cataloger { +// InstallationCatalogers returns a slice of locally implemented catalogers that are fit for detecting installations of packages. +func InstallationCatalogers(cfg Config) []Cataloger { return []Cataloger{ ruby.NewGemSpecCataloger(), python.NewPythonPackageCataloger(), @@ -47,8 +64,8 @@ func ImageCatalogers(cfg Config) []Cataloger { } } -// 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 { +// IndexCatalogers returns a slice of locally implemented catalogers that are fit for detecting packages from index files (and select installations) +func IndexCatalogers(cfg Config) []Cataloger { return []Cataloger{ ruby.NewGemFileLockCataloger(), python.NewPythonIndexCataloger(), @@ -85,3 +102,47 @@ func AllCatalogers(cfg Config) []Cataloger { dart.NewPubspecLockCataloger(), } } + +func SelectGroup(cfg Config) ([]Cataloger, error) { + switch cfg.CatalogerGroup { + case IndexGroup: + log.Info("cataloging index group") + return IndexCatalogers(cfg), nil + case InstallationGroup: + log.Info("cataloging installation group") + return InstallationCatalogers(cfg), nil + case AllGroup: + log.Info("cataloging all group") + return AllCatalogers(cfg), nil + default: + return nil, fmt.Errorf("unknown cataloger group, Group: %s", cfg.CatalogerGroup) + } +} + +func FilterCatalogers(cfg Config, groupCatalogers []Cataloger) []Cataloger { + return filterCatalogers(groupCatalogers, cfg.Catalogers) +} + +func filterCatalogers(catalogers []Cataloger, enabledCatalogers []string) []Cataloger { + // if enable-cataloger is not set, all applicable catalogers are enabled by default + if len(enabledCatalogers) == 0 { + return catalogers + } + var filteredCatalogers []Cataloger + for _, cataloger := range catalogers { + if contains(enabledCatalogers, cataloger.Name()) { + filteredCatalogers = append(filteredCatalogers, cataloger) + } + } + return filteredCatalogers +} + +func contains(catalogers []string, str string) bool { + for _, cataloger := range catalogers { + if cataloger == str || + fmt.Sprintf("%s-cataloger", cataloger) == str { + return true + } + } + return false +} diff --git a/syft/pkg/cataloger/config.go b/syft/pkg/cataloger/config.go index 4e82957c059..e770117085a 100644 --- a/syft/pkg/cataloger/config.go +++ b/syft/pkg/cataloger/config.go @@ -5,7 +5,9 @@ import ( ) type Config struct { - Search SearchConfig + Search SearchConfig + Catalogers []string + CatalogerGroup Group } func DefaultConfig() Config { diff --git a/test/integration/catalog_packages_test.go b/test/integration/catalog_packages_test.go index 6c701a72ef5..8dd03150f45 100644 --- a/test/integration/catalog_packages_test.go +++ b/test/integration/catalog_packages_test.go @@ -22,7 +22,7 @@ func BenchmarkImagePackageCatalogers(b *testing.B) { tarPath := imagetest.GetFixtureImageTarPath(b, fixtureImageName) var pc *pkg.Catalog - for _, c := range cataloger.ImageCatalogers(cataloger.DefaultConfig()) { + for _, c := range cataloger.InstallationCatalogers(cataloger.DefaultConfig()) { // in case of future alteration where state is persisted, assume no dependency is safe to reuse userInput := "docker-archive:" + tarPath sourceInput, err := source.ParseInput(userInput, "", false)