Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor source creation and add --from flag #2610

Merged
merged 47 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
23960b8
feat: selectable source providers
kzantow Feb 6, 2024
96faf7b
Merge remote-tracking branch 'upstream/main' into feat/from-flag
kzantow Feb 6, 2024
6473535
chore: cleanup and improve messaging
kzantow Feb 7, 2024
3920c2b
chore: cleanup stutters
kzantow Feb 7, 2024
dc2b54e
Merge remote-tracking branch 'upstream/main' into feat/from-flag
kzantow Feb 7, 2024
dd4d78d
chore: minor refactoring
kzantow Feb 8, 2024
cbe198c
Merge remote-tracking branch 'upstream/main' into feat/from-flag
kzantow Feb 8, 2024
7b52be5
chore: adjust stereoscope provider platform
kzantow Feb 9, 2024
96cd137
chore: address some PR feedback and cleanup
kzantow Feb 9, 2024
22b9981
Merge remote-tracking branch 'upstream/main' into feat/from-flag
kzantow Feb 9, 2024
6b028ab
Merge remote-tracking branch 'upstream/main' into feat/from-flag
kzantow Feb 9, 2024
a37f88f
chore: Provide -> ProvideSource
kzantow Feb 12, 2024
233b750
chore: update based on latest stereoscope changes
kzantow Feb 13, 2024
d619070
chore: replace -> go work
kzantow Feb 13, 2024
670d2ac
Merge remote-tracking branch 'upstream/main' into feat/from-flag
kzantow Feb 13, 2024
355020c
chore: update to anchore/go-collections
kzantow Feb 16, 2024
dc378bc
chore: rename provide method
kzantow Feb 16, 2024
1d5b761
Merge remote-tracking branch 'upstream/main' into feat/from-flag
kzantow Feb 21, 2024
9285d69
chore: update stereoscope
kzantow Feb 21, 2024
20b9476
chore: naming
kzantow Feb 21, 2024
3e4fc21
chore: naming / fix tests
kzantow Feb 21, 2024
b1d697e
chore: update tests
kzantow Feb 22, 2024
4364ca6
chore: update source metadata generator and tests
kzantow Feb 23, 2024
e4dc35e
chore: update getSource usages
kzantow Feb 23, 2024
95654ae
chore: update source test working dirs
kzantow Feb 23, 2024
2b7cc20
chore: lint-fix
kzantow Feb 23, 2024
3b59db5
chore: update more tests
kzantow Feb 23, 2024
ef82c61
chore: update more tests
kzantow Feb 23, 2024
e614b1d
chore: extract utility functions
kzantow Feb 23, 2024
2583d37
chore: update README
kzantow Feb 23, 2024
6bc1241
chore: naming
kzantow Feb 23, 2024
a723b8e
chore: naming without stutter
kzantow Feb 23, 2024
230a27d
chore: update test
kzantow Feb 23, 2024
0cc6daa
Merge remote-tracking branch 'upstream/main' into feat/from-flag
kzantow Feb 26, 2024
69b5449
chore: address PR feedback
kzantow Feb 26, 2024
225a035
chore: address PR feedback
kzantow Feb 27, 2024
ef46795
chore: missed refactoring
kzantow Feb 27, 2024
d6f0dc5
chore: PR feedback
kzantow Feb 27, 2024
028da84
chore: restore platform validation
kzantow Feb 27, 2024
4b5d36d
chore: source metadata in source package
kzantow Feb 27, 2024
f8ac4b8
chore: minor cleanup
kzantow Feb 27, 2024
40a99a0
chore: fix config setup
kzantow Feb 27, 2024
e3cf09e
chore: move sourceproviders under source
kzantow Feb 27, 2024
8e85c38
chore: revert metadata type name generator
kzantow Feb 27, 2024
bd44616
fix: incorrect source metadata check
kzantow Feb 27, 2024
8a929a8
chore: add constants for tags
kzantow Feb 27, 2024
4ddeeb1
chore: remove unused constant
kzantow Feb 27, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
33 changes: 16 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ syft <image> --scope all-layers

### Supported sources

Syft can generate an SBOM from a variety of sources:
Syft can generate an SBOM from a variety of sources including images, files, directories, and archives. Syft will attempt to
determine the type of source based on provided input, for example:

```bash
# catalog a container image archive (from the result of `docker image save ...`, `podman save ...`, or `skopeo copy` commands)
Expand All @@ -135,26 +136,24 @@ syft path/to/image.sif
syft path/to/dir
```

Sources can be explicitly provided with a scheme:
To explicitly specify the source behavior, use the `--from` flag. Allowable options are:

