github.com/anchore/syft@v1.38.2/cmd/syft/internal/options/catalog.go (about)

     1  package options
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  	"strings"
     7  
     8  	"github.com/iancoleman/strcase"
     9  
    10  	"github.com/anchore/clio"
    11  	"github.com/anchore/fangs"
    12  	intFile "github.com/anchore/syft/internal/file"
    13  	"github.com/anchore/syft/internal/log"
    14  	"github.com/anchore/syft/internal/task"
    15  	"github.com/anchore/syft/syft"
    16  	"github.com/anchore/syft/syft/cataloging"
    17  	"github.com/anchore/syft/syft/cataloging/filecataloging"
    18  	"github.com/anchore/syft/syft/cataloging/pkgcataloging"
    19  	"github.com/anchore/syft/syft/file/cataloger/executable"
    20  	"github.com/anchore/syft/syft/file/cataloger/filecontent"
    21  	"github.com/anchore/syft/syft/pkg/cataloger/binary"
    22  	"github.com/anchore/syft/syft/pkg/cataloger/dotnet"
    23  	"github.com/anchore/syft/syft/pkg/cataloger/golang"
    24  	"github.com/anchore/syft/syft/pkg/cataloger/java"
    25  	"github.com/anchore/syft/syft/pkg/cataloger/javascript"
    26  	"github.com/anchore/syft/syft/pkg/cataloger/kernel"
    27  	"github.com/anchore/syft/syft/pkg/cataloger/nix"
    28  	"github.com/anchore/syft/syft/pkg/cataloger/python"
    29  	"github.com/anchore/syft/syft/source"
    30  )
    31  
    32  type Catalog struct {
    33  	// high-level cataloger configuration
    34  	Catalogers        []string            `yaml:"-" json:"catalogers" mapstructure:"catalogers"` // deprecated and not shown in yaml output
    35  	DefaultCatalogers []string            `yaml:"default-catalogers" json:"default-catalogers" mapstructure:"default-catalogers"`
    36  	SelectCatalogers  []string            `yaml:"select-catalogers" json:"select-catalogers" mapstructure:"select-catalogers"`
    37  	Package           packageConfig       `yaml:"package" json:"package" mapstructure:"package"`
    38  	License           licenseConfig       `yaml:"license" json:"license" mapstructure:"license"`
    39  	File              fileConfig          `yaml:"file" json:"file" mapstructure:"file"`
    40  	Scope             string              `yaml:"scope" json:"scope" mapstructure:"scope"`
    41  	Parallelism       int                 `yaml:"parallelism" json:"parallelism" mapstructure:"parallelism"` // the number of catalog workers to run in parallel
    42  	Relationships     relationshipsConfig `yaml:"relationships" json:"relationships" mapstructure:"relationships"`
    43  	Compliance        complianceConfig    `yaml:"compliance" json:"compliance" mapstructure:"compliance"`
    44  	Enrich            []string            `yaml:"enrich" json:"enrich" mapstructure:"enrich"`
    45  
    46  	// ecosystem-specific cataloger configuration
    47  	Dotnet      dotnetConfig      `yaml:"dotnet" json:"dotnet" mapstructure:"dotnet"`
    48  	Golang      golangConfig      `yaml:"golang" json:"golang" mapstructure:"golang"`
    49  	Java        javaConfig        `yaml:"java" json:"java" mapstructure:"java"`
    50  	JavaScript  javaScriptConfig  `yaml:"javascript" json:"javascript" mapstructure:"javascript"`
    51  	LinuxKernel linuxKernelConfig `yaml:"linux-kernel" json:"linux-kernel" mapstructure:"linux-kernel"`
    52  	Nix         nixConfig         `yaml:"nix" json:"nix" mapstructure:"nix"`
    53  	Python      pythonConfig      `yaml:"python" json:"python" mapstructure:"python"`
    54  
    55  	// configuration for the source (the subject being analyzed)
    56  	Registry   registryConfig `yaml:"registry" json:"registry" mapstructure:"registry"`
    57  	From       []string       `yaml:"from" json:"from" mapstructure:"from"`
    58  	Platform   string         `yaml:"platform" json:"platform" mapstructure:"platform"`
    59  	Source     sourceConfig   `yaml:"source" json:"source" mapstructure:"source"`
    60  	Exclusions []string       `yaml:"exclude" json:"exclude" mapstructure:"exclude"`
    61  
    62  	// configuration for inclusion of unknown information within elements
    63  	Unknowns unknownsConfig `yaml:"unknowns" mapstructure:"unknowns"`
    64  }
    65  
    66  var _ interface {
    67  	clio.FlagAdder
    68  	clio.PostLoader
    69  	clio.FieldDescriber
    70  } = (*Catalog)(nil)
    71  
    72  func DefaultCatalog() Catalog {
    73  	cfg := syft.DefaultCreateSBOMConfig()
    74  	return Catalog{
    75  		Compliance:    defaultComplianceConfig(),
    76  		Scope:         source.SquashedScope.String(),
    77  		Package:       defaultPackageConfig(),
    78  		License:       defaultLicenseConfig(),
    79  		LinuxKernel:   defaultLinuxKernelConfig(),
    80  		Nix:           defaultNixConfig(),
    81  		Dotnet:        defaultDotnetConfig(),
    82  		Golang:        defaultGolangConfig(),
    83  		Java:          defaultJavaConfig(),
    84  		File:          defaultFileConfig(),
    85  		Relationships: defaultRelationshipsConfig(),
    86  		Unknowns:      defaultUnknowns(),
    87  		Source:        defaultSourceConfig(),
    88  		Parallelism:   cfg.Parallelism,
    89  	}
    90  }
    91  
    92  func (cfg Catalog) ToSBOMConfig(id clio.Identification) *syft.CreateSBOMConfig {
    93  	return syft.DefaultCreateSBOMConfig().
    94  		WithTool(id.Name, id.Version).
    95  		WithParallelism(cfg.Parallelism).
    96  		WithRelationshipsConfig(cfg.ToRelationshipsConfig()).
    97  		WithComplianceConfig(cfg.ToComplianceConfig()).
    98  		WithUnknownsConfig(cfg.ToUnknownsConfig()).
    99  		WithSearchConfig(cfg.ToSearchConfig()).
   100  		WithPackagesConfig(cfg.ToPackagesConfig()).
   101  		WithLicenseConfig(cfg.ToLicenseConfig()).
   102  		WithFilesConfig(cfg.ToFilesConfig()).
   103  		WithCatalogerSelection(
   104  			cataloging.NewSelectionRequest().
   105  				WithDefaults(cfg.DefaultCatalogers...).
   106  				WithExpression(cfg.SelectCatalogers...),
   107  		)
   108  }
   109  
   110  func (cfg Catalog) ToSearchConfig() cataloging.SearchConfig {
   111  	return cataloging.SearchConfig{
   112  		Scope: source.ParseScope(cfg.Scope),
   113  	}
   114  }
   115  
   116  func (cfg Catalog) ToRelationshipsConfig() cataloging.RelationshipsConfig {
   117  	return cataloging.RelationshipsConfig{
   118  		PackageFileOwnership:        cfg.Relationships.PackageFileOwnership,
   119  		PackageFileOwnershipOverlap: cfg.Relationships.PackageFileOwnershipOverlap,
   120  		// note: this option was surfaced in the syft application configuration before this relationships section was added
   121  		ExcludeBinaryPackagesWithFileOwnershipOverlap: cfg.Package.ExcludeBinaryOverlapByOwnership,
   122  	}
   123  }
   124  
   125  func (cfg Catalog) ToComplianceConfig() cataloging.ComplianceConfig {
   126  	return cataloging.ComplianceConfig{
   127  		MissingName:    cfg.Compliance.MissingName,
   128  		MissingVersion: cfg.Compliance.MissingVersion,
   129  	}
   130  }
   131  
   132  func (cfg Catalog) ToUnknownsConfig() cataloging.UnknownsConfig {
   133  	return cataloging.UnknownsConfig{
   134  		IncludeExecutablesWithoutPackages: cfg.Unknowns.ExecutablesWithoutPackages,
   135  		IncludeUnexpandedArchives:         cfg.Unknowns.UnexpandedArchives,
   136  	}
   137  }
   138  
   139  func (cfg Catalog) ToFilesConfig() filecataloging.Config {
   140  	hashers, err := intFile.Hashers(cfg.File.Metadata.Digests...)
   141  	if err != nil {
   142  		log.WithFields("error", err).Warn("unable to configure file hashers")
   143  	}
   144  
   145  	return filecataloging.Config{
   146  		Selection: cfg.File.Metadata.Selection,
   147  		Hashers:   hashers,
   148  		Content: filecontent.Config{
   149  			Globs:              cfg.File.Content.Globs,
   150  			SkipFilesAboveSize: cfg.File.Content.SkipFilesAboveSize,
   151  		},
   152  		Executable: executable.Config{
   153  			MIMETypes: executable.DefaultConfig().MIMETypes,
   154  			Globs:     cfg.File.Executable.Globs,
   155  		},
   156  	}
   157  }
   158  
   159  func (cfg Catalog) ToLicenseConfig() cataloging.LicenseConfig {
   160  	return cataloging.LicenseConfig{
   161  		IncludeContent: cfg.License.Content,
   162  		Coverage:       cfg.License.Coverage,
   163  	}
   164  }
   165  
   166  func (cfg Catalog) ToPackagesConfig() pkgcataloging.Config {
   167  	archiveSearch := cataloging.ArchiveSearchConfig{
   168  		IncludeIndexedArchives:   cfg.Package.SearchIndexedArchives,
   169  		IncludeUnindexedArchives: cfg.Package.SearchUnindexedArchives,
   170  	}
   171  	return pkgcataloging.Config{
   172  		Binary: binary.DefaultClassifierCatalogerConfig(),
   173  		Dotnet: dotnet.DefaultCatalogerConfig().
   174  			WithDepPackagesMustHaveDLL(cfg.Dotnet.DepPackagesMustHaveDLL).
   175  			WithDepPackagesMustClaimDLL(cfg.Dotnet.DepPackagesMustClaimDLL).
   176  			WithPropagateDLLClaimsToParents(cfg.Dotnet.PropagateDLLClaimsToParents).
   177  			WithRelaxDLLClaimsWhenBundlingDetected(cfg.Dotnet.RelaxDLLClaimsWhenBundlingDetected),
   178  		Golang: golang.DefaultCatalogerConfig().
   179  			WithSearchLocalModCacheLicenses(*multiLevelOption(false, enrichmentEnabled(cfg.Enrich, task.Go, task.Golang), cfg.Golang.SearchLocalModCacheLicenses)).
   180  			WithLocalModCacheDir(cfg.Golang.LocalModCacheDir).
   181  			WithSearchLocalVendorLicenses(*multiLevelOption(false, enrichmentEnabled(cfg.Enrich, task.Go, task.Golang), cfg.Golang.SearchLocalVendorLicenses)).
   182  			WithLocalVendorDir(cfg.Golang.LocalVendorDir).
   183  			WithSearchRemoteLicenses(*multiLevelOption(false, enrichmentEnabled(cfg.Enrich, task.Go, task.Golang), cfg.Golang.SearchRemoteLicenses)).
   184  			WithProxy(cfg.Golang.Proxy).
   185  			WithNoProxy(cfg.Golang.NoProxy).
   186  			WithMainModuleVersion(
   187  				golang.DefaultMainModuleVersionConfig().
   188  					WithFromContents(cfg.Golang.MainModuleVersion.FromContents).
   189  					WithFromBuildSettings(cfg.Golang.MainModuleVersion.FromBuildSettings).
   190  					WithFromLDFlags(cfg.Golang.MainModuleVersion.FromLDFlags),
   191  			),
   192  		JavaScript: javascript.DefaultCatalogerConfig().
   193  			WithIncludeDevDependencies(*multiLevelOption(false, cfg.JavaScript.IncludeDevDependencies)).
   194  			WithSearchRemoteLicenses(*multiLevelOption(false, enrichmentEnabled(cfg.Enrich, task.JavaScript, task.Node, task.NPM), cfg.JavaScript.SearchRemoteLicenses)).
   195  			WithNpmBaseURL(cfg.JavaScript.NpmBaseURL),
   196  		LinuxKernel: kernel.LinuxKernelCatalogerConfig{
   197  			CatalogModules: cfg.LinuxKernel.CatalogModules,
   198  		},
   199  		Nix: nix.DefaultConfig().
   200  			WithCaptureOwnedFiles(cfg.Nix.CaptureOwnedFiles),
   201  		Python: python.DefaultCatalogerConfig().
   202  			WithSearchRemoteLicenses(*multiLevelOption(false, enrichmentEnabled(cfg.Enrich, task.Python), cfg.Python.SearchRemoteLicenses)).
   203  			WithPypiBaseURL(cfg.Python.PypiBaseURL).
   204  			WithGuessUnpinnedRequirements(*multiLevelOption(false, enrichmentEnabled(cfg.Enrich, task.Python), cfg.Python.GuessUnpinnedRequirements)),
   205  		JavaArchive: java.DefaultArchiveCatalogerConfig().
   206  			WithUseMavenLocalRepository(*multiLevelOption(false, enrichmentEnabled(cfg.Enrich, task.Java, task.Maven), cfg.Java.UseMavenLocalRepository)).
   207  			WithMavenLocalRepositoryDir(cfg.Java.MavenLocalRepositoryDir).
   208  			WithUseNetwork(*multiLevelOption(false, enrichmentEnabled(cfg.Enrich, task.Java, task.Maven), cfg.Java.UseNetwork)).
   209  			WithMavenBaseURL(cfg.Java.MavenURL).
   210  			WithArchiveTraversal(archiveSearch, cfg.Java.MaxParentRecursiveDepth).
   211  			WithResolveTransitiveDependencies(cfg.Java.ResolveTransitiveDependencies),
   212  	}
   213  }
   214  
   215  func (cfg *Catalog) AddFlags(flags clio.FlagSet) {
   216  	var validScopeValues []string
   217  	for _, scope := range source.AllScopes {
   218  		validScopeValues = append(validScopeValues, strcase.ToDelimited(string(scope), '-'))
   219  	}
   220  	flags.StringVarP(&cfg.Scope, "scope", "s",
   221  		fmt.Sprintf("selection of layers to catalog, options=%v", validScopeValues))
   222  
   223  	flags.StringArrayVarP(&cfg.From, "from", "",
   224  		"specify the source behavior to use (e.g. docker, registry, oci-dir, ...)")
   225  
   226  	flags.StringVarP(&cfg.Platform, "platform", "",
   227  		"an optional platform specifier for container image sources (e.g. 'linux/arm64', 'linux/arm64/v8', 'arm64', 'linux')")
   228  
   229  	flags.StringArrayVarP(&cfg.Exclusions, "exclude", "",
   230  		"exclude paths from being scanned using a glob expression")
   231  
   232  	flags.StringArrayVarP(&cfg.Catalogers, "catalogers", "",
   233  		"enable one or more package catalogers")
   234  
   235  	flags.IntVarP(&cfg.Parallelism, "parallelism", "",
   236  		"number of cataloger workers to run in parallel")
   237  
   238  	if pfp, ok := flags.(fangs.PFlagSetProvider); ok {
   239  		if err := pfp.PFlagSet().MarkDeprecated("catalogers", "use: override-default-catalogers and select-catalogers"); err != nil {
   240  			panic(err)
   241  		}
   242  	} else {
   243  		panic("unable to mark flags as deprecated")
   244  	}
   245  
   246  	flags.StringArrayVarP(&cfg.DefaultCatalogers, "override-default-catalogers", "",
   247  		"set the base set of catalogers to use (defaults to 'image' or 'directory' depending on the scan source)")
   248  
   249  	flags.StringArrayVarP(&cfg.SelectCatalogers, "select-catalogers", "",
   250  		"add, remove, and filter the catalogers to be used")
   251  
   252  	flags.StringArrayVarP(&cfg.Enrich, "enrich", "",
   253  		fmt.Sprintf("enable package data enrichment from local and online sources (options: %s)", strings.Join(publicisedEnrichmentOptions, ", ")))
   254  
   255  	flags.StringVarP(&cfg.Source.Name, "source-name", "",
   256  		"set the name of the target being analyzed")
   257  
   258  	flags.StringVarP(&cfg.Source.Version, "source-version", "",
   259  		"set the version of the target being analyzed")
   260  
   261  	flags.StringVarP(&cfg.Source.BasePath, "base-path", "",
   262  		"base directory for scanning, no links will be followed above this directory, and all paths will be reported relative to this directory")
   263  
   264  	flags.StringVarP(&cfg.Source.Supplier, "source-supplier", "",
   265  		"the organization that supplied the component, which often may be the manufacturer, distributor, or repackager")
   266  }
   267  
   268  func (cfg *Catalog) DescribeFields(descriptions fangs.FieldDescriptionSet) {
   269  	descriptions.Add(&cfg.Parallelism, `number of cataloger workers to run in parallel
   270  by default, when set to 0: this will be based on runtime.NumCPU * 4, if set to less than 0 it will be unbounded`)
   271  
   272  	descriptions.Add(&cfg.Enrich, fmt.Sprintf(`Enable data enrichment operations, which can utilize services such as Maven Central and NPM.
   273  By default all enrichment is disabled, use: all to enable everything.
   274  Available options are: %s`, strings.Join(publicisedEnrichmentOptions, ", ")))
   275  }
   276  
   277  func (cfg *Catalog) PostLoad() error {
   278  	usingLegacyCatalogers := len(cfg.Catalogers) > 0
   279  	usingNewCatalogers := len(cfg.DefaultCatalogers) > 0 || len(cfg.SelectCatalogers) > 0
   280  
   281  	if usingLegacyCatalogers && usingNewCatalogers {
   282  		return fmt.Errorf("cannot use both 'catalogers' and 'select-catalogers'/'default-catalogers' flags")
   283  	}
   284  
   285  	cfg.From = Flatten(cfg.From)
   286  
   287  	cfg.Catalogers = FlattenAndSort(cfg.Catalogers)
   288  	cfg.DefaultCatalogers = FlattenAndSort(cfg.DefaultCatalogers)
   289  	cfg.SelectCatalogers = FlattenAndSort(cfg.SelectCatalogers)
   290  	cfg.Enrich = FlattenAndSort(cfg.Enrich)
   291  
   292  	// for backwards compatibility
   293  	cfg.DefaultCatalogers = append(cfg.DefaultCatalogers, cfg.Catalogers...)
   294  
   295  	s := source.ParseScope(cfg.Scope)
   296  	if s == source.UnknownScope {
   297  		return fmt.Errorf("bad scope value %q", cfg.Scope)
   298  	}
   299  
   300  	// the binary package exclusion code depends on the file overlap relationships being created upstream in processing
   301  	if !cfg.Relationships.PackageFileOwnershipOverlap && cfg.Package.ExcludeBinaryOverlapByOwnership {
   302  		return fmt.Errorf("cannot enable exclude-binary-overlap-by-ownership without enabling package-file-ownership-overlap")
   303  	}
   304  
   305  	return nil
   306  }
   307  
   308  func Flatten(commaSeparatedEntries []string) []string {
   309  	var out []string
   310  	for _, v := range commaSeparatedEntries {
   311  		for _, s := range strings.Split(v, ",") {
   312  			out = append(out, strings.TrimSpace(s))
   313  		}
   314  	}
   315  	return out
   316  }
   317  
   318  func FlattenAndSort(commaSeparatedEntries []string) []string {
   319  	out := Flatten(commaSeparatedEntries)
   320  	sort.Strings(out)
   321  	return out
   322  }
   323  
   324  var publicisedEnrichmentOptions = []string{
   325  	"all",
   326  	task.Golang,
   327  	task.Java,
   328  	task.JavaScript,
   329  	task.Python,
   330  }
   331  
   332  func enrichmentEnabled(enrichDirectives []string, features ...string) *bool {
   333  	if len(enrichDirectives) == 0 {
   334  		return nil
   335  	}
   336  
   337  	enabled := func(features ...string) *bool {
   338  		for _, directive := range enrichDirectives {
   339  			enable := true
   340  			directive = strings.TrimPrefix(directive, "+") // +java and java are equivalent
   341  			if strings.HasPrefix(directive, "-") {
   342  				directive = directive[1:]
   343  				enable = false
   344  			}
   345  			for _, feature := range features {
   346  				if directive == feature {
   347  					return &enable
   348  				}
   349  			}
   350  		}
   351  		return nil
   352  	}
   353  
   354  	enableAll := enabled("all")
   355  	disableAll := enabled("none")
   356  
   357  	if disableAll != nil && *disableAll {
   358  		if enableAll != nil {
   359  			log.Warn("you have specified to both enable and disable all enrichment functionality, defaulting to disabled")
   360  		}
   361  		enableAll = ptr(false)
   362  	}
   363  
   364  	// check for explicit enable/disable of feature names
   365  	for _, feat := range features {
   366  		enableFeature := enabled(feat)
   367  		if enableFeature != nil {
   368  			return enableFeature
   369  		}
   370  	}
   371  
   372  	return enableAll
   373  }
   374  
   375  func ptr[T any](val T) *T {
   376  	return &val
   377  }