Skip to content

Commit

Permalink
feat: add config command (#2892)
Browse files Browse the repository at this point in the history
Signed-off-by: Keith Zantow <kzantow@gmail.com>
  • Loading branch information
kzantow committed May 23, 2024
1 parent 7071f1e commit 1c37bab
Show file tree
Hide file tree
Showing 16 changed files with 219 additions and 32 deletions.
1 change: 1 addition & 0 deletions cmd/syft/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ func create(id clio.Identification, out io.Writer) (clio.Application, *cobra.Com
commands.Attest(app),
commands.Convert(app),
clio.VersionCommand(id),
clio.ConfigCommand(app, nil),
cranecmd.NewCmdAuthLogin(id.Name), // syft login uses the same command as crane
)

Expand Down
10 changes: 9 additions & 1 deletion cmd/syft/internal/options/attest.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,16 @@ type Attest struct {
Password secret `yaml:"password" json:"password" mapstructure:"password"`
}

var _ clio.FlagAdder = (*Attest)(nil)
var _ interface {
clio.FlagAdder
clio.FieldDescriber
} = (*Attest)(nil)

func (o *Attest) AddFlags(flags clio.FlagSet) {
flags.StringVarP((*string)(&o.Key), "key", "k", "the key to use for the attestation")
}

func (o *Attest) DescribeFields(descriptions clio.FieldDescriptionSet) {
descriptions.Add(&o.Password, `password to decrypt to given private key
additionally responds to COSIGN_PASSWORD env var`)
}
20 changes: 20 additions & 0 deletions cmd/syft/internal/options/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

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

"github.com/anchore/clio"
intFile "github.com/anchore/syft/internal/file"
"github.com/anchore/syft/syft/file"
)
Expand Down Expand Up @@ -45,6 +46,11 @@ func defaultFileConfig() fileConfig {
}
}

var _ interface {
clio.PostLoader
clio.FieldDescriber
} = (*fileConfig)(nil)

func (c *fileConfig) PostLoad() error {
digests := strset.New(c.Metadata.Digests...).List()
sort.Strings(digests)
Expand All @@ -56,3 +62,17 @@ func (c *fileConfig) PostLoad() error {
}
return fmt.Errorf("invalid file metadata selection: %q", c.Metadata.Selection)
}

func (c *fileConfig) DescribeFields(descriptions clio.FieldDescriptionSet) {
descriptions.Add(&c.Metadata.Selection, `select which files should be captured by the file-metadata cataloger and included in the SBOM.
Options include:
- "all": capture all files from the search space
- "owned-by-package": capture only files owned by packages
- "none", "": do not capture any files`)
descriptions.Add(&c.Metadata.Digests, `the file digest algorithms to use when cataloging files (options: "md5", "sha1", "sha224", "sha256", "sha384", "sha512")`)

descriptions.Add(&c.Content.SkipFilesAboveSize, `skip searching a file entirely if it is above the given size (default = 1MB; unit = bytes)`)
descriptions.Add(&c.Content.Globs, `file globs for the cataloger to match on`)

descriptions.Add(&c.Executable.Globs, `file globs for the cataloger to match on`)
}
42 changes: 36 additions & 6 deletions cmd/syft/internal/options/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,19 @@ import (
"github.com/anchore/syft/syft/sbom"
)

var _ clio.PostLoader = (*Format)(nil)
var _ interface {
clio.PostLoader
clio.FieldDescriber
} = (*Format)(nil)