```
docker:yourrepo/yourimage:tag use images from the Docker daemon
podman:yourrepo/yourimage:tag use images from the Podman daemon
containerd:yourrepo/yourimage:tag use images from the Containerd daemon
docker-archive:path/to/yourimage.tar use a tarball from disk for archives created from "docker save"
oci-archive:path/to/yourimage.tar use a tarball from disk for OCI archives (from Skopeo or otherwise)
oci-dir:path/to/yourimage read directly from a path on disk for OCI layout directories (from Skopeo or otherwise)
singularity:path/to/yourimage.sif read directly from a Singularity Image Format (SIF) container on disk
dir:path/to/yourproject read directly from a path on disk (any directory)
file:path/to/yourproject/file read directly from a path on disk (any single file)
registry:yourrepo/yourimage:tag pull image directly from a registry (no container runtime required)
docker use images from the Docker daemon
podman use images from the Podman daemon
containerd use images from the Containerd daemon
docker-archive use a tarball from disk for archives created from "docker save"
oci-archive use a tarball from disk for OCI archives (from Skopeo or otherwise)
oci-dir read directly from a path on disk for OCI layout directories (from Skopeo or otherwise)
singularity read directly from a Singularity Image Format (SIF) container on disk
dir read directly from a path on disk (any directory)
file read directly from a path on disk (any single file)
registry pull image directly from a registry (no container runtime required)
```
If a source is not provided and Syft identifies the input as a potential image reference, Syft will attempt to resolve it using:
the Docker, Podman, and Containerd daemons followed by direct registry access, in that order.

If an image source is not provided and cannot be detected from the given reference it is assumed the image should be pulled from the Docker daemon.
If docker is not present, then the Podman daemon is attempted next, followed by reaching out directly to the image registry last.


