mirror of
https://github.com/anchore/syft
synced 2024-11-13 23:57:07 +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
|
syft <image> --scope all-layers
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Supported sources
|
||||||
|
|
||||||
## Supported sources
|
|
||||||
|
|
||||||
Syft can generate a SBOM from a variety of 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)
|
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:
|
##### Image Scanning:
|
||||||
- alpmdb
|
- alpmdb
|
||||||
|
@ -179,7 +183,7 @@ registry:yourrepo/yourimage:tag pull image directly from a registry (no
|
||||||
- conan
|
- conan
|
||||||
- hackage
|
- hackage
|
||||||
|
|
||||||
#### Non Default:
|
##### Non Default:
|
||||||
- cargo-auditable-binary
|
- cargo-auditable-binary
|
||||||
|
|
||||||
### Excluding file paths
|
### Excluding file paths
|
||||||
|
@ -393,7 +397,7 @@ Certificate subject: test.email@testdomain.com
|
||||||
Certificate issuer URL: https://accounts.google.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:
|
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
|
# same as SYFT_CHECK_FOR_APP_UPDATE env var
|
||||||
check-for-app-update: true
|
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:
|
# a list of globs to exclude from scanning. same as --exclude ; for example:
|
||||||
# exclude:
|
# exclude:
|
||||||
# - "/etc/**"
|
# - "/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
|
// could be an image or a directory, with or without a scheme
|
||||||
// TODO: validate that source is image
|
// TODO: validate that source is image
|
||||||
userInput := args[0]
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("could not generate source input for packages command: %w", err)
|
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
|
// could be an image or a directory, with or without a scheme
|
||||||
userInput := args[0]
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("could not generate source input for packages command: %w", err)
|
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]
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("could not generate source input for packages command: %w", err)
|
return fmt.Errorf("could not generate source input for packages command: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,6 +60,7 @@ type Application struct {
|
||||||
Platform string `yaml:"platform" json:"platform" mapstructure:"platform"`
|
Platform string `yaml:"platform" json:"platform" mapstructure:"platform"`
|
||||||
Name string `yaml:"name" json:"name" mapstructure:"name"`
|
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
|
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 {
|
func (cfg Application) ToCatalogerConfig() cataloger.Config {
|
||||||
|
@ -130,6 +131,12 @@ func (cfg *Application) parseConfigValues() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := checkDefaultSourceValues(cfg.DefaultImagePullSource); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for valid default source options
|
||||||
// parse nested config options
|
// parse nested config options
|
||||||
// for each field in the configuration struct, see if the field implements the parser interface
|
// 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)
|
// 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("check-for-app-update", true)
|
||||||
v.SetDefault("catalogers", nil)
|
v.SetDefault("catalogers", nil)
|
||||||
v.SetDefault("parallelism", 1)
|
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
|
// 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{})
|
value := reflect.ValueOf(Application{})
|
||||||
|
@ -291,3 +299,15 @@ func loadConfig(v *viper.Viper, configPath string) error {
|
||||||
}
|
}
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -46,18 +46,17 @@ type Input struct {
|
||||||
Location string
|
Location string
|
||||||
Platform string
|
Platform string
|
||||||
Name string
|
Name string
|
||||||
autoDetectAvailableImageSources bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseInput generates a source Input that can be used as an argument to generate a new source
|
// ParseInput generates a source Input that can be used as an argument to generate a new source
|
||||||
// from specific providers including a registry.
|
// from specific providers including a registry.
|
||||||
func ParseInput(userInput string, platform string, detectAvailableImageSources bool) (*Input, error) {
|
func ParseInput(userInput string, platform string) (*Input, error) {
|
||||||
return ParseInputWithName(userInput, platform, detectAvailableImageSources, "")
|
return ParseInputWithName(userInput, platform, "", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseInputWithName generates a source Input that can be used as an argument to generate a new source
|
// 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.
|
// 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()
|
fs := afero.NewOsFs()
|
||||||
scheme, source, location, err := DetectScheme(fs, image.DetectSource, userInput)
|
scheme, source, location, err := DetectScheme(fs, image.DetectSource, userInput)
|
||||||
if err != nil {
|
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
|
// only check on packages command, attest we automatically try to pull from userInput
|
||||||
switch scheme {
|
switch scheme {
|
||||||
case ImageScheme, UnknownScheme:
|
case ImageScheme, UnknownScheme:
|
||||||
if detectAvailableImageSources {
|
|
||||||
if imagePullSource := image.DetermineDefaultImagePullSource(userInput); imagePullSource != image.UnknownSource {
|
|
||||||
scheme = ImageScheme
|
scheme = ImageScheme
|
||||||
source = imagePullSource
|
|
||||||
location = userInput
|
location = userInput
|
||||||
}
|
if defaultImageSource != "" {
|
||||||
|
source = parseDefaultImageSource(defaultImageSource)
|
||||||
|
} else {
|
||||||
|
imagePullSource := image.DetermineDefaultImagePullSource(userInput)
|
||||||
|
source = imagePullSource
|
||||||
}
|
}
|
||||||
if location == "" {
|
if location == "" {
|
||||||
location = userInput
|
location = userInput
|
||||||
|
@ -95,10 +95,22 @@ func ParseInputWithName(userInput string, platform string, detectAvailableImageS
|
||||||
Location: location,
|
Location: location,
|
||||||
Platform: platform,
|
Platform: platform,
|
||||||
Name: name,
|
Name: name,
|
||||||
autoDetectAvailableImageSources: detectAvailableImageSources,
|
|
||||||
}, nil
|
}, 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)
|
type sourceDetector func(string) (image.Source, string, error)
|
||||||
|
|
||||||
func NewFromRegistry(in Input, registryOptions *image.RegistryOptions, exclusions []string) (*Source, func(), 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
|
// We need to determine the image source again, such that this determination
|
||||||
// doesn't take scheme parsing into account.
|
// 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...)
|
img, err = stereoscope.GetImageFromSource(ctx, in.UserInput, in.ImageSource, opts...)
|
||||||
cleanup = func() {
|
cleanup = func() {
|
||||||
if err := img.Cleanup(); err != nil {
|
if err := img.Cleanup(); err != nil {
|
||||||
|
|
|
@ -52,7 +52,7 @@ func TestParseInput(t *testing.T) {
|
||||||
if test.errFn == nil {
|
if test.errFn == nil {
|
||||||
test.errFn = require.NoError
|
test.errFn = require.NoError
|
||||||
}
|
}
|
||||||
sourceInput, err := ParseInput(test.input, test.platform, true)
|
sourceInput, err := ParseInput(test.input, test.platform)
|
||||||
test.errFn(t, err)
|
test.errFn(t, err)
|
||||||
if test.expected != "" {
|
if test.expected != "" {
|
||||||
require.NotNil(t, sourceInput)
|
require.NotNil(t, sourceInput)
|
||||||
|
@ -596,7 +596,7 @@ func TestDirectoryExclusions(t *testing.T) {
|
||||||
registryOpts := &image.RegistryOptions{}
|
registryOpts := &image.RegistryOptions{}
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
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)
|
require.NoError(t, err)
|
||||||
src, fn, err := New(*sourceInput, registryOpts, test.exclusions)
|
src, fn, err := New(*sourceInput, registryOpts, test.exclusions)
|
||||||
defer fn()
|
defer fn()
|
||||||
|
@ -696,7 +696,7 @@ func TestImageExclusions(t *testing.T) {
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
t.Run(test.desc, func(t *testing.T) {
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
archiveLocation := imagetest.PrepareFixtureImage(t, "docker-archive", test.input)
|
archiveLocation := imagetest.PrepareFixtureImage(t, "docker-archive", test.input)
|
||||||
sourceInput, err := ParseInput(archiveLocation, "", false)
|
sourceInput, err := ParseInput(archiveLocation, "")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
src, fn, err := New(*sourceInput, registryOpts, test.exclusions)
|
src, fn, err := New(*sourceInput, registryOpts, test.exclusions)
|
||||||
defer fn()
|
defer fn()
|
||||||
|
|
|
@ -25,7 +25,7 @@ func BenchmarkImagePackageCatalogers(b *testing.B) {
|
||||||
for _, c := range cataloger.ImageCatalogers(cataloger.DefaultConfig()) {
|
for _, c := range cataloger.ImageCatalogers(cataloger.DefaultConfig()) {
|
||||||
// in case of future alteration where state is persisted, assume no dependency is safe to reuse
|
// in case of future alteration where state is persisted, assume no dependency is safe to reuse
|
||||||
userInput := "docker-archive:" + tarPath
|
userInput := "docker-archive:" + tarPath
|
||||||
sourceInput, err := source.ParseInput(userInput, "", false)
|
sourceInput, err := source.ParseInput(userInput, "")
|
||||||
require.NoError(b, err)
|
require.NoError(b, err)
|
||||||
theSource, cleanupSource, err := source.New(*sourceInput, nil, nil)
|
theSource, cleanupSource, err := source.New(*sourceInput, nil, nil)
|
||||||
b.Cleanup(cleanupSource)
|
b.Cleanup(cleanupSource)
|
||||||
|
|
|
@ -16,7 +16,7 @@ func catalogFixtureImage(t *testing.T, fixtureImageName string, scope source.Sco
|
||||||
imagetest.GetFixtureImage(t, "docker-archive", fixtureImageName)
|
imagetest.GetFixtureImage(t, "docker-archive", fixtureImageName)
|
||||||
tarPath := imagetest.GetFixtureImageTarPath(t, fixtureImageName)
|
tarPath := imagetest.GetFixtureImageTarPath(t, fixtureImageName)
|
||||||
userInput := "docker-archive:" + tarPath
|
userInput := "docker-archive:" + tarPath
|
||||||
sourceInput, err := source.ParseInput(userInput, "", false)
|
sourceInput, err := source.ParseInput(userInput, "")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
theSource, cleanupSource, err := source.New(*sourceInput, nil, nil)
|
theSource, cleanupSource, err := source.New(*sourceInput, nil, nil)
|
||||||
t.Cleanup(cleanupSource)
|
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) {
|
func catalogDirectory(t *testing.T, dir string) (sbom.SBOM, *source.Source) {
|
||||||
userInput := "dir:" + dir
|
userInput := "dir:" + dir
|
||||||
sourceInput, err := source.ParseInput(userInput, "", false)
|
sourceInput, err := source.ParseInput(userInput, "")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
theSource, cleanupSource, err := source.New(*sourceInput, nil, nil)
|
theSource, cleanupSource, err := source.New(*sourceInput, nil, nil)
|
||||||
t.Cleanup(cleanupSource)
|
t.Cleanup(cleanupSource)
|
||||||
|
|
Loading…
Reference in a new issue