// Format contains all user configuration for output formatting.
type Format struct {
Pretty *bool `yaml:"pretty" json:"pretty" mapstructure:"pretty"`
Template FormatTemplate `yaml:"template" json:"template" mapstructure:"template"`
SyftJSON FormatSyftJSON `yaml:"json" json:"json" mapstructure:"json"`
SPDXJSON FormatSPDXJSON `yaml:"spdx-json" json:"spdx-json" mapstructure:"spdx-json"`
CyclonedxJSON FormatCyclonedxJSON `yaml:"cyclonedx-json" json:"cyclonedx-json" mapstructure:"cyclonedx-json"`
CyclonedxXML FormatCyclonedxXML `yaml:"cyclonedx-xml" json:"cyclonedx-xml" mapstructure:"cyclonedx-xml"`
Template FormatTemplate `yaml:"template" json:"template" mapstructure:"template" description:"all template format options"`
SyftJSON FormatSyftJSON `yaml:"json" json:"json" mapstructure:"json" description:"all syft-json format options"`
SPDXJSON FormatSPDXJSON `yaml:"spdx-json" json:"spdx-json" mapstructure:"spdx-json" description:"all spdx-json format options"`
CyclonedxJSON FormatCyclonedxJSON `yaml:"cyclonedx-json" json:"cyclonedx-json" mapstructure:"cyclonedx-json" description:"all cyclonedx-json format options"`
CyclonedxXML FormatCyclonedxXML `yaml:"cyclonedx-xml" json:"cyclonedx-xml" mapstructure:"cyclonedx-xml" description:"all cyclonedx-xml format options"`
}