This default behavior can be overridden with the `default-image-pull-source` configuration option (See [Configuration](https://github.com/anchore/syft#configuration) for more details).
This default behavior can be overridden with the `default-image-pull-source` configuration option (See [Configuration](#configuration) for more details).


### File selection
Expand Down
11 changes: 2 additions & 9 deletions cmd/syft/internal/commands/attest.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import (
"github.com/anchore/syft/syft/format/spdxtagvalue"
"github.com/anchore/syft/syft/format/syftjson"
"github.com/anchore/syft/syft/sbom"
"github.com/anchore/syft/syft/source"
"github.com/anchore/syft/syft/source/stereoscopesource"
)

const (
Expand Down Expand Up @@ -247,7 +247,7 @@ func predicateType(outputName string) string {
}

func generateSBOMForAttestation(ctx context.Context, id clio.Identification, opts *options.Catalog, userInput string) (*sbom.SBOM, error) {
src, err := getSource(opts, userInput, onlyContainerImages)
src, err := getSource(ctx, opts, userInput, stereoscopesource.ImageTag)
kzantow marked this conversation as resolved.
Show resolved Hide resolved

if err != nil {
return nil, err
Expand All @@ -273,13 +273,6 @@ func generateSBOMForAttestation(ctx context.Context, id clio.Identification, opt
return s, nil
}

func onlyContainerImages(d *source.Detection) error {
if !d.IsContainerImage() {
return fmt.Errorf("attestations are only supported for oci images at this time")
}
return nil
}

func commandExists(cmd string) bool {
_, err := exec.LookPath(cmd)
return err == nil
Expand Down
84 changes: 46 additions & 38 deletions cmd/syft/internal/commands/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import (
"gopkg.in/yaml.v3"

"github.com/anchore/clio"
"github.com/anchore/go-collections"
"github.com/anchore/stereoscope"
"github.com/anchore/stereoscope/pkg/image"
"github.com/anchore/syft/cmd/syft/internal/options"
"github.com/anchore/syft/cmd/syft/internal/ui"
Expand Down Expand Up @@ -162,14 +164,13 @@ func validateArgs(cmd *cobra.Command, args []string, error string) error {
return cobra.MaximumNArgs(1)(cmd, args)
}

// nolint:funlen
func runScan(ctx context.Context, id clio.Identification, opts *scanOptions, userInput string) error {
writer, err := opts.SBOMWriter()
if err != nil {
return err
}

src, err := getSource(&opts.Catalog, userInput)
src, err := getSource(ctx, &opts.Catalog, userInput)

if err != nil {
return err
Expand Down Expand Up @@ -199,52 +200,55 @@ func runScan(ctx context.Context, id clio.Identification, opts *scanOptions, use
return nil
}

func getSource(opts *options.Catalog, userInput string, filters ...func(*source.Detection) error) (source.Source, error) {
detection, err := source.Detect(
userInput,
source.DetectConfig{
DefaultImageSource: opts.Source.Image.DefaultPullSource,
},
)
if err != nil {
return nil, fmt.Errorf("could not deteremine source: %w", err)
}

for _, filter := range filters {
if err := filter(detection); err != nil {
return nil, err
}
}

func getSource(ctx context.Context, opts *options.Catalog, userInput string, sources ...image.Source) (source.Source, error) {
cfg := syft.DefaultGetSourceConfig().
WithRegistryOptions(opts.Registry.ToOptions()).
WithAlias(source.Alias{
Name: opts.Source.Name,
Version: opts.Source.Version,
}).
WithExcludeConfig(source.ExcludeConfig{
Paths: opts.Exclusions,
}).
WithBasePath(opts.Source.BasePath)

var err error
var platform *image.Platform

if opts.Platform != "" {
platform, err = image.NewPlatform(opts.Platform)
if err != nil {
return nil, fmt.Errorf("invalid platform: %w", err)
}
cfg = cfg.WithPlatform(platform)
}

hashers, err := file.Hashers(opts.Source.File.Digests...)
if opts.Source.File.Digests != nil {
hashers, err := file.Hashers(opts.Source.File.Digests...)
if err != nil {
return nil, fmt.Errorf("invalid hash algorithm: %w", err)
}
cfg = cfg.WithDigestAlgorithms(hashers...)
}

explicitSources := opts.From
if len(explicitSources) == 0 {
// extract a scheme if it matches any provider tag; this is a holdover for compatibility, using the --from flag is recommended
explicitSource, newUserInput := stereoscope.ExtractSchemeSource(userInput, allSourceProviderTags()...)
if explicitSource != "" {
explicitSources = append(explicitSources, explicitSource)
userInput = newUserInput
}
}

cfg = cfg.WithBaseSources(sources...).
WithFromSource(explicitSources...).
WithDefaultImageSource(opts.Source.Image.DefaultPullSource)

src, err := syft.GetSource(ctx, userInput, cfg)
if err != nil {
return nil, fmt.Errorf("invalid hash algorithm: %w", err)
}

src, err := detection.NewSource(
source.DetectionSourceConfig{
Alias: source.Alias{
Name: opts.Source.Name,
Version: opts.Source.Version,
},
RegistryOptions: opts.Registry.ToOptions(),
Platform: platform,
Exclude: source.ExcludeConfig{
Paths: opts.Exclusions,
},
DigestAlgorithms: hashers,
BasePath: opts.Source.BasePath,
},
)
return nil, fmt.Errorf("could not determine source: %w", err)
}

if err != nil {
if userInput == "power-user" {
Expand Down Expand Up @@ -445,3 +449,7 @@ func getHintPhrase(expErr task.ErrInvalidExpression) string {
func trimOperation(x string) string {
return strings.TrimLeft(x, "+-")
}

func allSourceProviderTags() []string {
return collections.TaggedValueSet[source.Provider]{}.Join(syft.SourceProviders(syft.DefaultSourceProviderConfig())...).Tags()
}
4 changes: 4 additions & 0 deletions cmd/syft/internal/options/catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ type Catalog struct {

// configuration for the source (the subject being analyzed)
Registry registryConfig `yaml:"registry" json:"registry" mapstructure:"registry"`
From []string `yaml:"from" json:"from" mapstructure:"from"`
Platform string `yaml:"platform" json:"platform" mapstructure:"platform"`
Source sourceConfig `yaml:"source" json:"source" mapstructure:"source"`
Exclusions []string `yaml:"exclude" json:"exclude" mapstructure:"exclude"`
Expand Down Expand Up @@ -163,6 +164,9 @@ func (cfg *Catalog) AddFlags(flags clio.FlagSet) {
flags.StringVarP(&cfg.Scope, "scope", "s",
fmt.Sprintf("selection of layers to catalog, options=%v", validScopeValues))

flags.StringArrayVarP(&cfg.From, "from", "",
"specify the source behavior to use (e.g. docker, registry, oci-dir, ...)")

flags.StringVarP(&cfg.Platform, "platform", "",
"an optional platform specifier for container image sources (e.g. 'linux/arm64', 'linux/arm64/v8', 'arm64', 'linux')")

Expand Down
10 changes: 6 additions & 4 deletions cmd/syft/internal/options/source.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package options

import (
"crypto"
"fmt"
"sort"
"strings"

"github.com/scylladb/go-set/strset"

"github.com/anchore/syft/internal"
"github.com/anchore/syft/syft"
)

type sourceConfig struct {
Expand All @@ -25,12 +29,10 @@ type imageSource struct {
}

func defaultSourceConfig() sourceConfig {
defaults := syft.DefaultSourceProviderConfig()
return sourceConfig{
File: fileSource{
Digests: []string{"sha256"},
},
Image: imageSource{
DefaultPullSource: "",
Digests: internal.Map(defaults.DigestAlgorithms, func(alg crypto.Hash) string { return alg.String() }),
},
}
}
Expand Down
8 changes: 2 additions & 6 deletions cmd/syft/internal/test/integration/catalog_packages_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,11 @@ func BenchmarkImagePackageCatalogers(b *testing.B) {
tarPath := imagetest.GetFixtureImageTarPath(b, fixtureImageName)

// get the source object for the image
userInput := "docker-archive:" + tarPath
detection, err := source.Detect(userInput, source.DefaultDetectConfig())
require.NoError(b, err)

theSource, err := detection.NewSource(source.DefaultDetectionSourceConfig())
theSource, err := syft.GetSource(context.Background(), tarPath, syft.DefaultGetSourceConfig().WithFromSource("docker-archive"))
require.NoError(b, err)

b.Cleanup(func() {
theSource.Close()
_ = theSource.Close()
kzantow marked this conversation as resolved.
Show resolved Hide resolved
})

// build the SBOM
Expand Down
16 changes: 4 additions & 12 deletions cmd/syft/internal/test/integration/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,13 @@ func catalogFixtureImageWithConfig(t *testing.T, fixtureImageName string, cfg *s
// get the fixture image tar file
imagetest.GetFixtureImage(t, "docker-archive", fixtureImageName)
tarPath := imagetest.GetFixtureImageTarPath(t, fixtureImageName)
userInput := "docker-archive:" + tarPath

// get the source to build an SBOM against
detection, err := source.Detect(userInput, source.DefaultDetectConfig())
require.NoError(t, err)

theSource, err := detection.NewSource(source.DefaultDetectionSourceConfig())
theSource, err := syft.GetSource(context.Background(), tarPath, syft.DefaultGetSourceConfig().WithFromSource("docker-archive"))
require.NoError(t, err)

t.Cleanup(func() {
theSource.Close()
_ = theSource.Close()
})

s, err := syft.CreateSBOM(context.Background(), theSource, cfg)
Expand All @@ -71,14 +67,10 @@ func catalogDirectoryWithConfig(t *testing.T, dir string, cfg *syft.CreateSBOMCo
cfg.CatalogerSelection = cfg.CatalogerSelection.WithDefaults(pkgcataloging.DirectoryTag)

// get the source to build an sbom against
userInput := "dir:" + dir
detection, err := source.Detect(userInput, source.DefaultDetectConfig())
require.NoError(t, err)

theSource, err := detection.NewSource(source.DefaultDetectionSourceConfig())
theSource, err := syft.GetSource(context.Background(), dir, syft.DefaultGetSourceConfig().WithFromSource("dir"))
require.NoError(t, err)
t.Cleanup(func() {
theSource.Close()
_ = theSource.Close()
})

// build the SBOM
Expand Down
10 changes: 2 additions & 8 deletions examples/create_custom_sbom/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ func imageReference() string {
func getSource(input string) source.Source {
fmt.Println("detecting source type for input:", input, "...")

detection, err := source.Detect(input,
source.DetectConfig{
src, err := syft.GetSource(context.Background(), input,
syft.GetSourceConfig{
DefaultImageSource: "docker",
},
)
Expand All @@ -54,12 +54,6 @@ func getSource(input string) source.Source {
panic(err)
}

src, err := detection.NewSource(source.DefaultDetectionSourceConfig())

if err != nil {
panic(err)
}

return src
}

Expand Down
10 changes: 2 additions & 8 deletions examples/create_simple_sbom/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ func imageReference() string {
}

func getSource(input string) source.Source {
detection, err := source.Detect(input,
source.DetectConfig{
src, err := syft.GetSource(context.Background(), input,
syft.GetSourceConfig{
DefaultImageSource: "docker",
},
)
Expand All @@ -47,12 +47,6 @@ func getSource(input string) source.Source {
panic(err)
}

src, err := detection.NewSource(source.DefaultDetectionSourceConfig())

if err != nil {
panic(err)
}

return src
}

Expand Down
14 changes: 3 additions & 11 deletions examples/select_catalogers/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,9 @@ func imageReference() string {
}

func getSource(input string) source.Source {
detection, err := source.Detect(input,
source.DetectConfig{
DefaultImageSource: "docker",
},
)

if err != nil {
panic(err)
}

src, err := detection.NewSource(source.DefaultDetectionSourceConfig())
src, err := syft.GetSource(context.Background(), input, syft.GetSourceConfig{
DefaultImageSource: "docker",
})

if err != nil {
panic(err)
Expand Down