mirror of
https://github.com/anchore/syft.git
synced 2026-10-13 22:57:19 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7cf8b0b26 | ||
|
|
44a6e00f7a | ||
|
|
6c8102bf28 | ||
|
|
7789506dc6 | ||
|
|
fa03723617 | ||
|
|
2946813a74 |
@@ -29,7 +29,7 @@ notarize() {
|
||||
fi
|
||||
|
||||
# install gon
|
||||
which gon || (brew tap mitchellh/gon && brew install mitchellh/gon/gon)
|
||||
which gon || (go install github.com/mitchellh/gon/cmd/gon@latest)
|
||||
|
||||
# create config (note: json via stdin with gon is broken, can only use HCL from file)
|
||||
hcl_file=$(mktemp).hcl
|
||||
|
||||
@@ -211,13 +211,13 @@ integration-fingerprint:
|
||||
.PHONY: java-packages-fingerprint
|
||||
java-packages-fingerprint:
|
||||
$(call title,Java test fixture fingerprint)
|
||||
cd syft/cataloger/packages/java/test-fixtures/java-builds && \
|
||||
cd syft/pkg/cataloger/java/test-fixtures/java-builds && \
|
||||
make packages.fingerprint
|
||||
|
||||
.PHONY: fixtures
|
||||
fixtures:
|
||||
$(call title,Generating test fixtures)
|
||||
cd syft/cataloger/packages/java/test-fixtures/java-builds && make
|
||||
cd syft/pkg/cataloger/java/test-fixtures/java-builds && make
|
||||
|
||||
.PHONY: generate-json-schema
|
||||
generate-json-schema: ## Generate a new json schema
|
||||
|
||||
+8
-19
@@ -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)
|
||||
}
|
||||
|
||||
switch si.Type {
|
||||
case source.ImageType, source.UnknownType:
|
||||
switch si.Scheme {
|
||||
case source.ImageScheme, source.UnknownScheme:
|
||||
// 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.Type = source.ImageType
|
||||
si.Scheme = source.ImageScheme
|
||||
default:
|
||||
return fmt.Errorf("attest command can only be used with image sources but discovered %q when given %q", si.Type, userInput)
|
||||
return fmt.Errorf("attest command can only be used with image sources but discovered %q when given %q", si.Scheme, userInput)
|
||||
}
|
||||
|
||||
// if the original detection was from a local daemon we want to short circuit
|
||||
@@ -188,37 +188,26 @@ func attestExec(ctx context.Context, _ *cobra.Command, args []string) error {
|
||||
)
|
||||
}
|
||||
|
||||
func attestationExecWorker(si source.Input, format sbom.Format, predicateType string, sv *sign.SignerVerifier) <-chan error {
|
||||
func attestationExecWorker(sourceInput source.Input, format sbom.Format, predicateType string, sv *sign.SignerVerifier) <-chan error {
|
||||
errs := make(chan error)
|
||||
go func() {
|
||||
defer close(errs)
|
||||
|
||||
catalogingOpts, err := appConfig.ToCatalogingOptions()
|
||||
if err != nil {
|
||||
errs <- err
|
||||
return
|
||||
}
|
||||
|
||||
src, cleanup, err := source.NewFromRegistry(si, appConfig.Registry.ToOptions(), appConfig.Exclusions)
|
||||
src, cleanup, err := source.NewFromRegistry(sourceInput, appConfig.Registry.ToOptions(), appConfig.Exclusions)
|
||||
if cleanup != nil {
|
||||
defer cleanup()
|
||||
}
|
||||
if err != nil {
|
||||
errs <- fmt.Errorf("failed to construct source from user input %q: %w", si.UserInput, err)
|
||||
errs <- fmt.Errorf("failed to construct source from user input %q: %w", sourceInput.UserInput, err)
|
||||
return
|
||||
}
|
||||
|
||||
s, err := syft.Catalog(src, catalogingOpts...)
|
||||
s, err := generateSBOM(src, errs)
|
||||
if err != nil {
|
||||
errs <- err
|
||||
return
|
||||
}
|
||||
|
||||
if s == nil {
|
||||
errs <- fmt.Errorf("no SBOM produced for %q", si.UserInput)
|
||||
return
|
||||
}
|
||||
|
||||
sbomBytes, err := syft.Encode(*s, format)
|
||||
if err != nil {
|
||||
errs <- err
|
||||
|
||||
@@ -22,6 +22,8 @@ func formatAliases(ids ...sbom.FormatID) (aliases []string) {
|
||||
aliases = append(aliases, "cyclonedx-xml")
|
||||
case syft.CycloneDxJSONFormatID:
|
||||
aliases = append(aliases, "cyclonedx-json")
|
||||
case syft.GitHubID:
|
||||
aliases = append(aliases, "github", "github-json")
|
||||
default:
|
||||
aliases = append(aliases, string(id))
|
||||
}
|
||||
|
||||
+48
-21
@@ -13,8 +13,11 @@ import (
|
||||
"github.com/anchore/syft/internal/formats/table"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/ui"
|
||||
"github.com/anchore/syft/internal/version"
|
||||
"github.com/anchore/syft/syft"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/event"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/pkg/profile"
|
||||
@@ -94,7 +97,7 @@ func init() {
|
||||
func setPackageFlags(flags *pflag.FlagSet) {
|
||||
// Formatting & Input options //////////////////////////////////////////////
|
||||
flags.StringP(
|
||||
"scope", "s", syft.DefaultCatalogingConfig().DefaultScope.String(),
|
||||
"scope", "s", cataloger.DefaultSearchConfig().Scope.String(),
|
||||
fmt.Sprintf("selection of layers to catalog, options=%v", source.AllScopes))
|
||||
|
||||
flags.StringArrayP(
|
||||
@@ -112,12 +115,6 @@ func setPackageFlags(flags *pflag.FlagSet) {
|
||||
"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 //////////////////////////////////////////////////////////
|
||||
flags.StringP(
|
||||
"host", "H", "",
|
||||
@@ -171,11 +168,7 @@ func bindExclusivePackagesConfigOptions(flags *pflag.FlagSet) error {
|
||||
|
||||
// note: output is not included since this configuration option is shared between multiple subcommands
|
||||
|
||||
if err := viper.BindPFlag("scope", flags.Lookup("scope")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := viper.BindPFlag("catalogers", flags.Lookup("cataloger")); err != nil {
|
||||
if err := viper.BindPFlag("package.cataloger.scope", flags.Lookup("scope")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -267,17 +260,42 @@ func isVerbose() (result bool) {
|
||||
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 {
|
||||
errs := make(chan error)
|
||||
go func() {
|
||||
defer close(errs)
|
||||
|
||||
catalogingOpts, err := appConfig.ToCatalogingOptions()
|
||||
if err != nil {
|
||||
errs <- err
|
||||
return
|
||||
}
|
||||
|
||||
src, cleanup, err := source.New(si, appConfig.Registry.ToOptions(), appConfig.Exclusions)
|
||||
if cleanup != nil {
|
||||
defer cleanup()
|
||||
@@ -287,7 +305,7 @@ func packagesExecWorker(si source.Input, writer sbom.Writer) <-chan error {
|
||||
return
|
||||
}
|
||||
|
||||
s, err := syft.Catalog(src, catalogingOpts...)
|
||||
s, err := generateSBOM(src, errs)
|
||||
if err != nil {
|
||||
errs <- err
|
||||
return
|
||||
@@ -295,7 +313,6 @@ func packagesExecWorker(si source.Input, writer sbom.Writer) <-chan error {
|
||||
|
||||
if s == nil {
|
||||
errs <- fmt.Errorf("no SBOM produced for %q", si.UserInput)
|
||||
return
|
||||
}
|
||||
|
||||
if appConfig.Anchore.Host != "" {
|
||||
@@ -313,10 +330,20 @@ func packagesExecWorker(si source.Input, writer sbom.Writer) <-chan error {
|
||||
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 {
|
||||
log.Infof("uploading results to %s", appConfig.Anchore.Host)
|
||||
|
||||
if src.Metadata.Scheme != source.ImageType {
|
||||
if src.Metadata.Scheme != source.ImageScheme {
|
||||
return fmt.Errorf("unable to upload results: only images are supported")
|
||||
}
|
||||
|
||||
|
||||
+23
-16
@@ -2,7 +2,6 @@ package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/anchore/syft/syft"
|
||||
"os"
|
||||
|
||||
"github.com/anchore/stereoscope"
|
||||
@@ -11,6 +10,8 @@ import (
|
||||
"github.com/anchore/syft/internal/formats/syftjson"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"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/sbom"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
@@ -105,13 +106,11 @@ func powerUserExecWorker(userInput string, writer sbom.Writer) <-chan error {
|
||||
go func() {
|
||||
defer close(errs)
|
||||
|
||||
// TODO: replace
|
||||
//appConfig.Secrets.Cataloger.Enabled = true
|
||||
//appConfig.FileMetadata.Cataloger.Enabled = true
|
||||
//appConfig.FileContents.Cataloger.Enabled = true
|
||||
//appConfig.FileClassification.Cataloger.Enabled = true
|
||||
|
||||
catalogingOpts, err := appConfig.ToCatalogingOptions()
|
||||
appConfig.Secrets.Cataloger.Enabled = true
|
||||
appConfig.FileMetadata.Cataloger.Enabled = true
|
||||
appConfig.FileContents.Cataloger.Enabled = true
|
||||
appConfig.FileClassification.Cataloger.Enabled = true
|
||||
tasks, err := tasks()
|
||||
if err != nil {
|
||||
errs <- err
|
||||
return
|
||||
@@ -132,20 +131,28 @@ func powerUserExecWorker(userInput string, writer sbom.Writer) <-chan error {
|
||||
defer cleanup()
|
||||
}
|
||||
|
||||
s, err := syft.Catalog(src, catalogingOpts...)
|
||||
if err != nil {
|
||||
errs <- err
|
||||
return
|
||||
s := sbom.SBOM{
|
||||
Source: src.Metadata,
|
||||
Descriptor: sbom.Descriptor{
|
||||
Name: internal.ApplicationName,
|
||||
Version: version.FromBuild().Version,
|
||||
Configuration: appConfig,
|
||||
},
|
||||
}
|
||||
|
||||
if s == nil {
|
||||
errs <- fmt.Errorf("no SBOM produced for %q", si.UserInput)
|
||||
return
|
||||
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...)...)
|
||||
|
||||
bus.Publish(partybus.Event{
|
||||
Type: event.Exit,
|
||||
Value: func() error { return writer.Write(*s) },
|
||||
Value: func() error { return writer.Write(s) },
|
||||
})
|
||||
}()
|
||||
|
||||
|
||||
+231
@@ -0,0 +1,231 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -171,7 +171,7 @@ require (
|
||||
github.com/hashicorp/go-uuid v1.0.2 // indirect
|
||||
github.com/hashicorp/go-version v1.4.0 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||
github.com/hashicorp/vault/api v1.3.1 // indirect
|
||||
github.com/hashicorp/vault/api v1.4.1 // indirect
|
||||
github.com/hashicorp/vault/sdk v0.4.1 // indirect
|
||||
github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
|
||||
@@ -1349,8 +1349,9 @@ github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/J
|
||||
github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
|
||||
github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
|
||||
github.com/hashicorp/vault/api v1.3.0/go.mod h1:EabNQLI0VWbWoGlA+oBLC8PXmR9D60aUVgQGvangFWQ=
|
||||
github.com/hashicorp/vault/api v1.3.1 h1:pkDkcgTh47PRjY1NEFeofqR4W/HkNUi9qIakESO2aRM=
|
||||
github.com/hashicorp/vault/api v1.3.1/go.mod h1:QeJoWxMFt+MsuWcYhmwRLwKEXrjwAFFywzhptMsTIUw=
|
||||
github.com/hashicorp/vault/api v1.4.1 h1:mWLfPT0RhxBitjKr6swieCEP2v5pp/M//t70S3kMLRo=
|
||||
github.com/hashicorp/vault/api v1.4.1/go.mod h1:LkMdrZnWNrFaQyYYazWVn7KshilfDidgVBq6YiTq/bM=
|
||||
github.com/hashicorp/vault/sdk v0.3.0/go.mod h1:aZ3fNuL5VNydQk8GcLJ2TV8YCRVvyaakYkhZRoVuhj0=
|
||||
github.com/hashicorp/vault/sdk v0.4.1 h1:3SaHOJY687jY1fnB61PtL0cOkKItphrbLmux7T92HBo=
|
||||
github.com/hashicorp/vault/sdk v0.4.1/go.mod h1:aZ3fNuL5VNydQk8GcLJ2TV8YCRVvyaakYkhZRoVuhj0=
|
||||
|
||||
@@ -6,15 +6,14 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/anchore/syft/internal/bus"
|
||||
"github.com/anchore/syft/syft/event"
|
||||
"github.com/wagoodman/go-partybus"
|
||||
"github.com/wagoodman/go-progress"
|
||||
|
||||
"github.com/anchore/client-go/pkg/external"
|
||||
"github.com/anchore/stereoscope/pkg/image"
|
||||
"github.com/anchore/syft/internal/bus"
|
||||
"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-progress"
|
||||
)
|
||||
|
||||
type ImportConfig struct {
|
||||
|
||||
@@ -4,8 +4,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/anchore/syft/syft/cpe"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -24,7 +22,7 @@ import (
|
||||
"github.com/wagoodman/go-progress"
|
||||
)
|
||||
|
||||
func must(c cpe.CPE, e error) cpe.CPE {
|
||||
func must(c pkg.CPE, e error) pkg.CPE {
|
||||
if e != nil {
|
||||
panic(e)
|
||||
}
|
||||
@@ -54,13 +52,13 @@ func (m *mockPackageSBOMImportAPI) ImportImagePackages(ctx context.Context, sess
|
||||
func sbomFixture() sbom.SBOM {
|
||||
return sbom.SBOM{
|
||||
Artifacts: sbom.Artifacts{
|
||||
Packages: pkg.NewCollection(pkg.Package{
|
||||
PackageCatalog: pkg.NewCatalog(pkg.Package{
|
||||
Name: "name",
|
||||
Version: "version",
|
||||
FoundBy: "foundBy",
|
||||
Locations: []file.Location{
|
||||
Locations: []source.Location{
|
||||
{
|
||||
Coordinates: file.Coordinates{
|
||||
Coordinates: source.Coordinates{
|
||||
RealPath: "path",
|
||||
FileSystemID: "layerID",
|
||||
},
|
||||
@@ -69,8 +67,8 @@ func sbomFixture() sbom.SBOM {
|
||||
Licenses: []string{"license"},
|
||||
Language: pkg.Python,
|
||||
Type: pkg.PythonPkg,
|
||||
CPEs: []cpe.CPE{
|
||||
must(cpe.New("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*")),
|
||||
CPEs: []pkg.CPE{
|
||||
must(pkg.NewCPE("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*")),
|
||||
},
|
||||
PURL: "purl",
|
||||
MetadataType: pkg.PythonPackageMetadataType,
|
||||
@@ -104,13 +102,13 @@ func sbomFixture() sbom.SBOM {
|
||||
},
|
||||
Relationships: []artifact.Relationship{
|
||||
{
|
||||
From: file.NewLocation("/place1"),
|
||||
To: file.NewLocation("/place2"),
|
||||
From: source.NewLocation("/place1"),
|
||||
To: source.NewLocation("/place2"),
|
||||
Type: artifact.ContainsRelationship,
|
||||
},
|
||||
},
|
||||
Source: source.Metadata{
|
||||
Scheme: source.ImageType,
|
||||
Scheme: source.ImageScheme,
|
||||
ImageMetadata: source.ImageMetadata{
|
||||
UserInput: "user-in",
|
||||
Layers: nil,
|
||||
|
||||
@@ -3,16 +3,12 @@ package config
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"path"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/adrg/xdg"
|
||||
"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/sirupsen/logrus"
|
||||
"github.com/spf13/viper"
|
||||
@@ -21,7 +17,6 @@ import (
|
||||
|
||||
var ErrApplicationConfigNotFound = fmt.Errorf("application config not found")
|
||||
|
||||
// TODO: set all catalogers when this is set
|
||||
var catalogerEnabledDefault = false
|
||||
|
||||
type defaultValueLoader interface {
|
||||
@@ -34,66 +29,24 @@ type parser interface {
|
||||
|
||||
// Application is the main syft application configuration.
|
||||
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)
|
||||
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
|
||||
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
|
||||
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)
|
||||
Catalogers []string `yaml:"catalogers" json:"catalogers" mapstructure:"catalogers"`
|
||||
Scope string `yaml:"scope" json:"scope" mapstructure:"scope"`
|
||||
Dev development `yaml:"dev" json:"dev" mapstructure:"dev"`
|
||||
Log logging `yaml:"log" json:"log" mapstructure:"log"` // all logging-related options
|
||||
Package pkg `yaml:"package" json:"package" mapstructure:"package"`
|
||||
FileMetadata fileMetadata `yaml:"file-metadata" json:"file-metadata" mapstructure:"file-metadata"`
|
||||
FileContents fileContents `yaml:"file-contents" json:"file-contents" mapstructure:"file-contents"`
|
||||
Secrets secretsCfg `yaml:"secrets" json:"secrets" mapstructure:"secrets"`
|
||||
Registry registry `yaml:"registry" json:"registry" mapstructure:"registry"`
|
||||
Exclusions []string `yaml:"exclude" json:"exclude" mapstructure:"exclude"`
|
||||
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
|
||||
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
|
||||
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)
|
||||
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
|
||||
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"`
|
||||
Log logging `yaml:"log" json:"log" mapstructure:"log"` // all logging-related options
|
||||
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"`
|
||||
FileContents fileContents `yaml:"file-contents" json:"file-contents" mapstructure:"file-contents"`
|
||||
Secrets secrets `yaml:"secrets" json:"secrets" mapstructure:"secrets"`
|
||||
Registry registry `yaml:"registry" json:"registry" mapstructure:"registry"`
|
||||
Exclusions []string `yaml:"exclude" json:"exclude" mapstructure:"exclude"`
|
||||
Attest attest `yaml:"attest" json:"attest" mapstructure:"attest"`
|
||||
Platform string `yaml:"platform" json:"platform" mapstructure:"platform"`
|
||||
}
|
||||
|
||||
// PowerUserCatalogerEnabledDefault switches all catalogers to be enabled when running power-user command
|
||||
@@ -147,15 +100,6 @@ func (cfg Application) loadDefaultValues(v *viper.Viper) {
|
||||
}
|
||||
|
||||
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
|
||||
for _, optionFn := range []func() error{
|
||||
cfg.parseUploadOptions,
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
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
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/cataloger/files/filecontents"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/internal/file"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type fileContents struct {
|
||||
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"`
|
||||
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"`
|
||||
Globs []string `yaml:"globs" json:"globs" mapstructure:"globs"`
|
||||
}
|
||||
|
||||
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.globs", []string{})
|
||||
}
|
||||
|
||||
func (cfg fileContents) ToConfig() filecontents.Config {
|
||||
return filecontents.Config{
|
||||
Globs: cfg.Globs,
|
||||
SkipFilesAboveSizeInBytes: cfg.SkipFilesAboveSize,
|
||||
}
|
||||
func (cfg *fileContents) parseConfigValues() error {
|
||||
return cfg.Cataloger.parseConfigValues()
|
||||
}
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type fileMetadata struct {
|
||||
Digests []string `yaml:"digests" json:"digests" mapstructure:"digests"`
|
||||
type FileMetadata struct {
|
||||
Cataloger catalogerOptions `yaml:"cataloger" json:"cataloger" mapstructure:"cataloger"`
|
||||
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"})
|
||||
}
|
||||
|
||||
func (cfg *fileMetadata) parseConfigValues() error {
|
||||
return nil
|
||||
func (cfg *FileMetadata) parseConfigValues() error {
|
||||
return cfg.Cataloger.parseConfigValues()
|
||||
}
|
||||
|
||||
+17
-8
@@ -1,24 +1,33 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/cataloger/packages"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type pkg struct {
|
||||
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"`
|
||||
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"`
|
||||
}
|
||||
|
||||
func (cfg pkg) loadDefaultValues(v *viper.Viper) {
|
||||
c := packages.DefaultSearchConfig()
|
||||
cfg.Cataloger.loadDefaultValues(v)
|
||||
c := cataloger.DefaultSearchConfig()
|
||||
v.SetDefault("package.search-unindexed-archives", c.IncludeUnindexedArchives)
|
||||
v.SetDefault("package.search-indexed-archives", c.IncludeIndexedArchives)
|
||||
}
|
||||
|
||||
func (cfg pkg) ToConfig() packages.SearchConfig {
|
||||
return packages.SearchConfig{
|
||||
IncludeIndexedArchives: cfg.SearchIndexedArchives,
|
||||
IncludeUnindexedArchives: cfg.SearchUnindexedArchives,
|
||||
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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,28 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/anchore/syft/syft/cataloger/files/secrets"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/internal/file"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type secretsCfg struct {
|
||||
type secrets struct {
|
||||
Cataloger catalogerOptions `yaml:"cataloger" json:"cataloger" mapstructure:"cataloger"`
|
||||
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"`
|
||||
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"`
|
||||
Scope string `yaml:"scope" json:"scope" mapstructure:"scope"`
|
||||
}
|
||||
|
||||
func (cfg secretsCfg) loadDefaultValues(v *viper.Viper) {
|
||||
v.SetDefault("secrets.scope", source.AllLayersScope)
|
||||
func (cfg secrets) loadDefaultValues(v *viper.Viper) {
|
||||
v.SetDefault("secrets.cataloger.enabled", catalogerEnabledDefault)
|
||||
v.SetDefault("secrets.cataloger.scope", source.AllLayersScope)
|
||||
v.SetDefault("secrets.reveal-values", false)
|
||||
v.SetDefault("secrets.skip-files-above-size", 1*file.MB)
|
||||
v.SetDefault("secrets.additional-patterns", map[string]string{})
|
||||
v.SetDefault("secrets.exclude-pattern-names", []string{})
|
||||
}
|
||||
|
||||
func (cfg secretsCfg) ToConfig() (*secrets.Config, error) {
|
||||
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
|
||||
func (cfg *secrets) parseConfigValues() error {
|
||||
return cfg.Cataloger.parseConfigValues()
|
||||
}
|
||||
|
||||
@@ -6,5 +6,5 @@ const (
|
||||
|
||||
// JSONSchemaVersion is the current schema version output by the JSON encoder
|
||||
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
|
||||
JSONSchemaVersion = "3.0.1"
|
||||
JSONSchemaVersion = "3.1.0"
|
||||
)
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
package archive
|
||||
package file
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
)
|
||||
|
||||
const perFileReadLimit = 2 * file.GB
|
||||
const perFileReadLimit = 2 * GB
|
||||
|
||||
// safeCopy limits the copy from the reader. This is useful when extracting files from archives to
|
||||
// protect against decompression bomb attacks.
|
||||
@@ -1,8 +1,8 @@
|
||||
package internal
|
||||
package file
|
||||
|
||||
// FileNameGlobMatch evaluates the given glob pattern against the given "name" string, indicating if there is a match or not.
|
||||
// GlobMatch 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
|
||||
func FileNameGlobMatch(pattern, name string) bool {
|
||||
func GlobMatch(pattern, name string) bool {
|
||||
px := 0
|
||||
nx := 0
|
||||
nextPx := 0
|
||||
@@ -1,4 +1,4 @@
|
||||
package internal
|
||||
package file
|
||||
|
||||
import (
|
||||
"strings"
|
||||
@@ -32,7 +32,7 @@ func TestGlobMatch(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
if FileNameGlobMatch(test.pattern, test.data) != test.ok {
|
||||
if GlobMatch(test.pattern, test.data) != test.ok {
|
||||
t.Errorf("failed glob='%s' data='%s'", test.pattern, test.data)
|
||||
}
|
||||
}
|
||||
@@ -10,12 +10,6 @@ type Opener struct {
|
||||
path string
|
||||
}
|
||||
|
||||
func NewOpener(path string) Opener {
|
||||
return Opener{
|
||||
path: path,
|
||||
}
|
||||
}
|
||||
|
||||
// Open the stored path as a io.ReadCloser.
|
||||
func (o Opener) Open() (io.ReadCloser, error) {
|
||||
return os.Open(o.path)
|
||||
@@ -1,39 +1,38 @@
|
||||
package archive
|
||||
package file
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/bmatcuk/doublestar/v4"
|
||||
"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.
|
||||
func ExtractGlobsFromTarToUniqueTempFile(archivePath, dir string, globs ...string) (map[string]file.Opener, error) {
|
||||
results := make(map[string]file.Opener)
|
||||
func ExtractGlobsFromTarToUniqueTempFile(archivePath, dir string, globs ...string) (map[string]Opener, error) {
|
||||
results := make(map[string]Opener)
|
||||
|
||||
// don't allow for full traversal, only select traversal from given paths
|
||||
if len(globs) == 0 {
|
||||
return results, nil
|
||||
}
|
||||
|
||||
visitor := func(f archiver.File) error {
|
||||
defer f.Close()
|
||||
visitor := func(file archiver.File) error {
|
||||
defer file.Close()
|
||||
|
||||
// ignore directories
|
||||
if f.FileInfo.IsDir() {
|
||||
if file.FileInfo.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ignore any filename that doesn't match the given globs...
|
||||
if !matchesAnyGlob(f.Name(), globs...) {
|
||||
if !matchesAnyGlob(file.Name(), globs...) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// we have a file we want to extract....
|
||||
tempfilePrefix := filepath.Base(filepath.Clean(f.Name())) + "-"
|
||||
tempfilePrefix := filepath.Base(filepath.Clean(file.Name())) + "-"
|
||||
tempFile, err := ioutil.TempFile(dir, tempfilePrefix)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create temp file: %w", err)
|
||||
@@ -43,11 +42,11 @@ func ExtractGlobsFromTarToUniqueTempFile(archivePath, dir string, globs ...strin
|
||||
// provides a ReadCloser. It is up to the caller to handle closing the file explicitly.
|
||||
defer tempFile.Close()
|
||||
|
||||
if err := safeCopy(tempFile, f.ReadCloser); err != nil {
|
||||
return fmt.Errorf("unable to copy source=%q for tar=%q: %w", f.Name(), archivePath, err)
|
||||
if err := safeCopy(tempFile, file.ReadCloser); err != nil {
|
||||
return fmt.Errorf("unable to copy source=%q for tar=%q: %w", file.Name(), archivePath, err)
|
||||
}
|
||||
|
||||
results[f.Name()] = file.NewOpener(tempFile.Name())
|
||||
results[file.Name()] = Opener{path: tempFile.Name()}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package archive
|
||||
package file
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
@@ -1,4 +1,4 @@
|
||||
package archive
|
||||
package file
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -48,7 +48,7 @@ func (z ZipFileManifest) GlobMatch(patterns ...string) []string {
|
||||
// so that glob logic is consistent inside and outside of ZIP archives
|
||||
normalizedEntry := normalizeZipEntryName(entry)
|
||||
|
||||
if internal.FileNameGlobMatch(pattern, normalizedEntry) {
|
||||
if GlobMatch(pattern, normalizedEntry) {
|
||||
uniqueMatches.Add(entry)
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package archive
|
||||
package file
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@@ -1,4 +1,4 @@
|
||||
package archive
|
||||
package file
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
@@ -9,11 +9,17 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
)
|
||||
|
||||
const (
|
||||
// represents the order of bytes
|
||||
_ = iota
|
||||
KB = 1 << (10 * iota)
|
||||
MB
|
||||
GB
|
||||
)
|
||||
|
||||
type errZipSlipDetected struct {
|
||||
Prefix string
|
||||
JoinArgs []string
|
||||
@@ -65,16 +71,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.
|
||||
func ExtractFromZipToUniqueTempFile(archivePath, dir string, paths ...string) (map[string]file.Opener, error) {
|
||||
results := make(map[string]file.Opener)
|
||||
func ExtractFromZipToUniqueTempFile(archivePath, dir string, paths ...string) (map[string]Opener, error) {
|
||||
results := make(map[string]Opener)
|
||||
|
||||
// don't allow for full traversal, only select traversal from given paths
|
||||
if len(paths) == 0 {
|
||||
return results, nil
|
||||
}
|
||||
|
||||
visitor := func(f *zip.File) error {
|
||||
tempfilePrefix := filepath.Base(filepath.Clean(f.Name)) + "-"
|
||||
visitor := func(file *zip.File) error {
|
||||
tempfilePrefix := filepath.Base(filepath.Clean(file.Name)) + "-"
|
||||
|
||||
tempFile, err := ioutil.TempFile(dir, tempfilePrefix)
|
||||
if err != nil {
|
||||
@@ -85,26 +91,26 @@ func ExtractFromZipToUniqueTempFile(archivePath, dir string, paths ...string) (m
|
||||
// provides a ReadCloser. It is up to the caller to handle closing the file explicitly.
|
||||
defer tempFile.Close()
|
||||
|
||||
zippedFile, err := f.Open()
|
||||
zippedFile, err := file.Open()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read file=%q from zip=%q: %w", f.Name, archivePath, err)
|
||||
return fmt.Errorf("unable to read file=%q from zip=%q: %w", file.Name, archivePath, err)
|
||||
}
|
||||
defer func() {
|
||||
err := zippedFile.Close()
|
||||
if err != nil {
|
||||
log.Errorf("unable to close source file=%q from zip=%q: %+v", f.Name, archivePath, err)
|
||||
log.Errorf("unable to close source file=%q from zip=%q: %+v", file.Name, archivePath, err)
|
||||
}
|
||||
}()
|
||||
|
||||
if f.FileInfo().IsDir() {
|
||||
return fmt.Errorf("unable to extract directories, only files: %s", f.Name)
|
||||
if file.FileInfo().IsDir() {
|
||||
return fmt.Errorf("unable to extract directories, only files: %s", file.Name)
|
||||
}
|
||||
|
||||
if err := safeCopy(tempFile, zippedFile); err != nil {
|
||||
return fmt.Errorf("unable to copy source=%q for zip=%q: %w", f.Name, archivePath, err)
|
||||
return fmt.Errorf("unable to copy source=%q for zip=%q: %w", file.Name, archivePath, err)
|
||||
}
|
||||
|
||||
results[f.Name] = file.NewOpener(tempFile.Name())
|
||||
results[file.Name] = Opener{path: tempFile.Name()}
|
||||
|
||||
return nil
|
||||
}
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package archive
|
||||
package file
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
@@ -1,4 +1,4 @@
|
||||
package archive
|
||||
package file
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
@@ -1,7 +1,7 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package archive
|
||||
package file
|
||||
|
||||
import (
|
||||
"os"
|
||||
@@ -3,12 +3,11 @@ package cyclonedxhelpers
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
|
||||
"github.com/CycloneDX/cyclonedx-go"
|
||||
|
||||
"github.com/anchore/syft/internal/formats/common"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func encodeComponent(p pkg.Package) cyclonedx.Component {
|
||||
@@ -72,9 +71,9 @@ func decodeComponent(c *cyclonedx.Component) *pkg.Package {
|
||||
return p
|
||||
}
|
||||
|
||||
func decodeLocations(vals map[string]string) []file.Location {
|
||||
v := common.Decode(reflect.TypeOf([]file.Location{}), vals, "syft:location", CycloneDXFields)
|
||||
out, _ := v.([]file.Location)
|
||||
func decodeLocations(vals map[string]string) []source.Location {
|
||||
v := common.Decode(reflect.TypeOf([]source.Location{}), vals, "syft:location", CycloneDXFields)
|
||||
out, _ := v.([]source.Location)
|
||||
return out
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package cyclonedxhelpers
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"testing"
|
||||
|
||||
"github.com/CycloneDX/cyclonedx-go"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func Test_encodeComponentProperties(t *testing.T) {
|
||||
@@ -26,8 +26,8 @@ func Test_encodeComponentProperties(t *testing.T) {
|
||||
name: "from apk",
|
||||
input: pkg.Package{
|
||||
FoundBy: "cataloger",
|
||||
Locations: []file.Location{
|
||||
{Coordinates: file.Coordinates{RealPath: "test"}},
|
||||
Locations: []source.Location{
|
||||
{Coordinates: source.Coordinates{RealPath: "test"}},
|
||||
},
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Package: "libc-utils",
|
||||
|
||||
@@ -2,7 +2,6 @@ package cyclonedxhelpers
|
||||
|
||||
import (
|
||||
"github.com/CycloneDX/cyclonedx-go"
|
||||
"github.com/anchore/syft/syft/cpe"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@@ -12,7 +11,7 @@ func encodeSingleCPE(p pkg.Package) string {
|
||||
// Since the CPEs in a package are sorted by specificity
|
||||
// we can extract the first CPE as the one to output in cyclonedx
|
||||
if len(p.CPEs) > 0 {
|
||||
return cpe.String(p.CPEs[0])
|
||||
return pkg.CPEString(p.CPEs[0])
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -25,15 +24,15 @@ func encodeCPEs(p pkg.Package) (out []cyclonedx.Property) {
|
||||
}
|
||||
out = append(out, cyclonedx.Property{
|
||||
Name: "syft:cpe23",
|
||||
Value: cpe.String(c),
|
||||
Value: pkg.CPEString(c),
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func decodeCPEs(c *cyclonedx.Component) (out []cpe.CPE) {
|
||||
func decodeCPEs(c *cyclonedx.Component) (out []pkg.CPE) {
|
||||
if c.CPE != "" {
|
||||
cp, err := cpe.New(c.CPE)
|
||||
cp, err := pkg.NewCPE(c.CPE)
|
||||
if err != nil {
|
||||
log.Warnf("invalid CPE: %s", c.CPE)
|
||||
} else {
|
||||
@@ -44,7 +43,7 @@ func decodeCPEs(c *cyclonedx.Component) (out []cpe.CPE) {
|
||||
if c.Properties != nil {
|
||||
for _, p := range *c.Properties {
|
||||
if p.Name == "syft:cpe23" {
|
||||
cp, err := cpe.New(p.Value)
|
||||
cp, err := pkg.NewCPE(p.Value)
|
||||
if err != nil {
|
||||
log.Warnf("invalid CPE: %s", p.Value)
|
||||
} else {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package cyclonedxhelpers
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/cpe"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -10,8 +9,8 @@ import (
|
||||
)
|
||||
|
||||
func Test_encodeCPE(t *testing.T) {
|
||||
testCPE := cpe.Must("cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*")
|
||||
testCPE2 := cpe.Must("cpe:2.3:a:name:name2:3.2:*:*:*:*:*:*:*")
|
||||
testCPE := pkg.MustCPE("cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*")
|
||||
testCPE2 := pkg.MustCPE("cpe:2.3:a:name:name2:3.2:*:*:*:*:*:*:*")
|
||||
tests := []struct {
|
||||
name string
|
||||
input pkg.Package
|
||||
@@ -21,14 +20,14 @@ func Test_encodeCPE(t *testing.T) {
|
||||
// note: since this is an optional field, no value is preferred over NONE or NOASSERTION
|
||||
name: "no metadata",
|
||||
input: pkg.Package{
|
||||
CPEs: []cpe.CPE{},
|
||||
CPEs: []pkg.CPE{},
|
||||
},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "single CPE",
|
||||
input: pkg.Package{
|
||||
CPEs: []cpe.CPE{
|
||||
CPEs: []pkg.CPE{
|
||||
testCPE,
|
||||
},
|
||||
},
|
||||
@@ -37,7 +36,7 @@ func Test_encodeCPE(t *testing.T) {
|
||||
{
|
||||
name: "multiple CPEs",
|
||||
input: pkg.Package{
|
||||
CPEs: []cpe.CPE{
|
||||
CPEs: []pkg.CPE{
|
||||
testCPE2,
|
||||
testCPE,
|
||||
},
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"io"
|
||||
|
||||
"github.com/CycloneDX/cyclonedx-go"
|
||||
|
||||
"github.com/anchore/syft/internal/formats/common"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@@ -49,7 +51,7 @@ func toSyftModel(bom *cyclonedx.BOM) (*sbom.SBOM, error) {
|
||||
}
|
||||
s := &sbom.SBOM{
|
||||
Artifacts: sbom.Artifacts{
|
||||
Packages: pkg.NewCollection(),
|
||||
PackageCatalog: pkg.NewCatalog(),
|
||||
LinuxDistribution: linuxReleaseFromComponents(*bom.Components),
|
||||
},
|
||||
Source: meta,
|
||||
@@ -86,7 +88,7 @@ func collectPackages(component *cyclonedx.Component, s *sbom.SBOM, idMap map[str
|
||||
idMap[component.BOMRef] = p
|
||||
// TODO there must be a better way than needing to call this manually:
|
||||
p.SetID()
|
||||
s.Artifacts.Packages.Add(*p)
|
||||
s.Artifacts.PackageCatalog.Add(*p)
|
||||
}
|
||||
|
||||
if component.Components != nil {
|
||||
@@ -157,6 +159,14 @@ func linuxReleaseFromOSComponent(component *cyclonedx.Component) *linux.Release
|
||||
}
|
||||
}
|
||||
|
||||
if component.Properties != nil {
|
||||
values := map[string]string{}
|
||||
for _, p := range *component.Properties {
|
||||
values[p.Name] = p.Value
|
||||
}
|
||||
common.DecodeInto(&rel, values, "syft:distro", CycloneDXFields)
|
||||
}
|
||||
|
||||
return rel
|
||||
}
|
||||
|
||||
@@ -199,7 +209,7 @@ func decodeMetadata(component *cyclonedx.Component) source.Metadata {
|
||||
switch component.Type {
|
||||
case cyclonedx.ComponentTypeContainer:
|
||||
return source.Metadata{
|
||||
Scheme: source.ImageType,
|
||||
Scheme: source.ImageScheme,
|
||||
ImageMetadata: source.ImageMetadata{
|
||||
UserInput: component.Name,
|
||||
ID: component.BOMRef,
|
||||
@@ -208,7 +218,7 @@ func decodeMetadata(component *cyclonedx.Component) source.Metadata {
|
||||
}
|
||||
case cyclonedx.ComponentTypeFile:
|
||||
return source.Metadata{
|
||||
Scheme: source.FileType, // or source.DirectoryType
|
||||
Scheme: source.FileScheme, // or source.DirectoryScheme
|
||||
Path: component.Name,
|
||||
ImageMetadata: source.ImageMetadata{
|
||||
UserInput: component.Name,
|
||||
|
||||
@@ -212,7 +212,7 @@ func Test_decode(t *testing.T) {
|
||||
assert.Equal(t, e.ver, sbom.Artifacts.LinuxDistribution.VersionID)
|
||||
}
|
||||
if e.pkg != "" {
|
||||
for p := range sbom.Artifacts.Packages.Enumerate() {
|
||||
for p := range sbom.Artifacts.PackageCatalog.Enumerate() {
|
||||
if e.pkg != p.Name {
|
||||
continue
|
||||
}
|
||||
@@ -240,7 +240,7 @@ func Test_decode(t *testing.T) {
|
||||
if e.relation != "" {
|
||||
foundRelation := false
|
||||
for _, r := range sbom.Relationships {
|
||||
p := sbom.Artifacts.Packages.Package(r.To.ID())
|
||||
p := sbom.Artifacts.PackageCatalog.Package(r.To.ID())
|
||||
if e.relation == p.Name {
|
||||
foundRelation = true
|
||||
break
|
||||
|
||||
@@ -25,7 +25,7 @@ func ToFormatModel(s sbom.SBOM) *cyclonedx.BOM {
|
||||
cdxBOM.SerialNumber = uuid.New().URN()
|
||||
cdxBOM.Metadata = toBomDescriptor(internal.ApplicationName, versionInfo.Version, s.Source)
|
||||
|
||||
packages := s.Artifacts.Packages.Sorted()
|
||||
packages := s.Artifacts.PackageCatalog.Sorted()
|
||||
components := make([]cyclonedx.Component, len(packages))
|
||||
for i, p := range packages {
|
||||
components[i] = encodeComponent(p)
|
||||
@@ -155,7 +155,7 @@ func toDependencies(relationships []artifact.Relationship) []cyclonedx.Dependenc
|
||||
|
||||
func toBomDescriptorComponent(srcMetadata source.Metadata) *cyclonedx.Component {
|
||||
switch srcMetadata.Scheme {
|
||||
case source.ImageType:
|
||||
case source.ImageScheme:
|
||||
bomRef, err := artifact.IDByHash(srcMetadata.ImageMetadata.ID)
|
||||
if err != nil {
|
||||
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,
|
||||
Version: srcMetadata.ImageMetadata.ManifestDigest,
|
||||
}
|
||||
case source.DirectoryType, source.FileType:
|
||||
case source.DirectoryScheme, source.FileScheme:
|
||||
bomRef, err := artifact.IDByHash(srcMetadata.Path)
|
||||
if err != nil {
|
||||
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) {
|
||||
switch srcMetadata.Scheme {
|
||||
case source.ImageType:
|
||||
case source.ImageScheme:
|
||||
return cleanName(srcMetadata.ImageMetadata.UserInput), nil
|
||||
case source.DirectoryType, source.FileType:
|
||||
case source.DirectoryScheme, source.FileScheme:
|
||||
return cleanName(srcMetadata.Path), nil
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
|
||||
func Test_DocumentName(t *testing.T) {
|
||||
allSchemes := strset.New()
|
||||
for _, s := range source.AllTypes {
|
||||
for _, s := range source.AllSchemes {
|
||||
allSchemes.Add(string(s))
|
||||
}
|
||||
testedSchemes := strset.New()
|
||||
@@ -29,7 +29,7 @@ func Test_DocumentName(t *testing.T) {
|
||||
name: "image",
|
||||
inputName: "my-name",
|
||||
srcMetadata: source.Metadata{
|
||||
Scheme: source.ImageType,
|
||||
Scheme: source.ImageScheme,
|
||||
ImageMetadata: source.ImageMetadata{
|
||||
UserInput: "image-repo/name:tag",
|
||||
ID: "id",
|
||||
@@ -42,7 +42,7 @@ func Test_DocumentName(t *testing.T) {
|
||||
name: "directory",
|
||||
inputName: "my-name",
|
||||
srcMetadata: source.Metadata{
|
||||
Scheme: source.DirectoryType,
|
||||
Scheme: source.DirectoryScheme,
|
||||
Path: "some/path/to/place",
|
||||
},
|
||||
expected: "some/path/to/place",
|
||||
@@ -51,7 +51,7 @@ func Test_DocumentName(t *testing.T) {
|
||||
name: "file",
|
||||
inputName: "my-name",
|
||||
srcMetadata: source.Metadata{
|
||||
Scheme: source.FileType,
|
||||
Scheme: source.FileScheme,
|
||||
Path: "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 {
|
||||
input := "unknown-source-type"
|
||||
switch srcMetadata.Scheme {
|
||||
case source.ImageType:
|
||||
case source.ImageScheme:
|
||||
input = "image"
|
||||
case source.DirectoryType:
|
||||
case source.DirectoryScheme:
|
||||
input = "dir"
|
||||
case source.FileType:
|
||||
case source.FileScheme:
|
||||
input = "file"
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
func Test_documentNamespace(t *testing.T) {
|
||||
allSchemes := strset.New()
|
||||
for _, s := range source.AllTypes {
|
||||
for _, s := range source.AllSchemes {
|
||||
allSchemes.Add(string(s))
|
||||
}
|
||||
testedSchemes := strset.New()
|
||||
@@ -27,7 +27,7 @@ func Test_documentNamespace(t *testing.T) {
|
||||
name: "image",
|
||||
inputName: "my-name",
|
||||
srcMetadata: source.Metadata{
|
||||
Scheme: source.ImageType,
|
||||
Scheme: source.ImageScheme,
|
||||
ImageMetadata: source.ImageMetadata{
|
||||
UserInput: "image-repo/name:tag",
|
||||
ID: "id",
|
||||
@@ -40,7 +40,7 @@ func Test_documentNamespace(t *testing.T) {
|
||||
name: "directory",
|
||||
inputName: "my-name",
|
||||
srcMetadata: source.Metadata{
|
||||
Scheme: source.DirectoryType,
|
||||
Scheme: source.DirectoryScheme,
|
||||
Path: "some/path/to/place",
|
||||
},
|
||||
expected: "https://anchore.com/syft/dir/my-name-",
|
||||
@@ -49,7 +49,7 @@ func Test_documentNamespace(t *testing.T) {
|
||||
name: "file",
|
||||
inputName: "my-name",
|
||||
srcMetadata: source.Metadata{
|
||||
Scheme: source.FileType,
|
||||
Scheme: source.FileScheme,
|
||||
Path: "some/path/to/place",
|
||||
},
|
||||
expected: "https://anchore.com/syft/file/my-name-",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package spdxhelpers
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/cpe"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
@@ -11,7 +10,7 @@ func ExternalRefs(p pkg.Package) (externalRefs []ExternalRef) {
|
||||
for _, c := range p.CPEs {
|
||||
externalRefs = append(externalRefs, ExternalRef{
|
||||
ReferenceCategory: SecurityReferenceCategory,
|
||||
ReferenceLocator: cpe.String(c),
|
||||
ReferenceLocator: pkg.CPEString(c),
|
||||
ReferenceType: Cpe23ExternalRefType,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package spdxhelpers
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/cpe"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@@ -9,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
func Test_ExternalRefs(t *testing.T) {
|
||||
testCPE := cpe.Must("cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*")
|
||||
testCPE := pkg.MustCPE("cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*")
|
||||
tests := []struct {
|
||||
name string
|
||||
input pkg.Package
|
||||
@@ -18,7 +17,7 @@ func Test_ExternalRefs(t *testing.T) {
|
||||
{
|
||||
name: "cpe + purl",
|
||||
input: pkg.Package{
|
||||
CPEs: []cpe.CPE{
|
||||
CPEs: []pkg.CPE{
|
||||
testCPE,
|
||||
},
|
||||
PURL: "a-purl",
|
||||
@@ -26,7 +25,7 @@ func Test_ExternalRefs(t *testing.T) {
|
||||
expected: []ExternalRef{
|
||||
{
|
||||
ReferenceCategory: SecurityReferenceCategory,
|
||||
ReferenceLocator: cpe.String(testCPE),
|
||||
ReferenceLocator: pkg.CPEString(testCPE),
|
||||
ReferenceType: Cpe23ExternalRefType,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package spdxhelpers
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -18,9 +18,9 @@ func Test_SourceInfo(t *testing.T) {
|
||||
name: "locations are captured",
|
||||
input: pkg.Package{
|
||||
// note: no type given
|
||||
Locations: []file.Location{
|
||||
file.NewVirtualLocation("/a-place", "/b-place"),
|
||||
file.NewVirtualLocation("/c-place", "/d-place"),
|
||||
Locations: []source.Location{
|
||||
source.NewVirtualLocation("/a-place", "/b-place"),
|
||||
source.NewVirtualLocation("/c-place", "/d-place"),
|
||||
},
|
||||
},
|
||||
expected: []string{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package spdxhelpers
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/cpe"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -14,6 +13,7 @@ import (
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
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{
|
||||
Artifacts: sbom.Artifacts{
|
||||
Packages: pkg.NewCollection(),
|
||||
FileMetadata: map[file.Coordinates]file.Metadata{},
|
||||
FileDigests: map[file.Coordinates][]file.Digest{},
|
||||
PackageCatalog: pkg.NewCatalog(),
|
||||
FileMetadata: map[source.Coordinates]source.FileMetadata{},
|
||||
FileDigests: map[source.Coordinates][]file.Digest{},
|
||||
LinuxDistribution: findLinuxReleaseByPURL(doc),
|
||||
},
|
||||
}
|
||||
@@ -74,7 +74,7 @@ func collectSyftPackages(s *sbom.SBOM, spdxIDMap map[string]interface{}, doc *sp
|
||||
for _, p := range doc.Packages {
|
||||
syftPkg := toSyftPackage(p)
|
||||
spdxIDMap[string(p.PackageSPDXIdentifier)] = syftPkg
|
||||
s.Artifacts.Packages.Add(*syftPkg)
|
||||
s.Artifacts.PackageCatalog.Add(*syftPkg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ func toFileDigests(f *spdx.File2_2) (digests []file.Digest) {
|
||||
return digests
|
||||
}
|
||||
|
||||
func toFileMetadata(f *spdx.File2_2) (meta file.Metadata) {
|
||||
func toFileMetadata(f *spdx.File2_2) (meta source.FileMetadata) {
|
||||
// FIXME Syft is currently lossy due to the SPDX 2.2.1 spec not supporting arbitrary mimetypes
|
||||
for _, typ := range f.FileType {
|
||||
switch FileType(typ) {
|
||||
@@ -132,7 +132,7 @@ func toSyftRelationships(spdxIDMap map[string]interface{}, doc *spdx.Document2_2
|
||||
b := spdxIDMap[string(r.RefB.ElementRefID)]
|
||||
from, fromOk := a.(*pkg.Package)
|
||||
toPackage, toPackageOk := b.(*pkg.Package)
|
||||
toLocation, toLocationOk := b.(*file.Location)
|
||||
toLocation, toLocationOk := b.(*source.Location)
|
||||
if !fromOk || !(toPackageOk || toLocationOk) {
|
||||
log.Debugf("unable to find valid relationship mapping from SPDX 2.2 JSON, ignoring: (from: %+v) (to: %+v)", a, b)
|
||||
continue
|
||||
@@ -174,7 +174,7 @@ func toSyftRelationships(spdxIDMap map[string]interface{}, doc *spdx.Document2_2
|
||||
return out
|
||||
}
|
||||
|
||||
func toSyftCoordinates(f *spdx.File2_2) file.Coordinates {
|
||||
func toSyftCoordinates(f *spdx.File2_2) source.Coordinates {
|
||||
const layerIDPrefix = "layerID: "
|
||||
var fileSystemID string
|
||||
if strings.Index(f.FileComment, layerIDPrefix) == 0 {
|
||||
@@ -183,16 +183,16 @@ func toSyftCoordinates(f *spdx.File2_2) file.Coordinates {
|
||||
if strings.Index(string(f.FileSPDXIdentifier), layerIDPrefix) == 0 {
|
||||
fileSystemID = strings.TrimPrefix(string(f.FileSPDXIdentifier), layerIDPrefix)
|
||||
}
|
||||
return file.Coordinates{
|
||||
return source.Coordinates{
|
||||
RealPath: f.FileName,
|
||||
FileSystemID: fileSystemID,
|
||||
}
|
||||
}
|
||||
|
||||
func toSyftLocation(f *spdx.File2_2) *file.Location {
|
||||
return &file.Location{
|
||||
func toSyftLocation(f *spdx.File2_2) *source.Location {
|
||||
return &source.Location{
|
||||
Coordinates: toSyftCoordinates(f),
|
||||
AccessPath: f.FileName,
|
||||
VirtualPath: f.FileName,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,10 +317,10 @@ func findPURLValue(p *spdx.Package2_2) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func extractCPEs(p *spdx.Package2_2) (cpes []cpe.CPE) {
|
||||
func extractCPEs(p *spdx.Package2_2) (cpes []pkg.CPE) {
|
||||
for _, r := range p.PackageExternalReferences {
|
||||
if r.RefType == string(Cpe23ExternalRefType) {
|
||||
cpe, err := cpe.New(r.Locator)
|
||||
cpe, err := pkg.NewCPE(r.Locator)
|
||||
if err != nil {
|
||||
log.Warnf("unable to extract SPDX CPE=%q: %+v", r.Locator, err)
|
||||
continue
|
||||
|
||||
@@ -94,7 +94,7 @@ func TestToSyftModel(t *testing.T) {
|
||||
|
||||
assert.NotNil(t, sbom)
|
||||
|
||||
pkgs := sbom.Artifacts.Packages.Sorted()
|
||||
pkgs := sbom.Artifacts.PackageCatalog.Sorted()
|
||||
|
||||
assert.Len(t, pkgs, 2)
|
||||
|
||||
|
||||
@@ -2,12 +2,9 @@ package testutils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/anchore/syft/syft/cpe"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
|
||||
"github.com/anchore/go-testutils"
|
||||
"github.com/anchore/stereoscope/pkg/filetree"
|
||||
"github.com/anchore/stereoscope/pkg/image"
|
||||
@@ -100,7 +97,7 @@ func AssertEncoderAgainstGoldenSnapshot(t *testing.T, format sbom.Format, sbom s
|
||||
|
||||
func ImageInput(t testing.TB, testImage string, options ...ImageOption) sbom.SBOM {
|
||||
t.Helper()
|
||||
catalog := pkg.NewCollection()
|
||||
catalog := pkg.NewCatalog()
|
||||
var cfg imageCfg
|
||||
var img *image.Image
|
||||
for _, opt := range options {
|
||||
@@ -124,7 +121,7 @@ func ImageInput(t testing.TB, testImage string, options ...ImageOption) sbom.SBO
|
||||
|
||||
return sbom.SBOM{
|
||||
Artifacts: sbom.Artifacts{
|
||||
Packages: catalog,
|
||||
PackageCatalog: catalog,
|
||||
LinuxDistribution: &linux.Release{
|
||||
PrettyName: "debian",
|
||||
Name: "debian",
|
||||
@@ -152,7 +149,7 @@ func carriageRedactor(s []byte) []byte {
|
||||
return []byte(msg)
|
||||
}
|
||||
|
||||
func populateImageCatalog(catalog pkg.Collection, img *image.Image) {
|
||||
func populateImageCatalog(catalog *pkg.Catalog, img *image.Image) {
|
||||
_, ref1, _ := img.SquashedTree().File("/somefile-1.txt", filetree.FollowBasenameLinks)
|
||||
_, ref2, _ := img.SquashedTree().File("/somefile-2.txt", filetree.FollowBasenameLinks)
|
||||
|
||||
@@ -160,8 +157,8 @@ func populateImageCatalog(catalog pkg.Collection, img *image.Image) {
|
||||
catalog.Add(pkg.Package{
|
||||
Name: "package-1",
|
||||
Version: "1.0.1",
|
||||
Locations: []file.Location{
|
||||
file.NewLocationFromImage(string(ref1.RealPath), *ref1, img),
|
||||
Locations: []source.Location{
|
||||
source.NewLocationFromImage(string(ref1.RealPath), *ref1, img),
|
||||
},
|
||||
Type: pkg.PythonPkg,
|
||||
FoundBy: "the-cataloger-1",
|
||||
@@ -173,15 +170,15 @@ func populateImageCatalog(catalog pkg.Collection, img *image.Image) {
|
||||
Version: "1.0.1",
|
||||
},
|
||||
PURL: "a-purl-1",
|
||||
CPEs: []cpe.CPE{
|
||||
cpe.Must("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"),
|
||||
CPEs: []pkg.CPE{
|
||||
pkg.MustCPE("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"),
|
||||
},
|
||||
})
|
||||
catalog.Add(pkg.Package{
|
||||
Name: "package-2",
|
||||
Version: "2.0.1",
|
||||
Locations: []file.Location{
|
||||
file.NewLocationFromImage(string(ref2.RealPath), *ref2, img),
|
||||
Locations: []source.Location{
|
||||
source.NewLocationFromImage(string(ref2.RealPath), *ref2, img),
|
||||
},
|
||||
Type: pkg.DebPkg,
|
||||
FoundBy: "the-cataloger-2",
|
||||
@@ -191,8 +188,8 @@ func populateImageCatalog(catalog pkg.Collection, img *image.Image) {
|
||||
Version: "2.0.1",
|
||||
},
|
||||
PURL: "a-purl-2",
|
||||
CPEs: []cpe.CPE{
|
||||
cpe.Must("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
|
||||
CPEs: []pkg.CPE{
|
||||
pkg.MustCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -205,7 +202,7 @@ func DirectoryInput(t testing.TB) sbom.SBOM {
|
||||
|
||||
return sbom.SBOM{
|
||||
Artifacts: sbom.Artifacts{
|
||||
Packages: catalog,
|
||||
PackageCatalog: catalog,
|
||||
LinuxDistribution: &linux.Release{
|
||||
PrettyName: "debian",
|
||||
Name: "debian",
|
||||
@@ -228,8 +225,8 @@ func DirectoryInput(t testing.TB) sbom.SBOM {
|
||||
}
|
||||
}
|
||||
|
||||
func newDirectoryCatalog() pkg.Collection {
|
||||
catalog := pkg.NewCollection()
|
||||
func newDirectoryCatalog() *pkg.Catalog {
|
||||
catalog := pkg.NewCatalog()
|
||||
|
||||
// populate catalog with test data
|
||||
catalog.Add(pkg.Package{
|
||||
@@ -237,8 +234,8 @@ func newDirectoryCatalog() pkg.Collection {
|
||||
Version: "1.0.1",
|
||||
Type: pkg.PythonPkg,
|
||||
FoundBy: "the-cataloger-1",
|
||||
Locations: []file.Location{
|
||||
file.NewLocation("/some/path/pkg1"),
|
||||
Locations: []source.Location{
|
||||
source.NewLocation("/some/path/pkg1"),
|
||||
},
|
||||
Language: pkg.Python,
|
||||
MetadataType: pkg.PythonPackageMetadataType,
|
||||
@@ -253,8 +250,8 @@ func newDirectoryCatalog() pkg.Collection {
|
||||
},
|
||||
},
|
||||
PURL: "a-purl-2",
|
||||
CPEs: []cpe.CPE{
|
||||
cpe.Must("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
|
||||
CPEs: []pkg.CPE{
|
||||
pkg.MustCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
|
||||
},
|
||||
})
|
||||
catalog.Add(pkg.Package{
|
||||
@@ -262,8 +259,8 @@ func newDirectoryCatalog() pkg.Collection {
|
||||
Version: "2.0.1",
|
||||
Type: pkg.DebPkg,
|
||||
FoundBy: "the-cataloger-2",
|
||||
Locations: []file.Location{
|
||||
file.NewLocation("/some/path/pkg1"),
|
||||
Locations: []source.Location{
|
||||
source.NewLocation("/some/path/pkg1"),
|
||||
},
|
||||
MetadataType: pkg.DpkgMetadataType,
|
||||
Metadata: pkg.DpkgMetadata{
|
||||
@@ -271,8 +268,8 @@ func newDirectoryCatalog() pkg.Collection {
|
||||
Version: "2.0.1",
|
||||
},
|
||||
PURL: "a-purl-2",
|
||||
CPEs: []cpe.CPE{
|
||||
cpe.Must("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
|
||||
CPEs: []pkg.CPE{
|
||||
pkg.MustCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ func Test_decodeJSON(t *testing.T) {
|
||||
split = strings.SplitN(pkg, ":", 2)
|
||||
name = split[0]
|
||||
version = split[1]
|
||||
for p := range bom.Artifacts.Packages.Enumerate() {
|
||||
for p := range bom.Artifacts.PackageCatalog.Enumerate() {
|
||||
if p.Name == name {
|
||||
assert.Equal(t, version, p.Version)
|
||||
continue pkgs
|
||||
|
||||
@@ -57,7 +57,7 @@ func Test_decodeXML(t *testing.T) {
|
||||
split = strings.SplitN(pkg, ":", 2)
|
||||
name = split[0]
|
||||
version = split[1]
|
||||
for p := range bom.Artifacts.Packages.Enumerate() {
|
||||
for p := range bom.Artifacts.PackageCatalog.Enumerate() {
|
||||
if p.Name == name {
|
||||
assert.Equal(t, version, p.Version)
|
||||
continue pkgs
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mholt/archiver/v3"
|
||||
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/version"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
// toGithubModel converts the provided SBOM to a GitHub dependency model
|
||||
func toGithubModel(s *sbom.SBOM) DependencySnapshot {
|
||||
scanTime := time.Now().Format(time.RFC3339) // TODO is there a record of this somewhere?
|
||||
v := version.FromBuild().Version
|
||||
if v == "[not provided]" {
|
||||
v = "0.0.0-dev"
|
||||
}
|
||||
return DependencySnapshot{
|
||||
Version: 0,
|
||||
// TODO allow property input to specify the Job, Sha, and Ref
|
||||
Detector: DetectorMetadata{
|
||||
Name: internal.ApplicationName,
|
||||
URL: "https://github.com/anchore/syft",
|
||||
Version: v,
|
||||
},
|
||||
Metadata: toSnapshotMetadata(s),
|
||||
Manifests: toGithubManifests(s),
|
||||
Scanned: scanTime,
|
||||
}
|
||||
}
|
||||
|
||||
// toSnapshotMetadata captures the linux distribution information and other metadata
|
||||
func toSnapshotMetadata(s *sbom.SBOM) Metadata {
|
||||
out := Metadata{}
|
||||
|
||||
if s.Artifacts.LinuxDistribution != nil {
|
||||
d := s.Artifacts.LinuxDistribution
|
||||
qualifiers := packageurl.Qualifiers{}
|
||||
if len(d.IDLike) > 0 {
|
||||
qualifiers = append(qualifiers, packageurl.Qualifier{
|
||||
Key: "like",
|
||||
Value: strings.Join(d.IDLike, ","),
|
||||
})
|
||||
}
|
||||
purl := packageurl.NewPackageURL("generic", "", d.ID, d.VersionID, qualifiers, "")
|
||||
out["syft:distro"] = purl.ToString()
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func filesystem(p pkg.Package) string {
|
||||
if len(p.Locations) > 0 {
|
||||
return p.Locations[0].FileSystemID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// isArchive returns true if the path appears to be an archive
|
||||
func isArchive(path string) bool {
|
||||
_, err := archiver.ByExtension(path)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// toPath Generates a string representation of the package location, optionally including the layer hash
|
||||
func toPath(s source.Metadata, p pkg.Package) string {
|
||||
inputPath := strings.TrimPrefix(s.Path, "./")
|
||||
if inputPath == "." {
|
||||
inputPath = ""
|
||||
}
|
||||
if len(p.Locations) > 0 {
|
||||
location := p.Locations[0]
|
||||
packagePath := location.RealPath
|
||||
if location.VirtualPath != "" {
|
||||
packagePath = location.VirtualPath
|
||||
}
|
||||
packagePath = strings.TrimPrefix(packagePath, "/")
|
||||
switch s.Scheme {
|
||||
case source.ImageScheme:
|
||||
image := strings.ReplaceAll(s.ImageMetadata.UserInput, ":/", "//")
|
||||
return fmt.Sprintf("%s:/%s", image, packagePath)
|
||||
case source.FileScheme:
|
||||
if isArchive(inputPath) {
|
||||
return fmt.Sprintf("%s:/%s", inputPath, packagePath)
|
||||
}
|
||||
return inputPath
|
||||
case source.DirectoryScheme:
|
||||
if inputPath != "" {
|
||||
return fmt.Sprintf("%s/%s", inputPath, packagePath)
|
||||
}
|
||||
return packagePath
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("%s%s", inputPath, s.ImageMetadata.UserInput)
|
||||
}
|
||||
|
||||
// toGithubManifests manifests, each of which represents a specific location that has dependencies
|
||||
func toGithubManifests(s *sbom.SBOM) Manifests {
|
||||
manifests := map[string]*Manifest{}
|
||||
|
||||
for _, p := range s.Artifacts.PackageCatalog.Sorted() {
|
||||
path := toPath(s.Source, p)
|
||||
manifest, ok := manifests[path]
|
||||
if !ok {
|
||||
manifest = &Manifest{
|
||||
Name: path,
|
||||
File: FileInfo{
|
||||
SourceLocation: path,
|
||||
},
|
||||
Resolved: DependencyGraph{},
|
||||
}
|
||||
fs := filesystem(p)
|
||||
if fs != "" {
|
||||
manifest.Metadata = Metadata{
|
||||
"syft:filesystem": fs,
|
||||
}
|
||||
}
|
||||
manifests[path] = manifest
|
||||
}
|
||||
|
||||
name := dependencyName(p)
|
||||
manifest.Resolved[name] = DependencyNode{
|
||||
Purl: p.PURL,
|
||||
Metadata: toDependencyMetadata(p),
|
||||
Relationship: toDependencyRelationshipType(p),
|
||||
Scope: toDependencyScope(p),
|
||||
Dependencies: toDependencies(s, p),
|
||||
}
|
||||
}
|
||||
|
||||
out := Manifests{}
|
||||
for k, v := range manifests {
|
||||
out[k] = *v
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// dependencyName to make things a little nicer to read; this might end up being lossy
|
||||
func dependencyName(p pkg.Package) string {
|
||||
purl, err := packageurl.FromString(p.PURL)
|
||||
if err != nil {
|
||||
log.Warnf("Invalid PURL for package: '%s' PURL: '%s' (%w)", p.Name, p.PURL, err)
|
||||
return ""
|
||||
}
|
||||
// don't use qualifiers for this
|
||||
purl.Qualifiers = nil
|
||||
return purl.ToString()
|
||||
}
|
||||
|
||||
func toDependencyScope(_ pkg.Package) DependencyScope {
|
||||
return DependencyScopeRuntime
|
||||
}
|
||||
|
||||
func toDependencyRelationshipType(_ pkg.Package) DependencyRelationship {
|
||||
return DependencyRelationshipDirect
|
||||
}
|
||||
|
||||
func toDependencyMetadata(_ pkg.Package) Metadata {
|
||||
// We have limited properties: up to 8 with reasonably small values
|
||||
// For now, we are encoding the location as part of the key, we are encoding PURLs with most
|
||||
// of the other information Grype might need; and the distro information at the top level
|
||||
// so we don't need anything here yet
|
||||
return Metadata{}
|
||||
}
|
||||
|
||||
func toDependencies(s *sbom.SBOM, p pkg.Package) (out []string) {
|
||||
for _, r := range s.Relationships {
|
||||
if r.From.ID() == p.ID() {
|
||||
if p, ok := r.To.(pkg.Package); ok {
|
||||
out = append(out, dependencyName(p))
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func Test_toGithubModel(t *testing.T) {
|
||||
s := sbom.SBOM{
|
||||
Source: source.Metadata{
|
||||
Scheme: source.ImageScheme,
|
||||
ImageMetadata: source.ImageMetadata{
|
||||
UserInput: "ubuntu:18.04",
|
||||
Architecture: "amd64",
|
||||
},
|
||||
},
|
||||
Artifacts: sbom.Artifacts{
|
||||
LinuxDistribution: &linux.Release{
|
||||
ID: "ubuntu",
|
||||
VersionID: "18.04",
|
||||
IDLike: []string{"debian"},
|
||||
},
|
||||
PackageCatalog: pkg.NewCatalog(),
|
||||
},
|
||||
}
|
||||
for _, p := range []pkg.Package{
|
||||
{
|
||||
Name: "pkg-1",
|
||||
Version: "1.0.1",
|
||||
Locations: []source.Location{{
|
||||
Coordinates: source.Coordinates{
|
||||
RealPath: "/usr/lib",
|
||||
FileSystemID: "fsid-1",
|
||||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
Name: "pkg-2",
|
||||
Version: "2.0.2",
|
||||
Locations: []source.Location{{
|
||||
Coordinates: source.Coordinates{
|
||||
RealPath: "/usr/lib",
|
||||
FileSystemID: "fsid-1",
|
||||
},
|
||||
}},
|
||||
},
|
||||
{
|
||||
Name: "pkg-3",
|
||||
Version: "3.0.3",
|
||||
Locations: []source.Location{{
|
||||
Coordinates: source.Coordinates{
|
||||
RealPath: "/etc",
|
||||
FileSystemID: "fsid-1",
|
||||
},
|
||||
}},
|
||||
},
|
||||
} {
|
||||
p.PURL = packageurl.NewPackageURL(
|
||||
"generic",
|
||||
"",
|
||||
p.Name,
|
||||
p.Version,
|
||||
nil,
|
||||
"",
|
||||
).ToString()
|
||||
s.Artifacts.PackageCatalog.Add(p)
|
||||
}
|
||||
|
||||
actual := toGithubModel(&s)
|
||||
|
||||
expected := DependencySnapshot{
|
||||
Version: 0,
|
||||
Detector: DetectorMetadata{
|
||||
Name: "syft",
|
||||
Version: "0.0.0-dev",
|
||||
URL: "https://github.com/anchore/syft",
|
||||
},
|
||||
Metadata: Metadata{
|
||||
"syft:distro": "pkg:generic/ubuntu@18.04?like=debian",
|
||||
},
|
||||
Scanned: actual.Scanned,
|
||||
Manifests: Manifests{
|
||||
"ubuntu:18.04:/usr/lib": Manifest{
|
||||
Name: "ubuntu:18.04:/usr/lib",
|
||||
File: FileInfo{
|
||||
SourceLocation: "ubuntu:18.04:/usr/lib",
|
||||
},
|
||||
Metadata: Metadata{
|
||||
"syft:filesystem": "fsid-1",
|
||||
},
|
||||
Resolved: DependencyGraph{
|
||||
"pkg:generic/pkg-1@1.0.1": DependencyNode{
|
||||
Purl: "pkg:generic/pkg-1@1.0.1",
|
||||
Scope: DependencyScopeRuntime,
|
||||
Relationship: DependencyRelationshipDirect,
|
||||
},
|
||||
"pkg:generic/pkg-2@2.0.2": DependencyNode{
|
||||
Purl: "pkg:generic/pkg-2@2.0.2",
|
||||
Scope: DependencyScopeRuntime,
|
||||
Relationship: DependencyRelationshipDirect,
|
||||
},
|
||||
},
|
||||
},
|
||||
"ubuntu:18.04:/etc": Manifest{
|
||||
Name: "ubuntu:18.04:/etc",
|
||||
File: FileInfo{
|
||||
SourceLocation: "ubuntu:18.04:/etc",
|
||||
},
|
||||
Metadata: Metadata{
|
||||
"syft:filesystem": "fsid-1",
|
||||
},
|
||||
Resolved: DependencyGraph{
|
||||
"pkg:generic/pkg-3@3.0.3": DependencyNode{
|
||||
Purl: "pkg:generic/pkg-3@3.0.3",
|
||||
Scope: DependencyScopeRuntime,
|
||||
Relationship: DependencyRelationshipDirect,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// just using JSONEq because it gives a comprehensible diff
|
||||
s1, _ := json.Marshal(expected)
|
||||
s2, _ := json.Marshal(actual)
|
||||
assert.JSONEq(t, string(s1), string(s2))
|
||||
|
||||
// Just test the other schemes:
|
||||
s.Source.Path = "."
|
||||
s.Source.Scheme = source.DirectoryScheme
|
||||
actual = toGithubModel(&s)
|
||||
assert.Equal(t, "etc", actual.Manifests["etc"].Name)
|
||||
|
||||
s.Source.Path = "./artifacts"
|
||||
s.Source.Scheme = source.DirectoryScheme
|
||||
actual = toGithubModel(&s)
|
||||
assert.Equal(t, "artifacts/etc", actual.Manifests["artifacts/etc"].Name)
|
||||
|
||||
s.Source.Path = "/artifacts"
|
||||
s.Source.Scheme = source.DirectoryScheme
|
||||
actual = toGithubModel(&s)
|
||||
assert.Equal(t, "/artifacts/etc", actual.Manifests["/artifacts/etc"].Name)
|
||||
|
||||
s.Source.Path = "./executable"
|
||||
s.Source.Scheme = source.FileScheme
|
||||
actual = toGithubModel(&s)
|
||||
assert.Equal(t, "executable", actual.Manifests["executable"].Name)
|
||||
|
||||
s.Source.Path = "./archive.tar.gz"
|
||||
s.Source.Scheme = source.FileScheme
|
||||
actual = toGithubModel(&s)
|
||||
assert.Equal(t, "archive.tar.gz:/etc", actual.Manifests["archive.tar.gz:/etc"].Name)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
)
|
||||
|
||||
const ID sbom.FormatID = "github-0-json"
|
||||
|
||||
func Format() sbom.Format {
|
||||
return sbom.NewFormat(
|
||||
ID,
|
||||
func(writer io.Writer, sbom sbom.SBOM) error {
|
||||
bom := toGithubModel(&sbom)
|
||||
|
||||
bytes, err := json.MarshalIndent(bom, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = writer.Write(bytes)
|
||||
|
||||
return err
|
||||
},
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package github
|
||||
|
||||
// Derived from: https://gist.github.com/reiddraper/fdab2883db0f372c146d1a750fc1c43f
|
||||
|
||||
type DependencySnapshot struct {
|
||||
Version int `json:"version"`
|
||||
Job Job `json:"job,omitempty"` // !omitempty
|
||||
Sha string `json:"sha,omitempty"` // !omitempty sha of the Git commit
|
||||
Ref string `json:"ref,omitempty"` // !omitempty ref of the Git commit example "refs/heads/main"
|
||||
Detector DetectorMetadata `json:"detector,omitempty"`
|
||||
Metadata Metadata `json:"metadata,omitempty"`
|
||||
Manifests Manifests `json:"manifests,omitempty"`
|
||||
Scanned ISO8601Date `json:"scanned,omitempty"`
|
||||
}
|
||||
|
||||
type Job struct {
|
||||
Name string `json:"name,omitempty"` // !omitempty
|
||||
ID string `json:"id,omitempty"` // !omitempty
|
||||
HTMLURL string `json:"html_url,omitempty"`
|
||||
}
|
||||
|
||||
type DetectorMetadata struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
}
|
||||
|
||||
type Manifests map[string]Manifest
|
||||
|
||||
// Manifest A collection of related dependencies, either declared in a file,
|
||||
// or representing a logical group of dependencies.
|
||||
type Manifest struct {
|
||||
Name string `json:"name"`
|
||||
File FileInfo `json:"file"`
|
||||
Metadata Metadata `json:"metadata,omitempty"`
|
||||
Resolved DependencyGraph `json:"resolved,omitempty"`
|
||||
}
|
||||
|
||||
type FileInfo struct {
|
||||
SourceLocation string `json:"source_location,omitempty"`
|
||||
}
|
||||
|
||||
// DependencyRelationship A notation of whether a dependency is requested directly
|
||||
// by this manifest, or is a dependency of another dependency.
|
||||
type DependencyRelationship string
|
||||
|
||||
const (
|
||||
DependencyRelationshipDirect DependencyRelationship = "direct"
|
||||
DependencyRelationshipIndirect DependencyRelationship = "indirect"
|
||||
)
|
||||
|
||||
// DependencyScope A notation of whether the dependency is required for the primary
|
||||
// build artifact (runtime), or is only used for development.
|
||||
// Future versions of this specification may allow for more granular
|
||||
// scopes, like `runtimeserver`, `runtimeshipped`,
|
||||
// `developmenttest`, `developmentbenchmark`.
|
||||
type DependencyScope string
|
||||
|
||||
const (
|
||||
DependencyScopeRuntime DependencyScope = "runtime"
|
||||
DependencyScopeDevelopment DependencyScope = "development"
|
||||
)
|
||||
|
||||
type DependencyNode struct {
|
||||
Purl string `json:"purl,omitempty"`
|
||||
Metadata Metadata `json:"metadata,omitempty"`
|
||||
Relationship DependencyRelationship `json:"relationship,omitempty"`
|
||||
Scope DependencyScope `json:"scope,omitempty"`
|
||||
Dependencies []string `json:"dependencies,omitempty"`
|
||||
}
|
||||
|
||||
type DependencyGraph map[string]DependencyNode
|
||||
|
||||
type ISO8601Date = string
|
||||
|
||||
type Scalar interface{} // should be: null | boolean | string | number
|
||||
|
||||
type Metadata map[string]Scalar
|
||||
@@ -71,11 +71,11 @@ func TestSPDXJSONDecoder(t *testing.T) {
|
||||
}
|
||||
|
||||
if test.packages != nil {
|
||||
assert.Equal(t, sbom.Artifacts.Packages.Size(), len(test.packages))
|
||||
assert.Equal(t, sbom.Artifacts.PackageCatalog.PackageCount(), len(test.packages))
|
||||
|
||||
packages:
|
||||
for _, pkgName := range test.packages {
|
||||
for _, p := range sbom.Artifacts.Packages.Sorted() {
|
||||
for _, p := range sbom.Artifacts.PackageCatalog.Sorted() {
|
||||
if p.Name == pkgName {
|
||||
continue packages
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"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.
|
||||
@@ -42,13 +43,13 @@ func toFormatModel(s sbom.SBOM) (*model.Document, error) {
|
||||
},
|
||||
DataLicense: "CC0-1.0",
|
||||
DocumentNamespace: namespace,
|
||||
Packages: toPackages(s.Artifacts.Packages, s.Relationships),
|
||||
Packages: toPackages(s.Artifacts.PackageCatalog, s.Relationships),
|
||||
Files: toFiles(s),
|
||||
Relationships: toRelationships(s.Relationships),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func toPackages(catalog pkg.Collection, relationships []artifact.Relationship) []model.Package {
|
||||
func toPackages(catalog *pkg.Catalog, relationships []artifact.Relationship) []model.Package {
|
||||
packages := make([]model.Package, 0)
|
||||
|
||||
for _, p := range catalog.Sorted() {
|
||||
@@ -93,7 +94,7 @@ func fileIDsForPackage(packageSpdxID string, relationships []artifact.Relationsh
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := relationship.To.(file.Coordinates); !ok {
|
||||
if _, ok := relationship.To.(source.Coordinates); !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -109,7 +110,7 @@ func toFiles(s sbom.SBOM) []model.File {
|
||||
artifacts := s.Artifacts
|
||||
|
||||
for _, coordinates := range sbom.AllCoordinates(s) {
|
||||
var metadata *file.Metadata
|
||||
var metadata *source.FileMetadata
|
||||
if metadataForLocation, exists := artifacts.FileMetadata[coordinates]; exists {
|
||||
metadata = &metadataForLocation
|
||||
}
|
||||
@@ -164,7 +165,7 @@ func toChecksumAlgorithm(algorithm string) string {
|
||||
return strings.ToUpper(algorithm)
|
||||
}
|
||||
|
||||
func toFileTypes(metadata *file.Metadata) (ty []string) {
|
||||
func toFileTypes(metadata *source.FileMetadata) (ty []string) {
|
||||
if metadata == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/anchore/syft/internal/formats/common/spdxhelpers"
|
||||
"github.com/anchore/syft/internal/formats/spdx22json/model"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -18,12 +19,12 @@ func Test_toFileTypes(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
metadata file.Metadata
|
||||
metadata source.FileMetadata
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "application",
|
||||
metadata: file.Metadata{
|
||||
metadata: source.FileMetadata{
|
||||
MIMEType: "application/vnd.unknown",
|
||||
},
|
||||
expected: []string{
|
||||
@@ -32,7 +33,7 @@ func Test_toFileTypes(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "archive",
|
||||
metadata: file.Metadata{
|
||||
metadata: source.FileMetadata{
|
||||
MIMEType: "application/zip",
|
||||
},
|
||||
expected: []string{
|
||||
@@ -42,7 +43,7 @@ func Test_toFileTypes(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "audio",
|
||||
metadata: file.Metadata{
|
||||
metadata: source.FileMetadata{
|
||||
MIMEType: "audio/ogg",
|
||||
},
|
||||
expected: []string{
|
||||
@@ -51,7 +52,7 @@ func Test_toFileTypes(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "video",
|
||||
metadata: file.Metadata{
|
||||
metadata: source.FileMetadata{
|
||||
MIMEType: "video/3gpp",
|
||||
},
|
||||
expected: []string{
|
||||
@@ -60,7 +61,7 @@ func Test_toFileTypes(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "text",
|
||||
metadata: file.Metadata{
|
||||
metadata: source.FileMetadata{
|
||||
MIMEType: "text/html",
|
||||
},
|
||||
expected: []string{
|
||||
@@ -69,7 +70,7 @@ func Test_toFileTypes(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "image",
|
||||
metadata: file.Metadata{
|
||||
metadata: source.FileMetadata{
|
||||
MIMEType: "image/png",
|
||||
},
|
||||
expected: []string{
|
||||
@@ -78,7 +79,7 @@ func Test_toFileTypes(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "binary",
|
||||
metadata: file.Metadata{
|
||||
metadata: source.FileMetadata{
|
||||
MIMEType: "application/x-sharedlib",
|
||||
},
|
||||
expected: []string{
|
||||
@@ -174,7 +175,7 @@ func Test_fileIDsForPackage(t *testing.T) {
|
||||
Name: "bogus",
|
||||
}
|
||||
|
||||
c := file.Coordinates{
|
||||
c := source.Coordinates{
|
||||
RealPath: "/path",
|
||||
FileSystemID: "nowhere",
|
||||
}
|
||||
|
||||
@@ -85,13 +85,13 @@ func toFormatModel(s sbom.SBOM) (*spdx.Document2_2, error) {
|
||||
// Cardinality: optional, one
|
||||
DocumentComment: "",
|
||||
},
|
||||
Packages: toFormatPackages(s.Artifacts.Packages),
|
||||
Packages: toFormatPackages(s.Artifacts.PackageCatalog),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// packages populates all Package Information from the package Collection (see https://spdx.github.io/spdx-spec/3-package-information/)
|
||||
// packages populates all Package Information from the package Catalog (see https://spdx.github.io/spdx-spec/3-package-information/)
|
||||
// nolint: funlen
|
||||
func toFormatPackages(catalog pkg.Collection) map[spdx.ElementID]*spdx.Package2_2 {
|
||||
func toFormatPackages(catalog *pkg.Catalog) map[spdx.ElementID]*spdx.Package2_2 {
|
||||
results := make(map[spdx.ElementID]*spdx.Package2_2)
|
||||
|
||||
for _, p := range catalog.Sorted() {
|
||||
|
||||
@@ -28,8 +28,8 @@ func TestEncodeDecodeCycle(t *testing.T) {
|
||||
t.Errorf("metadata difference: %+v", d)
|
||||
}
|
||||
|
||||
actualPackages := actualSBOM.Artifacts.Packages.Sorted()
|
||||
for idx, p := range originalSBOM.Artifacts.Packages.Sorted() {
|
||||
actualPackages := actualSBOM.Artifacts.PackageCatalog.Sorted()
|
||||
for idx, p := range originalSBOM.Artifacts.PackageCatalog.Sorted() {
|
||||
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)
|
||||
continue
|
||||
|
||||
@@ -2,7 +2,6 @@ package syftjson
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"github.com/anchore/syft/syft/cpe"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
@@ -38,14 +37,14 @@ func TestImageEncoder(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEncodeFullJSONDocument(t *testing.T) {
|
||||
catalog := pkg.NewCollection()
|
||||
catalog := pkg.NewCatalog()
|
||||
|
||||
p1 := pkg.Package{
|
||||
Name: "package-1",
|
||||
Version: "1.0.1",
|
||||
Locations: []file.Location{
|
||||
Locations: []source.Location{
|
||||
{
|
||||
Coordinates: file.Coordinates{
|
||||
Coordinates: source.Coordinates{
|
||||
RealPath: "/a/place/a",
|
||||
},
|
||||
},
|
||||
@@ -61,17 +60,17 @@ func TestEncodeFullJSONDocument(t *testing.T) {
|
||||
Files: []pkg.PythonFileRecord{},
|
||||
},
|
||||
PURL: "a-purl-1",
|
||||
CPEs: []cpe.CPE{
|
||||
cpe.Must("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"),
|
||||
CPEs: []pkg.CPE{
|
||||
pkg.MustCPE("cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"),
|
||||
},
|
||||
}
|
||||
|
||||
p2 := pkg.Package{
|
||||
Name: "package-2",
|
||||
Version: "2.0.1",
|
||||
Locations: []file.Location{
|
||||
Locations: []source.Location{
|
||||
{
|
||||
Coordinates: file.Coordinates{
|
||||
Coordinates: source.Coordinates{
|
||||
RealPath: "/b/place/b",
|
||||
},
|
||||
},
|
||||
@@ -85,8 +84,8 @@ func TestEncodeFullJSONDocument(t *testing.T) {
|
||||
Files: []pkg.DpkgFileRecord{},
|
||||
},
|
||||
PURL: "a-purl-2",
|
||||
CPEs: []cpe.CPE{
|
||||
cpe.Must("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
|
||||
CPEs: []pkg.CPE{
|
||||
pkg.MustCPE("cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -95,50 +94,50 @@ func TestEncodeFullJSONDocument(t *testing.T) {
|
||||
|
||||
s := sbom.SBOM{
|
||||
Artifacts: sbom.Artifacts{
|
||||
Packages: catalog,
|
||||
FileMetadata: map[file.Coordinates]file.Metadata{
|
||||
file.NewLocation("/a/place").Coordinates: {
|
||||
PackageCatalog: catalog,
|
||||
FileMetadata: map[source.Coordinates]source.FileMetadata{
|
||||
source.NewLocation("/a/place").Coordinates: {
|
||||
Mode: 0775,
|
||||
Type: "directory",
|
||||
UserID: 0,
|
||||
GroupID: 0,
|
||||
},
|
||||
file.NewLocation("/a/place/a").Coordinates: {
|
||||
source.NewLocation("/a/place/a").Coordinates: {
|
||||
Mode: 0775,
|
||||
Type: "regularFile",
|
||||
UserID: 0,
|
||||
GroupID: 0,
|
||||
},
|
||||
file.NewLocation("/b").Coordinates: {
|
||||
source.NewLocation("/b").Coordinates: {
|
||||
Mode: 0775,
|
||||
Type: "symbolicLink",
|
||||
LinkDestination: "/c",
|
||||
UserID: 0,
|
||||
GroupID: 0,
|
||||
},
|
||||
file.NewLocation("/b/place/b").Coordinates: {
|
||||
source.NewLocation("/b/place/b").Coordinates: {
|
||||
Mode: 0644,
|
||||
Type: "regularFile",
|
||||
UserID: 1,
|
||||
GroupID: 2,
|
||||
},
|
||||
},
|
||||
FileDigests: map[file.Coordinates][]file.Digest{
|
||||
file.NewLocation("/a/place/a").Coordinates: {
|
||||
FileDigests: map[source.Coordinates][]file.Digest{
|
||||
source.NewLocation("/a/place/a").Coordinates: {
|
||||
{
|
||||
Algorithm: "sha256",
|
||||
Value: "366a3f5653e34673b875891b021647440d0127c2ef041e3b1a22da2a7d4f3703",
|
||||
},
|
||||
},
|
||||
file.NewLocation("/b/place/b").Coordinates: {
|
||||
source.NewLocation("/b/place/b").Coordinates: {
|
||||
{
|
||||
Algorithm: "sha256",
|
||||
Value: "1b3722da2a7d90d033b87581a2a3f12021647445653e34666ef041e3b4f3707c",
|
||||
},
|
||||
},
|
||||
},
|
||||
FileContents: map[file.Coordinates]string{
|
||||
file.NewLocation("/a/place/a").Coordinates: "the-contents",
|
||||
FileContents: map[source.Coordinates]string{
|
||||
source.NewLocation("/a/place/a").Coordinates: "the-contents",
|
||||
},
|
||||
LinuxDistribution: &linux.Release{
|
||||
ID: "redhat",
|
||||
@@ -160,7 +159,7 @@ func TestEncodeFullJSONDocument(t *testing.T) {
|
||||
},
|
||||
},
|
||||
Source: source.Metadata{
|
||||
Scheme: source.ImageType,
|
||||
Scheme: source.ImageScheme,
|
||||
ImageMetadata: source.ImageMetadata{
|
||||
UserInput: "user-image-input",
|
||||
ID: "sha256:c2b46b4eb06296933b7cf0722683964e9ecbd93265b9ef6ae9642e3952afbba0",
|
||||
|
||||
@@ -2,11 +2,13 @@ package model
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/file"
|
||||
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
type File struct {
|
||||
ID string `json:"id"`
|
||||
Location file.Coordinates `json:"location"`
|
||||
Location source.Coordinates `json:"location"`
|
||||
Metadata *FileMetadataEntry `json:"metadata,omitempty"`
|
||||
Contents string `json:"contents,omitempty"`
|
||||
Digests []file.Digest `json:"digests,omitempty"`
|
||||
@@ -14,10 +16,10 @@ type File struct {
|
||||
}
|
||||
|
||||
type FileMetadataEntry struct {
|
||||
Mode int `json:"mode"`
|
||||
Type file.Type `json:"type"`
|
||||
LinkDestination string `json:"linkDestination,omitempty"`
|
||||
UserID int `json:"userID"`
|
||||
GroupID int `json:"groupID"`
|
||||
MIMEType string `json:"mimeType"`
|
||||
Mode int `json:"mode"`
|
||||
Type source.FileType `json:"type"`
|
||||
LinkDestination string `json:"linkDestination,omitempty"`
|
||||
UserID int `json:"userID"`
|
||||
GroupID int `json:"groupID"`
|
||||
MIMEType string `json:"mimeType"`
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@@ -18,16 +18,16 @@ type Package struct {
|
||||
|
||||
// PackageBasicData contains non-ambiguous values (type-wise) from pkg.Package.
|
||||
type PackageBasicData struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Type pkg.Type `json:"type"`
|
||||
FoundBy string `json:"foundBy"`
|
||||
Locations []file.Coordinates `json:"locations"`
|
||||
Licenses []string `json:"licenses"`
|
||||
Language pkg.Language `json:"language"`
|
||||
CPEs []string `json:"cpes"`
|
||||
PURL string `json:"purl"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Type pkg.Type `json:"type"`
|
||||
FoundBy string `json:"foundBy"`
|
||||
Locations []source.Coordinates `json:"locations"`
|
||||
Licenses []string `json:"licenses"`
|
||||
Language pkg.Language `json:"language"`
|
||||
CPEs []string `json:"cpes"`
|
||||
PURL string `json:"purl"`
|
||||
}
|
||||
|
||||
// PackageCustomData contains ambiguous values (type-wise) from pkg.Package.
|
||||
|
||||
@@ -2,9 +2,10 @@ package model
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
type Secrets struct {
|
||||
Location file.Coordinates `json:"location"`
|
||||
Location source.Coordinates `json:"location"`
|
||||
Secrets []file.SearchResult `json:"secrets"`
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"version": "3.0.1",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-3.0.1.json"
|
||||
"version": "3.1.0",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-3.1.0.json"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,7 +184,7 @@
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"version": "3.0.1",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-3.0.1.json"
|
||||
"version": "3.1.0",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-3.1.0.json"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"version": "3.0.1",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-3.0.1.json"
|
||||
"version": "3.1.0",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-3.1.0.json"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package syftjson
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/anchore/syft/syft/cpe"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
@@ -31,7 +30,7 @@ func ToFormatModel(s sbom.SBOM) model.Document {
|
||||
}
|
||||
|
||||
return model.Document{
|
||||
Artifacts: toPackageModels(s.Artifacts.Packages),
|
||||
Artifacts: toPackageModels(s.Artifacts.PackageCatalog),
|
||||
ArtifactRelationships: toRelationshipModel(s.Relationships),
|
||||
Files: toFile(s),
|
||||
Secrets: toSecrets(s.Artifacts.Secrets),
|
||||
@@ -74,7 +73,7 @@ func toDescriptor(d sbom.Descriptor) model.Descriptor {
|
||||
}
|
||||
}
|
||||
|
||||
func toSecrets(data map[file.Coordinates][]file.SearchResult) []model.Secrets {
|
||||
func toSecrets(data map[source.Coordinates][]file.SearchResult) []model.Secrets {
|
||||
results := make([]model.Secrets, 0)
|
||||
for coordinates, secrets := range data {
|
||||
results = append(results, model.Secrets{
|
||||
@@ -95,7 +94,7 @@ func toFile(s sbom.SBOM) []model.File {
|
||||
artifacts := s.Artifacts
|
||||
|
||||
for _, coordinates := range sbom.AllCoordinates(s) {
|
||||
var metadata *file.Metadata
|
||||
var metadata *source.FileMetadata
|
||||
if metadataForLocation, exists := artifacts.FileMetadata[coordinates]; exists {
|
||||
metadata = &metadataForLocation
|
||||
}
|
||||
@@ -132,7 +131,7 @@ func toFile(s sbom.SBOM) []model.File {
|
||||
return results
|
||||
}
|
||||
|
||||
func toFileMetadataEntry(coordinates file.Coordinates, metadata *file.Metadata) *model.FileMetadataEntry {
|
||||
func toFileMetadataEntry(coordinates source.Coordinates, metadata *source.FileMetadata) *model.FileMetadataEntry {
|
||||
if metadata == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -153,7 +152,7 @@ func toFileMetadataEntry(coordinates file.Coordinates, metadata *file.Metadata)
|
||||
}
|
||||
}
|
||||
|
||||
func toPackageModels(catalog pkg.Collection) []model.Package {
|
||||
func toPackageModels(catalog *pkg.Catalog) []model.Package {
|
||||
artifacts := make([]model.Package, 0)
|
||||
if catalog == nil {
|
||||
return artifacts
|
||||
@@ -168,7 +167,7 @@ func toPackageModels(catalog pkg.Collection) []model.Package {
|
||||
func toPackageModel(p pkg.Package) model.Package {
|
||||
var cpes = make([]string, len(p.CPEs))
|
||||
for i, c := range p.CPEs {
|
||||
cpes[i] = cpe.String(c)
|
||||
cpes[i] = pkg.CPEString(c)
|
||||
}
|
||||
|
||||
var licenses = make([]string, 0)
|
||||
@@ -176,7 +175,7 @@ func toPackageModel(p pkg.Package) model.Package {
|
||||
licenses = p.Licenses
|
||||
}
|
||||
|
||||
var coordinates = make([]file.Coordinates, len(p.Locations))
|
||||
var coordinates = make([]source.Coordinates, len(p.Locations))
|
||||
for i, l := range p.Locations {
|
||||
coordinates[i] = l.Coordinates
|
||||
}
|
||||
@@ -217,7 +216,7 @@ func toRelationshipModel(relationships []artifact.Relationship) []model.Relation
|
||||
// toSourceModel creates a new source object to be represented into JSON.
|
||||
func toSourceModel(src source.Metadata) (model.Source, error) {
|
||||
switch src.Scheme {
|
||||
case source.ImageType:
|
||||
case source.ImageScheme:
|
||||
metadata := src.ImageMetadata
|
||||
// ensure that empty collections are not shown as null
|
||||
if metadata.RepoDigests == nil {
|
||||
@@ -230,12 +229,12 @@ func toSourceModel(src source.Metadata) (model.Source, error) {
|
||||
Type: "image",
|
||||
Target: metadata,
|
||||
}, nil
|
||||
case source.DirectoryType:
|
||||
case source.DirectoryScheme:
|
||||
return model.Source{
|
||||
Type: "directory",
|
||||
Target: src.Path,
|
||||
}, nil
|
||||
case source.FileType:
|
||||
case source.FileScheme:
|
||||
return model.Source{
|
||||
Type: "file",
|
||||
Target: src.Path,
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
func Test_toSourceModel(t *testing.T) {
|
||||
allSchemes := strset.New()
|
||||
for _, s := range source.AllTypes {
|
||||
for _, s := range source.AllSchemes {
|
||||
allSchemes.Add(string(s))
|
||||
}
|
||||
testedSchemes := strset.New()
|
||||
@@ -26,7 +26,7 @@ func Test_toSourceModel(t *testing.T) {
|
||||
{
|
||||
name: "directory",
|
||||
src: source.Metadata{
|
||||
Scheme: source.DirectoryType,
|
||||
Scheme: source.DirectoryScheme,
|
||||
Path: "some/path",
|
||||
},
|
||||
expected: model.Source{
|
||||
@@ -37,7 +37,7 @@ func Test_toSourceModel(t *testing.T) {
|
||||
{
|
||||
name: "file",
|
||||
src: source.Metadata{
|
||||
Scheme: source.FileType,
|
||||
Scheme: source.FileScheme,
|
||||
Path: "some/path",
|
||||
},
|
||||
expected: model.Source{
|
||||
@@ -48,7 +48,7 @@ func Test_toSourceModel(t *testing.T) {
|
||||
{
|
||||
name: "image",
|
||||
src: source.Metadata{
|
||||
Scheme: source.ImageType,
|
||||
Scheme: source.ImageScheme,
|
||||
ImageMetadata: source.ImageMetadata{
|
||||
UserInput: "user-input",
|
||||
ID: "id...",
|
||||
|
||||
@@ -4,8 +4,6 @@ import (
|
||||
"github.com/anchore/syft/internal/formats/syftjson/model"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"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/pkg"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
@@ -18,7 +16,7 @@ func toSyftModel(doc model.Document) (*sbom.SBOM, error) {
|
||||
|
||||
return &sbom.SBOM{
|
||||
Artifacts: sbom.Artifacts{
|
||||
Packages: catalog,
|
||||
PackageCatalog: catalog,
|
||||
LinuxDistribution: toSyftLinuxRelease(doc.Distro),
|
||||
},
|
||||
Source: *toSyftSourceData(doc.Source),
|
||||
@@ -48,7 +46,7 @@ func toSyftLinuxRelease(d model.LinuxRelease) *linux.Release {
|
||||
}
|
||||
}
|
||||
|
||||
func toSyftRelationships(doc *model.Document, catalog pkg.Collection, relationships []model.Relationship) []artifact.Relationship {
|
||||
func toSyftRelationships(doc *model.Document, catalog *pkg.Catalog, relationships []model.Relationship) []artifact.Relationship {
|
||||
idMap := make(map[string]interface{})
|
||||
|
||||
for _, p := range catalog.Sorted() {
|
||||
@@ -113,25 +111,25 @@ func toSyftSourceData(s model.Source) *source.Metadata {
|
||||
switch s.Type {
|
||||
case "directory":
|
||||
return &source.Metadata{
|
||||
Scheme: source.DirectoryType,
|
||||
Scheme: source.DirectoryScheme,
|
||||
Path: s.Target.(string),
|
||||
}
|
||||
case "file":
|
||||
return &source.Metadata{
|
||||
Scheme: source.FileType,
|
||||
Scheme: source.FileScheme,
|
||||
Path: s.Target.(string),
|
||||
}
|
||||
case "image":
|
||||
return &source.Metadata{
|
||||
Scheme: source.ImageType,
|
||||
Scheme: source.ImageScheme,
|
||||
ImageMetadata: s.Target.(source.ImageMetadata),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func toSyftCatalog(pkgs []model.Package) pkg.Collection {
|
||||
catalog := pkg.NewCollection()
|
||||
func toSyftCatalog(pkgs []model.Package) *pkg.Catalog {
|
||||
catalog := pkg.NewCatalog()
|
||||
for _, p := range pkgs {
|
||||
catalog.Add(toSyftPackage(p))
|
||||
}
|
||||
@@ -139,9 +137,9 @@ func toSyftCatalog(pkgs []model.Package) pkg.Collection {
|
||||
}
|
||||
|
||||
func toSyftPackage(p model.Package) pkg.Package {
|
||||
var cpes []cpe.CPE
|
||||
var cpes []pkg.CPE
|
||||
for _, c := range p.CPEs {
|
||||
value, err := cpe.New(c)
|
||||
value, err := pkg.NewCPE(c)
|
||||
if err != nil {
|
||||
log.Warnf("excluding invalid CPE %q: %v", c, err)
|
||||
continue
|
||||
@@ -150,9 +148,9 @@ func toSyftPackage(p model.Package) pkg.Package {
|
||||
cpes = append(cpes, value)
|
||||
}
|
||||
|
||||
var locations = make([]file.Location, len(p.Locations))
|
||||
var locations = make([]source.Location, len(p.Locations))
|
||||
for i, c := range p.Locations {
|
||||
locations[i] = file.NewLocationFromCoordinates(c)
|
||||
locations[i] = source.NewLocationFromCoordinates(c)
|
||||
}
|
||||
|
||||
return pkg.Package{
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
func Test_toSyftSourceData(t *testing.T) {
|
||||
allSchemes := strset.New()
|
||||
for _, s := range source.AllTypes {
|
||||
for _, s := range source.AllSchemes {
|
||||
allSchemes.Add(string(s))
|
||||
}
|
||||
testedSchemes := strset.New()
|
||||
@@ -24,7 +24,7 @@ func Test_toSyftSourceData(t *testing.T) {
|
||||
{
|
||||
name: "directory",
|
||||
expected: source.Metadata{
|
||||
Scheme: source.DirectoryType,
|
||||
Scheme: source.DirectoryScheme,
|
||||
Path: "some/path",
|
||||
},
|
||||
src: model.Source{
|
||||
@@ -35,7 +35,7 @@ func Test_toSyftSourceData(t *testing.T) {
|
||||
{
|
||||
name: "file",
|
||||
expected: source.Metadata{
|
||||
Scheme: source.FileType,
|
||||
Scheme: source.FileScheme,
|
||||
Path: "some/path",
|
||||
},
|
||||
src: model.Source{
|
||||
@@ -46,7 +46,7 @@ func Test_toSyftSourceData(t *testing.T) {
|
||||
{
|
||||
name: "image",
|
||||
expected: source.Metadata{
|
||||
Scheme: source.ImageType,
|
||||
Scheme: source.ImageScheme,
|
||||
ImageMetadata: source.ImageMetadata{
|
||||
UserInput: "user-input",
|
||||
ID: "id...",
|
||||
|
||||
@@ -15,7 +15,7 @@ func encoder(output io.Writer, s sbom.SBOM) error {
|
||||
var rows [][]string
|
||||
|
||||
columns := []string{"Name", "Version", "Type"}
|
||||
for _, p := range s.Artifacts.Packages.Sorted() {
|
||||
for _, p := range s.Artifacts.PackageCatalog.Sorted() {
|
||||
row := []string{
|
||||
p.Name,
|
||||
p.Version,
|
||||
|
||||
@@ -16,9 +16,9 @@ func encoder(output io.Writer, s sbom.SBOM) error {
|
||||
w.Init(output, 0, 8, 0, '\t', tabwriter.AlignRight)
|
||||
|
||||
switch s.Source.Scheme {
|
||||
case source.DirectoryType, source.FileType:
|
||||
case source.DirectoryScheme, source.FileScheme:
|
||||
fmt.Fprintf(w, "[Path: %s]\n", s.Source.Path)
|
||||
case source.ImageType:
|
||||
case source.ImageScheme:
|
||||
fmt.Fprintln(w, "[Image]")
|
||||
|
||||
for idx, l := range s.Source.ImageMetadata.Layers {
|
||||
@@ -35,7 +35,7 @@ func encoder(output io.Writer, s sbom.SBOM) error {
|
||||
|
||||
// populate artifacts...
|
||||
rows := 0
|
||||
for _, p := range s.Artifacts.Packages.Sorted() {
|
||||
for _, p := range s.Artifacts.PackageCatalog.Sorted() {
|
||||
fmt.Fprintf(w, "[%s]\n", p.Name)
|
||||
fmt.Fprintln(w, " Version:\t", p.Version)
|
||||
fmt.Fprintln(w, " Type:\t", string(p.Type))
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
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
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
-172
@@ -1,172 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
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,12 +0,0 @@
|
||||
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/*
|
||||
@@ -1,12 +0,0 @@
|
||||
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/*
|
||||
@@ -1 +0,0 @@
|
||||
file 1!
|
||||
@@ -1,40 +0,0 @@
|
||||
package cataloger
|
||||
|
||||
const (
|
||||
ApkDBID ID = "os-apkdb"
|
||||
DpkgID ID = "os-dpkg"
|
||||
RpmDBID ID = "os-rpmdb"
|
||||
RubyGemspecID ID = "ruby-gem-spec"
|
||||
RubyGemfileLockID ID = "ruby-gem-file-lock"
|
||||
PythonPackageID ID = "python-package"
|
||||
PythonRequirementsID ID = "python-requirements"
|
||||
PythonPoetryID ID = "python-poetry"
|
||||
PythonSetupID ID = "python-setup"
|
||||
PythonPipFileID ID = "python-pipfile"
|
||||
JavascriptPackageJSONID ID = "javascript-package-json"
|
||||
JavascriptPackageLockID ID = "javascript-package-lock"
|
||||
JavaScriptYarnLockID ID = "javascript-yarn-lock"
|
||||
JavaArchiveID ID = "java-archive"
|
||||
GoModID ID = "go-mod"
|
||||
GoBinaryID ID = "go-binary"
|
||||
RustCargoLockID ID = "rust-cargo-lock"
|
||||
PHPInstalledJSONID ID = "php-installed-json"
|
||||
PHPComposerLockID ID = "php-composer-lock"
|
||||
|
||||
FileMetadataID ID = "file-metadata"
|
||||
FileDigestsID ID = "file-digest"
|
||||
SecretsID ID = "secrets"
|
||||
FileClassifierID ID = "file-classifier"
|
||||
FileContentsID ID = "file-content"
|
||||
)
|
||||
|
||||
type ID string
|
||||
type IDs []ID
|
||||
|
||||
func (c IDs) Len() int { return len(c) }
|
||||
|
||||
func (c IDs) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
||||
|
||||
func (c IDs) Less(i, j int) bool {
|
||||
return c[i] < c[j]
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package packages
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func FindRelationships(catalog pkg.Collection, resolver file.Resolver) []artifact.Relationship {
|
||||
var allRelationships []artifact.Relationship
|
||||
for p := range catalog.Enumerate() {
|
||||
relationships, err := createFileOwnershipRelationships(p, resolver)
|
||||
if err != nil {
|
||||
log.Warnf("unable to create any package-file relationships for package name=%q: %w", p.Name, err)
|
||||
continue
|
||||
}
|
||||
allRelationships = append(allRelationships, relationships...)
|
||||
}
|
||||
|
||||
allRelationships = append(allRelationships, findOwnershipByFileOverlapRelationship(catalog)...)
|
||||
|
||||
return allRelationships
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package generic
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
)
|
||||
|
||||
// Parser standardizes a function signature for parser functions that accept the virtual file path (not usable for file reads) and contents and return any discovered packages from that file
|
||||
type Parser func(string, io.Reader) ([]*pkg.Package, []artifact.Relationship, error)
|
||||
@@ -1 +0,0 @@
|
||||
test-fixtures/last/path.txt file contents!
|
||||
@@ -1,17 +0,0 @@
|
||||
/*
|
||||
Package golang provides a concrete Cataloger implementation for go.mod files.
|
||||
*/
|
||||
package golang
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/cataloger/packages/generic"
|
||||
)
|
||||
|
||||
// NewGoModFileCataloger returns a new Go module cataloger object.
|
||||
func NewGoModFileCataloger() *generic.Cataloger {
|
||||
globParsers := map[string]generic.Parser{
|
||||
"**/go.mod": parseGoMod,
|
||||
}
|
||||
|
||||
return generic.NewCataloger(nil, globParsers)
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
Package javascript provides a concrete Cataloger implementation for JavaScript ecosystem files (yarn and npm).
|
||||
*/
|
||||
package javascript
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/cataloger/packages/generic"
|
||||
)
|
||||
|
||||
// NewJavascriptPackageCataloger returns a new JavaScript cataloger object based on detection of npm based packages.
|
||||
func NewJavascriptPackageCataloger() *generic.Cataloger {
|
||||
globParsers := map[string]generic.Parser{
|
||||
"**/package.json": parsePackageJSON,
|
||||
}
|
||||
|
||||
return generic.NewCataloger(nil, globParsers)
|
||||
}
|
||||
|
||||
// NewJavascriptLockCataloger returns a new Javascript cataloger object base on package lock files.
|
||||
func NewJavascriptPackageLockCataloger() *generic.Cataloger {
|
||||
globParsers := map[string]generic.Parser{
|
||||
"**/package-lock.json": parsePackageLock,
|
||||
}
|
||||
|
||||
return generic.NewCataloger(nil, globParsers)
|
||||
}
|
||||
|
||||
func NewJavascriptYarnLockCataloger() *generic.Cataloger {
|
||||
globParsers := map[string]generic.Parser{
|
||||
"**/yarn.lock": parseYarnLock,
|
||||
}
|
||||
|
||||
return generic.NewCataloger(nil, globParsers)
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
Package php provides a concrete Cataloger implementation for PHP ecosystem files.
|
||||
*/
|
||||
package php
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/cataloger/packages/generic"
|
||||
)
|
||||
|
||||
// NewPHPComposerInstalledCataloger returns a new cataloger for PHP installed.json files.
|
||||
func NewPHPComposerInstalledCataloger() *generic.Cataloger {
|
||||
globParsers := map[string]generic.Parser{
|
||||
"**/installed.json": parseInstalledJSON,
|
||||
}
|
||||
|
||||
return generic.NewCataloger(nil, globParsers)
|
||||
}
|
||||
|
||||
// NewPHPComposerLockCataloger returns a new cataloger for PHP composer.lock files.
|
||||
func NewPHPComposerLockCataloger() *generic.Cataloger {
|
||||
globParsers := map[string]generic.Parser{
|
||||
"**/composer.lock": parseComposerLock,
|
||||
}
|
||||
|
||||
return generic.NewCataloger(nil, globParsers)
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
Package python provides a concrete Cataloger implementation for Python ecosystem files (egg, wheel, requirements.txt).
|
||||
*/
|
||||
package python
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/cataloger/packages/generic"
|
||||
)
|
||||
|
||||
// NewPythonIndexCataloger returns a new cataloger for python packages referenced from poetry lock files, requirements.txt files, and setup.py files.
|
||||
func NewPythonRequirementsCataloger() *generic.Cataloger {
|
||||
globParsers := map[string]generic.Parser{
|
||||
"**/*requirements*.txt": parseRequirementsTxt,
|
||||
}
|
||||
|
||||
return generic.NewCataloger(nil, globParsers)
|
||||
}
|
||||
|
||||
func NewPythonPoetryCataloger() *generic.Cataloger {
|
||||
globParsers := map[string]generic.Parser{
|
||||
"**/poetry.lock": parsePoetryLock,
|
||||
}
|
||||
|
||||
return generic.NewCataloger(nil, globParsers)
|
||||
}
|
||||
|
||||
func NewPythonPipfileCataloger() *generic.Cataloger {
|
||||
globParsers := map[string]generic.Parser{
|
||||
"**/Pipfile.lock": parsePipfileLock,
|
||||
}
|
||||
|
||||
return generic.NewCataloger(nil, globParsers)
|
||||
}
|
||||
|
||||
func NewPythonSetupCataloger() *generic.Cataloger {
|
||||
globParsers := map[string]generic.Parser{
|
||||
"**/setup.py": parseSetup,
|
||||
}
|
||||
|
||||
return generic.NewCataloger(nil, globParsers)
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
package packages
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestOwnershipByFilesRelationship(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
setup func(t testing.TB) ([]pkg.Package, []artifact.Relationship)
|
||||
}{
|
||||
{
|
||||
name: "owns-by-real-path",
|
||||
setup: func(t testing.TB) ([]pkg.Package, []artifact.Relationship) {
|
||||
parent := pkg.Package{
|
||||
Locations: []file.Location{
|
||||
file.NewVirtualLocation("/a/path", "/another/path"),
|
||||
file.NewVirtualLocation("/b/path", "/bee/path"),
|
||||
},
|
||||
Type: pkg.RpmPkg,
|
||||
MetadataType: pkg.RpmdbMetadataType,
|
||||
Metadata: pkg.RpmdbMetadata{
|
||||
Files: []pkg.RpmdbFileRecord{
|
||||
{Path: "/owning/path/1"},
|
||||
{Path: "/owning/path/2"},
|
||||
{Path: "/d/path"},
|
||||
},
|
||||
},
|
||||
}
|
||||
parent.SetID()
|
||||
|
||||
child := pkg.Package{
|
||||
Locations: []file.Location{
|
||||
file.NewVirtualLocation("/c/path", "/another/path"),
|
||||
file.NewVirtualLocation("/d/path", "/another/path"),
|
||||
},
|
||||
Type: pkg.NpmPkg,
|
||||
}
|
||||
child.SetID()
|
||||
|
||||
relationship := artifact.Relationship{
|
||||
From: parent,
|
||||
To: child,
|
||||
Type: artifact.OwnershipByFileOverlapRelationship,
|
||||
Data: ownershipByFilesMetadata{
|
||||
Files: []string{
|
||||
"/d/path",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return []pkg.Package{parent, child}, []artifact.Relationship{relationship}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "owns-by-virtual-path",
|
||||
setup: func(t testing.TB) ([]pkg.Package, []artifact.Relationship) {
|
||||
parent := pkg.Package{
|
||||
Locations: []file.Location{
|
||||
file.NewVirtualLocation("/a/path", "/some/other/path"),
|
||||
file.NewVirtualLocation("/b/path", "/bee/path"),
|
||||
},
|
||||
Type: pkg.RpmPkg,
|
||||
MetadataType: pkg.RpmdbMetadataType,
|
||||
Metadata: pkg.RpmdbMetadata{
|
||||
Files: []pkg.RpmdbFileRecord{
|
||||
{Path: "/owning/path/1"},
|
||||
{Path: "/owning/path/2"},
|
||||
{Path: "/another/path"},
|
||||
},
|
||||
},
|
||||
}
|
||||
parent.SetID()
|
||||
|
||||
child := pkg.Package{
|
||||
Locations: []file.Location{
|
||||
file.NewVirtualLocation("/c/path", "/another/path"),
|
||||
file.NewLocation("/d/path"),
|
||||
},
|
||||
Type: pkg.NpmPkg,
|
||||
}
|
||||
child.SetID()
|
||||
|
||||
relationship := artifact.Relationship{
|
||||
From: parent,
|
||||
To: child,
|
||||
Type: artifact.OwnershipByFileOverlapRelationship,
|
||||
Data: ownershipByFilesMetadata{
|
||||
Files: []string{
|
||||
"/another/path",
|
||||
},
|
||||
},
|
||||
}
|
||||
return []pkg.Package{parent, child}, []artifact.Relationship{relationship}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ignore-empty-path",
|
||||
setup: func(t testing.TB) ([]pkg.Package, []artifact.Relationship) {
|
||||
parent := pkg.Package{
|
||||
Locations: []file.Location{
|
||||
file.NewVirtualLocation("/a/path", "/some/other/path"),
|
||||
file.NewVirtualLocation("/b/path", "/bee/path"),
|
||||
},
|
||||
Type: pkg.RpmPkg,
|
||||
MetadataType: pkg.RpmdbMetadataType,
|
||||
Metadata: pkg.RpmdbMetadata{
|
||||
Files: []pkg.RpmdbFileRecord{
|
||||
{Path: "/owning/path/1"},
|
||||
{Path: "/owning/path/2"},
|
||||
{Path: ""},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
parent.SetID()
|
||||
|
||||
child := pkg.Package{
|
||||
Locations: []file.Location{
|
||||
file.NewVirtualLocation("/c/path", "/another/path"),
|
||||
file.NewLocation("/d/path"),
|
||||
},
|
||||
Type: pkg.NpmPkg,
|
||||
}
|
||||
|
||||
child.SetID()
|
||||
|
||||
return []pkg.Package{parent, child}, nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
pkgs, expectedRelations := test.setup(t)
|
||||
c := pkg.NewCollection(pkgs...)
|
||||
relationships := findOwnershipByFileOverlapRelationship(c)
|
||||
|
||||
assert.Len(t, relationships, len(expectedRelations))
|
||||
for idx, expectedRelationship := range expectedRelations {
|
||||
actualRelationship := relationships[idx]
|
||||
assert.Equal(t, expectedRelationship.From.ID(), actualRelationship.From.ID())
|
||||
assert.Equal(t, expectedRelationship.To.ID(), actualRelationship.To.ID())
|
||||
assert.Equal(t, expectedRelationship.Type, actualRelationship.Type)
|
||||
assert.Equal(t, expectedRelationship.Data, actualRelationship.Data)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package syft
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/internal/version"
|
||||
"github.com/anchore/syft/syft/cataloger"
|
||||
"github.com/anchore/syft/syft/cataloger/files/fileclassifier"
|
||||
"github.com/anchore/syft/syft/cataloger/files/filecontents"
|
||||
"github.com/anchore/syft/syft/cataloger/files/secrets"
|
||||
"github.com/anchore/syft/syft/cataloger/packages"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
type CatalogingConfig struct {
|
||||
// tool-specific information
|
||||
ToolName string
|
||||
ToolVersion string
|
||||
ToolConfiguration interface{}
|
||||
// applies to all catalogers
|
||||
DefaultScope source.Scope // TODO: shouldn't this be in the package.SearchConfig?
|
||||
ProcessTasksInSerial bool // TODO: this seems a little odd, if this should be an option is this the right spot?
|
||||
EnabledCatalogers []cataloger.ID
|
||||
availableTasks *taskCollection
|
||||
// package
|
||||
PackageSearch packages.SearchConfig
|
||||
// file metadata
|
||||
DigestHashes []crypto.Hash
|
||||
// secrets
|
||||
SecretsSearch secrets.Config
|
||||
SecretsScope source.Scope
|
||||
// file classification
|
||||
FileClassifiers []fileclassifier.Classifier
|
||||
// file contents
|
||||
ContentsSearch filecontents.Config
|
||||
}
|
||||
|
||||
func DefaultCatalogingConfig() CatalogingConfig {
|
||||
return CatalogingConfig{
|
||||
DefaultScope: source.SquashedScope,
|
||||
ToolName: internal.ApplicationName,
|
||||
ToolVersion: version.Guess(),
|
||||
SecretsScope: source.AllLayersScope,
|
||||
SecretsSearch: secrets.DefaultConfig(),
|
||||
FileClassifiers: fileclassifier.DefaultClassifiers(),
|
||||
ContentsSearch: filecontents.DefaultConfig(),
|
||||
PackageSearch: packages.DefaultSearchConfig(),
|
||||
}
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
package syft
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"fmt"
|
||||
"github.com/anchore/syft/syft/cataloger"
|
||||
"github.com/anchore/syft/syft/cataloger/files/fileclassifier"
|
||||
"github.com/anchore/syft/syft/cataloger/files/secrets"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type CatalogingOption func(*source.Source, *CatalogingConfig) error
|
||||
|
||||
func WithConfig(override CatalogingConfig) CatalogingOption {
|
||||
return func(_ *source.Source, config *CatalogingConfig) error {
|
||||
*config = override
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithoutConcurrency() CatalogingOption {
|
||||
return func(_ *source.Source, config *CatalogingConfig) error {
|
||||
config.ProcessTasksInSerial = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithScope(scope source.Scope) CatalogingOption {
|
||||
return func(_ *source.Source, config *CatalogingConfig) error {
|
||||
config.DefaultScope = scope
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithToolIdentification(name, version string) CatalogingOption {
|
||||
return func(_ *source.Source, config *CatalogingConfig) error {
|
||||
config.ToolName = name
|
||||
config.ToolVersion = version
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithToolConfiguration(c interface{}) CatalogingOption {
|
||||
return func(_ *source.Source, config *CatalogingConfig) error {
|
||||
config.ToolConfiguration = c
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithPackageCataloger(id cataloger.ID, c pkg.Cataloger) CatalogingOption {
|
||||
return func(_ *source.Source, config *CatalogingConfig) error {
|
||||
if config.availableTasks == nil {
|
||||
var err error
|
||||
config.availableTasks, err = newTaskCollection()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
gen := func(id cataloger.ID, cfg CatalogingConfig) (task, error) {
|
||||
return newPkgCatalogerTask(id, cfg, c), nil
|
||||
}
|
||||
config.EnabledCatalogers = append(config.EnabledCatalogers, id)
|
||||
|
||||
return config.availableTasks.add(string(id), gen)
|
||||
}
|
||||
}
|
||||
|
||||
func WithDefaultCatalogers() CatalogingOption {
|
||||
return func(src *source.Source, config *CatalogingConfig) error {
|
||||
if config.availableTasks == nil {
|
||||
var err error
|
||||
config.availableTasks, err = newTaskCollection()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// override any previously added catalogers
|
||||
tc := config.availableTasks
|
||||
if len(config.EnabledCatalogers) == 0 {
|
||||
switch src.Metadata.Scheme {
|
||||
case source.ImageType:
|
||||
config.EnabledCatalogers = tc.withLabels(packageTaskLabel, installedTaskLabel)
|
||||
case source.FileType:
|
||||
config.EnabledCatalogers = tc.all()
|
||||
case source.DirectoryType:
|
||||
// TODO: it looks like gemspec was left out on main, is this intentional? if so it's not accounted for here...
|
||||
config.EnabledCatalogers = tc.withLabels(packageTaskLabel)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithCatalogers(catalogers ...string) CatalogingOption {
|
||||
return func(src *source.Source, config *CatalogingConfig) error {
|
||||
if config.availableTasks == nil {
|
||||
var err error
|
||||
config.availableTasks, err = newTaskCollection()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// override any previously added catalogers
|
||||
for _, q := range catalogers {
|
||||
var ids []cataloger.ID
|
||||
isAdditive := strings.HasPrefix(q, "+")
|
||||
if isAdditive {
|
||||
ids = config.availableTasks.query(strings.TrimPrefix(q, "+"))
|
||||
} else {
|
||||
ids = config.availableTasks.query(q)
|
||||
}
|
||||
|
||||
if len(ids) == 0 {
|
||||
return fmt.Errorf("cataloger selection invalid: %q", q)
|
||||
}
|
||||
|
||||
if isAdditive && len(config.EnabledCatalogers) == 0 {
|
||||
// stick in the default set of catalogers first
|
||||
if err := WithDefaultCatalogers()(src, config); err != nil {
|
||||
return fmt.Errorf("unable to set default catalogers: %w", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
config.EnabledCatalogers = append(config.EnabledCatalogers, ids...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithFileMetadata() CatalogingOption {
|
||||
return func(_ *source.Source, config *CatalogingConfig) error {
|
||||
config.EnabledCatalogers = append(config.EnabledCatalogers, cataloger.FileMetadataID)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithFileDigests(hashes ...crypto.Hash) CatalogingOption {
|
||||
return func(_ *source.Source, config *CatalogingConfig) error {
|
||||
config.EnabledCatalogers = append(config.EnabledCatalogers, cataloger.FileDigestsID)
|
||||
config.DigestHashes = hashes
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithSecrets(secretConfig *secrets.Config) CatalogingOption {
|
||||
return func(_ *source.Source, config *CatalogingConfig) error {
|
||||
config.EnabledCatalogers = append(config.EnabledCatalogers, cataloger.SecretsID)
|
||||
if secretConfig != nil {
|
||||
config.SecretsSearch = *secretConfig
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithFileClassification() CatalogingOption {
|
||||
return func(_ *source.Source, config *CatalogingConfig) error {
|
||||
if len(config.FileClassifiers) > 0 {
|
||||
config.EnabledCatalogers = append(config.EnabledCatalogers, cataloger.FileClassifierID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithFileClassifiers(classifiers ...fileclassifier.Classifier) CatalogingOption {
|
||||
return func(_ *source.Source, config *CatalogingConfig) error {
|
||||
config.FileClassifiers = classifiers
|
||||
if len(config.FileClassifiers) > 0 {
|
||||
config.EnabledCatalogers = append(config.EnabledCatalogers, cataloger.FileClassifierID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithFileContents(globs ...string) CatalogingOption {
|
||||
return func(_ *source.Source, config *CatalogingConfig) error {
|
||||
config.EnabledCatalogers = append(config.EnabledCatalogers, cataloger.FileContentsID)
|
||||
config.ContentsSearch.Globs = globs
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithFileSizeLimit(byteLimit int64) CatalogingOption {
|
||||
return func(_ *source.Source, config *CatalogingConfig) error {
|
||||
config.ContentsSearch.SkipFilesAboveSizeInBytes = byteLimit
|
||||
config.SecretsSearch.MaxFileSize = byteLimit
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
package cpe
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCPESpecificity(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input []CPE
|
||||
expected []CPE
|
||||
}{
|
||||
{
|
||||
name: "sort strictly by wfn *",
|
||||
input: []CPE{
|
||||
Must("cpe:2.3:a:*:package:1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:some:package:1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:*:package:1:*:*:*:*:some:*:*"),
|
||||
Must("cpe:2.3:a:some:package:1:*:*:*:*:some:*:*"),
|
||||
Must("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*"),
|
||||
},
|
||||
expected: []CPE{
|
||||
Must("cpe:2.3:a:some:package:1:*:*:*:*:some:*:*"),
|
||||
Must("cpe:2.3:a:some:package:1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:some:package:*:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:*:package:1:*:*:*:*:some:*:*"),
|
||||
Must("cpe:2.3:a:*:package:1:*:*:*:*:*:*:*"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sort strictly by field length",
|
||||
input: []CPE{
|
||||
Must("cpe:2.3:a:1:22:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:55555:1:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:1:333:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:666666:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:1:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:1:1:*:*:*:*:4444:*:*"),
|
||||
},
|
||||
expected: []CPE{
|
||||
Must("cpe:2.3:a:1:666666:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:55555:1:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:1:1:*:*:*:*:4444:*:*"),
|
||||
Must("cpe:2.3:a:1:1:333:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:22:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:1:1:*:*:*:*:1:*:*"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sort by mix of field length and specificity",
|
||||
input: []CPE{
|
||||
Must("cpe:2.3:a:1:666666:*:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:*:1:1:*:*:*:*:4444:*:*"),
|
||||
Must("cpe:2.3:a:1:*:333:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:1:1:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:22:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:55555:1:1:*:*:*:*:1:*:*"),
|
||||
},
|
||||
expected: []CPE{
|
||||
Must("cpe:2.3:a:55555:1:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:22:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:1:1:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:1:666666:*:*:*:*:*:1:*:*"),
|
||||
Must("cpe:2.3:a:*:1:1:*:*:*:*:4444:*:*"),
|
||||
Must("cpe:2.3:a:1:*:333:*:*:*:*:*:*:*"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sort by mix of field length, specificity, dash",
|
||||
input: []CPE{
|
||||
Must("cpe:2.3:a:alpine:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:alpine_keys:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:alpine-keys:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:alpine:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:alpine-keys:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:alpine_keys:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
},
|
||||
expected: []CPE{
|
||||
Must("cpe:2.3:a:alpine-keys:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:alpine-keys:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:alpine_keys:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:alpine_keys:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:alpine:alpine-keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
Must("cpe:2.3:a:alpine:alpine_keys:2.3-r1:*:*:*:*:*:*:*"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
sort.Sort(BySpecificity(test.input))
|
||||
assert.Equal(t, test.expected, test.input)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user