func (o *Format) PostLoad() error {
Expand All @@ -28,6 +31,33 @@ func (o *Format) PostLoad() error {
return nil
}

func (o *Format) DescribeFields(descriptions clio.FieldDescriptionSet) {
descriptions.Add(&o.Pretty, `default value for all formats that support the "pretty" option (default is unset)`)
descriptions.Add(&o.SyftJSON, `all syft-json format options`)
descriptions.Add(&o.SyftJSON.Legacy, `transform any syft-json output to conform to an approximation of the v11.0.1 schema. This includes:
- using the package metadata type names from before v12 of the JSON schema (changed in https://github.com/anchore/syft/pull/1983)
Note: this will still include package types and fields that were added at or after json schema v12. This means
that output might not strictly be json schema v11 compliant, however, for consumers that require time to port
over to the final syft 1.0 json output this option can be used to ease the transition.
Note: long term support for this option is not guaranteed (it may change or break at any time)`)

descriptions.Add(&o.Template.Path, `path to the template file to use when rendering the output with the template output format.
Note that all template paths are based on the current syft-json schema`)
descriptions.Add(&o.Template.Legacy, `if true, uses the go structs for the syft-json format for templating.
if false, uses the syft-json output for templating (which follows the syft JSON schema exactly).
Note: long term support for this option is not guaranteed (it may change or break at any time)`)

prettyDescription := `include space indention and newlines
note: inherits default value from 'format.pretty' or 'false' if parent is unset`
descriptions.Add(&o.SyftJSON.Pretty, prettyDescription)
descriptions.Add(&o.SPDXJSON.Pretty, prettyDescription)
descriptions.Add(&o.CyclonedxJSON.Pretty, prettyDescription)
descriptions.Add(&o.CyclonedxXML.Pretty, prettyDescription)
}

func DefaultFormat() Format {
return Format{
Template: DefaultFormatTemplate(),
Expand Down
23 changes: 23 additions & 0 deletions cmd/syft/internal/options/golang.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package options
import (
"strings"

"github.com/anchore/clio"
"github.com/anchore/syft/syft/pkg/cataloger/golang"
)

Expand All @@ -15,6 +16,28 @@ type golangConfig struct {
MainModuleVersion golangMainModuleVersionConfig `json:"main-module-version" yaml:"main-module-version" mapstructure:"main-module-version"`
}

var _ interface {
clio.FieldDescriber
} = (*golangConfig)(nil)

func (o *golangConfig) DescribeFields(descriptions clio.FieldDescriptionSet) {
descriptions.Add(&o.SearchLocalModCacheLicenses, `search for go package licences in the GOPATH of the system running Syft, note that this is outside the
container filesystem and potentially outside the root of a local directory scan`)
descriptions.Add(&o.LocalModCacheDir, `specify an explicit go mod cache directory, if unset this defaults to $GOPATH/pkg/mod or $HOME/go/pkg/mod`)
descriptions.Add(&o.SearchRemoteLicenses, `search for go package licences by retrieving the package from a network proxy`)
descriptions.Add(&o.Proxy, `remote proxy to use when retrieving go packages from the network,
if unset this defaults to $GOPROXY followed by https://proxy.golang.org`)
descriptions.Add(&o.NoProxy, `specifies packages which should not be fetched by proxy
if unset this defaults to $GONOPROXY`)
descriptions.Add(&o.MainModuleVersion, `the go main module version discovered from binaries built with the go compiler will
always show (devel) as the version. Use these options to control heuristics to guess
a more accurate version from the binary.`)
descriptions.Add(&o.MainModuleVersion.FromLDFlags, `look for LD flags that appear to be setting a version (e.g. -X main.version=1.0.0)`)
descriptions.Add(&o.MainModuleVersion.FromBuildSettings, `use the build settings (e.g. vcs.version & vcs.time) to craft a v0 pseudo version
(e.g. v0.0.0-20220308212642-53e6d0aaf6fb) when a more accurate version cannot be found otherwise`)
descriptions.Add(&o.MainModuleVersion.FromContents, `search for semver-like strings in the binary contents`)
}

type golangMainModuleVersionConfig struct {
FromLDFlags bool `json:"from-ld-flags" yaml:"from-ld-flags" mapstructure:"from-ld-flags"`
FromContents bool `json:"from-contents" yaml:"from-contents" mapstructure:"from-contents"`
Expand Down
17 changes: 17 additions & 0 deletions cmd/syft/internal/options/java.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,24 @@
package options

import "github.com/anchore/clio"

type javaConfig struct {
UseNetwork bool `yaml:"use-network" json:"use-network" mapstructure:"use-network"`
MavenURL string `yaml:"maven-url" json:"maven-url" mapstructure:"maven-url"`
MaxParentRecursiveDepth int `yaml:"max-parent-recursive-depth" json:"max-parent-recursive-depth" mapstructure:"max-parent-recursive-depth"`
}

var _ interface {
clio.FieldDescriber
} = (*javaConfig)(nil)

func (o *javaConfig) DescribeFields(descriptions clio.FieldDescriptionSet) {
descriptions.Add(&o.UseNetwork, `enables Syft to use the network to fill in more detailed information about artifacts
currently this enables searching maven-url for license data
when running across pom.xml files that could have more information, syft will
explicitly search maven for license information by querying the online pom when this is true
this option is helpful for when the parent pom has more data,
that is not accessible from within the final built artifact`)
descriptions.Add(&o.MavenURL, `maven repository to use, defaults to Maven central`)
descriptions.Add(&o.MaxParentRecursiveDepth, `depth to recursively resolve parent POMs`)
}
11 changes: 11 additions & 0 deletions cmd/syft/internal/options/javascript.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
package options

import "github.com/anchore/clio"

type javaScriptConfig struct {
SearchRemoteLicenses bool `json:"search-remote-licenses" yaml:"search-remote-licenses" mapstructure:"search-remote-licenses"`
NpmBaseURL string `json:"npm-base-url" yaml:"npm-base-url" mapstructure:"npm-base-url"`
}

var _ interface {
clio.FieldDescriber
} = (*javaScriptConfig)(nil)

func (o *javaScriptConfig) DescribeFields(descriptions clio.FieldDescriptionSet) {
descriptions.Add(&o.SearchRemoteLicenses, `enables Syft to use the network to fill in more detailed license information`)
descriptions.Add(&o.NpmBaseURL, `base NPM url to use`)
}
10 changes: 10 additions & 0 deletions cmd/syft/internal/options/linux_kernel.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package options

import "github.com/anchore/clio"

type linuxKernelConfig struct {
CatalogModules bool `json:"catalog-modules" yaml:"catalog-modules" mapstructure:"catalog-modules"`
}
Expand All @@ -9,3 +11,11 @@ func defaultLinuxKernelConfig() linuxKernelConfig {
CatalogModules: true,
}
}

var _ interface {
clio.FieldDescriber
} = (*linuxKernelConfig)(nil)

func (o *linuxKernelConfig) DescribeFields(descriptions clio.FieldDescriptionSet) {
descriptions.Add(&o.CatalogModules, `whether to catalog linux kernel modules found within lib/modules/** directories`)
}
10 changes: 10 additions & 0 deletions cmd/syft/internal/options/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
var _ interface {
clio.FlagAdder
clio.PostLoader
clio.FieldDescriber
} = (*Output)(nil)

// Output has the standard output options syft accepts: multiple -o, --file, --template
Expand Down Expand Up @@ -70,6 +71,15 @@ func (o *Output) AddFlags(flags clio.FlagSet) {
fmt.Sprintf("report output format (<format>=<file> to output to a file), formats=%v", names))
}

func (o *Output) DescribeFields(descriptions clio.FieldDescriptionSet) {
descriptions.Add(&o.Outputs, `the output format(s) of the SBOM report (options: syft-table, syft-text, syft-json, spdx-json, ...)
to specify multiple output files in differing formats, use a list:
output:
- "syft-json=<syft-json-output-file>"
- "spdx-json=<spdx-json-output-file>"
`)
}

func (o Output) SBOMWriter() (sbom.Writer, error) {
names := o.OutputNameSet()

Expand Down
19 changes: 18 additions & 1 deletion cmd/syft/internal/options/pkg.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,30 @@
package options

import "github.com/anchore/syft/syft/cataloging"
import (
"github.com/anchore/clio"
"github.com/anchore/syft/syft/cataloging"
)

type packageConfig 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"`
ExcludeBinaryOverlapByOwnership bool `yaml:"exclude-binary-overlap-by-ownership" json:"exclude-binary-overlap-by-ownership" mapstructure:"exclude-binary-overlap-by-ownership"` // exclude synthetic binary packages owned by os package files
}

var _ interface {
clio.FieldDescriber
} = (*packageConfig)(nil)

func (o *packageConfig) DescribeFields(descriptions clio.FieldDescriptionSet) {
descriptions.Add(&o.SearchUnindexedArchives, `search within archives that do contain a file index to search against (zip)
note: for now this only applies to the java package cataloger`)
descriptions.Add(&o.SearchIndexedArchives, `search within archives that do not contain a file index to search against (tar, tar.gz, tar.bz2, etc)
note: enabling this may result in a performance impact since all discovered compressed tars will be decompressed
note: for now this only applies to the java package cataloger`)
descriptions.Add(&o.ExcludeBinaryOverlapByOwnership, `allows users to exclude synthetic binary packages from the sbom
these packages are removed if an overlap with a non-synthetic package is found`)
}

func defaultPackageConfig() packageConfig {
c := cataloging.DefaultArchiveSearchConfig()
return packageConfig{
Expand Down
13 changes: 13 additions & 0 deletions cmd/syft/internal/options/python.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
package options

import "github.com/anchore/clio"

type pythonConfig struct {
GuessUnpinnedRequirements bool `json:"guess-unpinned-requirements" yaml:"guess-unpinned-requirements" mapstructure:"guess-unpinned-requirements"`
}

var _ interface {
clio.FieldDescriber
} = (*pythonConfig)(nil)

func (o *pythonConfig) DescribeFields(descriptions clio.FieldDescriptionSet) {
descriptions.Add(&o.GuessUnpinnedRequirements, `when running across entries in requirements.txt that do not specify a specific version
(e.g. "sqlalchemy >= 1.0.0, <= 2.0.0, != 3.0.0, <= 3.0.0"), attempt to guess what the version could
be based on the version requirements specified (e.g. "1.0.0"). When enabled the lowest expressible version
when given an arbitrary constraint will be used (even if that version may not be available/published).`)
}
19 changes: 18 additions & 1 deletion cmd/syft/internal/options/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ type registryConfig struct {
CACert string `yaml:"ca-cert" json:"ca-cert" mapstructure:"ca-cert"`
}

var _ clio.PostLoader = (*registryConfig)(nil)
var _ interface {
clio.PostLoader
clio.FieldDescriber
} = (*registryConfig)(nil)

func (cfg *registryConfig) PostLoad() error {
// there may be additional credentials provided by env var that should be appended to the set of credentials
Expand Down Expand Up @@ -55,6 +58,20 @@ func (cfg *registryConfig) PostLoad() error {
return nil
}

func (cfg *registryConfig) DescribeFields(descriptions clio.FieldDescriptionSet) {
descriptions.Add(&cfg.InsecureSkipTLSVerify, "skip TLS verification when communicating with the registry")
descriptions.Add(&cfg.InsecureUseHTTP, "use http instead of https when connecting to the registry")
descriptions.Add(&cfg.CACert, "filepath to a CA certificate (or directory containing *.crt, *.cert, *.pem) used to generate the client certificate")
descriptions.Add(&cfg.Auth, `Authentication credentials for specific registries. Each entry describes authentication for a specific authority:
- authority: the registry authority URL the URL to the registry (e.g. "docker.io", "localhost:5000", etc.) (env: SYFT_REGISTRY_AUTH_AUTHORITY)
username: a username if using basic credentials (env: SYFT_REGISTRY_AUTH_USERNAME)
password: a corresponding password (env: SYFT_REGISTRY_AUTH_PASSWORD)
token: a token if using token-based authentication, mutually exclusive with username/password (env: SYFT_REGISTRY_AUTH_TOKEN)
tls-cert: filepath to the client certificate used for TLS authentication to the registry (env: SYFT_REGISTRY_AUTH_TLS_CERT)
tls-key: filepath to the client key used for TLS authentication to the registry (env: SYFT_REGISTRY_AUTH_TLS_KEY)
`)
}

func hasNonEmptyCredentials(username, password, token, tlsCert, tlsKey string) bool {
hasUserPass := username != "" && password != ""
hasToken := token != ""
Expand Down
4 changes: 2 additions & 2 deletions cmd/syft/internal/options/relationships.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ func defaultRelationshipsConfig() relationshipsConfig {
}

func (r *relationshipsConfig) DescribeFields(descriptions fangs.FieldDescriptionSet) {
descriptions.Add(&r.PackageFileOwnership, "include package-to-file relationships that indicate which files are owned by which packages.")
descriptions.Add(&r.PackageFileOwnershipOverlap, "include package-to-package relationships that indicate one package is owned by another due to files claimed to be owned by one package are also evidence of another package's existence.")
descriptions.Add(&r.PackageFileOwnership, "include package-to-file relationships that indicate which files are owned by which packages")
descriptions.Add(&r.PackageFileOwnershipOverlap, "include package-to-package relationships that indicate one package is owned by another due to files claimed to be owned by one package are also evidence of another package's existence")
}
11 changes: 11 additions & 0 deletions cmd/syft/internal/options/source.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

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

"github.com/anchore/clio"
"github.com/anchore/syft/syft/source/sourceproviders"
)

Expand All @@ -22,6 +23,16 @@ type fileSource struct {
Digests []string `json:"digests" yaml:"digests" mapstructure:"digests"`
}

var _ interface {
clio.FieldDescriber
} = (*sourceConfig)(nil)

func (o *sourceConfig) DescribeFields(descriptions clio.FieldDescriptionSet) {
descriptions.Add(&o.File.Digests, `the file digest algorithms to use on the scanned file (options: "md5", "sha1", "sha224", "sha256", "sha384", "sha512")`)
descriptions.Add(&o.Image.DefaultPullSource, `allows users to specify which image source should be used to generate the sbom
valid values are: registry, docker, podman`)
}

type imageSource struct {
DefaultPullSource string `json:"default-pull-source" yaml:"default-pull-source" mapstructure:"default-pull-source"`
}
Expand Down
Loading

0 comments on commit 1c37bab

Please sign in to comment.