mirror of
https://github.com/anchore/syft.git
synced 2026-10-24 20:12:22 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b81c9805dc | ||
|
|
ba55963104 | ||
|
|
8c91605541 | ||
|
|
4c0aef09b8 | ||
|
|
02fb757c21 | ||
|
|
674a54512c | ||
|
|
21ba5d0806 | ||
|
|
0ba57a5936 | ||
|
|
3269bc98d4 | ||
|
|
7f3382f7eb | ||
|
|
65e5ff63f0 | ||
|
|
d287c22b69 | ||
|
|
e8be93a8eb | ||
|
|
e58050bac0 | ||
|
|
36a0945c95 | ||
|
|
396441e921 | ||
|
|
972e4cdaeb | ||
|
|
99f55f6a81 | ||
|
|
285112fe29 | ||
|
|
f29bea5921 | ||
|
|
39cdbc42aa | ||
|
|
27b62ce833 | ||
|
|
499e7c4e16 | ||
|
|
0f75f975c8 | ||
|
|
7427445fe9 | ||
|
|
09a5baf523 | ||
|
|
33c08c8545 | ||
|
|
fd002db802 | ||
|
|
cb3e4b8e49 | ||
|
|
5917f8d8f9 | ||
|
|
70e6d0f2e3 | ||
|
|
31a763c46d | ||
|
|
ae6c9c2e97 | ||
|
|
f6a0dd33d1 | ||
|
|
b77c104aa6 | ||
|
|
10ca7f56ab | ||
|
|
6b2dc08ffb |
@@ -0,0 +1,10 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@@ -59,4 +59,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
uses: github/codeql-action/analyze@v2
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-go@v2
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
stable: ${{ env.GO_STABLE_VERSION }}
|
||||
@@ -31,22 +31,22 @@ jobs:
|
||||
COSIGN_LATEST_VERSION=$(go list -m -json github.com/sigstore/cosign@latest 2>/dev/null | jq -r '.Version')
|
||||
|
||||
# update version variables in the Makefile
|
||||
sed -r -i -e 's/^(GOLANGCILINT_VERSION = ).*/\1'${GOLANGCILINT_LATEST_VERSION}'/' Makefile
|
||||
sed -r -i -e 's/^(BOUNCER_VERSION = ).*/\1'${BOUNCER_LATEST_VERSION}'/' Makefile
|
||||
sed -r -i -e 's/^(CHRONICLE_VERSION = ).*/\1'${CHRONICLE_LATEST_VERSION}'/' Makefile
|
||||
sed -r -i -e 's/^(GORELEASER_VERSION = ).*/\1'${GORELEASER_LATEST_VERSION}'/' Makefile
|
||||
sed -r -i -e 's/^(GOSIMPORTS_VERSION = ).*/\1'${GOSIMPORTS_LATEST_VERSION}'/' Makefile
|
||||
sed -r -i -e 's/^(YAJSV_VERSION = ).*/\1'${YAJSV_LATEST_VERSION}'/' Makefile
|
||||
sed -r -i -e 's/^(COSIGN_VERSION = ).*/\1'${COSIGN_LATEST_VERSION}'/' Makefile
|
||||
sed -r -i -e 's/^(GOLANGCILINT_VERSION := ).*/\1'${GOLANGCILINT_LATEST_VERSION}'/' Makefile
|
||||
sed -r -i -e 's/^(BOUNCER_VERSION := ).*/\1'${BOUNCER_LATEST_VERSION}'/' Makefile
|
||||
sed -r -i -e 's/^(CHRONICLE_VERSION := ).*/\1'${CHRONICLE_LATEST_VERSION}'/' Makefile
|
||||
sed -r -i -e 's/^(GORELEASER_VERSION := ).*/\1'${GORELEASER_LATEST_VERSION}'/' Makefile
|
||||
sed -r -i -e 's/^(GOSIMPORTS_VERSION := ).*/\1'${GOSIMPORTS_LATEST_VERSION}'/' Makefile
|
||||
sed -r -i -e 's/^(YAJSV_VERSION := ).*/\1'${YAJSV_LATEST_VERSION}'/' Makefile
|
||||
sed -r -i -e 's/^(COSIGN_VERSION := ).*/\1'${COSIGN_LATEST_VERSION}'/' Makefile
|
||||
|
||||
# export the versions for use with create-pull-request
|
||||
echo "::set-output name=GOLANGCILINT::$GOLANGCILINT_LATEST_VERSION"
|
||||
echo "::set-output name=BOUNCER::$BOUNCER_LATEST_VERSION"
|
||||
echo "::set-output name=CHRONICLE::$CHRONICLE_LATEST_VERSION"
|
||||
echo "::set-output name=GORELEASER::$GORELEASER_LATEST_VERSION"
|
||||
echo "::set-output name=GOSIMPORTS::$GOSIMPORTS_LATEST_VERSION"
|
||||
echo "::set-output name=YAJSV::$YAJSV_LATEST_VERSION"
|
||||
echo "::set-output name=COSIGN::$COSIGN_LATEST_VERSION"
|
||||
echo "GOLANGCILINT=$GOLANGCILINT_LATEST_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "BOUNCER=$BOUNCER_LATEST_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "CHRONICLE=$CHRONICLE_LATEST_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "GORELEASER=$GORELEASER_LATEST_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "GOSIMPORTS=$GOSIMPORTS_LATEST_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "YAJSV=$YAJSV_LATEST_VERSION" >> $GITHUB_OUTPUT
|
||||
echo "COSIGN=$COSIGN_LATEST_VERSION" >> $GITHUB_OUTPUT
|
||||
id: latest-versions
|
||||
|
||||
- uses: tibdex/github-app-token@v1
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-go@v2
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
stable: ${{ env.GO_STABLE_VERSION }}
|
||||
|
||||
@@ -11,7 +11,7 @@ SNAPSHOT_CMD := $(RELEASE_CMD) --skip-publish --skip-sign --snapshot
|
||||
GOLANGCILINT_VERSION := v1.50.1
|
||||
GOSIMPORTS_VERSION := v0.3.5
|
||||
BOUNCER_VERSION := v0.4.0
|
||||
CHRONICLE_VERSION := v0.4.2
|
||||
CHRONICLE_VERSION := v0.5.1
|
||||
GORELEASER_VERSION := v1.14.1
|
||||
YAJSV_VERSION := v1.4.1
|
||||
COSIGN_VERSION := v1.13.1
|
||||
|
||||
@@ -20,8 +20,7 @@ const (
|
||||
attestHelp = attestExample + attestSchemeHelp
|
||||
)
|
||||
|
||||
//nolint:dupl
|
||||
func Attest(v *viper.Viper, app *config.Application, ro *options.RootOptions, po *options.PackagesOptions) *cobra.Command {
|
||||
func Attest(v *viper.Viper, app *config.Application, ro *options.RootOptions, po *options.PackagesOptions, ao *options.AttestOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "attest --output [FORMAT] <IMAGE>",
|
||||
Short: "Generate an SBOM as an attestation for the given [SOURCE] container image",
|
||||
@@ -50,11 +49,17 @@ func Attest(v *viper.Viper, app *config.Application, ro *options.RootOptions, po
|
||||
},
|
||||
}
|
||||
|
||||
// syft attest is an enhancment of the packages command, so it should have the same flags
|
||||
// syft attest is an enhancement of the packages command, so it should have the same flags
|
||||
err := po.AddFlags(cmd, v)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// syft attest has its own options not included as part of the packages command
|
||||
err = ao.AddFlags(cmd, v)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -97,6 +97,7 @@ func buildSBOM(app *config.Application, si source.Input, writer sbom.Writer, err
|
||||
return sBytes, nil
|
||||
}
|
||||
|
||||
//nolint:funlen
|
||||
func execWorker(app *config.Application, si source.Input, writer sbom.Writer) <-chan error {
|
||||
errs := make(chan error)
|
||||
go func() {
|
||||
@@ -131,9 +132,18 @@ func execWorker(app *config.Application, si source.Input, writer sbom.Writer) <-
|
||||
}
|
||||
|
||||
args := []string{"attest", si.UserInput, "--type", "custom", "--predicate", f.Name()}
|
||||
if app.Attest.Key != "" {
|
||||
args = append(args, "--key", app.Attest.Key)
|
||||
}
|
||||
|
||||
execCmd := exec.Command(cmd, args...)
|
||||
execCmd.Env = os.Environ()
|
||||
execCmd.Env = append(execCmd.Env, "COSIGN_EXPERIMENTAL=1")
|
||||
if app.Attest.Key != "" {
|
||||
execCmd.Env = append(execCmd.Env, fmt.Sprintf("COSIGN_PASSWORD=%s", app.Attest.Password))
|
||||
} else {
|
||||
// no key provided, use cosign's keyless mode
|
||||
execCmd.Env = append(execCmd.Env, "COSIGN_EXPERIMENTAL=1")
|
||||
}
|
||||
|
||||
// bus adapter for ui to hook into stdout via an os pipe
|
||||
r, w, err := os.Pipe()
|
||||
|
||||
@@ -45,12 +45,13 @@ func New() (*cobra.Command, error) {
|
||||
// we also need the command to have information about the `root` options because of this alias
|
||||
ro := &options.RootOptions{}
|
||||
po := &options.PackagesOptions{}
|
||||
ao := &options.AttestOptions{}
|
||||
packagesCmd := Packages(v, app, ro, po)
|
||||
|
||||
// root options are also passed to the attestCmd so that a user provided config location can be discovered
|
||||
poweruserCmd := PowerUser(v, app, ro)
|
||||
convertCmd := Convert(v, app, ro, po)
|
||||
attestCmd := Attest(v, app, ro, po)
|
||||
attestCmd := Attest(v, app, ro, po, ao)
|
||||
|
||||
// rootCmd is currently an alias for the packages command
|
||||
rootCmd := &cobra.Command{
|
||||
|
||||
@@ -11,9 +11,9 @@ import (
|
||||
"github.com/anchore/syft/syft"
|
||||
)
|
||||
|
||||
func Run(ctx context.Context, app *config.Application, args []string) error {
|
||||
func Run(_ context.Context, app *config.Application, args []string) error {
|
||||
log.Warn("convert is an experimental feature, run `syft convert -h` for help")
|
||||
writer, err := options.MakeWriter(app.Outputs, app.File, "")
|
||||
writer, err := options.MakeWriter(app.Outputs, app.File, app.OutputTemplatePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -30,7 +30,9 @@ func Run(ctx context.Context, app *config.Application, args []string) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open SBOM file: %w", err)
|
||||
}
|
||||
defer f.Close()
|
||||
defer func() {
|
||||
_ = f.Close()
|
||||
}()
|
||||
|
||||
sbom, _, err := syft.Decode(f)
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package options
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type AttestOptions struct {
|
||||
Key string
|
||||
}
|
||||
|
||||
var _ Interface = (*AttestOptions)(nil)
|
||||
|
||||
func (o AttestOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error {
|
||||
cmd.Flags().StringVarP(&o.Key, "key", "k", "", "the key to use for the attestation")
|
||||
return bindAttestConfigOptions(cmd.Flags(), v)
|
||||
}
|
||||
|
||||
func bindAttestConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error {
|
||||
if err := v.BindPFlag("attest.key", flags.Lookup("key")); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -5,20 +5,20 @@ go 1.18
|
||||
require (
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
|
||||
github.com/acobaugh/osrelease v0.1.0
|
||||
github.com/adrg/xdg v0.3.3
|
||||
github.com/adrg/xdg v0.4.0
|
||||
github.com/anchore/go-macholibre v0.0.0-20220308212642-53e6d0aaf6fb
|
||||
github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501
|
||||
github.com/bmatcuk/doublestar/v4 v4.0.2
|
||||
github.com/dustin/go-humanize v1.0.0
|
||||
github.com/facebookincubator/nvdtools v0.1.4
|
||||
github.com/go-test/deep v1.0.8
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.0
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/facebookincubator/nvdtools v0.1.5
|
||||
github.com/go-test/deep v1.1.0
|
||||
github.com/google/go-cmp v0.5.9
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/gookit/color v1.4.2
|
||||
github.com/gookit/color v1.5.2
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/jinzhu/copier v0.3.2
|
||||
github.com/jinzhu/copier v0.3.5
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/mholt/archiver/v3 v3.5.1
|
||||
github.com/microsoft/go-rustaudit v0.0.0-20220730194248-4b17361d90a5
|
||||
@@ -29,37 +29,37 @@ require (
|
||||
github.com/pelletier/go-toml v1.9.5
|
||||
// pinned to pull in 386 arch fix: https://github.com/scylladb/go-set/commit/cc7b2070d91ebf40d233207b633e28f5bd8f03a5
|
||||
github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e
|
||||
github.com/sergi/go-diff v1.2.0
|
||||
github.com/sergi/go-diff v1.3.1
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/spdx/tools-golang v0.3.1-0.20221108182156-8a01147e6342
|
||||
github.com/spf13/afero v1.8.2
|
||||
github.com/spf13/cobra v1.6.0
|
||||
github.com/spdx/tools-golang v0.4.0
|
||||
github.com/spf13/afero v1.9.3
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/spf13/viper v1.13.0
|
||||
github.com/spf13/viper v1.15.0
|
||||
github.com/stretchr/testify v1.8.1
|
||||
github.com/vifraa/gopom v0.1.0
|
||||
github.com/vifraa/gopom v0.2.1
|
||||
github.com/wagoodman/go-partybus v0.0.0-20210627031916-db1f5573bbc5
|
||||
github.com/wagoodman/go-progress v0.0.0-20200731105512-1020f39e6240
|
||||
github.com/wagoodman/jotframe v0.0.0-20211129225309-56b0d0a4aebb
|
||||
github.com/xeipuuv/gojsonschema v1.2.0
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4
|
||||
golang.org/x/net v0.4.0
|
||||
golang.org/x/term v0.3.0
|
||||
golang.org/x/mod v0.7.0
|
||||
golang.org/x/net v0.5.0
|
||||
golang.org/x/term v0.4.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/CycloneDX/cyclonedx-go v0.7.1-0.20221222100750-41a1ac565cce
|
||||
github.com/Masterminds/sprig/v3 v3.2.2
|
||||
github.com/Masterminds/sprig/v3 v3.2.3
|
||||
github.com/anchore/go-logger v0.0.0-20220728155337-03b66a5207d8
|
||||
github.com/anchore/stereoscope v0.0.0-20221208011002-c5ff155d72f1
|
||||
github.com/docker/docker v20.10.17+incompatible
|
||||
github.com/google/go-containerregistry v0.11.0
|
||||
github.com/docker/docker v20.10.23+incompatible
|
||||
github.com/google/go-containerregistry v0.13.0
|
||||
github.com/invopop/jsonschema v0.7.0
|
||||
github.com/knqyf263/go-rpmdb v0.0.0-20221030135625-4082a22221ce
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
github.com/sassoftware/go-rpmutils v0.2.0
|
||||
github.com/vbatts/go-mtree v0.5.0
|
||||
github.com/vbatts/go-mtree v0.5.2
|
||||
golang.org/x/exp v0.0.0-20220823124025-807a23277127
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
@@ -67,18 +67,18 @@ require (
|
||||
require (
|
||||
github.com/DataDog/zstd v1.4.5 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.1.1 // indirect
|
||||
github.com/Microsoft/go-winio v0.5.2 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.2.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.0 // indirect
|
||||
github.com/containerd/containerd v1.6.12 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.12.0 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.12.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/docker/cli v20.10.17+incompatible // indirect
|
||||
github.com/docker/cli v20.10.20+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.1+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.6.4 // indirect
|
||||
github.com/docker/docker-credential-helpers v0.7.0 // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-units v0.4.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.0 // indirect
|
||||
github.com/go-restruct/restruct v1.2.0-alpha // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
@@ -86,15 +86,15 @@ require (
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/huandu/xstrings v1.3.2 // indirect
|
||||
github.com/huandu/xstrings v1.3.3 // indirect
|
||||
github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/klauspost/compress v1.15.9 // indirect
|
||||
github.com/klauspost/compress v1.15.11 // indirect
|
||||
github.com/klauspost/pgzip v1.2.5 // indirect
|
||||
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 // indirect
|
||||
github.com/magiconair/properties v1.8.6 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
@@ -102,8 +102,8 @@ require (
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
|
||||
github.com/nwaples/rardecode v1.1.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.15 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
||||
@@ -113,7 +113,7 @@ require (
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/stretchr/objx v0.5.0 // indirect
|
||||
github.com/subosito/gotenv v1.4.1 // indirect
|
||||
github.com/subosito/gotenv v1.4.2 // indirect
|
||||
github.com/sylabs/sif/v2 v2.8.1 // indirect
|
||||
github.com/sylabs/squashfs v0.6.1 // indirect
|
||||
github.com/therootcompany/xz v1.0.1 // indirect
|
||||
@@ -123,14 +123,13 @@ require (
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
|
||||
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0 // indirect
|
||||
golang.org/x/sys v0.3.0 // indirect
|
||||
golang.org/x/text v0.5.0 // indirect
|
||||
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.org/x/sys v0.4.0 // indirect
|
||||
golang.org/x/text v0.6.0 // indirect
|
||||
golang.org/x/tools v0.1.12 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e // indirect
|
||||
google.golang.org/grpc v1.50.1 // indirect
|
||||
google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef // indirect
|
||||
google.golang.org/grpc v1.52.0 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
lukechampine.com/uint128 v1.1.1 // indirect
|
||||
@@ -150,7 +149,7 @@ require (
|
||||
// go: warning: github.com/andybalholm/brotli@v1.0.1: retracted by module author: occasional panics and data corruption
|
||||
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be // indirect
|
||||
golang.org/x/crypto v0.3.0 // indirect
|
||||
)
|
||||
|
||||
retract (
|
||||
|
||||
@@ -47,6 +47,7 @@ type Application struct {
|
||||
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"`
|
||||
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"`
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package config
|
||||
|
||||
import "github.com/spf13/viper"
|
||||
|
||||
type attest struct {
|
||||
Key string `yaml:"key" json:"key" mapstructure:"key"`
|
||||
Password string `yaml:"password" json:"password" mapstructure:"password"`
|
||||
}
|
||||
|
||||
func (cfg attest) loadDefaultValues(v *viper.Viper) {
|
||||
v.SetDefault("attest.key", "")
|
||||
v.SetDefault("attest.password", "")
|
||||
}
|
||||
@@ -48,11 +48,11 @@ func TestSPDXRelationshipOrder(t *testing.T) {
|
||||
|
||||
func spdxJsonRedactor(s []byte) []byte {
|
||||
// each SBOM reports the time it was generated, which is not useful during snapshot testing
|
||||
s = regexp.MustCompile(`"created":\s+"[^"]*",?`).ReplaceAll(s, []byte(""))
|
||||
s = regexp.MustCompile(`"created":\s+"[^"]*"`).ReplaceAll(s, []byte(`"created":""`))
|
||||
|
||||
// each SBOM reports a unique documentNamespace when generated, this is not useful for snapshot testing
|
||||
s = regexp.MustCompile(`"documentNamespace":\s+"[^"]*",?`).ReplaceAll(s, []byte(""))
|
||||
s = regexp.MustCompile(`"documentNamespace":\s+"[^"]*"`).ReplaceAll(s, []byte(`"documentNamespace":""`))
|
||||
|
||||
// the license list will be updated periodically, the value here should not be directly tested in snapshot tests
|
||||
return regexp.MustCompile(`"licenseListVersion":\s+"[^"]*",?`).ReplaceAll(s, []byte(""))
|
||||
return regexp.MustCompile(`"licenseListVersion":\s+"[^"]*"`).ReplaceAll(s, []byte(`"licenseListVersion":""`))
|
||||
}
|
||||
|
||||
@@ -3,15 +3,14 @@
|
||||
"dataLicense": "CC0-1.0",
|
||||
"SPDXID": "SPDXRef-DOCUMENT",
|
||||
"name": "/some/path",
|
||||
"documentNamespace": "https://anchore.com/syft/dir/some/path-e13c8924-4bbc-42f8-bd30-4e1554472d62",
|
||||
"documentNamespace": "https://anchore.com/syft/dir/some/path-1fe34646-a616-48c7-974b-3d1e27d406e3",
|
||||
"creationInfo": {
|
||||
"licenseListVersion": "3.19",
|
||||
"creators": [
|
||||
"Organization: Anchore, Inc",
|
||||
"Tool: syft-v0.42.0-bogus"
|
||||
],
|
||||
"created": "2022-12-22T23:33:52Z",
|
||||
"comment": ""
|
||||
"created": "2023-01-20T21:41:03Z"
|
||||
},
|
||||
"packages": [
|
||||
{
|
||||
@@ -27,14 +26,12 @@
|
||||
{
|
||||
"referenceCategory": "SECURITY",
|
||||
"referenceType": "cpe23Type",
|
||||
"referenceLocator": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*",
|
||||
"comment": ""
|
||||
"referenceLocator": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"
|
||||
},
|
||||
{
|
||||
"referenceCategory": "PACKAGE-MANAGER",
|
||||
"referenceType": "purl",
|
||||
"referenceLocator": "a-purl-2",
|
||||
"comment": ""
|
||||
"referenceLocator": "a-purl-2"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -51,14 +48,12 @@
|
||||
{
|
||||
"referenceCategory": "SECURITY",
|
||||
"referenceType": "cpe23Type",
|
||||
"referenceLocator": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*",
|
||||
"comment": ""
|
||||
"referenceLocator": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"
|
||||
},
|
||||
{
|
||||
"referenceCategory": "PACKAGE-MANAGER",
|
||||
"referenceType": "purl",
|
||||
"referenceLocator": "pkg:deb/debian/package-2@2.0.1",
|
||||
"comment": ""
|
||||
"referenceLocator": "pkg:deb/debian/package-2@2.0.1"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -3,15 +3,14 @@
|
||||
"dataLicense": "CC0-1.0",
|
||||
"SPDXID": "SPDXRef-DOCUMENT",
|
||||
"name": "user-image-input",
|
||||
"documentNamespace": "https://anchore.com/syft/image/user-image-input-a1cc9d58-830a-4a4b-9dcd-f41ea3001216",
|
||||
"documentNamespace": "https://anchore.com/syft/image/user-image-input-33759ac3-6006-4f2c-bdc4-f40b9287a7f0",
|
||||
"creationInfo": {
|
||||
"licenseListVersion": "3.19",
|
||||
"creators": [
|
||||
"Organization: Anchore, Inc",
|
||||
"Tool: syft-v0.42.0-bogus"
|
||||
],
|
||||
"created": "2022-12-22T23:33:53Z",
|
||||
"comment": ""
|
||||
"created": "2023-01-20T21:41:03Z"
|
||||
},
|
||||
"packages": [
|
||||
{
|
||||
@@ -27,14 +26,12 @@
|
||||
{
|
||||
"referenceCategory": "SECURITY",
|
||||
"referenceType": "cpe23Type",
|
||||
"referenceLocator": "cpe:2.3:*:some:package:1:*:*:*:*:*:*:*",
|
||||
"comment": ""
|
||||
"referenceLocator": "cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"
|
||||
},
|
||||
{
|
||||
"referenceCategory": "PACKAGE-MANAGER",
|
||||
"referenceType": "purl",
|
||||
"referenceLocator": "a-purl-1",
|
||||
"comment": ""
|
||||
"referenceLocator": "a-purl-1"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -51,14 +48,12 @@
|
||||
{
|
||||
"referenceCategory": "SECURITY",
|
||||
"referenceType": "cpe23Type",
|
||||
"referenceLocator": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*",
|
||||
"comment": ""
|
||||
"referenceLocator": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"
|
||||
},
|
||||
{
|
||||
"referenceCategory": "PACKAGE-MANAGER",
|
||||
"referenceType": "purl",
|
||||
"referenceLocator": "pkg:deb/debian/package-2@2.0.1",
|
||||
"comment": ""
|
||||
"referenceLocator": "pkg:deb/debian/package-2@2.0.1"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -3,15 +3,14 @@
|
||||
"dataLicense": "CC0-1.0",
|
||||
"SPDXID": "SPDXRef-DOCUMENT",
|
||||
"name": "user-image-input",
|
||||
"documentNamespace": "https://anchore.com/syft/image/user-image-input-fc663ee3-0f9b-402e-827f-3f29aeff164e",
|
||||
"documentNamespace": "https://anchore.com/syft/image/user-image-input-ce98f51f-b483-4e93-9a15-5a8a16d35de6",
|
||||
"creationInfo": {
|
||||
"licenseListVersion": "3.19",
|
||||
"creators": [
|
||||
"Organization: Anchore, Inc",
|
||||
"Tool: syft-v0.42.0-bogus"
|
||||
],
|
||||
"created": "2022-12-22T23:33:53Z",
|
||||
"comment": ""
|
||||
"created": "2023-01-20T21:41:03Z"
|
||||
},
|
||||
"packages": [
|
||||
{
|
||||
@@ -27,14 +26,12 @@
|
||||
{
|
||||
"referenceCategory": "SECURITY",
|
||||
"referenceType": "cpe23Type",
|
||||
"referenceLocator": "cpe:2.3:*:some:package:1:*:*:*:*:*:*:*",
|
||||
"comment": ""
|
||||
"referenceLocator": "cpe:2.3:*:some:package:1:*:*:*:*:*:*:*"
|
||||
},
|
||||
{
|
||||
"referenceCategory": "PACKAGE-MANAGER",
|
||||
"referenceType": "purl",
|
||||
"referenceLocator": "a-purl-1",
|
||||
"comment": ""
|
||||
"referenceLocator": "a-purl-1"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -51,14 +48,12 @@
|
||||
{
|
||||
"referenceCategory": "SECURITY",
|
||||
"referenceType": "cpe23Type",
|
||||
"referenceLocator": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*",
|
||||
"comment": ""
|
||||
"referenceLocator": "cpe:2.3:*:some:package:2:*:*:*:*:*:*:*"
|
||||
},
|
||||
{
|
||||
"referenceCategory": "PACKAGE-MANAGER",
|
||||
"referenceType": "purl",
|
||||
"referenceLocator": "pkg:deb/debian/package-2@2.0.1",
|
||||
"comment": ""
|
||||
"referenceLocator": "pkg:deb/debian/package-2@2.0.1"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package syftjson
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
@@ -20,8 +21,9 @@ func TestDirectoryEncoder(t *testing.T) {
|
||||
testutils.AssertEncoderAgainstGoldenSnapshot(t,
|
||||
Format(),
|
||||
testutils.DirectoryInput(t),
|
||||
true,
|
||||
*updateJson,
|
||||
true,
|
||||
schemaVersionRedactor,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -31,11 +33,18 @@ func TestImageEncoder(t *testing.T) {
|
||||
Format(),
|
||||
testutils.ImageInput(t, testImage, testutils.FromSnapshot()),
|
||||
testImage,
|
||||
true,
|
||||
*updateJson,
|
||||
true,
|
||||
schemaVersionRedactor,
|
||||
)
|
||||
}
|
||||
|
||||
func schemaVersionRedactor(s []byte) []byte {
|
||||
pattern := regexp.MustCompile(`,?\s*"schema":\s*\{[^}]*}`)
|
||||
out := pattern.ReplaceAll(s, []byte(""))
|
||||
return out
|
||||
}
|
||||
|
||||
func TestEncodeFullJSONDocument(t *testing.T) {
|
||||
catalog := pkg.NewCatalog()
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@ func TestTableEncoder(t *testing.T) {
|
||||
testutils.AssertEncoderAgainstGoldenSnapshot(t,
|
||||
Format(),
|
||||
testutils.DirectoryInput(t),
|
||||
false,
|
||||
*updateTableGoldenFiles,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -71,6 +71,14 @@ func parseApkDB(_ source.FileResolver, env *generic.Environment, reader source.L
|
||||
log.Warnf("unable to parse field data from line %q", line)
|
||||
continue
|
||||
}
|
||||
if len(field.name) == 0 {
|
||||
log.Warnf("failed to parse field name from line %q", line)
|
||||
continue
|
||||
}
|
||||
if len(field.value) == 0 {
|
||||
log.Debugf("line %q: parsed field %q appears to have an empty value, skipping", line, field.name)
|
||||
continue
|
||||
}
|
||||
|
||||
entryParsingInProgress = true
|
||||
|
||||
@@ -353,5 +361,11 @@ func stripVersionSpecifier(s string) string {
|
||||
// examples:
|
||||
// musl>=1 --> musl
|
||||
// cmd:scanelf=1.3.4-r0 --> cmd:scanelf
|
||||
return splitAny(s, "<>=")[0]
|
||||
|
||||
items := splitAny(s, "<>=")
|
||||
if len(items) == 0 {
|
||||
return s
|
||||
}
|
||||
|
||||
return items[0]
|
||||
}
|
||||
|
||||
@@ -160,6 +160,43 @@ func TestSinglePackageDetails(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/empty-deps-and-provides",
|
||||
expected: pkg.ApkMetadata{
|
||||
Package: "alpine-baselayout-data",
|
||||
OriginPackage: "alpine-baselayout",
|
||||
Version: "3.4.0-r0",
|
||||
Description: "Alpine base dir structure and init scripts",
|
||||
Maintainer: "Natanael Copa <ncopa@alpinelinux.org>",
|
||||
License: "GPL-2.0-only",
|
||||
Architecture: "x86_64",
|
||||
URL: "https://git.alpinelinux.org/cgit/aports/tree/main/alpine-baselayout",
|
||||
Size: 11664,
|
||||
InstalledSize: 77824,
|
||||
Dependencies: []string{},
|
||||
Provides: []string{},
|
||||
Checksum: "Q15ffjKT28lB7iSXjzpI/eDdYRCwM=",
|
||||
GitCommit: "bd965a7ebf7fd8f07d7a0cc0d7375bf3e4eb9b24",
|
||||
Files: []pkg.ApkFileRecord{
|
||||
{Path: "/etc"},
|
||||
{Path: "/etc/fstab"},
|
||||
{Path: "/etc/group"},
|
||||
{Path: "/etc/hostname"},
|
||||
{Path: "/etc/hosts"},
|
||||
{Path: "/etc/inittab"},
|
||||
{Path: "/etc/modules"},
|
||||
{Path: "/etc/mtab", OwnerUID: "0", OwnerGID: "0", Permissions: "0777"},
|
||||
{Path: "/etc/nsswitch.conf"},
|
||||
{Path: "/etc/passwd"},
|
||||
{Path: "/etc/profile"},
|
||||
{Path: "/etc/protocols"},
|
||||
{Path: "/etc/services"},
|
||||
{Path: "/etc/shadow", OwnerUID: "0", OwnerGID: "148", Permissions: "0640"},
|
||||
{Path: "/etc/shells"},
|
||||
{Path: "/etc/sysctl.conf"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
fixture: "test-fixtures/base",
|
||||
expected: pkg.ApkMetadata{
|
||||
@@ -874,6 +911,27 @@ func Test_discoverPackageDependencies(t *testing.T) {
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "strip version specifiers with empty provides value",
|
||||
genFn: func() ([]pkg.Package, []artifact.Relationship) {
|
||||
a := pkg.Package{
|
||||
Name: "package-a",
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Dependencies: []string{"so:libc.musl-x86_64.so.1"},
|
||||
},
|
||||
}
|
||||
a.SetID()
|
||||
b := pkg.Package{
|
||||
Name: "package-b",
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Provides: []string{""},
|
||||
},
|
||||
}
|
||||
b.SetID()
|
||||
|
||||
return []pkg.Package{a, b}, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "depends on package name",
|
||||
genFn: func() ([]pkg.Package, []artifact.Relationship) {
|
||||
@@ -1089,3 +1147,42 @@ func newLocationReadCloser(t *testing.T, path string) source.LocationReadCloser
|
||||
|
||||
return source.NewLocationReadCloser(source.NewLocation(path), f)
|
||||
}
|
||||
|
||||
func Test_stripVersionSpecifier(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
version string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "empty expression",
|
||||
version: "",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "no expression",
|
||||
version: "cmd:foo",
|
||||
want: "cmd:foo",
|
||||
},
|
||||
{
|
||||
name: "=",
|
||||
version: "cmd:scanelf=1.3.4-r0",
|
||||
want: "cmd:scanelf",
|
||||
},
|
||||
{
|
||||
name: ">=",
|
||||
version: "cmd:scanelf>=1.3.4-r0",
|
||||
want: "cmd:scanelf",
|
||||
},
|
||||
{
|
||||
name: "<",
|
||||
version: "cmd:scanelf<1.3.4-r0",
|
||||
want: "cmd:scanelf",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.want, stripVersionSpecifier(tt.version))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
P:alpine-baselayout-data
|
||||
V:3.4.0-r0
|
||||
A:x86_64
|
||||
L:GPL-2.0-only
|
||||
T:Alpine base dir structure and init scripts
|
||||
o:alpine-baselayout
|
||||
m:Natanael Copa <ncopa@alpinelinux.org>
|
||||
U:https://git.alpinelinux.org/cgit/aports/tree/main/alpine-baselayout
|
||||
D:
|
||||
p:
|
||||
c:bd965a7ebf7fd8f07d7a0cc0d7375bf3e4eb9b24
|
||||
i:[]
|
||||
t:1667573027
|
||||
S:11664
|
||||
I:77824
|
||||
k:0
|
||||
C:Q15ffjKT28lB7iSXjzpI/eDdYRCwM=
|
||||
F:etc
|
||||
R:fstab
|
||||
R:group
|
||||
R:hostname
|
||||
R:hosts
|
||||
R:inittab
|
||||
R:modules
|
||||
R:mtab
|
||||
a:0:0:0777
|
||||
R:nsswitch.conf
|
||||
R:passwd
|
||||
R:profile
|
||||
R:protocols
|
||||
R:services
|
||||
R:shadow
|
||||
a:0:148:0640
|
||||
R:shells
|
||||
R:sysctl.conf
|
||||
@@ -1,6 +1,7 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
@@ -30,41 +31,52 @@ func (c Cataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artif
|
||||
var packages []pkg.Package
|
||||
var relationships []artifact.Relationship
|
||||
|
||||
for _, classifier := range defaultClassifiers {
|
||||
locations, err := resolver.FilesByGlob(classifier.FileGlob)
|
||||
for _, cls := range defaultClassifiers {
|
||||
pkgs, err := catalog(resolver, cls)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
for _, location := range locations {
|
||||
reader, err := resolver.FileContentsByLocation(location)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
locationReader := source.NewLocationReadCloser(location, reader)
|
||||
newPkgs, err := classifier.EvidenceMatcher(classifier, locationReader)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
newPackages:
|
||||
for i := range newPkgs {
|
||||
newPkg := &newPkgs[i]
|
||||
for j := range packages {
|
||||
p := &packages[j]
|
||||
// consolidate identical packages found in different locations,
|
||||
// but continue to track each location
|
||||
if packagesMatch(p, newPkg) {
|
||||
p.Locations.Add(newPkg.Locations.ToSlice()...)
|
||||
continue newPackages
|
||||
}
|
||||
}
|
||||
packages = append(packages, *newPkg)
|
||||
}
|
||||
log.WithFields("error", err, "classifier", cls.Class).Warn("unable to catalog binary package: %w", err)
|
||||
continue
|
||||
}
|
||||
packages = append(packages, pkgs...)
|
||||
}
|
||||
|
||||
return packages, relationships, nil
|
||||
}
|
||||
|
||||
func catalog(resolver source.FileResolver, cls classifier) ([]pkg.Package, error) {
|
||||
var pkgs []pkg.Package
|
||||
locations, err := resolver.FilesByGlob(cls.FileGlob)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, location := range locations {
|
||||
reader, err := resolver.FileContentsByLocation(location)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
locationReader := source.NewLocationReadCloser(location, reader)
|
||||
newPkgs, err := cls.EvidenceMatcher(cls, locationReader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newPackages:
|
||||
for i := range newPkgs {
|
||||
newPkg := &newPkgs[i]
|
||||
for j := range pkgs {
|
||||
p := &pkgs[j]
|
||||
// consolidate identical packages found in different locations,
|
||||
// but continue to track each location
|
||||
if packagesMatch(p, newPkg) {
|
||||
p.Locations.Add(newPkg.Locations.ToSlice()...)
|
||||
continue newPackages
|
||||
}
|
||||
}
|
||||
pkgs = append(pkgs, *newPkg)
|
||||
}
|
||||
}
|
||||
return pkgs, nil
|
||||
}
|
||||
|
||||
// packagesMatch returns true if the binary packages "match" based on basic criteria
|
||||
func packagesMatch(p1 *pkg.Package, p2 *pkg.Package) bool {
|
||||
if p1.Name != p2.Name ||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -17,6 +19,20 @@ func TestClassifierCataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
|
||||
fixtureDir string
|
||||
expected pkg.Package
|
||||
}{
|
||||
{
|
||||
name: "positive-memcached-1.6.18",
|
||||
fixtureDir: "test-fixtures/classifiers/positive/memcached-1.6.18",
|
||||
expected: pkg.Package{
|
||||
Name: "memcached",
|
||||
Version: "1.6.18",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/memcached@1.6.18",
|
||||
Locations: singleLocation("memcached"),
|
||||
Metadata: pkg.BinaryMetadata{
|
||||
Classifier: "memcached-binary",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "positive-httpd-2.4.54",
|
||||
fixtureDir: "test-fixtures/classifiers/positive/httpd-2.4.54",
|
||||
@@ -30,7 +46,8 @@ func TestClassifierCataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
|
||||
Classifier: "httpd-binary",
|
||||
},
|
||||
},
|
||||
}, {
|
||||
},
|
||||
{
|
||||
name: "positive-php-cli-8.2.1",
|
||||
fixtureDir: "test-fixtures/classifiers/positive/php-cli-8.2.1",
|
||||
expected: pkg.Package{
|
||||
@@ -418,3 +435,49 @@ func assertPackagesAreEqual(t *testing.T, expected pkg.Package, p pkg.Package) {
|
||||
assert.Failf(t, "packages not equal", "%v != %v", expected, p)
|
||||
}
|
||||
}
|
||||
|
||||
type panicyResolver struct {
|
||||
globCalled bool
|
||||
}
|
||||
|
||||
func (p panicyResolver) FileContentsByLocation(location source.Location) (io.ReadCloser, error) {
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (p panicyResolver) HasPath(s string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (p panicyResolver) FilesByPath(paths ...string) ([]source.Location, error) {
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (p *panicyResolver) FilesByGlob(patterns ...string) ([]source.Location, error) {
|
||||
p.globCalled = true
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (p panicyResolver) FilesByMIMEType(types ...string) ([]source.Location, error) {
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (p panicyResolver) RelativeFileByPath(_ source.Location, path string) *source.Location {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p panicyResolver) AllLocations() <-chan source.Location {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p panicyResolver) FileMetadataByLocation(location source.Location) (source.FileMetadata, error) {
|
||||
return source.FileMetadata{}, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func Test_Cataloger_ResilientToErrors(t *testing.T) {
|
||||
c := NewCataloger()
|
||||
|
||||
resolver := &panicyResolver{}
|
||||
_, _, err := c.Catalog(resolver)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, resolver.globCalled)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package binary
|
||||
|
||||
import "github.com/anchore/syft/syft/pkg"
|
||||
import (
|
||||
"github.com/anchore/syft/syft/cpe"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
var defaultClassifiers = []classifier{
|
||||
{
|
||||
@@ -11,6 +14,10 @@ var defaultClassifiers = []classifier{
|
||||
`(?m)(?P<version>{{ .version }}\.[0-9]+[-_a-zA-Z0-9]*)`),
|
||||
Package: "python",
|
||||
PURL: mustPURL("pkg:generic/python@version"),
|
||||
CPEs: []cpe.CPE{
|
||||
cpe.Must("cpe:2.3:a:python_software_foundation:python:*:*:*:*:*:*:*:*"),
|
||||
cpe.Must("cpe:2.3:a:python:python:*:*:*:*:*:*:*:*"),
|
||||
},
|
||||
},
|
||||
{
|
||||
Class: "python-binary-lib",
|
||||
@@ -20,6 +27,10 @@ var defaultClassifiers = []classifier{
|
||||
`(?m)(?P<version>{{ .version }}\.[0-9]+[-_a-zA-Z0-9]*)`),
|
||||
Package: "python",
|
||||
PURL: mustPURL("pkg:generic/python@version"),
|
||||
CPEs: []cpe.CPE{
|
||||
cpe.Must("cpe:2.3:a:python_software_foundation:python:*:*:*:*:*:*:*:*"),
|
||||
cpe.Must("cpe:2.3:a:python:python:*:*:*:*:*:*:*:*"),
|
||||
},
|
||||
},
|
||||
{
|
||||
Class: "cpython-source",
|
||||
@@ -28,6 +39,10 @@ var defaultClassifiers = []classifier{
|
||||
`(?m)#define\s+PY_VERSION\s+"?(?P<version>[0-9\.\-_a-zA-Z]+)"?`),
|
||||
Package: "python",
|
||||
PURL: mustPURL("pkg:generic/python@version"),
|
||||
CPEs: []cpe.CPE{
|
||||
cpe.Must("cpe:2.3:a:python_software_foundation:python:*:*:*:*:*:*:*:*"),
|
||||
cpe.Must("cpe:2.3:a:python:python:*:*:*:*:*:*:*:*"),
|
||||
},
|
||||
},
|
||||
{
|
||||
Class: "go-binary",
|
||||
@@ -103,6 +118,7 @@ var defaultClassifiers = []classifier{
|
||||
EvidenceMatcher: fileContentsVersionMatcher(
|
||||
`(?m)BusyBox\s+v(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
|
||||
Package: "busybox",
|
||||
CPEs: singleCPE("cpe:2.3:a:busybox:busybox:*:*:*:*:*:*:*:*"),
|
||||
},
|
||||
{
|
||||
Class: "php-cli-binary",
|
||||
@@ -141,4 +157,12 @@ var defaultClassifiers = []classifier{
|
||||
PURL: mustPURL("pkg:generic/httpd@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:apache:http_server:*:*:*:*:*:*:*:*"),
|
||||
},
|
||||
{
|
||||
Class: "memcached-binary",
|
||||
FileGlob: "**/memcached",
|
||||
EvidenceMatcher: fileContentsVersionMatcher(
|
||||
`(?m)memcached\s(?P<version>[0-9]+\.[0-9]+\.[0-9]+)`),
|
||||
Package: "memcached",
|
||||
PURL: mustPURL("pkg:generic/memcached@version"),
|
||||
},
|
||||
}
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
secs_since_last_cmd
|
||||
listen
|
||||
memcached 1.6.18
|
||||
udp-port
|
||||
memcached 1.6.18
|
||||
Failed to allocate memory
|
||||
%s,%s
|
||||
--
|
||||
bget
|
||||
flush_all
|
||||
VERSION 1.6.18
|
||||
quit
|
||||
BADCLASS invalid class id
|
||||
@@ -28,7 +28,8 @@ func (c *Cataloger) WithParserByGlobs(parser Parser, globs ...string) *Cataloger
|
||||
func(resolver source.FileResolver, env Environment) []request {
|
||||
var requests []request
|
||||
for _, g := range globs {
|
||||
// TODO: add more trace logging here
|
||||
log.WithFields("glob", g).Trace("searching for paths matching glob")
|
||||
|
||||
matches, err := resolver.FilesByGlob(g)
|
||||
if err != nil {
|
||||
log.Warnf("unable to process glob=%q: %+v", g, err)
|
||||
@@ -47,7 +48,8 @@ func (c *Cataloger) WithParserByMimeTypes(parser Parser, types ...string) *Catal
|
||||
func(resolver source.FileResolver, env Environment) []request {
|
||||
var requests []request
|
||||
for _, t := range types {
|
||||
// TODO: add more trace logging here
|
||||
log.WithFields("mimetype", t).Trace("searching for paths matching mimetype")
|
||||
|
||||
matches, err := resolver.FilesByMIMEType(t)
|
||||
if err != nil {
|
||||
log.Warnf("unable to process mimetype=%q: %+v", t, err)
|
||||
@@ -65,11 +67,12 @@ func (c *Cataloger) WithParserByPath(parser Parser, paths ...string) *Cataloger
|
||||
c.processor = append(c.processor,
|
||||
func(resolver source.FileResolver, env Environment) []request {
|
||||
var requests []request
|
||||
for _, g := range paths {
|
||||
// TODO: add more trace logging here
|
||||
matches, err := resolver.FilesByPath(g)
|
||||
for _, p := range paths {
|
||||
log.WithFields("path", p).Trace("searching for path")
|
||||
|
||||
matches, err := resolver.FilesByPath(p)
|
||||
if err != nil {
|
||||
log.Warnf("unable to process path=%q: %+v", g, err)
|
||||
log.Warnf("unable to process path=%q: %+v", p, err)
|
||||
continue
|
||||
}
|
||||
requests = append(requests, makeRequests(parser, matches)...)
|
||||
|
||||
@@ -53,7 +53,8 @@ func getBuildInfo(r io.ReaderAt) (bi *debug.BuildInfo, err error) {
|
||||
if err != nil {
|
||||
if err.Error() == "not a Go executable" {
|
||||
// since the cataloger can only select executables and not distinguish if they are a go-compiled
|
||||
// binary, we should not show warnings/logs in this case.
|
||||
// binary, we should not show warnings/logs in this case. For this reason we nil-out err here.
|
||||
err = nil
|
||||
return
|
||||
}
|
||||
// in this case we could not read the or parse the file, but not explicitly because it is not a
|
||||
|
||||
@@ -133,12 +133,14 @@ func (j *archiveParser) parse() ([]pkg.Package, []artifact.Relationship, error)
|
||||
// add pURLs to all packages found
|
||||
// note: since package information may change after initial creation when parsing multiple locations within the
|
||||
// jar, we wait until the conclusion of the parsing process before synthesizing pURLs.
|
||||
for i, p := range pkgs {
|
||||
for i := range pkgs {
|
||||
p := &pkgs[i]
|
||||
if m, ok := p.Metadata.(pkg.JavaMetadata); ok {
|
||||
pkgs[i].PURL = packageURL(p.Name, p.Version, m)
|
||||
p.PURL = packageURL(p.Name, p.Version, m)
|
||||
} else {
|
||||
log.WithFields("package", p.String()).Warn("unable to extract java metadata to generate purl")
|
||||
}
|
||||
p.SetID()
|
||||
}
|
||||
|
||||
return pkgs, relationships, nil
|
||||
|
||||
@@ -277,6 +277,10 @@ func TestParseJar(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, a := range actual {
|
||||
if a.ID() == "" {
|
||||
t.Fatalf("empty package ID: %+v", a)
|
||||
}
|
||||
|
||||
e, ok := test.expected[a.Name]
|
||||
if !ok {
|
||||
t.Errorf("entry not found: %s", a.Name)
|
||||
|
||||
@@ -117,7 +117,7 @@ func getPackage(component nativeImageComponent) pkg.Package {
|
||||
for _, property := range component.Properties {
|
||||
c, err := cpe.New(property.Value)
|
||||
if err != nil {
|
||||
log.Debugf("native-image cataloger: could not parse CPE: %v.", err)
|
||||
log.Debugf("unable to parse CPE: %v", err)
|
||||
continue
|
||||
}
|
||||
cpes = append(cpes, c)
|
||||
@@ -156,7 +156,7 @@ func decompressSbom(dataBuf []byte, sbomStart uint64, lengthStart uint64) ([]pkg
|
||||
return nil, fmt.Errorf("could not read from binary file: %w", err)
|
||||
}
|
||||
|
||||
log.WithFields("len", storedLength).Trace("native-image cataloger: found SBOM")
|
||||
log.WithFields("len", storedLength).Trace("found java native-image SBOM")
|
||||
sbomEnd := sbomStart + storedLength
|
||||
if sbomEnd > uint64(bufLen) {
|
||||
return nil, errors.New("the sbom symbol overflows the binary")
|
||||
@@ -166,18 +166,18 @@ func decompressSbom(dataBuf []byte, sbomStart uint64, lengthStart uint64) ([]pkg
|
||||
p = bytes.NewBuffer(sbomCompressed)
|
||||
gzreader, err := gzip.NewReader(p)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not decompress the native-image SBOM: %w", err)
|
||||
return nil, fmt.Errorf("could not decompress the java native-image SBOM: %w", err)
|
||||
}
|
||||
|
||||
output, err := io.ReadAll(gzreader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read the native-image SBOM: %w", err)
|
||||
return nil, fmt.Errorf("could not read the java native-image SBOM: %w", err)
|
||||
}
|
||||
|
||||
var sbomContent nativeImageCycloneDX
|
||||
err = json.Unmarshal(output, &sbomContent)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not unmarshal the native-image SBOM: %w", err)
|
||||
return nil, fmt.Errorf("could not unmarshal the java native-image SBOM: %w", err)
|
||||
}
|
||||
|
||||
for _, component := range sbomContent.Components {
|
||||
@@ -199,10 +199,18 @@ func newElf(filename string, r io.ReaderAt) (nativeImage, error) {
|
||||
// First attempt to read an ELF file.
|
||||
bi, err := elf.NewFile(r)
|
||||
|
||||
// The reader does not refer to an ELF file.
|
||||
if err != nil {
|
||||
var fmtErr *elf.FormatError
|
||||
if errors.As(err, &fmtErr) {
|
||||
// this is not an elf file
|
||||
log.WithFields("filename", filename, "error", err).Trace("not an ELF binary")
|
||||
return nil, nil
|
||||
}
|
||||
return fileError(filename, err)
|
||||
}
|
||||
if bi == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return nativeImageElf{
|
||||
file: bi,
|
||||
}, nil
|
||||
@@ -213,9 +221,16 @@ func newMachO(filename string, r io.ReaderAt) (nativeImage, error) {
|
||||
// First attempt to read an ELF file.
|
||||
bi, err := macho.NewFile(r)
|
||||
|
||||
// The reader does not refer to an MachO file.
|
||||
if err != nil {
|
||||
return fileError(filename, err)
|
||||
var fmtErr *macho.FormatError
|
||||
if errors.As(err, &fmtErr) {
|
||||
// this is not a MachO file
|
||||
log.WithFields("filename", filename, "error", err).Trace("not a MachO binary")
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
if bi == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return nativeImageMachO{
|
||||
file: bi,
|
||||
@@ -224,13 +239,19 @@ func newMachO(filename string, r io.ReaderAt) (nativeImage, error) {
|
||||
|
||||
// newPE reads a Native Image from a Portable Executable file.
|
||||
func newPE(filename string, r io.ReaderAt) (nativeImage, error) {
|
||||
// First attempt to read an ELF file.
|
||||
// First attempt to read an PE file.
|
||||
bi, err := pe.NewFile(r)
|
||||
|
||||
// The reader does not refer to an MachO file.
|
||||
// The reader does not refer to a PE file.
|
||||
if err != nil {
|
||||
return fileError(filename, err)
|
||||
// note: there isn't a good way to distinguish between a format error and other kinds of errors
|
||||
log.WithFields("filename", filename, "error", err).Trace("not a PE binary")
|
||||
return nil, nil
|
||||
}
|
||||
if bi == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var exportSymbolsDataDirectory pe.DataDirectory
|
||||
switch h := bi.OptionalHeader.(type) {
|
||||
case *pe.OptionalHeader32:
|
||||
@@ -282,10 +303,6 @@ func (ni nativeImageElf) fetchPkgs() (pkgs []pkg.Package, retErr error) {
|
||||
}()
|
||||
|
||||
bi := ni.file
|
||||
if bi == nil {
|
||||
log.Debugf("native-image cataloger: file is nil")
|
||||
return nil, nil
|
||||
}
|
||||
var sbom elf.Symbol
|
||||
var sbomLength elf.Symbol
|
||||
var svmVersion elf.Symbol
|
||||
@@ -340,10 +357,6 @@ func (ni nativeImageMachO) fetchPkgs() (pkgs []pkg.Package, retErr error) {
|
||||
var svmVersion macho.Symbol
|
||||
|
||||
bi := ni.file
|
||||
if bi == nil {
|
||||
log.Debugf("native-image cataloger: file is nil")
|
||||
return nil, nil
|
||||
}
|
||||
if bi.Symtab == nil {
|
||||
return nil, errors.New(nativeImageMissingSymbolsError)
|
||||
}
|
||||
@@ -367,7 +380,7 @@ func (ni nativeImageMachO) fetchPkgs() (pkgs []pkg.Package, retErr error) {
|
||||
}
|
||||
dataBuf, err := dataSegment.Data()
|
||||
if err != nil {
|
||||
log.Debugf("native-image cataloger: cannot obtain buffer from data segment.")
|
||||
log.Tracef("cannot obtain buffer from data segment")
|
||||
return nil, nil
|
||||
}
|
||||
sbomLocation := sbom.Value - dataSegment.Addr
|
||||
@@ -382,13 +395,13 @@ func (ni nativeImagePE) fetchExportAttribute(i int) (uint32, error) {
|
||||
n := len(ni.exports)
|
||||
j := int(unsafe.Sizeof(ni.header)) + i*int(unsafe.Sizeof(ni.t.headerAttribute))
|
||||
if j+4 >= n {
|
||||
log.Debugf("native-image cataloger: invalid index to export directory entry attribute: %v.", j)
|
||||
log.Tracef("invalid index to export directory entry attribute: %v", j)
|
||||
return uint32(0), errors.New(nativeImageInvalidIndexError)
|
||||
}
|
||||
p := bytes.NewBuffer(ni.exports[j : j+4])
|
||||
err := binary.Read(p, binary.LittleEndian, &attribute)
|
||||
if err != nil {
|
||||
log.Debugf("native-image cataloger: error fetching export directory entry attribute: %v.", err)
|
||||
log.Tracef("error fetching export directory entry attribute: %v", err)
|
||||
return uint32(0), err
|
||||
}
|
||||
return attribute, nil
|
||||
@@ -402,13 +415,13 @@ func (ni nativeImagePE) fetchExportFunctionPointer(functionsBase uint32, i uint3
|
||||
sz := uint32(unsafe.Sizeof(ni.t.functionPointer))
|
||||
j := functionsBase + i*sz
|
||||
if j+sz >= n {
|
||||
log.Debugf("native-image cataloger: invalid index to exported function: %v.", j)
|
||||
log.Tracef("invalid index to exported function: %v", j)
|
||||
return uint32(0), errors.New(nativeImageInvalidIndexError)
|
||||
}
|
||||
p := bytes.NewBuffer(ni.exports[j : j+sz])
|
||||
err := binary.Read(p, binary.LittleEndian, &pointer)
|
||||
if err != nil {
|
||||
log.Debugf("native-image cataloger: error fetching exported function: %v.", err)
|
||||
log.Tracef("error fetching exported function: %v", err)
|
||||
return uint32(0), err
|
||||
}
|
||||
return pointer, nil
|
||||
@@ -452,7 +465,7 @@ func (ni nativeImagePE) fetchSbomSymbols(content *exportContentPE) {
|
||||
k := addressBase + j
|
||||
sz := uint32(unsafe.Sizeof(ni.t.namePointer))
|
||||
if k+sz >= n {
|
||||
log.Debugf("native-image cataloger: invalid index to exported function: %v.", k)
|
||||
log.Tracef("invalid index to exported function: %v", k)
|
||||
// If we are at the end of exports, stop looking
|
||||
return
|
||||
}
|
||||
@@ -460,12 +473,12 @@ func (ni nativeImagePE) fetchSbomSymbols(content *exportContentPE) {
|
||||
p := bytes.NewBuffer(ni.exports[k : k+sz])
|
||||
err := binary.Read(p, binary.LittleEndian, &symbolAddress)
|
||||
if err != nil {
|
||||
log.Debugf("native-image cataloger: error fetching address of symbol %v.", err)
|
||||
log.Tracef("error fetching address of symbol %v", err)
|
||||
return
|
||||
}
|
||||
symbolBase := symbolAddress - ni.exportSymbols.VirtualAddress
|
||||
if symbolBase >= n {
|
||||
log.Debugf("native-image cataloger: invalid index to exported symbol: %v.", symbolBase)
|
||||
log.Tracef("invalid index to exported symbol: %v", symbolBase)
|
||||
return
|
||||
}
|
||||
switch {
|
||||
@@ -491,7 +504,7 @@ func (ni nativeImagePE) fetchPkgs() (pkgs []pkg.Package, retErr error) {
|
||||
|
||||
content, err := ni.fetchExportContent()
|
||||
if err != nil {
|
||||
log.Debugf("native-image cataloger: could not fetch the content of the export directory entry: %v.", err)
|
||||
log.Debugf("could not fetch the content of the export directory entry: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
ni.fetchSbomSymbols(content)
|
||||
@@ -516,7 +529,7 @@ func (ni nativeImagePE) fetchPkgs() (pkgs []pkg.Package, retErr error) {
|
||||
}
|
||||
dataBuf, err := dataSection.Data()
|
||||
if err != nil {
|
||||
log.Debugf("native-image cataloger: cannot obtain buffer from .data section.")
|
||||
log.Tracef("cannot obtain buffer from the java native-image .data section")
|
||||
return nil, nil
|
||||
}
|
||||
sbomLocation := sbomAddress - dataSection.VirtualAddress
|
||||
@@ -528,27 +541,30 @@ func (ni nativeImagePE) fetchPkgs() (pkgs []pkg.Package, retErr error) {
|
||||
// fetchPkgs provides the packages available in a UnionReader.
|
||||
func fetchPkgs(reader unionreader.UnionReader, filename string) []pkg.Package {
|
||||
var pkgs []pkg.Package
|
||||
imageformats := []func(string, io.ReaderAt) (nativeImage, error){newElf, newMachO, newPE}
|
||||
imageFormats := []func(string, io.ReaderAt) (nativeImage, error){newElf, newMachO, newPE}
|
||||
|
||||
// NOTE: multiple readers are returned to cover universal binaries, which are files
|
||||
// with more than one binary
|
||||
readers, err := unionreader.GetReaders(reader)
|
||||
if err != nil {
|
||||
log.Debugf("native-image cataloger: failed to open a binary: %v.", err)
|
||||
log.Debugf("failed to open the java native-image binary: %v", err)
|
||||
return nil
|
||||
}
|
||||
for _, r := range readers {
|
||||
for _, makeNativeImage := range imageformats {
|
||||
for _, makeNativeImage := range imageFormats {
|
||||
ni, err := makeNativeImage(filename, r)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
newpkgs, err := ni.fetchPkgs()
|
||||
if err != nil {
|
||||
log.Debugf("native-image cataloger: error extracting SBOM from %s: %v.", filename, err)
|
||||
if ni == nil {
|
||||
continue
|
||||
}
|
||||
pkgs = append(pkgs, newpkgs...)
|
||||
newPkgs, err := ni.fetchPkgs()
|
||||
if err != nil {
|
||||
log.Tracef("unable to extract SBOM from possible java native-image %s: %v", filename, err)
|
||||
continue
|
||||
}
|
||||
pkgs = append(pkgs, newPkgs...)
|
||||
}
|
||||
}
|
||||
return pkgs
|
||||
@@ -565,16 +581,16 @@ func (c *NativeImageCataloger) Catalog(resolver source.FileResolver) ([]pkg.Pack
|
||||
for _, location := range fileMatches {
|
||||
readerCloser, err := resolver.FileContentsByLocation(location)
|
||||
if err != nil {
|
||||
log.Debugf("native-image cataloger: error opening file: %v.", err)
|
||||
log.Debugf("error opening file: %v", err)
|
||||
continue
|
||||
}
|
||||
log.Tracef("native-image cataloger: found an executable file %v.", location)
|
||||
|
||||
reader, err := unionreader.GetUnionReader(readerCloser)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
newpkgs := fetchPkgs(reader, location.RealPath)
|
||||
pkgs = append(pkgs, newpkgs...)
|
||||
newPkgs := fetchPkgs(reader, location.RealPath)
|
||||
pkgs = append(pkgs, newPkgs...)
|
||||
internal.CloseAndLogError(readerCloser, location.RealPath)
|
||||
}
|
||||
|
||||
|
||||
@@ -78,8 +78,8 @@ func newPackageLockV1Package(resolver source.FileResolver, location source.Locat
|
||||
func newPackageLockV2Package(resolver source.FileResolver, location source.Location, name string, u lockPackage) pkg.Package {
|
||||
var licenses []string
|
||||
|
||||
if u.License != "" {
|
||||
licenses = append(licenses, u.License)
|
||||
if u.License != nil {
|
||||
licenses = u.License
|
||||
}
|
||||
|
||||
return finalizeLockPkg(
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
@@ -24,6 +25,39 @@ type packageLock struct {
|
||||
Packages map[string]lockPackage
|
||||
}
|
||||
|
||||
// packageLockLicense
|
||||
type packageLockLicense []string
|
||||
|
||||
func (licenses *packageLockLicense) UnmarshalJSON(data []byte) (err error) {
|
||||
// The license field could be either a string or an array.
|
||||
|
||||
// 1. An array
|
||||
var arr []string
|
||||
if err := json.Unmarshal(data, &arr); err == nil {
|
||||
*licenses = arr
|
||||
return nil
|
||||
}
|
||||
|
||||
// 2. A string
|
||||
var str string
|
||||
if err = json.Unmarshal(data, &str); err == nil {
|
||||
*licenses = make([]string, 1)
|
||||
(*licenses)[0] = str
|
||||
return nil
|
||||
}
|
||||
|
||||
// debug the content we did not expect
|
||||
if len(data) > 0 {
|
||||
log.WithFields("license", string(data)).Debug("Unable to parse the following `license` value in package-lock.json")
|
||||
}
|
||||
|
||||
// 3. Unexpected
|
||||
// In case we are unable to parse the license field,
|
||||
// i.e if we have not covered the full specification,
|
||||
// we do not want to throw an error, instead assign nil.
|
||||
return nil
|
||||
}
|
||||
|
||||
// lockDependency represents a single package dependency listed in the package.lock json file
|
||||
type lockDependency struct {
|
||||
Version string `json:"version"`
|
||||
@@ -32,11 +66,11 @@ type lockDependency struct {
|
||||
}
|
||||
|
||||
type lockPackage struct {
|
||||
Name string `json:"name"` // only present in the root package entry (named "")
|
||||
Version string `json:"version"`
|
||||
Resolved string `json:"resolved"`
|
||||
Integrity string `json:"integrity"`
|
||||
License string `json:"license"`
|
||||
Name string `json:"name"` // only present in the root package entry (named "")
|
||||
Version string `json:"version"`
|
||||
Resolved string `json:"resolved"`
|
||||
Integrity string `json:"integrity"`
|
||||
License packageLockLicense `json:"license"`
|
||||
}
|
||||
|
||||
// parsePackageLock parses a package-lock.json and returns the discovered JavaScript packages.
|
||||
|
||||
@@ -297,3 +297,44 @@ func TestParsePackageLockAlias(t *testing.T) {
|
||||
pkgtest.TestFileParser(t, packageLock, parsePackageLock, expected, expectedRelationships)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePackageLockLicenseWithArray(t *testing.T) {
|
||||
fixture := "test-fixtures/pkg-lock/array-license-package-lock.json"
|
||||
var expectedRelationships []artifact.Relationship
|
||||
expectedPkgs := []pkg.Package{
|
||||
{
|
||||
Name: "tmp",
|
||||
Version: "1.0.0",
|
||||
Licenses: []string{"ISC"},
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
PURL: "pkg:npm/tmp@1.0.0",
|
||||
MetadataType: "NpmPackageLockJsonMetadata",
|
||||
Metadata: pkg.NpmPackageLockJSONMetadata{},
|
||||
},
|
||||
{
|
||||
Name: "pause-stream",
|
||||
Version: "0.0.11",
|
||||
Licenses: []string{"MIT", "Apache2"},
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
PURL: "pkg:npm/pause-stream@0.0.11",
|
||||
MetadataType: "NpmPackageLockJsonMetadata",
|
||||
Metadata: pkg.NpmPackageLockJSONMetadata{},
|
||||
},
|
||||
{
|
||||
Name: "through",
|
||||
Version: "2.3.8",
|
||||
Licenses: []string{"MIT"},
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
PURL: "pkg:npm/through@2.3.8",
|
||||
MetadataType: "NpmPackageLockJsonMetadata",
|
||||
Metadata: pkg.NpmPackageLockJSONMetadata{},
|
||||
},
|
||||
}
|
||||
for i := range expectedPkgs {
|
||||
expectedPkgs[i].Locations.Add(source.NewLocation(fixture))
|
||||
}
|
||||
pkgtest.TestFileParser(t, fixture, parsePackageLock, expectedPkgs, expectedRelationships)
|
||||
}
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "tmp",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "tmp",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"pause-stream": "0.0.11"
|
||||
}
|
||||
},
|
||||
"node_modules/pause-stream": {
|
||||
"version": "0.0.11",
|
||||
"license": [
|
||||
"MIT",
|
||||
"Apache2"
|
||||
],
|
||||
"dependencies": {
|
||||
"through": "~2.3"
|
||||
}
|
||||
},
|
||||
"node_modules/through": {
|
||||
"version": "2.3.8",
|
||||
"license": "MIT"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"pause-stream": {
|
||||
"version": "0.0.11",
|
||||
"requires": {
|
||||
"through": "~2.3"
|
||||
}
|
||||
},
|
||||
"through": {
|
||||
"version": "2.3.8"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,7 @@ type pathFilterFn func(string, os.FileInfo) bool
|
||||
// directoryResolver implements path and content access for the directory data source.
|
||||
type directoryResolver struct {
|
||||
path string
|
||||
base string
|
||||
currentWdRelativeToRoot string
|
||||
currentWd string
|
||||
fileTree *filetree.FileTree
|
||||
@@ -47,7 +48,7 @@ type directoryResolver struct {
|
||||
errPaths map[string]error
|
||||
}
|
||||
|
||||
func newDirectoryResolver(root string, pathFilters ...pathFilterFn) (*directoryResolver, error) {
|
||||
func newDirectoryResolver(root string, base string, pathFilters ...pathFilterFn) (*directoryResolver, error) {
|
||||
currentWD, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get CWD: %w", err)
|
||||
@@ -64,6 +65,18 @@ func newDirectoryResolver(root string, pathFilters ...pathFilterFn) (*directoryR
|
||||
return nil, fmt.Errorf("could not evaluate root=%q symlinks: %w", root, err)
|
||||
}
|
||||
|
||||
cleanBase := ""
|
||||
if base != "" {
|
||||
cleanBase, err = filepath.EvalSymlinks(base)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not evaluate base=%q symlinks: %w", base, err)
|
||||
}
|
||||
cleanBase, err = filepath.Abs(cleanBase)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var currentWdRelRoot string
|
||||
if path.IsAbs(cleanRoot) {
|
||||
currentWdRelRoot, err = filepath.Rel(cleanCWD, cleanRoot)
|
||||
@@ -76,6 +89,7 @@ func newDirectoryResolver(root string, pathFilters ...pathFilterFn) (*directoryR
|
||||
|
||||
resolver := directoryResolver{
|
||||
path: cleanRoot,
|
||||
base: cleanBase,
|
||||
currentWd: cleanCWD,
|
||||
currentWdRelativeToRoot: currentWdRelRoot,
|
||||
fileTree: filetree.NewFileTree(),
|
||||
@@ -244,10 +258,25 @@ func (r directoryResolver) addSymlinkToIndex(p string, info os.FileInfo) (string
|
||||
return "", fmt.Errorf("unable to readlink for path=%q: %w", p, err)
|
||||
}
|
||||
|
||||
// note: if the link is not absolute (e.g, /dev/stderr -> fd/2 ) we need to resolve it relative to the directory
|
||||
// in question (e.g. resolve to /dev/fd/2)
|
||||
if !filepath.IsAbs(linkTarget) {
|
||||
linkTarget = filepath.Join(filepath.Dir(p), linkTarget)
|
||||
if filepath.IsAbs(linkTarget) {
|
||||
// if the link is absolute (e.g, /bin/ls -> /bin/busybox) we need to
|
||||
// resolve relative to the root of the base directory
|
||||
linkTarget = filepath.Join(r.base, filepath.Clean(linkTarget))
|
||||
} else {
|
||||
// if the link is not absolute (e.g, /dev/stderr -> fd/2 ) we need to
|
||||
// resolve it relative to the directory in question (e.g. resolve to
|
||||
// /dev/fd/2)
|
||||
if r.base == "" {
|
||||
linkTarget = filepath.Join(filepath.Dir(p), linkTarget)
|
||||
} else {
|
||||
// if the base is set, then we first need to resolve the link,
|
||||
// before finding it's location in the base
|
||||
dir, err := filepath.Rel(r.base, filepath.Dir(p))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to resolve relative path for path=%q: %w", p, err)
|
||||
}
|
||||
linkTarget = filepath.Join(r.base, filepath.Clean(filepath.Join("/", dir, linkTarget)))
|
||||
}
|
||||
}
|
||||
|
||||
ref, err := r.fileTree.AddSymLink(file.Path(p), file.Path(linkTarget))
|
||||
@@ -336,14 +365,17 @@ func (r directoryResolver) FilesByPath(userPaths ...string) ([]Location, error)
|
||||
}
|
||||
|
||||
// we should be resolving symlinks and preserving this information as a VirtualPath to the real file
|
||||
evaluatedPath, err := filepath.EvalSymlinks(userStrPath)
|
||||
exists, ref, err := r.fileTree.File(file.Path(userStrPath), filetree.FollowBasenameLinks)
|
||||
if err != nil {
|
||||
log.Debugf("directory resolver unable to evaluate symlink for path=%q : %+v", userPath, err)
|
||||
log.Tracef("unable to evaluate symlink for path=%q : %+v", userPath, err)
|
||||
continue
|
||||
}
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO: why not use stored metadata?
|
||||
fileMeta, err := os.Stat(evaluatedPath)
|
||||
fileMeta, err := os.Stat(string(ref.RealPath))
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
// note: there are other kinds of errors other than os.ErrNotExist that may be given that is platform
|
||||
// specific, but essentially hints at the same overall problem (that the path does not exist). Such an
|
||||
@@ -354,7 +386,7 @@ func (r directoryResolver) FilesByPath(userPaths ...string) ([]Location, error)
|
||||
// invalid paths. This logging statement is meant to raise IO or permissions related problems.
|
||||
var pathErr *os.PathError
|
||||
if !errors.As(err, &pathErr) {
|
||||
log.Warnf("path is not valid (%s): %+v", evaluatedPath, err)
|
||||
log.Warnf("path is not valid (%s): %+v", ref.RealPath, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -368,15 +400,12 @@ func (r directoryResolver) FilesByPath(userPaths ...string) ([]Location, error)
|
||||
userStrPath = windowsToPosix(userStrPath)
|
||||
}
|
||||
|
||||
exists, ref, err := r.fileTree.File(file.Path(userStrPath), filetree.FollowBasenameLinks)
|
||||
if err == nil && exists {
|
||||
loc := NewVirtualLocationFromDirectory(
|
||||
r.responsePath(string(ref.RealPath)), // the actual path relative to the resolver root
|
||||
r.responsePath(userStrPath), // the path used to access this file, relative to the resolver root
|
||||
*ref,
|
||||
)
|
||||
references = append(references, loc)
|
||||
}
|
||||
loc := NewVirtualLocationFromDirectory(
|
||||
r.responsePath(string(ref.RealPath)), // the actual path relative to the resolver root
|
||||
r.responsePath(userStrPath), // the path used to access this file, relative to the resolver root
|
||||
*ref,
|
||||
)
|
||||
references = append(references, loc)
|
||||
}
|
||||
|
||||
return references, nil
|
||||
|
||||
@@ -57,7 +57,7 @@ func TestDirectoryResolver_FilesByPath_relativeRoot(t *testing.T) {
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
resolver, err := newDirectoryResolver(c.relativeRoot)
|
||||
resolver, err := newDirectoryResolver(c.relativeRoot, "")
|
||||
assert.NoError(t, err)
|
||||
|
||||
refs, err := resolver.FilesByPath(c.input)
|
||||
@@ -111,7 +111,7 @@ func TestDirectoryResolver_FilesByPath_absoluteRoot(t *testing.T) {
|
||||
absRoot, err := filepath.Abs(c.relativeRoot)
|
||||
require.NoError(t, err)
|
||||
|
||||
resolver, err := newDirectoryResolver(absRoot)
|
||||
resolver, err := newDirectoryResolver(absRoot, "")
|
||||
assert.NoError(t, err)
|
||||
|
||||
refs, err := resolver.FilesByPath(c.input)
|
||||
@@ -172,7 +172,7 @@ func TestDirectoryResolver_FilesByPath(t *testing.T) {
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
resolver, err := newDirectoryResolver(c.root)
|
||||
resolver, err := newDirectoryResolver(c.root, "")
|
||||
assert.NoError(t, err)
|
||||
|
||||
hasPath := resolver.HasPath(c.input)
|
||||
@@ -220,7 +220,7 @@ func TestDirectoryResolver_MultipleFilesByPath(t *testing.T) {
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
resolver, err := newDirectoryResolver("./test-fixtures")
|
||||
resolver, err := newDirectoryResolver("./test-fixtures", "")
|
||||
assert.NoError(t, err)
|
||||
refs, err := resolver.FilesByPath(c.input...)
|
||||
assert.NoError(t, err)
|
||||
@@ -233,7 +233,7 @@ func TestDirectoryResolver_MultipleFilesByPath(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDirectoryResolver_FilesByGlobMultiple(t *testing.T) {
|
||||
resolver, err := newDirectoryResolver("./test-fixtures")
|
||||
resolver, err := newDirectoryResolver("./test-fixtures", "")
|
||||
assert.NoError(t, err)
|
||||
refs, err := resolver.FilesByGlob("**/image-symlinks/file*")
|
||||
assert.NoError(t, err)
|
||||
@@ -242,7 +242,7 @@ func TestDirectoryResolver_FilesByGlobMultiple(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDirectoryResolver_FilesByGlobRecursive(t *testing.T) {
|
||||
resolver, err := newDirectoryResolver("./test-fixtures/image-symlinks")
|
||||
resolver, err := newDirectoryResolver("./test-fixtures/image-symlinks", "")
|
||||
assert.NoError(t, err)
|
||||
refs, err := resolver.FilesByGlob("**/*.txt")
|
||||
assert.NoError(t, err)
|
||||
@@ -250,7 +250,7 @@ func TestDirectoryResolver_FilesByGlobRecursive(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDirectoryResolver_FilesByGlobSingle(t *testing.T) {
|
||||
resolver, err := newDirectoryResolver("./test-fixtures")
|
||||
resolver, err := newDirectoryResolver("./test-fixtures", "")
|
||||
assert.NoError(t, err)
|
||||
refs, err := resolver.FilesByGlob("**/image-symlinks/*1.txt")
|
||||
assert.NoError(t, err)
|
||||
@@ -277,7 +277,7 @@ func TestDirectoryResolver_FilesByPath_ResolvesSymlinks(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
resolver, err := newDirectoryResolver("./test-fixtures/symlinks-simple")
|
||||
resolver, err := newDirectoryResolver("./test-fixtures/symlinks-simple", "")
|
||||
assert.NoError(t, err)
|
||||
|
||||
refs, err := resolver.FilesByPath(test.fixture)
|
||||
@@ -300,7 +300,7 @@ func TestDirectoryResolver_FilesByPath_ResolvesSymlinks(t *testing.T) {
|
||||
|
||||
func TestDirectoryResolverDoesNotIgnoreRelativeSystemPaths(t *testing.T) {
|
||||
// let's make certain that "dev/place" is not ignored, since it is not "/dev/place"
|
||||
resolver, err := newDirectoryResolver("test-fixtures/system_paths/target")
|
||||
resolver, err := newDirectoryResolver("test-fixtures/system_paths/target", "")
|
||||
assert.NoError(t, err)
|
||||
// ensure the correct filter function is wired up by default
|
||||
expectedFn := reflect.ValueOf(isUnallowableFileType)
|
||||
@@ -431,7 +431,7 @@ func Test_isUnallowableFileType(t *testing.T) {
|
||||
|
||||
func Test_directoryResolver_index(t *testing.T) {
|
||||
// note: this test is testing the effects from newDirectoryResolver, indexTree, and addPathToIndex
|
||||
r, err := newDirectoryResolver("test-fixtures/system_paths/target")
|
||||
r, err := newDirectoryResolver("test-fixtures/system_paths/target", "")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get indexed dir resolver: %+v", err)
|
||||
}
|
||||
@@ -608,7 +608,7 @@ func Test_directoryResolver_FilesByMIMEType(t *testing.T) {
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.fixturePath, func(t *testing.T) {
|
||||
resolver, err := newDirectoryResolver(test.fixturePath)
|
||||
resolver, err := newDirectoryResolver(test.fixturePath, "")
|
||||
assert.NoError(t, err)
|
||||
locations, err := resolver.FilesByMIMEType(test.mimeType)
|
||||
assert.NoError(t, err)
|
||||
@@ -621,7 +621,7 @@ func Test_directoryResolver_FilesByMIMEType(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_IndexingNestedSymLinks(t *testing.T) {
|
||||
resolver, err := newDirectoryResolver("./test-fixtures/symlinks-simple")
|
||||
resolver, err := newDirectoryResolver("./test-fixtures/symlinks-simple", "")
|
||||
require.NoError(t, err)
|
||||
|
||||
// check that we can get the real path
|
||||
@@ -674,7 +674,7 @@ func Test_IndexingNestedSymLinks_ignoredIndexes(t *testing.T) {
|
||||
return strings.HasSuffix(path, string(filepath.Separator)+"readme")
|
||||
}
|
||||
|
||||
resolver, err := newDirectoryResolver("./test-fixtures/symlinks-simple", filterFn)
|
||||
resolver, err := newDirectoryResolver("./test-fixtures/symlinks-simple", "", filterFn)
|
||||
require.NoError(t, err)
|
||||
|
||||
// the path to the real file is PRUNED from the index, so we should NOT expect a location returned
|
||||
@@ -694,7 +694,7 @@ func Test_IndexingNestedSymLinks_ignoredIndexes(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_IndexingNestedSymLinksOutsideOfRoot(t *testing.T) {
|
||||
resolver, err := newDirectoryResolver("./test-fixtures/symlinks-multiple-roots/root")
|
||||
resolver, err := newDirectoryResolver("./test-fixtures/symlinks-multiple-roots/root", "")
|
||||
require.NoError(t, err)
|
||||
|
||||
// check that we can get the real path
|
||||
@@ -712,7 +712,7 @@ func Test_IndexingNestedSymLinksOutsideOfRoot(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_RootViaSymlink(t *testing.T) {
|
||||
resolver, err := newDirectoryResolver("./test-fixtures/symlinked-root/nested/link-root")
|
||||
resolver, err := newDirectoryResolver("./test-fixtures/symlinked-root/nested/link-root", "")
|
||||
require.NoError(t, err)
|
||||
|
||||
locations, err := resolver.FilesByPath("./file1.txt")
|
||||
@@ -753,7 +753,7 @@ func Test_directoryResolver_FileContentsByLocation(t *testing.T) {
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
r, err := newDirectoryResolver(".")
|
||||
r, err := newDirectoryResolver(".", "")
|
||||
require.NoError(t, err)
|
||||
|
||||
actual, err := r.FileContentsByLocation(test.location)
|
||||
@@ -819,7 +819,7 @@ func Test_isUnixSystemRuntimePath(t *testing.T) {
|
||||
|
||||
func Test_SymlinkLoopWithGlobsShouldResolve(t *testing.T) {
|
||||
test := func(t *testing.T) {
|
||||
resolver, err := newDirectoryResolver("./test-fixtures/symlinks-loop")
|
||||
resolver, err := newDirectoryResolver("./test-fixtures/symlinks-loop", "")
|
||||
require.NoError(t, err)
|
||||
|
||||
locations, err := resolver.FilesByGlob("**/file.target")
|
||||
@@ -853,7 +853,7 @@ func Test_IncludeRootPathInIndex(t *testing.T) {
|
||||
return path != "/"
|
||||
}
|
||||
|
||||
resolver, err := newDirectoryResolver("/", filterFn)
|
||||
resolver, err := newDirectoryResolver("/", "", filterFn)
|
||||
require.NoError(t, err)
|
||||
|
||||
exists, ref, err := resolver.fileTree.File(file.Path("/"))
|
||||
@@ -870,7 +870,7 @@ func TestDirectoryResolver_indexPath(t *testing.T) {
|
||||
tempFile, err := os.CreateTemp("", "")
|
||||
require.NoError(t, err)
|
||||
|
||||
resolver, err := newDirectoryResolver(tempFile.Name())
|
||||
resolver, err := newDirectoryResolver(tempFile.Name(), "")
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("filtering path with nil os.FileInfo", func(t *testing.T) {
|
||||
@@ -885,3 +885,76 @@ func TestDirectoryResolver_indexPath(t *testing.T) {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestDirectoryResolver_FilesByPath_baseRoot(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
root string
|
||||
input string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "should find the base file",
|
||||
root: "./test-fixtures/symlinks-base/",
|
||||
input: "./base",
|
||||
expected: []string{
|
||||
"base",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "should follow a link with a pivoted root",
|
||||
root: "./test-fixtures/symlinks-base/",
|
||||
input: "./foo",
|
||||
expected: []string{
|
||||
"base",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "should follow a relative link with extra parents",
|
||||
root: "./test-fixtures/symlinks-base/",
|
||||
input: "./bar",
|
||||
expected: []string{
|
||||
"base",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "should follow an absolute link with extra parents",
|
||||
root: "./test-fixtures/symlinks-base/",
|
||||
input: "./baz",
|
||||
expected: []string{
|
||||
"base",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "should follow an absolute link with extra parents",
|
||||
root: "./test-fixtures/symlinks-base/",
|
||||
input: "./sub/link",
|
||||
expected: []string{
|
||||
"sub/item",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "should follow chained pivoted link",
|
||||
root: "./test-fixtures/symlinks-base/",
|
||||
input: "./chain",
|
||||
expected: []string{
|
||||
"base",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
resolver, err := newDirectoryResolver(c.root, c.root)
|
||||
assert.NoError(t, err)
|
||||
|
||||
refs, err := resolver.FilesByPath(c.input)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, refs, len(c.expected))
|
||||
s := strset.New()
|
||||
for _, actual := range refs {
|
||||
s.Add(actual.RealPath)
|
||||
}
|
||||
assert.ElementsMatch(t, c.expected, s.List())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,5 +6,6 @@ type Metadata struct {
|
||||
Scheme Scheme // the source data scheme type (directory or image)
|
||||
ImageMetadata ImageMetadata // all image info (image only)
|
||||
Path string // the root path to be cataloged (directory only)
|
||||
Base string // the base path to be cataloged (directory only)
|
||||
Name string
|
||||
}
|
||||
|
||||
+24
-1
@@ -32,6 +32,7 @@ type Source struct {
|
||||
Metadata Metadata
|
||||
directoryResolver *directoryResolver `hash:"ignore"`
|
||||
path string
|
||||
base string
|
||||
mutex *sync.Mutex
|
||||
Exclusions []string `hash:"ignore"`
|
||||
}
|
||||
@@ -252,6 +253,11 @@ func NewFromDirectory(path string) (Source, error) {
|
||||
return NewFromDirectoryWithName(path, "")
|
||||
}
|
||||
|
||||
// NewFromDirectory creates a new source object tailored to catalog a given filesystem directory recursively.
|
||||
func NewFromDirectoryRoot(path string) (Source, error) {
|
||||
return NewFromDirectoryRootWithName(path, "")
|
||||
}
|
||||
|
||||
// NewFromDirectoryWithName creates a new source object tailored to catalog a given filesystem directory recursively, with an explicitly provided name.
|
||||
func NewFromDirectoryWithName(path string, name string) (Source, error) {
|
||||
s := Source{
|
||||
@@ -267,6 +273,23 @@ func NewFromDirectoryWithName(path string, name string) (Source, error) {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// NewFromDirectoryRootWithName creates a new source object tailored to catalog a given filesystem directory recursively, with an explicitly provided name.
|
||||
func NewFromDirectoryRootWithName(path string, name string) (Source, error) {
|
||||
s := Source{
|
||||
mutex: &sync.Mutex{},
|
||||
Metadata: Metadata{
|
||||
Name: name,
|
||||
Scheme: DirectoryScheme,
|
||||
Path: path,
|
||||
Base: path,
|
||||
},
|
||||
path: path,
|
||||
base: path,
|
||||
}
|
||||
s.SetID()
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// NewFromFile creates a new source object tailored to catalog a file.
|
||||
func NewFromFile(path string) (Source, func()) {
|
||||
return NewFromFileWithName(path, "")
|
||||
@@ -428,7 +451,7 @@ func (s *Source) FileResolver(scope Scope) (FileResolver, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resolver, err := newDirectoryResolver(s.path, exclusionFunctions...)
|
||||
resolver, err := newDirectoryResolver(s.path, s.base, exclusionFunctions...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create directory resolver: %w", err)
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ func TestSetID(t *testing.T) {
|
||||
Path: "test-fixtures/image-simple",
|
||||
},
|
||||
},
|
||||
expected: artifact.ID("14b60020c4f9955"),
|
||||
expected: artifact.ID("1b0dc351e6577b01"),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
../../base
|
||||
@@ -0,0 +1 @@
|
||||
/../../base
|
||||
@@ -0,0 +1 @@
|
||||
/foo
|
||||
@@ -0,0 +1 @@
|
||||
/base
|
||||
@@ -0,0 +1 @@
|
||||
../sub/item
|
||||
@@ -1,6 +1,7 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -9,13 +10,8 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type conversion struct {
|
||||
To string
|
||||
From string
|
||||
}
|
||||
|
||||
func TestConvertCmdFlags(t *testing.T) {
|
||||
commonAssertions := []traitAssertion{
|
||||
assertions := []traitAssertion{
|
||||
func(tb testing.TB, stdout, _ string, _ int) {
|
||||
tb.Helper()
|
||||
if len(stdout) < 1000 {
|
||||
@@ -26,52 +22,48 @@ func TestConvertCmdFlags(t *testing.T) {
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
conversions []conversion
|
||||
env map[string]string
|
||||
assertions []traitAssertion
|
||||
to string
|
||||
from string
|
||||
template string
|
||||
env map[string]string
|
||||
}{
|
||||
{
|
||||
name: "syft-format convertable to spdx-json",
|
||||
conversions: []conversion{
|
||||
{To: "syft-json", From: "spdx-json"},
|
||||
{To: "syft-json", From: "cyclonedx-json"},
|
||||
{To: "spdx-json", From: "syft-json"},
|
||||
{To: "spdx-json", From: "cyclonedx-json"},
|
||||
{To: "cyclonedx-json", From: "syft-json"},
|
||||
{To: "cyclonedx-json", From: "spdx-json"},
|
||||
},
|
||||
assertions: commonAssertions,
|
||||
},
|
||||
{to: "syft-json", from: "spdx-json"},
|
||||
{to: "syft-json", from: "cyclonedx-json"},
|
||||
{to: "spdx-json", from: "syft-json"},
|
||||
{to: "template", from: "syft-json", template: "test-fixtures/csv.template"},
|
||||
{to: "spdx-json", from: "cyclonedx-json"},
|
||||
{to: "cyclonedx-json", from: "syft-json"},
|
||||
{to: "cyclonedx-json", from: "spdx-json"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
for _, c := range test.conversions {
|
||||
sbomArgs := []string{"dir:./test-fixtures/image-pkg-coverage", "-o", c.From}
|
||||
cmd, stdout, stderr := runSyft(t, test.env, sbomArgs...)
|
||||
if cmd.ProcessState.ExitCode() != 0 {
|
||||
t.Fatalf("failure executing syft creating an sbom")
|
||||
t.Log("STDOUT:\n", stdout)
|
||||
t.Log("STDERR:\n", stderr)
|
||||
t.Log("COMMAND:", strings.Join(cmd.Args, " "))
|
||||
return
|
||||
}
|
||||
t.Run(fmt.Sprintf("from %s to %s", test.from, test.to), func(t *testing.T) {
|
||||
sbomArgs := []string{"dir:./test-fixtures/image-pkg-coverage", "-o", test.from}
|
||||
cmd, stdout, stderr := runSyft(t, test.env, sbomArgs...)
|
||||
if cmd.ProcessState.ExitCode() != 0 {
|
||||
t.Log("STDOUT:\n", stdout)
|
||||
t.Log("STDERR:\n", stderr)
|
||||
t.Log("COMMAND:", strings.Join(cmd.Args, " "))
|
||||
t.Fatalf("failure executing syft creating an sbom")
|
||||
return
|
||||
}
|
||||
|
||||
tempDir := t.TempDir()
|
||||
sbomFile := filepath.Join(tempDir, "sbom.json")
|
||||
require.NoError(t, os.WriteFile(sbomFile, []byte(stdout), 0666))
|
||||
tempDir := t.TempDir()
|
||||
sbomFile := filepath.Join(tempDir, "sbom.json")
|
||||
require.NoError(t, os.WriteFile(sbomFile, []byte(stdout), 0666))
|
||||
|
||||
convertArgs := []string{"convert", sbomFile, "-o", c.To}
|
||||
cmd, stdout, stderr = runSyft(t, test.env, convertArgs...)
|
||||
for _, traitFn := range test.assertions {
|
||||
traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode())
|
||||
}
|
||||
if t.Failed() {
|
||||
t.Log("STDOUT:\n", stdout)
|
||||
t.Log("STDERR:\n", stderr)
|
||||
t.Log("COMMAND:", strings.Join(cmd.Args, " "))
|
||||
}
|
||||
convertArgs := []string{"convert", sbomFile, "-o", test.to}
|
||||
if test.template != "" {
|
||||
convertArgs = append(convertArgs, "--template", test.template)
|
||||
}
|
||||
cmd, stdout, stderr = runSyft(t, test.env, convertArgs...)
|
||||
for _, traitFn := range assertions {
|
||||
traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode())
|
||||
}
|
||||
if t.Failed() {
|
||||
t.Log("STDOUT:\n", stdout)
|
||||
t.Log("STDERR:\n", stderr)
|
||||
t.Log("COMMAND:", strings.Join(cmd.Args, " "))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -36,6 +36,8 @@ func TestSpdxValidationTooling(t *testing.T) {
|
||||
fixturesPath := filepath.Join(cwd, "test-fixtures", "image-java-spdx-tools")
|
||||
buildCmd := exec.Command("make", "build")
|
||||
buildCmd.Dir = fixturesPath
|
||||
buildCmd.Stdout = os.Stdout
|
||||
buildCmd.Stderr = os.Stderr
|
||||
err = buildCmd.Run()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM cgr.dev/chainguard/jdk
|
||||
FROM openjdk:11
|
||||
|
||||
RUN wget https://github.com/spdx/tools-java/releases/download/v1.1.3/tools-java-1.1.3.zip && \
|
||||
unzip tools-java-1.1.3.zip && \
|
||||
|
||||
@@ -618,6 +618,9 @@ func AttestationStartedHandler(ctx context.Context, fr *frame.Frame, event party
|
||||
text := s.Text()
|
||||
if strings.Contains(text, "tlog entry created with index") {
|
||||
tlogEntry = text
|
||||
} else {
|
||||
// no tlog entry create so user used personal PKI
|
||||
tlogEntry = "signed attestation using provided key"
|
||||
}
|
||||
_, err = line.Write([]byte(fmt.Sprintf(" %s %s", auxInfoFormat.Sprintf("░░"), text)))
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user