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 }