mirror of
https://github.com/anchore/syft
synced 2024-11-10 06:14:16 +00:00
feat: Add config option to allow user to select the default image source location
Signed-off-by: Christopher Phillips <christopher.phillips@anchore.com>
This commit is contained in:
parent
2fa238af7c
commit
dfcc07e512
9 changed files with 99 additions and 61 deletions
20
README.md
20
README.md
|
@ -110,9 +110,7 @@ The above output includes only software that is visible in the container (i.e.,
|
|||
syft <image> --scope all-layers
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Supported sources
|
||||
### Supported sources
|
||||
|
||||
Syft can generate a SBOM from a variety of sources:
|
||||
|
||||
|
@ -141,7 +139,13 @@ file:path/to/yourproject/file read directly from a path on disk (any
|
|||
registry:yourrepo/yourimage:tag pull image directly from a registry (no container runtime required)
|
||||
```
|
||||
|
||||
#### Default Cataloger Configuration by scan type
|
||||
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).
|
||||
|
||||
### Default Cataloger Configuration by scan type
|
||||
|
||||
##### Image Scanning:
|
||||
- alpmdb
|
||||
|
@ -179,7 +183,7 @@ registry:yourrepo/yourimage:tag pull image directly from a registry (no
|
|||
- conan
|
||||
- hackage
|
||||
|
||||
#### Non Default:
|
||||
##### Non Default:
|
||||
- cargo-auditable-binary
|
||||
|
||||
### Excluding file paths
|
||||
|
@ -393,7 +397,7 @@ Certificate subject: test.email@testdomain.com
|
|||
Certificate issuer URL: https://accounts.google.com
|
||||
```
|
||||
|
||||
#### Local private key support
|
||||
### Local private key support
|
||||
|
||||
To generate an SBOM attestation for a container image using a local private key:
|
||||
```
|
||||
|
@ -436,6 +440,10 @@ file: ""
|
|||
# same as SYFT_CHECK_FOR_APP_UPDATE env var
|
||||
check-for-app-update: true
|
||||
|
||||
# allows users to specify which image source should be used to generate the sbom
|
||||
# valid values are: registry, docker, podman
|
||||
default-image-pull-source: ""
|
||||
|
||||
# a list of globs to exclude from scanning. same as --exclude ; for example:
|
||||
# exclude:
|
||||
# - "/etc/**"
|
||||
|
|
|
@ -47,7 +47,7 @@ func Run(_ context.Context, app *config.Application, args []string) error {
|
|||
// could be an image or a directory, with or without a scheme
|
||||
// TODO: validate that source is image
|
||||
userInput := args[0]
|
||||
si, err := source.ParseInputWithName(userInput, app.Platform, true, app.Name)
|
||||
si, err := source.ParseInputWithName(userInput, app.Platform, app.Name, app.DefaultImagePullSource)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not generate source input for packages command: %w", err)
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ func Run(_ context.Context, app *config.Application, args []string) error {
|
|||
|
||||
// could be an image or a directory, with or without a scheme
|
||||
userInput := args[0]
|
||||
si, err := source.ParseInputWithName(userInput, app.Platform, true, app.Name)
|
||||
si, err := source.ParseInputWithName(userInput, app.Platform, app.Name, app.DefaultImagePullSource)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not generate source input for packages command: %w", err)
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ func Run(_ context.Context, app *config.Application, args []string) error {
|
|||
}()
|
||||
|
||||
userInput := args[0]
|
||||
si, err := source.ParseInputWithName(userInput, app.Platform, true, app.Name)
|
||||
si, err := source.ParseInputWithName(userInput, app.Platform, app.Name, app.DefaultImagePullSource)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not generate source input for packages command: %w", err)
|
||||
}
|
||||
|
|
|
@ -40,26 +40,27 @@ type Application struct {
|
|||
ConfigPath string `yaml:"configPath,omitempty" json:"configPath" mapstructure:"config"`
|
||||
Verbosity uint `yaml:"verbosity,omitempty" json:"verbosity" mapstructure:"verbosity"`
|
||||
// -q, indicates to not show any status output to stderr (ETUI or logging UI)
|
||||
Quiet bool `yaml:"quiet" json:"quiet" mapstructure:"quiet"`
|
||||
Outputs []string `yaml:"output" json:"output" mapstructure:"output"` // -o, the format to use for output
|
||||
OutputTemplatePath string `yaml:"output-template-path" json:"output-template-path" mapstructure:"output-template-path"` // -t template file to use for output
|
||||
File string `yaml:"file" json:"file" mapstructure:"file"` // --file, the file to write report output to
|
||||
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
|
||||
Dev development `yaml:"dev" json:"dev" mapstructure:"dev"`
|
||||
Log logging `yaml:"log" json:"log" mapstructure:"log"` // all logging-related options
|
||||
Catalogers []string `yaml:"catalogers" json:"catalogers" mapstructure:"catalogers"`
|
||||
Package pkg `yaml:"package" json:"package" mapstructure:"package"`
|
||||
Golang golang `yaml:"golang" json:"golang" mapstructure:"golang"`
|
||||
Attest attest `yaml:"attest" json:"attest" mapstructure:"attest"`
|
||||
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"`
|
||||
Platform string `yaml:"platform" json:"platform" mapstructure:"platform"`
|
||||
Name string `yaml:"name" json:"name" mapstructure:"name"`
|
||||
Parallelism int `yaml:"parallelism" json:"parallelism" mapstructure:"parallelism"` // the number of catalog workers to run in parallel
|
||||
Quiet bool `yaml:"quiet" json:"quiet" mapstructure:"quiet"`
|
||||
Outputs []string `yaml:"output" json:"output" mapstructure:"output"` // -o, the format to use for output
|
||||
OutputTemplatePath string `yaml:"output-template-path" json:"output-template-path" mapstructure:"output-template-path"` // -t template file to use for output
|
||||
File string `yaml:"file" json:"file" mapstructure:"file"` // --file, the file to write report output to
|
||||
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
|
||||
Dev development `yaml:"dev" json:"dev" mapstructure:"dev"`
|
||||
Log logging `yaml:"log" json:"log" mapstructure:"log"` // all logging-related options
|
||||
Catalogers []string `yaml:"catalogers" json:"catalogers" mapstructure:"catalogers"`
|
||||
Package pkg `yaml:"package" json:"package" mapstructure:"package"`
|
||||
Golang golang `yaml:"golang" json:"golang" mapstructure:"golang"`
|
||||
Attest attest `yaml:"attest" json:"attest" mapstructure:"attest"`
|
||||
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"`
|
||||
Platform string `yaml:"platform" json:"platform" mapstructure:"platform"`
|
||||
Name string `yaml:"name" json:"name" mapstructure:"name"`
|
||||
Parallelism int `yaml:"parallelism" json:"parallelism" mapstructure:"parallelism"` // the number of catalog workers to run in parallel
|
||||
DefaultImagePullSource string `yaml:"default-image-pull-source" json:"default-image-pull-source" mapstructure:"default-image-pull-source"` // specify default image pull source
|
||||
}
|
||||
|
||||
func (cfg Application) ToCatalogerConfig() cataloger.Config {
|
||||
|
@ -130,6 +131,12 @@ func (cfg *Application) parseConfigValues() error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := checkDefaultSourceValues(cfg.DefaultImagePullSource); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check for valid default source options
|
||||
// parse nested config options
|
||||
// for each field in the configuration struct, see if the field implements the parser interface
|
||||
// note: the app config is a pointer, so we need to grab the elements explicitly (to traverse the address)
|
||||
|
@ -192,6 +199,7 @@ func loadDefaultValues(v *viper.Viper) {
|
|||
v.SetDefault("check-for-app-update", true)
|
||||
v.SetDefault("catalogers", nil)
|
||||
v.SetDefault("parallelism", 1)
|
||||
v.SetDefault("default-image-pull-source", "")
|
||||
|
||||
// for each field in the configuration struct, see if the field implements the defaultValueLoader interface and invoke it if it does
|
||||
value := reflect.ValueOf(Application{})
|
||||
|
@ -291,3 +299,15 @@ func loadConfig(v *viper.Viper, configPath string) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var validDefaultSourceValues = []string{"registry", "docker", "podman", ""}
|
||||
|
||||
func checkDefaultSourceValues(source string) error {
|
||||
validValues := internal.NewStringSet(validDefaultSourceValues...)
|
||||
if !validValues.Contains(source) {
|
||||
validValuesString := strings.Join(validDefaultSourceValues, ", ")
|
||||
return fmt.Errorf("%s is not a valid default source; please use one of the following: %s''", source, validValuesString)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -40,24 +40,23 @@ type Source struct {
|
|||
// Input is an object that captures the detected user input regarding source location, scheme, and provider type.
|
||||
// It acts as a struct input for some source constructors.
|
||||
type Input struct {
|
||||
UserInput string
|
||||
Scheme Scheme
|
||||
ImageSource image.Source
|
||||
Location string
|
||||
Platform string
|
||||
Name string
|
||||
autoDetectAvailableImageSources bool
|
||||
UserInput string
|
||||
Scheme Scheme
|
||||
ImageSource image.Source
|
||||
Location string
|
||||
Platform string
|
||||
Name string
|
||||
}
|
||||
|
||||
// ParseInput generates a source Input that can be used as an argument to generate a new source
|
||||
// from specific providers including a registry.
|
||||
func ParseInput(userInput string, platform string, detectAvailableImageSources bool) (*Input, error) {
|
||||
return ParseInputWithName(userInput, platform, detectAvailableImageSources, "")
|
||||
func ParseInput(userInput string, platform string) (*Input, error) {
|
||||
return ParseInputWithName(userInput, platform, "", "")
|
||||
}
|
||||
|
||||
// ParseInputWithName generates a source Input that can be used as an argument to generate a new source
|
||||
// from specific providers including a registry, with an explicit name.
|
||||
func ParseInputWithName(userInput string, platform string, detectAvailableImageSources bool, name string) (*Input, error) {
|
||||
func ParseInputWithName(userInput string, platform, name, defaultImageSource string) (*Input, error) {
|
||||
fs := afero.NewOsFs()
|
||||
scheme, source, location, err := DetectScheme(fs, image.DetectSource, userInput)
|
||||
if err != nil {
|
||||
|
@ -69,12 +68,13 @@ func ParseInputWithName(userInput string, platform string, detectAvailableImageS
|
|||
// only check on packages command, attest we automatically try to pull from userInput
|
||||
switch scheme {
|
||||
case ImageScheme, UnknownScheme:
|
||||
if detectAvailableImageSources {
|
||||
if imagePullSource := image.DetermineDefaultImagePullSource(userInput); imagePullSource != image.UnknownSource {
|
||||
scheme = ImageScheme
|
||||
source = imagePullSource
|
||||
location = userInput
|
||||
}
|
||||
scheme = ImageScheme
|
||||
location = userInput
|
||||
if defaultImageSource != "" {
|
||||
source = parseDefaultImageSource(defaultImageSource)
|
||||
} else {
|
||||
imagePullSource := image.DetermineDefaultImagePullSource(userInput)
|
||||
source = imagePullSource
|
||||
}
|
||||
if location == "" {
|
||||
location = userInput
|
||||
|
@ -89,16 +89,28 @@ func ParseInputWithName(userInput string, platform string, detectAvailableImageS
|
|||
|
||||
// collect user input for downstream consumption
|
||||
return &Input{
|
||||
UserInput: userInput,
|
||||
Scheme: scheme,
|
||||
ImageSource: source,
|
||||
Location: location,
|
||||
Platform: platform,
|
||||
Name: name,
|
||||
autoDetectAvailableImageSources: detectAvailableImageSources,
|
||||
UserInput: userInput,
|
||||
Scheme: scheme,
|
||||
ImageSource: source,
|
||||
Location: location,
|
||||
Platform: platform,
|
||||
Name: name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseDefaultImageSource(defaultImageSource string) image.Source {
|
||||
switch defaultImageSource {
|
||||
case "registry":
|
||||
return image.OciRegistrySource
|
||||
case "docker":
|
||||
return image.DockerDaemonSource
|
||||
case "podman":
|
||||
return image.PodmanDaemonSource
|
||||
default:
|
||||
return image.UnknownSource
|
||||
}
|
||||
}
|
||||
|
||||
type sourceDetector func(string) (image.Source, string, error)
|
||||
|
||||
func NewFromRegistry(in Input, registryOptions *image.RegistryOptions, exclusions []string) (*Source, func(), error) {
|
||||
|
@ -203,9 +215,7 @@ func getImageWithRetryStrategy(in Input, registryOptions *image.RegistryOptions)
|
|||
|
||||
// We need to determine the image source again, such that this determination
|
||||
// doesn't take scheme parsing into account.
|
||||
if in.autoDetectAvailableImageSources {
|
||||
in.ImageSource = image.DetermineDefaultImagePullSource(in.UserInput)
|
||||
}
|
||||
in.ImageSource = image.DetermineDefaultImagePullSource(in.UserInput)
|
||||
img, err = stereoscope.GetImageFromSource(ctx, in.UserInput, in.ImageSource, opts...)
|
||||
cleanup = func() {
|
||||
if err := img.Cleanup(); err != nil {
|
||||
|
|
|
@ -52,7 +52,7 @@ func TestParseInput(t *testing.T) {
|
|||
if test.errFn == nil {
|
||||
test.errFn = require.NoError
|
||||
}
|
||||
sourceInput, err := ParseInput(test.input, test.platform, true)
|
||||
sourceInput, err := ParseInput(test.input, test.platform)
|
||||
test.errFn(t, err)
|
||||
if test.expected != "" {
|
||||
require.NotNil(t, sourceInput)
|
||||
|
@ -596,7 +596,7 @@ func TestDirectoryExclusions(t *testing.T) {
|
|||
registryOpts := &image.RegistryOptions{}
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
sourceInput, err := ParseInput("dir:"+test.input, "", false)
|
||||
sourceInput, err := ParseInput("dir:"+test.input, "")
|
||||
require.NoError(t, err)
|
||||
src, fn, err := New(*sourceInput, registryOpts, test.exclusions)
|
||||
defer fn()
|
||||
|
@ -696,7 +696,7 @@ func TestImageExclusions(t *testing.T) {
|
|||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
archiveLocation := imagetest.PrepareFixtureImage(t, "docker-archive", test.input)
|
||||
sourceInput, err := ParseInput(archiveLocation, "", false)
|
||||
sourceInput, err := ParseInput(archiveLocation, "")
|
||||
require.NoError(t, err)
|
||||
src, fn, err := New(*sourceInput, registryOpts, test.exclusions)
|
||||
defer fn()
|
||||
|
|
|
@ -25,7 +25,7 @@ func BenchmarkImagePackageCatalogers(b *testing.B) {
|
|||
for _, c := range cataloger.ImageCatalogers(cataloger.DefaultConfig()) {
|
||||
// in case of future alteration where state is persisted, assume no dependency is safe to reuse
|
||||
userInput := "docker-archive:" + tarPath
|
||||
sourceInput, err := source.ParseInput(userInput, "", false)
|
||||
sourceInput, err := source.ParseInput(userInput, "")
|
||||
require.NoError(b, err)
|
||||
theSource, cleanupSource, err := source.New(*sourceInput, nil, nil)
|
||||
b.Cleanup(cleanupSource)
|
||||
|
|
|
@ -16,7 +16,7 @@ func catalogFixtureImage(t *testing.T, fixtureImageName string, scope source.Sco
|
|||
imagetest.GetFixtureImage(t, "docker-archive", fixtureImageName)
|
||||
tarPath := imagetest.GetFixtureImageTarPath(t, fixtureImageName)
|
||||
userInput := "docker-archive:" + tarPath
|
||||
sourceInput, err := source.ParseInput(userInput, "", false)
|
||||
sourceInput, err := source.ParseInput(userInput, "")
|
||||
require.NoError(t, err)
|
||||
theSource, cleanupSource, err := source.New(*sourceInput, nil, nil)
|
||||
t.Cleanup(cleanupSource)
|
||||
|
@ -52,7 +52,7 @@ func catalogFixtureImage(t *testing.T, fixtureImageName string, scope source.Sco
|
|||
|
||||
func catalogDirectory(t *testing.T, dir string) (sbom.SBOM, *source.Source) {
|
||||
userInput := "dir:" + dir
|
||||
sourceInput, err := source.ParseInput(userInput, "", false)
|
||||
sourceInput, err := source.ParseInput(userInput, "")
|
||||
require.NoError(t, err)
|
||||
theSource, cleanupSource, err := source.New(*sourceInput, nil, nil)
|
||||
t.Cleanup(cleanupSource)
|
||||
|
|
Loading…
Reference in a new issue