mirror of
https://github.com/anchore/syft.git
synced 2026-10-17 08:37:19 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
244b797a19 | ||
|
|
95176d7e0c | ||
|
|
5a7bab972c | ||
|
|
b69259534d | ||
|
|
44422853be | ||
|
|
cc731c7b19 | ||
|
|
5d156b8241 | ||
|
|
05715489c4 | ||
|
|
661d256b85 |
@@ -34,33 +34,35 @@ jobs:
|
||||
uses: ./.github/actions/bootstrap
|
||||
|
||||
- name: Restore Java test-fixture cache
|
||||
id: unit-java-cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: syft/pkg/cataloger/java/test-fixtures/java-builds/packages
|
||||
key: ${{ runner.os }}-unit-java-cache-${{ hashFiles( 'syft/pkg/cataloger/java/test-fixtures/java-builds/packages.fingerprint' ) }}
|
||||
|
||||
- name: Restore RPM test-fixture cache
|
||||
id: unit-rpm-cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: syft/pkg/cataloger/rpm/test-fixtures/rpms
|
||||
key: ${{ runner.os }}-unit-rpm-cache-${{ hashFiles( 'syft/pkg/cataloger/rpm/test-fixtures/rpms.fingerprint' ) }}
|
||||
|
||||
- name: Restore go binary test-fixture cache
|
||||
id: unit-go-binary-cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: syft/pkg/cataloger/golang/test-fixtures/archs/binaries
|
||||
key: ${{ runner.os }}-unit-go-binaries-cache-${{ hashFiles( 'syft/pkg/cataloger/golang/test-fixtures/archs/binaries.fingerprint' ) }}
|
||||
|
||||
- name: Restore binary cataloger test-fixture cache
|
||||
id: unit-binary-cataloger-cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: syft/pkg/cataloger/binary/test-fixtures/classifiers/dynamic
|
||||
key: ${{ runner.os }}-unit-binary-cataloger-cache-${{ hashFiles( 'syft/pkg/cataloger/binary/test-fixtures/cache.fingerprint' ) }}
|
||||
|
||||
- name: Restore Kernel test-fixture cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: syft/pkg/cataloger/kernel/test-fixtures/cache
|
||||
key: ${{ runner.os }}-unit-kernel-cache-${{ hashFiles( 'syft/pkg/cataloger/kernel/test-fixtures/cache.fingerprint' ) }}
|
||||
|
||||
- name: Run unit tests
|
||||
run: make unit
|
||||
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ After cloning the following step can help you get setup:
|
||||
- this command `go run cmd/syft/main.go alpine:latest` will compile and run syft against `alpine:latest`
|
||||
5. view the README or syft help output for more output options
|
||||
|
||||
The main make tasks for common static analysis and testing are `lint`, `lint-fix`, `unit`, `integration`, and `cli`.
|
||||
The main make tasks for common static analysis and testing are `lint`, `format`, `lint-fix`, `unit`, `integration`, and `cli`.
|
||||
|
||||
See `make help` for all the current make tasks.
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ GOLANGCILINT_VERSION := v1.52.2
|
||||
GOSIMPORTS_VERSION := v0.3.8
|
||||
BOUNCER_VERSION := v0.4.0
|
||||
CHRONICLE_VERSION := v0.6.0
|
||||
GORELEASER_VERSION := v1.17.0
|
||||
GORELEASER_VERSION := v1.17.1
|
||||
YAJSV_VERSION := v1.4.1
|
||||
COSIGN_VERSION := v1.13.1
|
||||
QUILL_VERSION := v0.2.0
|
||||
@@ -118,14 +118,18 @@ lint: ## Run gofmt + golangci lint checks
|
||||
$(eval MALFORMED_FILENAMES := $(shell find . | grep -e ':'))
|
||||
@bash -c "[[ '$(MALFORMED_FILENAMES)' == '' ]] || (printf '\nfound unsupported filename characters:\n$(MALFORMED_FILENAMES)\n\n' && false)"
|
||||
|
||||
.PHONY: lint-fix
|
||||
lint-fix: ## Auto-format all source code + run golangci lint fixers
|
||||
$(call title,Running lint fixers)
|
||||
.PHONY: format
|
||||
format: ## Auto-format all source code
|
||||
$(call title,Running formatters)
|
||||
gofmt -w -s .
|
||||
$(GOIMPORTS_CMD) -w .
|
||||
$(LINT_CMD) --fix
|
||||
go mod tidy
|
||||
|
||||
.PHONY: lint-fix
|
||||
lint-fix: format ## Auto-format all source code + run golangci lint fixers
|
||||
$(call title,Running lint fixers)
|
||||
$(LINT_CMD) --fix
|
||||
|
||||
.PHONY: check-licenses
|
||||
check-licenses: ## Ensure transitive dependencies are compliant with the current license policy
|
||||
$(call title,Checking for license compliance)
|
||||
@@ -205,6 +209,10 @@ fingerprints:
|
||||
cd syft/pkg/cataloger/rpm/test-fixtures && \
|
||||
make rpms.fingerprint
|
||||
|
||||
# for Kernel test fixtures
|
||||
cd syft/pkg/cataloger/kernel/test-fixtures && \
|
||||
make cache.fingerprint
|
||||
|
||||
# for INSTALL integration test fixtures
|
||||
cd test/install && \
|
||||
make cache.fingerprint
|
||||
|
||||
@@ -45,6 +45,8 @@ For commercial support options with Syft or Grype, please [contact Anchore](http
|
||||
- Java (jar, ear, war, par, sar, nar, native-image)
|
||||
- JavaScript (npm, yarn)
|
||||
- Jenkins Plugins (jpi, hpi)
|
||||
- Linux kernel archives (vmlinz)
|
||||
- Linux kernel modules (ko)
|
||||
- Nix (outputs in /nix/store)
|
||||
- PHP (composer)
|
||||
- Python (wheel, egg, poetry, requirements.txt)
|
||||
@@ -513,6 +515,25 @@ golang:
|
||||
# SYFT_GOLANG_LOCAL_MOD_CACHE_DIR env var
|
||||
local-mod-cache-dir: ""
|
||||
|
||||
# search for go package licences by retrieving the package from a network proxy
|
||||
# SYFT_GOLANG_SEARCH_REMOTE_LICENSES env var
|
||||
search-remote-licenses: false
|
||||
|
||||
# remote proxy to use when retrieving go packages from the network,
|
||||
# if unset this defaults to $GOPROXY followed by https://proxy.golang.org
|
||||
# SYFT_GOLANG_PROXY env var
|
||||
proxy: ""
|
||||
|
||||
# specifies packages which should not be fetched by proxy
|
||||
# if unset this defaults to $GONOPROXY
|
||||
# SYFT_GOLANG_NOPROXY env var
|
||||
no-proxy: ""
|
||||
|
||||
linux-kernel:
|
||||
# whether to catalog linux kernel modules found within lib/modules/** directories
|
||||
# SYFT_LINUX_KERNEL_CATALOG_MODULES env var
|
||||
catalog-modules: true
|
||||
|
||||
# cataloging file contents is exposed through the power-user subcommand
|
||||
file-contents:
|
||||
cataloger:
|
||||
|
||||
@@ -53,7 +53,10 @@ require (
|
||||
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-20230406143206-e95d60a265e3
|
||||
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da
|
||||
github.com/docker/docker v23.0.3+incompatible
|
||||
github.com/go-git/go-billy/v5 v5.4.1
|
||||
github.com/go-git/go-git/v5 v5.6.1
|
||||
github.com/google/go-containerregistry v0.14.0
|
||||
github.com/google/licensecheck v0.3.1
|
||||
github.com/invopop/jsonschema v0.7.0
|
||||
@@ -70,8 +73,11 @@ require (
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.2.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.0 // indirect
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect
|
||||
github.com/acomagu/bufpipe v1.0.4 // indirect
|
||||
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect
|
||||
github.com/becheran/wildmatch-go v1.0.0 // indirect
|
||||
github.com/cloudflare/circl v1.1.0 // indirect
|
||||
github.com/containerd/containerd v1.6.18 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
@@ -81,8 +87,10 @@ require (
|
||||
github.com/docker/go-connections 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/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.0 // indirect
|
||||
github.com/go-git/gcfg v1.5.0 // indirect
|
||||
github.com/go-restruct/restruct v1.2.0-alpha // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
@@ -91,9 +99,11 @@ require (
|
||||
github.com/hashicorp/hcl v1.0.0 // 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/imdario/mergo v0.3.13 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/klauspost/compress v1.16.0 // indirect
|
||||
github.com/klauspost/pgzip v1.2.5 // indirect
|
||||
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 // indirect
|
||||
@@ -108,11 +118,13 @@ require (
|
||||
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/pjbgf/sha1cd v0.3.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.8.0 // indirect
|
||||
github.com/shopspring/decimal v1.2.0 // indirect
|
||||
github.com/skeema/knownhosts v1.1.0 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/stretchr/objx v0.5.0 // indirect
|
||||
@@ -122,6 +134,7 @@ require (
|
||||
github.com/therootcompany/xz v1.0.1 // indirect
|
||||
github.com/ulikunitz/xz v0.5.10 // indirect
|
||||
github.com/vbatts/tar-split v0.11.2 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||
@@ -135,6 +148,7 @@ require (
|
||||
google.golang.org/grpc v1.52.0 // indirect
|
||||
google.golang.org/protobuf v1.29.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
lukechampine.com/uint128 v1.1.1 // indirect
|
||||
modernc.org/cc/v3 v3.36.0 // indirect
|
||||
modernc.org/ccgo/v3 v3.16.6 // indirect
|
||||
@@ -152,7 +166,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.3.0 // indirect
|
||||
golang.org/x/crypto v0.6.0 // indirect
|
||||
)
|
||||
|
||||
retract (
|
||||
|
||||
@@ -65,13 +65,18 @@ github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7Y
|
||||
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||
github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
|
||||
github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
|
||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||
github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg=
|
||||
github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g=
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
|
||||
github.com/acobaugh/osrelease v0.1.0 h1:Yb59HQDGGNhCj4suHaFQQfBps5wyoKLSSX/J/+UifRE=
|
||||
github.com/acobaugh/osrelease v0.1.0/go.mod h1:4bFEs0MtgHNHBrmHCt67gNisnabCRAlzdVasCEGHTWY=
|
||||
github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ=
|
||||
github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
|
||||
github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
|
||||
github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
@@ -96,12 +101,16 @@ github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo
|
||||
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/becheran/wildmatch-go v1.0.0 h1:mE3dGGkTmpKtT4Z+88t8RStG40yN9T+kFEGj2PZFSzA=
|
||||
github.com/becheran/wildmatch-go v1.0.0/go.mod h1:gbMvj0NtVdJ15Mg/mH9uxk2R1QCistMyU7d9KFzroX4=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
@@ -111,6 +120,7 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.0 h1:HTuxyug8GyFbRkrffIpzNCSK4luc0TY3wzXvzIZhEXc=
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||
github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M=
|
||||
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
@@ -122,6 +132,8 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
|
||||
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
|
||||
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/circl v1.1.0 h1:bZgT/A+cikZnKIwn7xL2OBj012Bmvho/o6RpRvv3GKY=
|
||||
github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
@@ -141,10 +153,13 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da h1:ZOjWpVsFZ06eIhnh4mkaceTiVoktdU67+M7KDHJ268M=
|
||||
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da/go.mod h1:B3tI9iGHi4imdLi4Asdha1Sc6feLMTfPLXh9IUYmysk=
|
||||
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4=
|
||||
github.com/docker/cli v23.0.1+incompatible h1:LRyWITpGzl2C9e9uGxzisptnxAn1zfZKXy13Ul2Q5oM=
|
||||
github.com/docker/cli v23.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
@@ -164,6 +179,8 @@ github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdf
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
@@ -190,6 +207,17 @@ github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbS
|
||||
github.com/gabriel-vasile/mimetype v1.4.0 h1:Cn9dkdYsMIu56tGho+fqzh7XmvY2YyGU0FnbhiOsEro=
|
||||
github.com/gabriel-vasile/mimetype v1.4.0/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
|
||||
github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
|
||||
github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
|
||||
github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
|
||||
github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
|
||||
github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4=
|
||||
github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.1 h1:y5z6dd3qi8Hl+stezc8p3JxDkoTRqMAlKnXHuzrfjTQ=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.1/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo=
|
||||
github.com/go-git/go-git/v5 v5.6.1 h1:q4ZRqQl4pR/ZJHc1L5CFjGA1a10u76aV1iC+nh+bHsk=
|
||||
github.com/go-git/go-git/v5 v5.6.1/go.mod h1:mvyoL6Unz0PiTQrGQfSfiLFhBH1c1e84ylC2MDs4ee8=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
@@ -341,13 +369,16 @@ github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
|
||||
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
|
||||
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/invopop/jsonschema v0.7.0 h1:2vgQcBz1n256N+FpX3Jq7Y17AjYt46Ig3zIWyy770So=
|
||||
github.com/invopop/jsonschema v0.7.0/go.mod h1:O9uiLokuu0+MGFlyiaqtWxwqJm41/+8Nj0lD7A36YH0=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
|
||||
github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg=
|
||||
github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
@@ -360,6 +391,8 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
|
||||
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
||||
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
@@ -377,16 +410,20 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 h1:bqDmpDG49ZRnB5PcgP0RXtQvnMSgIF14M7CBd2shtXs=
|
||||
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||
github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w=
|
||||
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
|
||||
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
@@ -435,6 +472,7 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR
|
||||
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/mmcloughlin/avo v0.5.0/go.mod h1:ChHFdoV7ql95Wi7vuq2YT1bwCJqiWdZrQ1im3VujLYM=
|
||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc=
|
||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@@ -444,6 +482,7 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ=
|
||||
github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
@@ -462,6 +501,8 @@ github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha
|
||||
github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0=
|
||||
github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
|
||||
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@@ -515,6 +556,8 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/skeema/knownhosts v1.1.0 h1:Wvr9V0MxhjRbl3f9nMnKnFfiWTJmtECJ9Njkea3ysW0=
|
||||
github.com/skeema/knownhosts v1.1.0/go.mod h1:sKFq3RD6/TKZkSWn8boUbDC7Qkgcv+8XXijpFO6roag=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb/go.mod h1:uKWaldnbMnjsSAXRurWqqrdyZen1R7kxl8TkmWk2OyM=
|
||||
github.com/spdx/tools-golang v0.5.0 h1:/fqihV2Jna7fmow65dHpgKNsilgLK7ICpd2tkCnPEyY=
|
||||
@@ -582,6 +625,8 @@ github.com/wagoodman/go-progress v0.0.0-20230301185719-21920a456ad5 h1:lwgTsTy18
|
||||
github.com/wagoodman/go-progress v0.0.0-20230301185719-21920a456ad5/go.mod h1:jLXFoL31zFaHKAAyZUh+sxiTDFe1L1ZHrcK2T1itVKA=
|
||||
github.com/wagoodman/jotframe v0.0.0-20211129225309-56b0d0a4aebb h1:Yz6VVOcLuWLAHYlJzTw7JKnWxdV/WXpug2X0quEzRnY=
|
||||
github.com/wagoodman/jotframe v0.0.0-20211129225309-56b0d0a4aebb/go.mod h1:nDi3BAC5nEbVbg+WSJDHLbjHv0ZToq8nMPA97XMxF3E=
|
||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
@@ -615,6 +660,7 @@ go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0=
|
||||
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
||||
golang.org/x/arch v0.1.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
@@ -629,9 +675,14 @@ golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWP
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
|
||||
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -672,6 +723,7 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
|
||||
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
|
||||
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -719,7 +771,11 @@ golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@@ -796,6 +852,7 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -824,16 +881,24 @@ golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -846,6 +911,7 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -911,6 +977,7 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
|
||||
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
|
||||
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -1074,11 +1141,15 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
@@ -1090,6 +1161,7 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
||||
@@ -1137,6 +1209,7 @@ modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
modernc.org/z v1.5.1 h1:RTNHdsrOpeoSeOF4FbzTo8gBYByaJ5xT7NgZ9ZqRiJM=
|
||||
modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger"
|
||||
golangCataloger "github.com/anchore/syft/syft/pkg/cataloger/golang"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/kernel"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -50,6 +51,7 @@ type Application struct {
|
||||
Catalogers []string `yaml:"catalogers" json:"catalogers" mapstructure:"catalogers"`
|
||||
Package pkg `yaml:"package" json:"package" mapstructure:"package"`
|
||||
Golang golang `yaml:"golang" json:"golang" mapstructure:"golang"`
|
||||
LinuxKernel linuxKernel `yaml:"linux-kernel" json:"linux-kernel" mapstructure:"linux-kernel"`
|
||||
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"`
|
||||
@@ -72,9 +74,14 @@ func (cfg Application) ToCatalogerConfig() cataloger.Config {
|
||||
},
|
||||
Catalogers: cfg.Catalogers,
|
||||
Parallelism: cfg.Parallelism,
|
||||
Golang: golangCataloger.GoCatalogerOpts{
|
||||
SearchLocalModCacheLicenses: cfg.Golang.SearchLocalModCacheLicenses,
|
||||
LocalModCacheDir: cfg.Golang.LocalModCacheDir,
|
||||
Golang: golangCataloger.NewGoCatalogerOpts().
|
||||
WithSearchLocalModCacheLicenses(cfg.Golang.SearchLocalModCacheLicenses).
|
||||
WithLocalModCacheDir(cfg.Golang.LocalModCacheDir).
|
||||
WithSearchRemoteLicenses(cfg.Golang.SearchRemoteLicenses).
|
||||
WithProxy(cfg.Golang.Proxy).
|
||||
WithNoProxy(cfg.Golang.NoProxy),
|
||||
LinuxKernel: kernel.LinuxCatalogerConfig{
|
||||
CatalogModules: cfg.LinuxKernel.CatalogModules,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,15 @@ import "github.com/spf13/viper"
|
||||
type golang struct {
|
||||
SearchLocalModCacheLicenses bool `json:"search-local-mod-cache-licenses" yaml:"search-local-mod-cache-licenses" mapstructure:"search-local-mod-cache-licenses"`
|
||||
LocalModCacheDir string `json:"local-mod-cache-dir" yaml:"local-mod-cache-dir" mapstructure:"local-mod-cache-dir"`
|
||||
SearchRemoteLicenses bool `json:"search-remote-licenses" yaml:"search-remote-licenses" mapstructure:"search-remote-licenses"`
|
||||
Proxy string `json:"proxy" yaml:"proxy" mapstructure:"proxy"`
|
||||
NoProxy string `json:"no-proxy" yaml:"no-proxy" mapstructure:"no-proxy"`
|
||||
}
|
||||
|
||||
func (cfg golang) loadDefaultValues(v *viper.Viper) {
|
||||
v.SetDefault("golang.search-local-mod-cache-licenses", false)
|
||||
v.SetDefault("golang.local-mod-cache-dir", "")
|
||||
v.SetDefault("golang.search-remote-licenses", false)
|
||||
v.SetDefault("golang.proxy", "")
|
||||
v.SetDefault("golang.no-proxy", "")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package config
|
||||
|
||||
import "github.com/spf13/viper"
|
||||
|
||||
type linuxKernel struct {
|
||||
CatalogModules bool `json:"catalog-modules" yaml:"catalog-modules" mapstructure:"catalog-modules"`
|
||||
}
|
||||
|
||||
func (cfg linuxKernel) loadDefaultValues(v *viper.Viper) {
|
||||
v.SetDefault("linux-kernel.catalog-modules", true)
|
||||
}
|
||||
@@ -6,5 +6,5 @@ const (
|
||||
|
||||
// JSONSchemaVersion is the current schema version output by the JSON encoder
|
||||
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
|
||||
JSONSchemaVersion = "7.1.1"
|
||||
JSONSchemaVersion = "7.1.4"
|
||||
)
|
||||
|
||||
@@ -45,6 +45,8 @@ type artifactMetadataContainer struct {
|
||||
Hackage pkg.HackageMetadata
|
||||
Java pkg.JavaMetadata
|
||||
KbPackage pkg.KbPackageMetadata
|
||||
LinuxKernel pkg.LinuxKernelMetadata
|
||||
LinuxKernelModule pkg.LinuxKernelModuleMetadata
|
||||
Nix pkg.NixStoreMetadata
|
||||
NpmPackage pkg.NpmPackageJSONMetadata
|
||||
NpmPackageLock pkg.NpmPackageLockJSONMetadata
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -7,6 +7,12 @@ const (
|
||||
// has been completed.
|
||||
OwnershipByFileOverlapRelationship RelationshipType = "ownership-by-file-overlap"
|
||||
|
||||
// EvidentByRelationship is a package-to-file relationship indicating the that existence of this package is evident
|
||||
// by the contents of a file. This does not necessarily mean that the package is contained within that file
|
||||
// or that it is described by it (either or both may be true). This does NOT map to an existing specific SPDX
|
||||
// relationship. Instead, this should be mapped to OTHER and the comment field be updated to show EVIDENT_BY.
|
||||
EvidentByRelationship RelationshipType = "evident-by"
|
||||
|
||||
// ContainsRelationship (supports any-to-any linkages) is a proxy for the SPDX 2.2 CONTAINS relationship.
|
||||
ContainsRelationship RelationshipType = "contains"
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
"github.com/wagoodman/go-partybus"
|
||||
"github.com/wagoodman/go-progress"
|
||||
|
||||
"github.com/anchore/syft/internal/bus"
|
||||
)
|
||||
|
||||
type CatalogerTask struct {
|
||||
prog *progress.Manual
|
||||
// Title
|
||||
Title string
|
||||
// TitleOnCompletion a string to use as title when completed
|
||||
TitleOnCompletion string
|
||||
// SubStatus indicates this progress should be rendered as a sub-item
|
||||
SubStatus bool
|
||||
// RemoveOnCompletion indicates this progress line will be removed when completed
|
||||
RemoveOnCompletion bool
|
||||
// value is the value to display -- not public as SetValue needs to be called to initialize this progress
|
||||
value string
|
||||
}
|
||||
|
||||
func (e *CatalogerTask) init() {
|
||||
e.prog = progress.NewManual(-1)
|
||||
|
||||
bus.Publish(partybus.Event{
|
||||
Type: CatalogerTaskStarted,
|
||||
Source: e,
|
||||
})
|
||||
}
|
||||
|
||||
func (e *CatalogerTask) SetCompleted() {
|
||||
if e.prog != nil {
|
||||
e.prog.SetCompleted()
|
||||
}
|
||||
}
|
||||
|
||||
func (e *CatalogerTask) SetValue(value string) {
|
||||
if e.prog == nil {
|
||||
e.init()
|
||||
}
|
||||
e.value = value
|
||||
}
|
||||
|
||||
func (e *CatalogerTask) GetValue() string {
|
||||
return e.value
|
||||
}
|
||||
|
||||
func (e *CatalogerTask) GetMonitor() *progress.Manual {
|
||||
return e.prog
|
||||
}
|
||||
@@ -34,4 +34,7 @@ const (
|
||||
|
||||
// AttestationStarted is a partybus event that occurs when starting an SBOM attestation process
|
||||
AttestationStarted partybus.EventType = "syft-attestation-started-event"
|
||||
|
||||
// CatalogerTaskStarted is a partybus event that occurs when starting a task within a cataloger
|
||||
CatalogerTaskStarted partybus.EventType = "syft-cataloger-task-started"
|
||||
)
|
||||
|
||||
@@ -111,6 +111,19 @@ func ParseFileIndexingStarted(e partybus.Event) (string, progress.StagedProgress
|
||||
return path, prog, nil
|
||||
}
|
||||
|
||||
func ParseCatalogerTaskStarted(e partybus.Event) (*event.CatalogerTask, error) {
|
||||
if err := checkEventType(e.Type, event.CatalogerTaskStarted); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
source, ok := e.Source.(*event.CatalogerTask)
|
||||
if !ok {
|
||||
return nil, newPayloadErr(e.Type, "Source", e.Source)
|
||||
}
|
||||
|
||||
return source, nil
|
||||
}
|
||||
|
||||
func ParseExit(e partybus.Event) (func() error, error) {
|
||||
if err := checkEventType(e.Type, event.Exit); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -29,7 +29,7 @@ func Test_encodeComponentProperties(t *testing.T) {
|
||||
input: pkg.Package{
|
||||
FoundBy: "cataloger",
|
||||
Locations: source.NewLocationSet(
|
||||
source.Location{Coordinates: source.Coordinates{RealPath: "test"}},
|
||||
source.NewLocationFromCoordinates(source.Coordinates{RealPath: "test"}),
|
||||
),
|
||||
Metadata: pkg.ApkMetadata{
|
||||
Package: "libc-utils",
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
//nolint:funlen
|
||||
func SourceInfo(p pkg.Package) string {
|
||||
answer := ""
|
||||
switch p.Type {
|
||||
@@ -45,6 +46,10 @@ func SourceInfo(p pkg.Package) string {
|
||||
answer = "acquired package info from cabal or stack manifest files"
|
||||
case pkg.HexPkg:
|
||||
answer = "acquired package info from rebar3 or mix manifest file"
|
||||
case pkg.LinuxKernelPkg:
|
||||
answer = "acquired package info from linux kernel archive"
|
||||
case pkg.LinuxKernelModulePkg:
|
||||
answer = "acquired package info from linux kernel module files"
|
||||
case pkg.NixPkg:
|
||||
answer = "acquired package info from nix store path"
|
||||
default:
|
||||
|
||||
@@ -199,6 +199,22 @@ func Test_SourceInfo(t *testing.T) {
|
||||
"from rebar3 or mix manifest file",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.LinuxKernelPkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from linux kernel archive",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.LinuxKernelModulePkg,
|
||||
},
|
||||
expected: []string{
|
||||
"from linux kernel module files",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: pkg.Package{
|
||||
Type: pkg.NixPkg,
|
||||
|
||||
@@ -408,6 +408,8 @@ func lookupRelationship(ty artifact.RelationshipType) (bool, RelationshipType, s
|
||||
return true, DependencyOfRelationship, ""
|
||||
case artifact.OwnershipByFileOverlapRelationship:
|
||||
return true, OtherRelationship, fmt.Sprintf("%s: indicates that the parent package claims ownership of a child package since the parent metadata indicates overlap with a location that a cataloger found the child package by", ty)
|
||||
case artifact.EvidentByRelationship:
|
||||
return true, OtherRelationship, fmt.Sprintf("%s: indicates the package's existence is evident by the given file", ty)
|
||||
}
|
||||
return false, "", ""
|
||||
}
|
||||
|
||||
@@ -209,6 +209,12 @@ func Test_lookupRelationship(t *testing.T) {
|
||||
ty: OtherRelationship,
|
||||
comment: "ownership-by-file-overlap: indicates that the parent package claims ownership of a child package since the parent metadata indicates overlap with a location that a cataloger found the child package by",
|
||||
},
|
||||
{
|
||||
input: artifact.EvidentByRelationship,
|
||||
exists: true,
|
||||
ty: OtherRelationship,
|
||||
comment: "evident-by: indicates the package's existence is evident by the given file",
|
||||
},
|
||||
{
|
||||
input: "made-up",
|
||||
exists: false,
|
||||
|
||||
@@ -176,9 +176,16 @@ func toSyftRelationships(spdxIDMap map[string]interface{}, doc *spdx.Document) [
|
||||
var to artifact.Identifiable
|
||||
var typ artifact.RelationshipType
|
||||
if toLocationOk {
|
||||
if r.Relationship == string(ContainsRelationship) {
|
||||
switch RelationshipType(r.Relationship) {
|
||||
case ContainsRelationship:
|
||||
typ = artifact.ContainsRelationship
|
||||
to = toLocation
|
||||
case OtherRelationship:
|
||||
// Encoding uses a specifically formatted comment...
|
||||
if strings.Index(r.RelationshipComment, string(artifact.EvidentByRelationship)) == 0 {
|
||||
typ = artifact.EvidentByRelationship
|
||||
to = toLocation
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch RelationshipType(r.Relationship) {
|
||||
@@ -188,7 +195,7 @@ func toSyftRelationships(spdxIDMap map[string]interface{}, doc *spdx.Document) [
|
||||
case OtherRelationship:
|
||||
// Encoding uses a specifically formatted comment...
|
||||
if strings.Index(r.RelationshipComment, string(artifact.OwnershipByFileOverlapRelationship)) == 0 {
|
||||
typ = artifact.DependencyOfRelationship
|
||||
typ = artifact.OwnershipByFileOverlapRelationship
|
||||
to = toPackage
|
||||
}
|
||||
}
|
||||
@@ -220,10 +227,8 @@ func toSyftCoordinates(f *spdx.File) source.Coordinates {
|
||||
}
|
||||
|
||||
func toSyftLocation(f *spdx.File) *source.Location {
|
||||
return &source.Location{
|
||||
Coordinates: toSyftCoordinates(f),
|
||||
VirtualPath: f.FileName,
|
||||
}
|
||||
l := source.NewVirtualLocationFromCoordinates(toSyftCoordinates(f), f.FileName)
|
||||
return &l
|
||||
}
|
||||
|
||||
func requireAndTrimPrefix(val interface{}, prefix string) string {
|
||||
|
||||
@@ -4,9 +4,11 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/spdx/tools-golang/spdx"
|
||||
"github.com/spdx/tools-golang/spdx/v2/common"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
@@ -307,3 +309,113 @@ func TestH1Digest(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_toSyftRelationships(t *testing.T) {
|
||||
type args struct {
|
||||
spdxIDMap map[string]interface{}
|
||||
doc *spdx.Document
|
||||
}
|
||||
|
||||
pkg1 := pkg.Package{
|
||||
Name: "github.com/googleapis/gnostic",
|
||||
Version: "v0.5.5",
|
||||
}
|
||||
pkg1.SetID()
|
||||
|
||||
pkg2 := pkg.Package{
|
||||
Name: "rfc3339",
|
||||
Version: "1.2",
|
||||
Type: pkg.RpmPkg,
|
||||
}
|
||||
pkg2.SetID()
|
||||
|
||||
pkg3 := pkg.Package{
|
||||
Name: "rfc3339",
|
||||
Version: "1.2",
|
||||
Type: pkg.PythonPkg,
|
||||
}
|
||||
pkg3.SetID()
|
||||
|
||||
loc1 := source.NewLocationFromCoordinates(source.Coordinates{
|
||||
RealPath: "/somewhere/real",
|
||||
FileSystemID: "abc",
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []artifact.Relationship
|
||||
}{
|
||||
{
|
||||
name: "evident-by relationship",
|
||||
args: args{
|
||||
spdxIDMap: map[string]interface{}{
|
||||
string(toSPDXID(pkg1)): &pkg1,
|
||||
string(toSPDXID(loc1)): &loc1,
|
||||
},
|
||||
doc: &spdx.Document{
|
||||
Relationships: []*spdx.Relationship{
|
||||
{
|
||||
RefA: common.DocElementID{
|
||||
ElementRefID: toSPDXID(pkg1),
|
||||
},
|
||||
RefB: common.DocElementID{
|
||||
ElementRefID: toSPDXID(loc1),
|
||||
},
|
||||
Relationship: spdx.RelationshipOther,
|
||||
RelationshipComment: "evident-by: indicates the package's existence is evident by the given file",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []artifact.Relationship{
|
||||
{
|
||||
From: pkg1,
|
||||
To: loc1,
|
||||
Type: artifact.EvidentByRelationship,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ownership-by-file-overlap relationship",
|
||||
args: args{
|
||||
spdxIDMap: map[string]interface{}{
|
||||
string(toSPDXID(pkg2)): &pkg2,
|
||||
string(toSPDXID(pkg3)): &pkg3,
|
||||
},
|
||||
doc: &spdx.Document{
|
||||
Relationships: []*spdx.Relationship{
|
||||
{
|
||||
RefA: common.DocElementID{
|
||||
ElementRefID: toSPDXID(pkg2),
|
||||
},
|
||||
RefB: common.DocElementID{
|
||||
ElementRefID: toSPDXID(pkg3),
|
||||
},
|
||||
Relationship: spdx.RelationshipOther,
|
||||
RelationshipComment: "ownership-by-file-overlap: indicates that the parent package claims ownership of a child package since the parent metadata indicates overlap with a location that a cataloger found the child package by",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: []artifact.Relationship{
|
||||
{
|
||||
From: pkg2,
|
||||
To: pkg3,
|
||||
Type: artifact.OwnershipByFileOverlapRelationship,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
actual := toSyftRelationships(tt.args.spdxIDMap, tt.args.doc)
|
||||
require.Len(t, actual, len(tt.want))
|
||||
for i := range actual {
|
||||
require.Equal(t, tt.want[i].From.ID(), actual[i].From.ID())
|
||||
require.Equal(t, tt.want[i].To.ID(), actual[i].To.ID())
|
||||
require.Equal(t, tt.want[i].Type, actual[i].Type)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,36 +36,30 @@ func Test_toGithubModel(t *testing.T) {
|
||||
Name: "pkg-1",
|
||||
Version: "1.0.1",
|
||||
Locations: source.NewLocationSet(
|
||||
source.Location{
|
||||
Coordinates: source.Coordinates{
|
||||
RealPath: "/usr/lib",
|
||||
FileSystemID: "fsid-1",
|
||||
},
|
||||
},
|
||||
source.NewLocationFromCoordinates(source.Coordinates{
|
||||
RealPath: "/usr/lib",
|
||||
FileSystemID: "fsid-1",
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
Name: "pkg-2",
|
||||
Version: "2.0.2",
|
||||
Locations: source.NewLocationSet(
|
||||
source.Location{
|
||||
Coordinates: source.Coordinates{
|
||||
RealPath: "/usr/lib",
|
||||
FileSystemID: "fsid-1",
|
||||
},
|
||||
},
|
||||
source.NewLocationFromCoordinates(source.Coordinates{
|
||||
RealPath: "/usr/lib",
|
||||
FileSystemID: "fsid-1",
|
||||
}),
|
||||
),
|
||||
},
|
||||
{
|
||||
Name: "pkg-3",
|
||||
Version: "3.0.3",
|
||||
Locations: source.NewLocationSet(
|
||||
source.Location{
|
||||
Coordinates: source.Coordinates{
|
||||
RealPath: "/etc",
|
||||
FileSystemID: "fsid-1",
|
||||
},
|
||||
},
|
||||
source.NewLocationFromCoordinates(source.Coordinates{
|
||||
RealPath: "/etc",
|
||||
FileSystemID: "fsid-1",
|
||||
}),
|
||||
),
|
||||
},
|
||||
} {
|
||||
|
||||
@@ -53,11 +53,9 @@ func TestEncodeFullJSONDocument(t *testing.T) {
|
||||
Name: "package-1",
|
||||
Version: "1.0.1",
|
||||
Locations: source.NewLocationSet(
|
||||
source.Location{
|
||||
Coordinates: source.Coordinates{
|
||||
RealPath: "/a/place/a",
|
||||
},
|
||||
},
|
||||
source.NewLocationFromCoordinates(source.Coordinates{
|
||||
RealPath: "/a/place/a",
|
||||
}),
|
||||
),
|
||||
Type: pkg.PythonPkg,
|
||||
FoundBy: "the-cataloger-1",
|
||||
@@ -79,11 +77,9 @@ func TestEncodeFullJSONDocument(t *testing.T) {
|
||||
Name: "package-2",
|
||||
Version: "2.0.1",
|
||||
Locations: source.NewLocationSet(
|
||||
source.Location{
|
||||
Coordinates: source.Coordinates{
|
||||
RealPath: "/b/place/b",
|
||||
},
|
||||
},
|
||||
source.NewLocationFromCoordinates(source.Coordinates{
|
||||
RealPath: "/b/place/b",
|
||||
}),
|
||||
),
|
||||
Type: pkg.DebPkg,
|
||||
FoundBy: "the-cataloger-2",
|
||||
|
||||
@@ -20,4 +20,5 @@ type FileMetadataEntry struct {
|
||||
UserID int `json:"userID"`
|
||||
GroupID int `json:"groupID"`
|
||||
MIMEType string `json:"mimeType"`
|
||||
Size int64 `json:"size"`
|
||||
}
|
||||
|
||||
@@ -21,16 +21,16 @@ type Package struct {
|
||||
|
||||
// PackageBasicData contains non-ambiguous values (type-wise) from pkg.Package.
|
||||
type PackageBasicData struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Type pkg.Type `json:"type"`
|
||||
FoundBy string `json:"foundBy"`
|
||||
Locations []source.Coordinates `json:"locations"`
|
||||
Licenses []string `json:"licenses"`
|
||||
Language pkg.Language `json:"language"`
|
||||
CPEs []string `json:"cpes"`
|
||||
PURL string `json:"purl"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Type pkg.Type `json:"type"`
|
||||
FoundBy string `json:"foundBy"`
|
||||
Locations []source.Location `json:"locations"`
|
||||
Licenses []string `json:"licenses"`
|
||||
Language pkg.Language `json:"language"`
|
||||
CPEs []string `json:"cpes"`
|
||||
PURL string `json:"purl"`
|
||||
}
|
||||
|
||||
// PackageCustomData contains ambiguous values (type-wise) from pkg.Package.
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"version": "7.1.1",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-7.1.1.json"
|
||||
"version": "7.1.4",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-7.1.4.json"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,8 @@
|
||||
"type": "Directory",
|
||||
"userID": 0,
|
||||
"groupID": 0,
|
||||
"mimeType": ""
|
||||
"mimeType": "",
|
||||
"size": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -94,7 +95,8 @@
|
||||
"type": "RegularFile",
|
||||
"userID": 0,
|
||||
"groupID": 0,
|
||||
"mimeType": ""
|
||||
"mimeType": "",
|
||||
"size": 0
|
||||
},
|
||||
"contents": "the-contents",
|
||||
"digests": [
|
||||
@@ -115,7 +117,8 @@
|
||||
"linkDestination": "/c",
|
||||
"userID": 0,
|
||||
"groupID": 0,
|
||||
"mimeType": ""
|
||||
"mimeType": "",
|
||||
"size": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -128,7 +131,8 @@
|
||||
"type": "RegularFile",
|
||||
"userID": 1,
|
||||
"groupID": 2,
|
||||
"mimeType": ""
|
||||
"mimeType": "",
|
||||
"size": 0
|
||||
},
|
||||
"digests": [
|
||||
{
|
||||
@@ -185,7 +189,7 @@
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"version": "7.1.1",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-7.1.1.json"
|
||||
"version": "7.1.4",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-7.1.4.json"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@
|
||||
}
|
||||
},
|
||||
"schema": {
|
||||
"version": "7.1.1",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-7.1.1.json"
|
||||
"version": "7.1.4",
|
||||
"url": "https://raw.githubusercontent.com/anchore/syft/main/schema/json/schema-7.1.4.json"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,6 +144,7 @@ func toFileMetadataEntry(coordinates source.Coordinates, metadata *source.FileMe
|
||||
UserID: metadata.UserID,
|
||||
GroupID: metadata.GroupID,
|
||||
MIMEType: metadata.MIMEType,
|
||||
Size: metadata.Size,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,12 +196,6 @@ func toPackageModel(p pkg.Package) model.Package {
|
||||
licenses = p.Licenses
|
||||
}
|
||||
|
||||
locations := p.Locations.ToSlice()
|
||||
var coordinates = make([]source.Coordinates, len(locations))
|
||||
for i, l := range locations {
|
||||
coordinates[i] = l.Coordinates
|
||||
}
|
||||
|
||||
return model.Package{
|
||||
PackageBasicData: model.PackageBasicData{
|
||||
ID: string(p.ID()),
|
||||
@@ -208,7 +203,7 @@ func toPackageModel(p pkg.Package) model.Package {
|
||||
Version: p.Version,
|
||||
Type: p.Type,
|
||||
FoundBy: p.FoundBy,
|
||||
Locations: coordinates,
|
||||
Locations: p.Locations.ToSlice(),
|
||||
Licenses: licenses,
|
||||
Language: p.Language,
|
||||
CPEs: cpes,
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
package syftjson
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
stereoscopeFile "github.com/anchore/stereoscope/pkg/file"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/cpe"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/formats/syftjson/model"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@@ -20,9 +24,13 @@ func toSyftModel(doc model.Document) (*sbom.SBOM, error) {
|
||||
|
||||
catalog := toSyftCatalog(doc.Artifacts, idAliases)
|
||||
|
||||
fileArtifacts := toSyftFiles(doc.Files)
|
||||
|
||||
return &sbom.SBOM{
|
||||
Artifacts: sbom.Artifacts{
|
||||
PackageCatalog: catalog,
|
||||
FileMetadata: fileArtifacts.FileMetadata,
|
||||
FileDigests: fileArtifacts.FileDigests,
|
||||
LinuxDistribution: toSyftLinuxRelease(doc.Distro),
|
||||
},
|
||||
Source: *toSyftSourceData(doc.Source),
|
||||
@@ -31,6 +39,72 @@ func toSyftModel(doc model.Document) (*sbom.SBOM, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func toSyftFiles(files []model.File) sbom.Artifacts {
|
||||
ret := sbom.Artifacts{
|
||||
FileMetadata: make(map[source.Coordinates]source.FileMetadata),
|
||||
FileDigests: make(map[source.Coordinates][]file.Digest),
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
coord := f.Location
|
||||
if f.Metadata != nil {
|
||||
mode, err := strconv.ParseInt(strconv.Itoa(f.Metadata.Mode), 8, 64)
|
||||
if err != nil {
|
||||
log.Warnf("invalid mode found in file catalog @ location=%+v mode=%q: %+v", coord, f.Metadata.Mode, err)
|
||||
mode = 0
|
||||
}
|
||||
|
||||
fm := os.FileMode(mode)
|
||||
|
||||
ret.FileMetadata[coord] = source.FileMetadata{
|
||||
Path: coord.RealPath,
|
||||
LinkDestination: f.Metadata.LinkDestination,
|
||||
Size: f.Metadata.Size,
|
||||
UserID: f.Metadata.UserID,
|
||||
GroupID: f.Metadata.GroupID,
|
||||
Type: toSyftFileType(f.Metadata.Type),
|
||||
IsDir: fm.IsDir(),
|
||||
Mode: fm,
|
||||
MIMEType: f.Metadata.MIMEType,
|
||||
}
|
||||
}
|
||||
|
||||
for _, d := range f.Digests {
|
||||
ret.FileDigests[coord] = append(ret.FileDigests[coord], file.Digest{
|
||||
Algorithm: d.Algorithm,
|
||||
Value: d.Value,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func toSyftFileType(ty string) stereoscopeFile.Type {
|
||||
switch ty {
|
||||
case "SymbolicLink":
|
||||
return stereoscopeFile.TypeSymLink
|
||||
case "HardLink":
|
||||
return stereoscopeFile.TypeHardLink
|
||||
case "Directory":
|
||||
return stereoscopeFile.TypeDirectory
|
||||
case "Socket":
|
||||
return stereoscopeFile.TypeSocket
|
||||
case "BlockDevice":
|
||||
return stereoscopeFile.TypeBlockDevice
|
||||
case "CharacterDevice":
|
||||
return stereoscopeFile.TypeCharacterDevice
|
||||
case "FIFONode":
|
||||
return stereoscopeFile.TypeFIFO
|
||||
case "RegularFile":
|
||||
return stereoscopeFile.TypeRegular
|
||||
case "IrregularFile":
|
||||
return stereoscopeFile.TypeIrregular
|
||||
default:
|
||||
return stereoscopeFile.TypeIrregular
|
||||
}
|
||||
}
|
||||
|
||||
func toSyftLinuxRelease(d model.LinuxRelease) *linux.Release {
|
||||
if cmp.Equal(d, model.LinuxRelease{}) {
|
||||
return nil
|
||||
@@ -117,7 +191,7 @@ func toSyftRelationship(idMap map[string]interface{}, relationship model.Relatio
|
||||
typ := artifact.RelationshipType(relationship.Type)
|
||||
|
||||
switch typ {
|
||||
case artifact.OwnershipByFileOverlapRelationship, artifact.ContainsRelationship, artifact.DependencyOfRelationship:
|
||||
case artifact.OwnershipByFileOverlapRelationship, artifact.ContainsRelationship, artifact.DependencyOfRelationship, artifact.EvidentByRelationship:
|
||||
default:
|
||||
if !strings.Contains(string(typ), "dependency-of") {
|
||||
log.Warnf("unknown relationship type: %s", typ)
|
||||
@@ -202,16 +276,11 @@ func toSyftPackage(p model.Package, idAliases map[string]string) pkg.Package {
|
||||
cpes = append(cpes, value)
|
||||
}
|
||||
|
||||
var locations = make([]source.Location, len(p.Locations))
|
||||
for i, c := range p.Locations {
|
||||
locations[i] = source.NewLocationFromCoordinates(c)
|
||||
}
|
||||
|
||||
out := pkg.Package{
|
||||
Name: p.Name,
|
||||
Version: p.Version,
|
||||
FoundBy: p.FoundBy,
|
||||
Locations: source.NewLocationSet(locations...),
|
||||
Locations: source.NewLocationSet(p.Locations...),
|
||||
Licenses: p.Licenses,
|
||||
Language: p.Language,
|
||||
Type: p.Type,
|
||||
|
||||
@@ -6,8 +6,11 @@ import (
|
||||
"github.com/scylladb/go-set/strset"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
stereoFile "github.com/anchore/stereoscope/pkg/file"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/formats/syftjson/model"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
@@ -124,3 +127,104 @@ func Test_idsHaveChanged(t *testing.T) {
|
||||
assert.NotNil(t, to)
|
||||
assert.Equal(t, "pkg-2", to.Name)
|
||||
}
|
||||
|
||||
func Test_toSyftFiles(t *testing.T) {
|
||||
coord := source.Coordinates{
|
||||
RealPath: "/somerwhere/place",
|
||||
FileSystemID: "abc",
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
files []model.File
|
||||
want sbom.Artifacts
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
files: []model.File{},
|
||||
want: sbom.Artifacts{
|
||||
FileMetadata: map[source.Coordinates]source.FileMetadata{},
|
||||
FileDigests: map[source.Coordinates][]file.Digest{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no metadata",
|
||||
files: []model.File{
|
||||
{
|
||||
ID: string(coord.ID()),
|
||||
Location: coord,
|
||||
Metadata: nil,
|
||||
Digests: []file.Digest{
|
||||
{
|
||||
Algorithm: "sha256",
|
||||
Value: "123",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: sbom.Artifacts{
|
||||
FileMetadata: map[source.Coordinates]source.FileMetadata{},
|
||||
FileDigests: map[source.Coordinates][]file.Digest{
|
||||
coord: {
|
||||
{
|
||||
Algorithm: "sha256",
|
||||
Value: "123",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "single file",
|
||||
files: []model.File{
|
||||
{
|
||||
ID: string(coord.ID()),
|
||||
Location: coord,
|
||||
Metadata: &model.FileMetadataEntry{
|
||||
Mode: 777,
|
||||
Type: "RegularFile",
|
||||
LinkDestination: "",
|
||||
UserID: 42,
|
||||
GroupID: 32,
|
||||
MIMEType: "text/plain",
|
||||
Size: 92,
|
||||
},
|
||||
Digests: []file.Digest{
|
||||
{
|
||||
Algorithm: "sha256",
|
||||
Value: "123",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: sbom.Artifacts{
|
||||
FileMetadata: map[source.Coordinates]source.FileMetadata{
|
||||
coord: {
|
||||
Path: coord.RealPath,
|
||||
LinkDestination: "",
|
||||
Size: 92,
|
||||
UserID: 42,
|
||||
GroupID: 32,
|
||||
Type: stereoFile.TypeRegular,
|
||||
IsDir: false,
|
||||
Mode: 511, // 777 octal = 511 decimal
|
||||
MIMEType: "text/plain",
|
||||
},
|
||||
},
|
||||
FileDigests: map[source.Coordinates][]file.Digest{
|
||||
coord: {
|
||||
{
|
||||
Algorithm: "sha256",
|
||||
Value: "123",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.want, toSyftFiles(tt.files))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+16
-16
@@ -327,45 +327,45 @@ func TestCatalog_MergeRecords(t *testing.T) {
|
||||
{
|
||||
CPEs: []cpe.CPE{cpe.Must("cpe:2.3:a:package:1:1:*:*:*:*:*:*:*")},
|
||||
Locations: source.NewLocationSet(
|
||||
source.Location{
|
||||
Coordinates: source.Coordinates{
|
||||
source.NewVirtualLocationFromCoordinates(
|
||||
source.Coordinates{
|
||||
RealPath: "/b/path",
|
||||
FileSystemID: "a",
|
||||
},
|
||||
VirtualPath: "/another/path",
|
||||
},
|
||||
"/another/path",
|
||||
),
|
||||
),
|
||||
Type: RpmPkg,
|
||||
},
|
||||
{
|
||||
CPEs: []cpe.CPE{cpe.Must("cpe:2.3:b:package:1:1:*:*:*:*:*:*:*")},
|
||||
Locations: source.NewLocationSet(
|
||||
source.Location{
|
||||
Coordinates: source.Coordinates{
|
||||
source.NewVirtualLocationFromCoordinates(
|
||||
source.Coordinates{
|
||||
RealPath: "/b/path",
|
||||
FileSystemID: "b",
|
||||
},
|
||||
VirtualPath: "/another/path",
|
||||
},
|
||||
"/another/path",
|
||||
),
|
||||
),
|
||||
Type: RpmPkg,
|
||||
},
|
||||
},
|
||||
expectedLocations: []source.Location{
|
||||
{
|
||||
Coordinates: source.Coordinates{
|
||||
source.NewVirtualLocationFromCoordinates(
|
||||
source.Coordinates{
|
||||
RealPath: "/b/path",
|
||||
FileSystemID: "a",
|
||||
},
|
||||
VirtualPath: "/another/path",
|
||||
},
|
||||
{
|
||||
Coordinates: source.Coordinates{
|
||||
"/another/path",
|
||||
),
|
||||
source.NewVirtualLocationFromCoordinates(
|
||||
source.Coordinates{
|
||||
RealPath: "/b/path",
|
||||
FileSystemID: "b",
|
||||
},
|
||||
VirtualPath: "/another/path",
|
||||
},
|
||||
"/another/path",
|
||||
),
|
||||
},
|
||||
expectedCPECount: 2,
|
||||
},
|
||||
|
||||
@@ -70,7 +70,11 @@ func parseAlpmDB(resolver source.FileResolver, env *generic.Environment, reader
|
||||
}
|
||||
|
||||
return []pkg.Package{
|
||||
newPackage(*metadata, env.LinuxRelease, reader.Location),
|
||||
newPackage(
|
||||
*metadata,
|
||||
env.LinuxRelease,
|
||||
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
}, nil, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ func parseApkDB(resolver source.FileResolver, env *generic.Environment, reader s
|
||||
|
||||
pkgs := make([]pkg.Package, 0, len(apks))
|
||||
for _, apk := range apks {
|
||||
pkgs = append(pkgs, newPackage(apk, r, reader.Location))
|
||||
pkgs = append(pkgs, newPackage(apk, r, reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)))
|
||||
}
|
||||
|
||||
return pkgs, discoverPackageDependencies(pkgs), nil
|
||||
|
||||
@@ -714,6 +714,18 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
|
||||
Metadata: metadata("ruby-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "positive-consul-1.15.2",
|
||||
fixtureDir: "test-fixtures/classifiers/dynamic/consul-1.15.2",
|
||||
expected: pkg.Package{
|
||||
Name: "consul",
|
||||
Version: "1.15.2",
|
||||
Type: "binary",
|
||||
PURL: "pkg:golang/github.com/hashicorp/consul@1.15.2",
|
||||
Locations: locations("consul"),
|
||||
Metadata: metadata("consul-binary"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
@@ -829,12 +841,12 @@ func match(classifier string, paths ...string) pkg.ClassifierMatch {
|
||||
}
|
||||
return pkg.ClassifierMatch{
|
||||
Classifier: classifier,
|
||||
Location: source.Location{
|
||||
Coordinates: source.Coordinates{
|
||||
Location: source.NewVirtualLocationFromCoordinates(
|
||||
source.Coordinates{
|
||||
RealPath: realPath,
|
||||
},
|
||||
VirtualPath: virtualPath,
|
||||
},
|
||||
virtualPath,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"debug/pe"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"text/template"
|
||||
@@ -107,7 +106,13 @@ func fileNameTemplateVersionMatcher(fileNamePattern string, contentTemplate stri
|
||||
}
|
||||
|
||||
matchMetadata := internal.MatchNamedCaptureGroups(tmplPattern, string(contents))
|
||||
return singlePackage(classifier, location, matchMetadata), nil
|
||||
|
||||
p := newPackage(classifier, location, matchMetadata)
|
||||
if p == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return []pkg.Package{*p}, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,7 +125,13 @@ func fileContentsVersionMatcher(pattern string) evidenceMatcher {
|
||||
}
|
||||
|
||||
matchMetadata := internal.MatchNamedCaptureGroups(pat, string(contents))
|
||||
return singlePackage(classifier, location, matchMetadata), nil
|
||||
|
||||
p := newPackage(classifier, location, matchMetadata)
|
||||
if p == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return []pkg.Package{*p}, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,8 +152,8 @@ func sharedLibraryLookup(sharedLibraryPattern string, sharedLibraryMatcher evide
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, libraryLication := range locations {
|
||||
pkgs, err := sharedLibraryMatcher(resolver, classifier, libraryLication)
|
||||
for _, libraryLocation := range locations {
|
||||
pkgs, err := sharedLibraryMatcher(resolver, classifier, libraryLocation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -176,58 +187,6 @@ func mustPURL(purl string) packageurl.PackageURL {
|
||||
return p
|
||||
}
|
||||
|
||||
func singlePackage(classifier classifier, location source.Location, matchMetadata map[string]string) []pkg.Package {
|
||||
version, ok := matchMetadata["version"]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
update := matchMetadata["update"]
|
||||
|
||||
var cpes []cpe.CPE
|
||||
for _, c := range classifier.CPEs {
|
||||
c.Version = version
|
||||
c.Update = update
|
||||
cpes = append(cpes, c)
|
||||
}
|
||||
|
||||
p := pkg.Package{
|
||||
Name: classifier.Package,
|
||||
Version: version,
|
||||
Locations: source.NewLocationSet(location),
|
||||
Type: pkg.BinaryPkg,
|
||||
CPEs: cpes,
|
||||
FoundBy: catalogerName,
|
||||
MetadataType: pkg.BinaryMetadataType,
|
||||
Metadata: pkg.BinaryMetadata{
|
||||
Matches: []pkg.ClassifierMatch{
|
||||
{
|
||||
Classifier: classifier.Class,
|
||||
Location: location,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if classifier.Type != "" {
|
||||
p.Type = classifier.Type
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(classifier.PURL, emptyPURL) {
|
||||
purl := classifier.PURL
|
||||
purl.Version = version
|
||||
p.PURL = purl.ToString()
|
||||
}
|
||||
|
||||
if classifier.Language != "" {
|
||||
p.Language = classifier.Language
|
||||
}
|
||||
|
||||
p.SetID()
|
||||
|
||||
return []pkg.Package{p}
|
||||
}
|
||||
|
||||
func getContents(resolver source.FileResolver, location source.Location) ([]byte, error) {
|
||||
reader, err := resolver.FileContentsByLocation(location)
|
||||
if err != nil {
|
||||
|
||||
@@ -270,6 +270,17 @@ var defaultClassifiers = []classifier{
|
||||
PURL: mustPURL("pkg:generic/ruby@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:ruby-lang:ruby:*:*:*:*:*:*:*:*"),
|
||||
},
|
||||
{
|
||||
Class: "consul-binary",
|
||||
FileGlob: "**/consul",
|
||||
EvidenceMatcher: fileContentsVersionMatcher(
|
||||
// NOTE: This is brittle and may not work for past or future versions
|
||||
`CONSUL_VERSION: (?P<version>\d+\.\d+\.\d+)`,
|
||||
),
|
||||
Package: "consul",
|
||||
PURL: mustPURL("pkg:golang/github.com/hashicorp/consul@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:hashicorp:consul:*:*:*:*:*:*:*:*"),
|
||||
},
|
||||
}
|
||||
|
||||
// in both binaries and shared libraries, the version pattern is [NUL]3.11.2[NUL]
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/anchore/syft/syft/cpe"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func newPackage(classifier classifier, location source.Location, matchMetadata map[string]string) *pkg.Package {
|
||||
version, ok := matchMetadata["version"]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
update := matchMetadata["update"]
|
||||
|
||||
var cpes []cpe.CPE
|
||||
for _, c := range classifier.CPEs {
|
||||
c.Version = version
|
||||
c.Update = update
|
||||
cpes = append(cpes, c)
|
||||
}
|
||||
|
||||
p := pkg.Package{
|
||||
Name: classifier.Package,
|
||||
Version: version,
|
||||
Locations: source.NewLocationSet(
|
||||
location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
Type: pkg.BinaryPkg,
|
||||
CPEs: cpes,
|
||||
FoundBy: catalogerName,
|
||||
MetadataType: pkg.BinaryMetadataType,
|
||||
Metadata: pkg.BinaryMetadata{
|
||||
Matches: []pkg.ClassifierMatch{
|
||||
{
|
||||
Classifier: classifier.Class,
|
||||
Location: location,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if classifier.Type != "" {
|
||||
p.Type = classifier.Type
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(classifier.PURL, emptyPURL) {
|
||||
purl := classifier.PURL
|
||||
purl.Version = version
|
||||
p.PURL = purl.ToString()
|
||||
}
|
||||
|
||||
if classifier.Language != "" {
|
||||
p.Language = classifier.Language
|
||||
}
|
||||
|
||||
p.SetID()
|
||||
|
||||
return &p
|
||||
}
|
||||
@@ -15,7 +15,9 @@ all: \
|
||||
classifiers/dynamic/kubectl-1.25.7 \
|
||||
classifiers/dynamic/kubectl-1.26.2 \
|
||||
classifiers/dynamic/kustomize-4.5.7 \
|
||||
classifiers/dynamic/kustomize-5.0.0
|
||||
classifiers/dynamic/kustomize-5.0.0 \
|
||||
classifiers/dynamic/consul-1.15.2
|
||||
|
||||
|
||||
|
||||
classifiers/dynamic/python-binary-shared-lib-3.11:
|
||||
@@ -141,6 +143,12 @@ classifiers/dynamic/kustomize-5.0.0:
|
||||
/usr/local/bin/kustomize \
|
||||
$@/kustomize
|
||||
|
||||
classifiers/dynamic/consul-1.15.2:
|
||||
$(eval $@_image := "hashicorp/consul:1.15.2@sha256:c2169f3bb18dd947ae8eb5f6766896695c71fb439f050a3343e0007d895615b8")
|
||||
./get-image-file.sh $($@_image) \
|
||||
/bin/consul \
|
||||
$@/consul
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -rf classifiers/dynamic
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/haskell"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/java"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/javascript"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/kernel"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/nix"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/php"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/portage"
|
||||
@@ -55,6 +56,7 @@ func ImageCatalogers(cfg Config) []pkg.Cataloger {
|
||||
nix.NewStoreCataloger(),
|
||||
sbom.NewSBOMCataloger(),
|
||||
binary.NewCataloger(),
|
||||
kernel.NewLinuxKernelCataloger(cfg.Kernel()),
|
||||
}, cfg.Catalogers)
|
||||
}
|
||||
|
||||
@@ -88,6 +90,7 @@ func DirectoryCatalogers(cfg Config) []pkg.Cataloger {
|
||||
binary.NewCataloger(),
|
||||
elixir.NewMixLockCataloger(),
|
||||
erlang.NewRebarLockCataloger(),
|
||||
kernel.NewLinuxKernelCataloger(cfg.Kernel()),
|
||||
nix.NewStoreCataloger(),
|
||||
}, cfg.Catalogers)
|
||||
}
|
||||
@@ -126,6 +129,7 @@ func AllCatalogers(cfg Config) []pkg.Cataloger {
|
||||
binary.NewCataloger(),
|
||||
elixir.NewMixLockCataloger(),
|
||||
erlang.NewRebarLockCataloger(),
|
||||
kernel.NewLinuxKernelCataloger(cfg.Kernel()),
|
||||
nix.NewStoreCataloger(),
|
||||
}, cfg.Catalogers)
|
||||
}
|
||||
|
||||
@@ -3,11 +3,15 @@ package cataloger
|
||||
import (
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/golang"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/java"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/kernel"
|
||||
)
|
||||
|
||||
// TODO: these field naming vs helper function naming schemes are inconsistent.
|
||||
|
||||
type Config struct {
|
||||
Search SearchConfig
|
||||
Golang golang.GoCatalogerOpts
|
||||
LinuxKernel kernel.LinuxCatalogerConfig
|
||||
Catalogers []string
|
||||
Parallelism int
|
||||
}
|
||||
@@ -16,6 +20,7 @@ func DefaultConfig() Config {
|
||||
return Config{
|
||||
Search: DefaultSearchConfig(),
|
||||
Parallelism: 1,
|
||||
LinuxKernel: kernel.DefaultLinuxCatalogerConfig(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,3 +34,7 @@ func (c Config) Java() java.Config {
|
||||
func (c Config) Go() golang.GoCatalogerOpts {
|
||||
return c.Golang
|
||||
}
|
||||
|
||||
func (c Config) Kernel() kernel.LinuxCatalogerConfig {
|
||||
return c.LinuxKernel
|
||||
}
|
||||
|
||||
@@ -48,7 +48,10 @@ func parseConanfile(_ source.FileResolver, _ *generic.Environment, reader source
|
||||
continue
|
||||
}
|
||||
|
||||
p := newConanfilePackage(m, reader.Location)
|
||||
p := newConanfilePackage(
|
||||
m,
|
||||
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
)
|
||||
if p == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -44,7 +44,10 @@ func parseConanlock(_ source.FileResolver, _ *generic.Environment, reader source
|
||||
Context: node.Context,
|
||||
}
|
||||
|
||||
p := newConanlockPackage(metadata, reader.Location)
|
||||
p := newConanlockPackage(
|
||||
metadata,
|
||||
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
)
|
||||
|
||||
if p != nil {
|
||||
pkgs = append(pkgs, *p)
|
||||
|
||||
@@ -58,7 +58,13 @@ func parsePubspecLock(_ source.FileResolver, _ *generic.Environment, reader sour
|
||||
|
||||
for _, name := range names {
|
||||
pubPkg := p.Packages[name]
|
||||
pkgs = append(pkgs, newPubspecLockPackage(name, pubPkg, reader.Location))
|
||||
pkgs = append(pkgs,
|
||||
newPubspecLockPackage(
|
||||
name,
|
||||
pubPkg,
|
||||
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
return pkgs, nil, nil
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Package dpkg provides a concrete Cataloger implementation for Debian package DB status files.
|
||||
Package deb provides a concrete Cataloger implementation for Debian package DB status files.
|
||||
*/
|
||||
package deb
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ func newDpkgPackage(d pkg.DpkgMetadata, dbLocation source.Location, resolver sou
|
||||
p := pkg.Package{
|
||||
Name: d.Package,
|
||||
Version: d.Version,
|
||||
Locations: source.NewLocationSet(dbLocation),
|
||||
Locations: source.NewLocationSet(dbLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
|
||||
PURL: packageURL(d, release),
|
||||
Type: pkg.DebPkg,
|
||||
MetadataType: pkg.DpkgMetadataType,
|
||||
@@ -162,6 +162,7 @@ func getAdditionalFileListing(resolver source.FileResolver, dbLocation source.Lo
|
||||
return files, locations
|
||||
}
|
||||
|
||||
//nolint:dupl
|
||||
func fetchMd5Contents(resolver source.FileResolver, dbLocation source.Location, m pkg.DpkgMetadata) (io.ReadCloser, *source.Location) {
|
||||
var md5Reader io.ReadCloser
|
||||
var err error
|
||||
@@ -182,17 +183,22 @@ func fetchMd5Contents(resolver source.FileResolver, dbLocation source.Location,
|
||||
location = resolver.RelativeFileByPath(dbLocation, path.Join(parentPath, "info", m.Package+md5sumsExt))
|
||||
}
|
||||
|
||||
// this is unexpected, but not a show-stopper
|
||||
if location != nil {
|
||||
md5Reader, err = resolver.FileContentsByLocation(*location)
|
||||
if err != nil {
|
||||
log.Warnf("failed to fetch deb md5 contents (package=%s): %+v", m.Package, err)
|
||||
}
|
||||
if location == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return md5Reader, location
|
||||
// this is unexpected, but not a show-stopper
|
||||
md5Reader, err = resolver.FileContentsByLocation(*location)
|
||||
if err != nil {
|
||||
log.Warnf("failed to fetch deb md5 contents (package=%s): %+v", m.Package, err)
|
||||
}
|
||||
|
||||
l := location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation)
|
||||
|
||||
return md5Reader, &l
|
||||
}
|
||||
|
||||
//nolint:dupl
|
||||
func fetchConffileContents(resolver source.FileResolver, dbLocation source.Location, m pkg.DpkgMetadata) (io.ReadCloser, *source.Location) {
|
||||
var reader io.ReadCloser
|
||||
var err error
|
||||
@@ -213,15 +219,19 @@ func fetchConffileContents(resolver source.FileResolver, dbLocation source.Locat
|
||||
location = resolver.RelativeFileByPath(dbLocation, path.Join(parentPath, "info", m.Package+conffilesExt))
|
||||
}
|
||||
|
||||
// this is unexpected, but not a show-stopper
|
||||
if location != nil {
|
||||
reader, err = resolver.FileContentsByLocation(*location)
|
||||
if err != nil {
|
||||
log.Warnf("failed to fetch deb conffiles contents (package=%s): %+v", m.Package, err)
|
||||
}
|
||||
if location == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return reader, location
|
||||
// this is unexpected, but not a show-stopper
|
||||
reader, err = resolver.FileContentsByLocation(*location)
|
||||
if err != nil {
|
||||
log.Warnf("failed to fetch deb conffiles contents (package=%s): %+v", m.Package, err)
|
||||
}
|
||||
|
||||
l := location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation)
|
||||
|
||||
return reader, &l
|
||||
}
|
||||
|
||||
func fetchCopyrightContents(resolver source.FileResolver, dbLocation source.Location, m pkg.DpkgMetadata) (io.ReadCloser, *source.Location) {
|
||||
@@ -243,7 +253,9 @@ func fetchCopyrightContents(resolver source.FileResolver, dbLocation source.Loca
|
||||
log.Warnf("failed to fetch deb copyright contents (package=%s): %w", m.Package, err)
|
||||
}
|
||||
|
||||
return reader, location
|
||||
l := location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation)
|
||||
|
||||
return reader, &l
|
||||
}
|
||||
|
||||
func md5Key(metadata pkg.DpkgMetadata) string {
|
||||
|
||||
@@ -45,7 +45,11 @@ func parseDotnetDeps(_ source.FileResolver, _ *generic.Environment, reader sourc
|
||||
|
||||
for _, nameVersion := range names {
|
||||
lib := p.Libraries[nameVersion]
|
||||
dotnetPkg := newDotnetDepsPackage(nameVersion, lib, reader.Location)
|
||||
dotnetPkg := newDotnetDepsPackage(
|
||||
nameVersion,
|
||||
lib,
|
||||
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
)
|
||||
|
||||
if dotnetPkg != nil {
|
||||
pkgs = append(pkgs, *dotnetPkg)
|
||||
|
||||
@@ -43,11 +43,16 @@ func parseMixLock(_ source.FileResolver, _ *generic.Environment, reader source.L
|
||||
continue
|
||||
}
|
||||
|
||||
packages = append(packages, newPackage(pkg.MixLockMetadata{
|
||||
Name: name,
|
||||
Version: version,
|
||||
PkgHash: hash,
|
||||
PkgHashExt: hashExt,
|
||||
}))
|
||||
packages = append(packages,
|
||||
newPackage(
|
||||
pkg.MixLockMetadata{
|
||||
Name: name,
|
||||
Version: version,
|
||||
PkgHash: hash,
|
||||
PkgHashExt: hashExt,
|
||||
},
|
||||
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,15 +6,18 @@ import (
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func TestParseMixLock(t *testing.T) {
|
||||
locations := source.NewLocationSet(source.NewLocation("test-fixtures/mix.lock"))
|
||||
expected := []pkg.Package{
|
||||
{
|
||||
Name: "castore",
|
||||
Version: "0.1.17",
|
||||
Language: pkg.Elixir,
|
||||
Type: pkg.HexPkg,
|
||||
Locations: locations,
|
||||
PURL: "pkg:hex/castore@0.1.17",
|
||||
MetadataType: pkg.MixLockMetadataType,
|
||||
Metadata: pkg.MixLockMetadata{
|
||||
@@ -29,6 +32,7 @@ func TestParseMixLock(t *testing.T) {
|
||||
Version: "1.1.0",
|
||||
Language: pkg.Elixir,
|
||||
Type: pkg.HexPkg,
|
||||
Locations: locations,
|
||||
PURL: "pkg:hex/connection@1.1.0",
|
||||
MetadataType: pkg.MixLockMetadataType,
|
||||
Metadata: pkg.MixLockMetadata{
|
||||
@@ -43,6 +47,7 @@ func TestParseMixLock(t *testing.T) {
|
||||
Version: "2.9.0",
|
||||
Language: pkg.Elixir,
|
||||
Type: pkg.HexPkg,
|
||||
Locations: locations,
|
||||
PURL: "pkg:hex/cowboy@2.9.0",
|
||||
MetadataType: pkg.MixLockMetadataType,
|
||||
Metadata: pkg.MixLockMetadata{
|
||||
@@ -57,6 +62,7 @@ func TestParseMixLock(t *testing.T) {
|
||||
Version: "0.4.0",
|
||||
Language: pkg.Elixir,
|
||||
Type: pkg.HexPkg,
|
||||
Locations: locations,
|
||||
PURL: "pkg:hex/cowboy_telemetry@0.4.0",
|
||||
MetadataType: pkg.MixLockMetadataType,
|
||||
Metadata: pkg.MixLockMetadata{
|
||||
@@ -71,6 +77,7 @@ func TestParseMixLock(t *testing.T) {
|
||||
Version: "2.11.0",
|
||||
Language: pkg.Elixir,
|
||||
Type: pkg.HexPkg,
|
||||
Locations: locations,
|
||||
PURL: "pkg:hex/cowlib@2.11.0",
|
||||
MetadataType: pkg.MixLockMetadataType,
|
||||
Metadata: pkg.MixLockMetadata{
|
||||
@@ -85,6 +92,7 @@ func TestParseMixLock(t *testing.T) {
|
||||
Version: "2.4.2",
|
||||
Language: pkg.Elixir,
|
||||
Type: pkg.HexPkg,
|
||||
Locations: locations,
|
||||
PURL: "pkg:hex/db_connection@2.4.2",
|
||||
MetadataType: pkg.MixLockMetadataType,
|
||||
Metadata: pkg.MixLockMetadata{
|
||||
@@ -99,6 +107,7 @@ func TestParseMixLock(t *testing.T) {
|
||||
Version: "2.0.0",
|
||||
Language: pkg.Elixir,
|
||||
Type: pkg.HexPkg,
|
||||
Locations: locations,
|
||||
PURL: "pkg:hex/decimal@2.0.0",
|
||||
MetadataType: pkg.MixLockMetadataType,
|
||||
Metadata: pkg.MixLockMetadata{
|
||||
@@ -113,6 +122,7 @@ func TestParseMixLock(t *testing.T) {
|
||||
Version: "1.4.25",
|
||||
Language: pkg.Elixir,
|
||||
Type: pkg.HexPkg,
|
||||
Locations: locations,
|
||||
PURL: "pkg:hex/earmark_parser@1.4.25",
|
||||
MetadataType: pkg.MixLockMetadataType,
|
||||
Metadata: pkg.MixLockMetadata{
|
||||
@@ -127,6 +137,7 @@ func TestParseMixLock(t *testing.T) {
|
||||
Version: "3.8.1",
|
||||
Language: pkg.Elixir,
|
||||
Type: pkg.HexPkg,
|
||||
Locations: locations,
|
||||
PURL: "pkg:hex/ecto@3.8.1",
|
||||
MetadataType: pkg.MixLockMetadataType,
|
||||
Metadata: pkg.MixLockMetadata{
|
||||
@@ -141,6 +152,7 @@ func TestParseMixLock(t *testing.T) {
|
||||
Version: "3.8.1",
|
||||
Language: pkg.Elixir,
|
||||
Type: pkg.HexPkg,
|
||||
Locations: locations,
|
||||
PURL: "pkg:hex/ecto_sql@3.8.1",
|
||||
MetadataType: pkg.MixLockMetadataType,
|
||||
Metadata: pkg.MixLockMetadata{
|
||||
@@ -155,6 +167,7 @@ func TestParseMixLock(t *testing.T) {
|
||||
Version: "0.5.0",
|
||||
Language: pkg.Elixir,
|
||||
Type: pkg.HexPkg,
|
||||
Locations: locations,
|
||||
PURL: "pkg:hex/esbuild@0.5.0",
|
||||
MetadataType: pkg.MixLockMetadataType,
|
||||
Metadata: pkg.MixLockMetadata{
|
||||
@@ -169,6 +182,7 @@ func TestParseMixLock(t *testing.T) {
|
||||
Version: "0.28.4",
|
||||
Language: pkg.Elixir,
|
||||
Type: pkg.HexPkg,
|
||||
Locations: locations,
|
||||
PURL: "pkg:hex/ex_doc@0.28.4",
|
||||
MetadataType: pkg.MixLockMetadataType,
|
||||
Metadata: pkg.MixLockMetadata{
|
||||
@@ -183,6 +197,7 @@ func TestParseMixLock(t *testing.T) {
|
||||
Version: "0.19.1",
|
||||
Language: pkg.Elixir,
|
||||
Type: pkg.HexPkg,
|
||||
Locations: locations,
|
||||
PURL: "pkg:hex/gettext@0.19.1",
|
||||
MetadataType: pkg.MixLockMetadataType,
|
||||
Metadata: pkg.MixLockMetadata{
|
||||
@@ -197,6 +212,7 @@ func TestParseMixLock(t *testing.T) {
|
||||
Version: "0.1.1",
|
||||
Language: pkg.Elixir,
|
||||
Type: pkg.HexPkg,
|
||||
Locations: locations,
|
||||
PURL: "pkg:hex/hpax@0.1.1",
|
||||
MetadataType: pkg.MixLockMetadataType,
|
||||
Metadata: pkg.MixLockMetadata{
|
||||
@@ -211,6 +227,7 @@ func TestParseMixLock(t *testing.T) {
|
||||
Version: "1.3.0",
|
||||
Language: pkg.Elixir,
|
||||
Type: pkg.HexPkg,
|
||||
Locations: locations,
|
||||
PURL: "pkg:hex/jason@1.3.0",
|
||||
MetadataType: pkg.MixLockMetadataType,
|
||||
Metadata: pkg.MixLockMetadata{
|
||||
|
||||
@@ -48,10 +48,13 @@ func parseRebarLock(_ source.FileResolver, _ *generic.Environment, reader source
|
||||
version = versionNode.Get(2).Get(1).String()
|
||||
}
|
||||
|
||||
p := newPackage(pkg.RebarLockMetadata{
|
||||
Name: name,
|
||||
Version: version,
|
||||
})
|
||||
p := newPackage(
|
||||
pkg.RebarLockMetadata{
|
||||
Name: name,
|
||||
Version: version,
|
||||
},
|
||||
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
)
|
||||
|
||||
pkgMap[name] = &p
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func TestParseRebarLock(t *testing.T) {
|
||||
@@ -261,6 +262,10 @@ func TestParseRebarLock(t *testing.T) {
|
||||
// TODO: relationships are not under test
|
||||
var expectedRelationships []artifact.Relationship
|
||||
|
||||
for idx := range test.expected {
|
||||
test.expected[idx].Locations = source.NewLocationSet(source.NewLocation(test.fixture))
|
||||
}
|
||||
|
||||
pkgtest.TestFileParser(t, test.fixture, parseRebarLock, test.expected, expectedRelationships)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
package golang
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
|
||||
"github.com/go-git/go-billy/v5"
|
||||
)
|
||||
|
||||
// billyFSAdapter is a fs.FS, fs.ReadDirFS, and fs.StatFS wrapping what billyfs returns
|
||||
type billyFSAdapter struct {
|
||||
fs billy.Filesystem
|
||||
}
|
||||
|
||||
func (b billyFSAdapter) Stat(name string) (fs.FileInfo, error) {
|
||||
return b.fs.Stat(name)
|
||||
}
|
||||
|
||||
func (b billyFSAdapter) ReadDir(name string) (out []fs.DirEntry, _ error) {
|
||||
entries, err := b.fs.ReadDir(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, e := range entries {
|
||||
out = append(out, billyDirEntry{fi: e})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (b billyFSAdapter) Open(name string) (fs.File, error) {
|
||||
f, err := b.fs.Open(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fi, err := b.Stat(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return billyFile{f: f, fi: fi}, nil
|
||||
}
|
||||
|
||||
var _ fs.FS = (*billyFSAdapter)(nil)
|
||||
var _ fs.ReadDirFS = (*billyFSAdapter)(nil)
|
||||
var _ fs.StatFS = (*billyFSAdapter)(nil)
|
||||
|
||||
// billyFile is a fs.File wrapping what billyfs returns
|
||||
type billyFile struct {
|
||||
f billy.File
|
||||
fi fs.FileInfo
|
||||
}
|
||||
|
||||
func (b billyFile) Stat() (fs.FileInfo, error) {
|
||||
return b.fi, nil
|
||||
}
|
||||
|
||||
func (b billyFile) Read(i []byte) (int, error) {
|
||||
return b.f.Read(i)
|
||||
}
|
||||
|
||||
func (b billyFile) Close() error {
|
||||
return b.f.Close()
|
||||
}
|
||||
|
||||
var _ fs.File = (*billyFile)(nil)
|
||||
|
||||
// billyDirEntry is a fs.DirEntry wrapping what billyfs returns
|
||||
type billyDirEntry struct {
|
||||
fi os.FileInfo
|
||||
}
|
||||
|
||||
func (b billyDirEntry) Name() string {
|
||||
return b.fi.Name()
|
||||
}
|
||||
|
||||
func (b billyDirEntry) IsDir() bool {
|
||||
return b.fi.IsDir()
|
||||
}
|
||||
|
||||
func (b billyDirEntry) Type() fs.FileMode {
|
||||
return b.fi.Mode()
|
||||
}
|
||||
|
||||
func (b billyDirEntry) Info() (fs.FileInfo, error) {
|
||||
return b.fi, nil
|
||||
}
|
||||
|
||||
var _ fs.DirEntry = (*billyDirEntry)(nil)
|
||||
@@ -0,0 +1,33 @@
|
||||
package golang
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_billyFSAdapter(t *testing.T) {
|
||||
r, err := git.PlainInit("test-fixtures/repo", false)
|
||||
|
||||
t.Cleanup(func() {
|
||||
_ = os.RemoveAll("test-fixtures/repo/.git")
|
||||
})
|
||||
|
||||
wt, err := r.Worktree()
|
||||
require.NoError(t, err)
|
||||
f := billyFSAdapter{
|
||||
fs: wt.Filesystem,
|
||||
}
|
||||
|
||||
found := ""
|
||||
err = fs.WalkDir(f, ".", func(path string, d fs.DirEntry, err error) error {
|
||||
found = path
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, "LICENSE", found)
|
||||
}
|
||||
@@ -5,28 +5,47 @@ package golang
|
||||
|
||||
import (
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/event"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
type GoCatalogerOpts struct {
|
||||
SearchLocalModCacheLicenses bool
|
||||
LocalModCacheDir string
|
||||
}
|
||||
|
||||
// NewGoModFileCataloger returns a new Go module cataloger object.
|
||||
func NewGoModFileCataloger(opts GoCatalogerOpts) *generic.Cataloger {
|
||||
func NewGoModFileCataloger(opts GoCatalogerOpts) pkg.Cataloger {
|
||||
c := goModCataloger{
|
||||
licenses: newGoLicenses(opts),
|
||||
}
|
||||
return generic.NewCataloger("go-mod-file-cataloger").
|
||||
WithParserByGlobs(c.parseGoModFile, "**/go.mod")
|
||||
return &progressingCataloger{
|
||||
progress: c.licenses.progress,
|
||||
cataloger: generic.NewCataloger("go-mod-file-cataloger").
|
||||
WithParserByGlobs(c.parseGoModFile, "**/go.mod"),
|
||||
}
|
||||
}
|
||||
|
||||
// NewGoModuleBinaryCataloger returns a new Golang cataloger object.
|
||||
func NewGoModuleBinaryCataloger(opts GoCatalogerOpts) *generic.Cataloger {
|
||||
func NewGoModuleBinaryCataloger(opts GoCatalogerOpts) pkg.Cataloger {
|
||||
c := goBinaryCataloger{
|
||||
licenses: newGoLicenses(opts),
|
||||
}
|
||||
return generic.NewCataloger("go-module-binary-cataloger").
|
||||
WithParserByMimeTypes(c.parseGoBinary, internal.ExecutableMIMETypeSet.List()...)
|
||||
return &progressingCataloger{
|
||||
progress: c.licenses.progress,
|
||||
cataloger: generic.NewCataloger("go-module-binary-cataloger").
|
||||
WithParserByMimeTypes(c.parseGoBinary, internal.ExecutableMIMETypeSet.List()...),
|
||||
}
|
||||
}
|
||||
|
||||
type progressingCataloger struct {
|
||||
progress *event.CatalogerTask
|
||||
cataloger *generic.Cataloger
|
||||
}
|
||||
|
||||
func (p *progressingCataloger) Name() string {
|
||||
return p.cataloger.Name()
|
||||
}
|
||||
|
||||
func (p *progressingCataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
defer p.progress.SetCompleted()
|
||||
return p.cataloger.Catalog(resolver)
|
||||
}
|
||||
|
||||
@@ -1,105 +1,158 @@
|
||||
package golang
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/go-git/go-billy/v5/memfs"
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/storage/memory"
|
||||
|
||||
"github.com/anchore/syft/internal/licenses"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/syft/event"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
type goLicenses struct {
|
||||
searchLocalModCacheLicenses bool
|
||||
localModCacheResolver source.FileResolver
|
||||
opts GoCatalogerOpts
|
||||
localModCacheResolver source.WritableFileResolver
|
||||
progress *event.CatalogerTask
|
||||
}
|
||||
|
||||
func newGoLicenses(opts GoCatalogerOpts) goLicenses {
|
||||
return goLicenses{
|
||||
searchLocalModCacheLicenses: opts.SearchLocalModCacheLicenses,
|
||||
localModCacheResolver: modCacheResolver(opts.LocalModCacheDir),
|
||||
opts: opts,
|
||||
localModCacheResolver: modCacheResolver(opts.localModCacheDir),
|
||||
progress: &event.CatalogerTask{
|
||||
SubStatus: true,
|
||||
RemoveOnCompletion: true,
|
||||
Title: "Downloading go mod",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func defaultGoPath() string {
|
||||
goPath := os.Getenv("GOPATH")
|
||||
|
||||
if goPath == "" {
|
||||
homeDir, err := homedir.Dir()
|
||||
if err != nil {
|
||||
log.Debug("unable to determine user home dir: %v", err)
|
||||
} else {
|
||||
goPath = path.Join(homeDir, "go")
|
||||
func remotesForModule(proxies []string, noProxy []string, module string) []string {
|
||||
for _, pattern := range noProxy {
|
||||
if matched, err := path.Match(pattern, module); err == nil && matched {
|
||||
// matched to be direct for this module
|
||||
return directProxiesOnly
|
||||
}
|
||||
}
|
||||
|
||||
return goPath
|
||||
return proxies
|
||||
}
|
||||
|
||||
// resolver needs to be shared between mod file & binary scanners so it's only scanned once
|
||||
var modCacheResolvers = map[string]source.FileResolver{}
|
||||
|
||||
func modCacheResolver(modCacheDir string) source.FileResolver {
|
||||
if modCacheDir == "" {
|
||||
goPath := defaultGoPath()
|
||||
if goPath != "" {
|
||||
modCacheDir = path.Join(goPath, "pkg", "mod")
|
||||
}
|
||||
}
|
||||
|
||||
if r, ok := modCacheResolvers[modCacheDir]; ok {
|
||||
return r
|
||||
}
|
||||
|
||||
var r source.FileResolver
|
||||
func modCacheResolver(modCacheDir string) source.WritableFileResolver {
|
||||
var r source.WritableFileResolver
|
||||
|
||||
if modCacheDir == "" {
|
||||
log.Trace("unable to determine mod cache directory, skipping mod cache resolver")
|
||||
r = source.NewMockResolverForPaths()
|
||||
r = source.EmptyResolver{}
|
||||
} else {
|
||||
stat, err := os.Stat(modCacheDir)
|
||||
|
||||
if os.IsNotExist(err) || stat == nil || !stat.IsDir() {
|
||||
log.Tracef("unable to open mod cache directory: %s, skipping mod cache resolver", modCacheDir)
|
||||
r = source.NewMockResolverForPaths()
|
||||
r = source.EmptyResolver{}
|
||||
} else {
|
||||
r = source.NewDeferredResolverFromSource(func() (source.Source, error) {
|
||||
return source.NewFromDirectory(modCacheDir)
|
||||
})
|
||||
r = source.NewUnindexedDirectoryResolver(modCacheDir)
|
||||
}
|
||||
}
|
||||
|
||||
modCacheResolvers[modCacheDir] = r
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (c *goLicenses) getLicenses(resolver source.FileResolver, moduleName, moduleVersion string) (licenses []string, err error) {
|
||||
moduleName = processCaps(moduleName)
|
||||
|
||||
licenses, err = findLicenses(resolver,
|
||||
fmt.Sprintf(`**/go/pkg/mod/%s@%s/*`, moduleName, moduleVersion),
|
||||
fmt.Sprintf(`**/go/pkg/mod/%s@%s/*`, processCaps(moduleName), moduleVersion),
|
||||
)
|
||||
|
||||
if c.searchLocalModCacheLicenses && err == nil && len(licenses) == 0 {
|
||||
// if we're running against a directory on the filesystem, it may not include the
|
||||
// user's homedir / GOPATH, so we defer to using the localModCacheResolver
|
||||
licenses, err = findLicenses(c.localModCacheResolver,
|
||||
fmt.Sprintf(`**/%s@%s/*`, moduleName, moduleVersion),
|
||||
)
|
||||
if err != nil || len(licenses) > 0 {
|
||||
return requireCollection(licenses), err
|
||||
}
|
||||
|
||||
// always return a non-nil slice
|
||||
// look in the local host mod cache...
|
||||
licenses, err = c.getLicensesFromLocal(moduleName, moduleVersion)
|
||||
if err != nil || len(licenses) > 0 {
|
||||
return requireCollection(licenses), err
|
||||
}
|
||||
|
||||
// we did not find it yet and remote searching was enabled
|
||||
licenses, err = c.getLicensesFromRemote(moduleName, moduleVersion)
|
||||
return requireCollection(licenses), err
|
||||
}
|
||||
|
||||
func (c *goLicenses) getLicensesFromLocal(moduleName, moduleVersion string) ([]string, error) {
|
||||
if !c.opts.searchLocalModCacheLicenses {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// if we're running against a directory on the filesystem, it may not include the
|
||||
// user's homedir / GOPATH, so we defer to using the localModCacheResolver
|
||||
return findLicenses(c.localModCacheResolver, moduleSearchGlob(moduleName, moduleVersion))
|
||||
}
|
||||
|
||||
func (c *goLicenses) getLicensesFromRemote(moduleName, moduleVersion string) ([]string, error) {
|
||||
if !c.opts.searchRemoteLicenses {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
proxies := remotesForModule(c.opts.proxies, c.opts.noProxy, moduleName)
|
||||
|
||||
fsys, err := getModule(c.progress, proxies, moduleName, moduleVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dir := moduleDir(moduleName, moduleVersion)
|
||||
|
||||
// populate the mod cache with the results
|
||||
err = fs.WalkDir(fsys, ".", func(filePath string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
log.Debug(err)
|
||||
return nil
|
||||
}
|
||||
if d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
f, err := fsys.Open(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.localModCacheResolver.Write(source.NewLocation(path.Join(dir, filePath)), f)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Tracef("remote proxy walk failed for: %s", moduleName)
|
||||
}
|
||||
|
||||
return findLicenses(c.localModCacheResolver, moduleSearchGlob(moduleName, moduleVersion))
|
||||
}
|
||||
|
||||
func moduleDir(moduleName, moduleVersion string) string {
|
||||
return fmt.Sprintf("%s@%s", processCaps(moduleName), moduleVersion)
|
||||
}
|
||||
|
||||
func moduleSearchGlob(moduleName, moduleVersion string) string {
|
||||
return fmt.Sprintf("%s/*", moduleDir(moduleName, moduleVersion))
|
||||
}
|
||||
|
||||
func requireCollection(licenses []string) []string {
|
||||
if licenses == nil {
|
||||
licenses = []string{}
|
||||
return []string{}
|
||||
}
|
||||
|
||||
return
|
||||
return licenses
|
||||
}
|
||||
|
||||
func findLicenses(resolver source.FileResolver, globMatch string) (out []string, err error) {
|
||||
@@ -138,3 +191,96 @@ func processCaps(s string) string {
|
||||
return "!" + strings.ToLower(s)
|
||||
})
|
||||
}
|
||||
|
||||
func getModule(progress *event.CatalogerTask, proxies []string, moduleName, moduleVersion string) (fsys fs.FS, err error) {
|
||||
for _, proxy := range proxies {
|
||||
u, _ := url.Parse(proxy)
|
||||
if proxy == "direct" {
|
||||
fsys, err = getModuleRepository(progress, moduleName, moduleVersion)
|
||||
continue
|
||||
}
|
||||
switch u.Scheme {
|
||||
case "https", "http":
|
||||
fsys, err = getModuleProxy(progress, proxy, moduleName, moduleVersion)
|
||||
case "file":
|
||||
p := filepath.Join(u.Path, moduleName, "@v", moduleVersion)
|
||||
progress.SetValue(fmt.Sprintf("file: %s", p))
|
||||
fsys = os.DirFS(p)
|
||||
}
|
||||
if fsys != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getModuleProxy(progress *event.CatalogerTask, proxy string, moduleName string, moduleVersion string) (out fs.FS, _ error) {
|
||||
u := fmt.Sprintf("%s/%s/@v/%s.zip", proxy, moduleName, moduleVersion)
|
||||
progress.SetValue(u)
|
||||
// get the module zip
|
||||
resp, err := http.Get(u) //nolint:gosec
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
u = fmt.Sprintf("%s/%s/@v/%s.zip", proxy, strings.ToLower(moduleName), moduleVersion)
|
||||
progress.SetValue(u)
|
||||
// try lowercasing it; some packages have mixed casing that really messes up the proxy
|
||||
resp, err = http.Get(u) //nolint:gosec
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("failed to get module zip: %s", resp.Status)
|
||||
}
|
||||
}
|
||||
// read the zip
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err = zip.NewReader(bytes.NewReader(b), resp.ContentLength)
|
||||
versionPath := findVersionPath(out, ".")
|
||||
out = getSubFS(out, versionPath)
|
||||
return out, err
|
||||
}
|
||||
|
||||
func findVersionPath(f fs.FS, dir string) string {
|
||||
list, _ := fs.ReadDir(f, dir)
|
||||
for _, entry := range list {
|
||||
name := entry.Name()
|
||||
if strings.Contains(name, "@") {
|
||||
return name
|
||||
}
|
||||
found := findVersionPath(f, path.Join(dir, name))
|
||||
if found != "" {
|
||||
return path.Join(name, found)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func getModuleRepository(progress *event.CatalogerTask, moduleName string, moduleVersion string) (fs.FS, error) {
|
||||
repoName := moduleName
|
||||
parts := strings.Split(moduleName, "/")
|
||||
if len(parts) > 2 {
|
||||
repoName = fmt.Sprintf("%s/%s/%s", parts[0], parts[1], parts[2])
|
||||
}
|
||||
progress.SetValue(fmt.Sprintf("git: %s", repoName))
|
||||
f := memfs.New()
|
||||
buf := &bytes.Buffer{}
|
||||
_, err := git.Clone(memory.NewStorage(), f, &git.CloneOptions{
|
||||
URL: fmt.Sprintf("https://%s", repoName),
|
||||
ReferenceName: plumbing.NewTagReferenceName(moduleVersion), // FIXME version might be a SHA
|
||||
SingleBranch: true,
|
||||
Depth: 1,
|
||||
Progress: buf,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w -- %s", err, buf.String())
|
||||
}
|
||||
|
||||
return billyFSAdapter{fs: f}, nil
|
||||
}
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
package golang
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -10,7 +16,7 @@ import (
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func Test_LicenseSearch(t *testing.T) {
|
||||
func Test_LocalLicenseSearch(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
version string
|
||||
@@ -34,10 +40,85 @@ func Test_LicenseSearch(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
l := newGoLicenses(GoCatalogerOpts{
|
||||
SearchLocalModCacheLicenses: true,
|
||||
LocalModCacheDir: path.Join(wd, "test-fixtures", "licenses"),
|
||||
searchLocalModCacheLicenses: true,
|
||||
localModCacheDir: path.Join(wd, "test-fixtures", "licenses", "pkg", "mod"),
|
||||
})
|
||||
licenses, err := l.getLicenses(source.MockResolver{}, test.name, test.version)
|
||||
licenses, err := l.getLicenses(source.EmptyResolver{}, test.name, test.version)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, licenses, 1)
|
||||
|
||||
require.Equal(t, test.expected, licenses[0])
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_RemoteProxyLicenseSearch(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
buf := &bytes.Buffer{}
|
||||
uri := strings.TrimPrefix(strings.TrimSuffix(r.RequestURI, ".zip"), "/")
|
||||
|
||||
parts := strings.Split(uri, "/@v/")
|
||||
modPath := parts[0]
|
||||
modVersion := parts[1]
|
||||
|
||||
wd, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
testDir := path.Join(wd, "test-fixtures", "licenses", "pkg", "mod", processCaps(modPath)+"@"+modVersion)
|
||||
|
||||
archive := zip.NewWriter(buf)
|
||||
|
||||
entries, err := os.ReadDir(testDir)
|
||||
require.NoError(t, err)
|
||||
for _, f := range entries {
|
||||
// the zip files downloaded contain a path to the repo that somewhat matches where it ends up on disk,
|
||||
// so prefix entries with something similar
|
||||
writer, err := archive.Create(path.Join("github.com/something/some@version", f.Name()))
|
||||
require.NoError(t, err)
|
||||
contents, err := os.ReadFile(path.Join(testDir, f.Name()))
|
||||
require.NoError(t, err)
|
||||
_, err = writer.Write(contents)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
err = archive.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
w.Header().Add("Content-Length", fmt.Sprintf("%d", buf.Len()))
|
||||
|
||||
_, err = w.Write(buf.Bytes())
|
||||
require.NoError(t, err)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
version string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "github.com/someorg/somename",
|
||||
version: "v0.3.2",
|
||||
expected: "Apache-2.0",
|
||||
},
|
||||
{
|
||||
name: "github.com/CapORG/CapProject",
|
||||
version: "v4.111.5",
|
||||
expected: "MIT",
|
||||
},
|
||||
}
|
||||
|
||||
modDir := path.Join(t.TempDir())
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
l := newGoLicenses(GoCatalogerOpts{
|
||||
searchRemoteLicenses: true,
|
||||
proxies: []string{server.URL},
|
||||
localModCacheDir: modDir,
|
||||
})
|
||||
|
||||
licenses, err := l.getLicenses(source.EmptyResolver{}, test.name, test.version)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, licenses, 1)
|
||||
@@ -74,3 +155,42 @@ func Test_processCaps(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_remotesForModule(t *testing.T) {
|
||||
allProxies := []string{"https://somewhere.org", "direct"}
|
||||
directProxy := []string{"direct"}
|
||||
|
||||
tests := []struct {
|
||||
module string
|
||||
noProxy string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
module: "github.com/anchore/syft",
|
||||
expected: allProxies,
|
||||
},
|
||||
{
|
||||
module: "github.com/anchore/sbom-action",
|
||||
noProxy: "*/anchore/*",
|
||||
expected: directProxy,
|
||||
},
|
||||
{
|
||||
module: "github.com/anchore/sbom-action",
|
||||
noProxy: "*/user/mod,*/anchore/sbom-action",
|
||||
expected: directProxy,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.module, func(t *testing.T) {
|
||||
got := remotesForModule(allProxies, strings.Split(test.noProxy, ","), test.module)
|
||||
require.Equal(t, test.expected, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_findVersionPath(t *testing.T) {
|
||||
f := os.DirFS("test-fixtures/zip-fs")
|
||||
vp := findVersionPath(f, ".")
|
||||
require.Equal(t, "github.com/someorg/somepkg@version", vp)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
package golang
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
|
||||
"github.com/anchore/syft/internal/log"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultProxies = "https://proxy.golang.org,direct"
|
||||
directProxyOnly = "direct"
|
||||
)
|
||||
|
||||
var (
|
||||
directProxiesOnly = []string{directProxyOnly}
|
||||
)
|
||||
|
||||
type GoCatalogerOpts struct {
|
||||
searchLocalModCacheLicenses bool
|
||||
localModCacheDir string
|
||||
searchRemoteLicenses bool
|
||||
proxies []string
|
||||
noProxy []string
|
||||
}
|
||||
|
||||
func (g GoCatalogerOpts) WithSearchLocalModCacheLicenses(input bool) GoCatalogerOpts {
|
||||
g.searchLocalModCacheLicenses = input
|
||||
return g
|
||||
}
|
||||
|
||||
func (g GoCatalogerOpts) WithLocalModCacheDir(input string) GoCatalogerOpts {
|
||||
if input == "" {
|
||||
return g
|
||||
}
|
||||
g.localModCacheDir = input
|
||||
return g
|
||||
}
|
||||
|
||||
func (g GoCatalogerOpts) WithSearchRemoteLicenses(input bool) GoCatalogerOpts {
|
||||
g.searchRemoteLicenses = input
|
||||
return g
|
||||
}
|
||||
|
||||
func (g GoCatalogerOpts) WithProxy(input string) GoCatalogerOpts {
|
||||
if input == "" {
|
||||
return g
|
||||
}
|
||||
if input == "off" {
|
||||
input = directProxyOnly
|
||||
}
|
||||
g.proxies = strings.Split(input, ",")
|
||||
return g
|
||||
}
|
||||
|
||||
func (g GoCatalogerOpts) WithNoProxy(input string) GoCatalogerOpts {
|
||||
if input == "" {
|
||||
return g
|
||||
}
|
||||
g.noProxy = strings.Split(input, ",")
|
||||
return g
|
||||
}
|
||||
|
||||
// NewGoCatalogerOpts create a GoCatalogerOpts with default options, which includes:
|
||||
// - setting the default remote proxy if none is provided
|
||||
// - setting the default no proxy if none is provided
|
||||
// - setting the default local module cache dir if none is provided
|
||||
func NewGoCatalogerOpts() GoCatalogerOpts {
|
||||
g := GoCatalogerOpts{}
|
||||
|
||||
// first process the proxy settings
|
||||
if len(g.proxies) == 0 {
|
||||
goProxy := os.Getenv("GOPROXY")
|
||||
if goProxy == "" {
|
||||
goProxy = defaultProxies
|
||||
}
|
||||
g = g.WithProxy(goProxy)
|
||||
}
|
||||
|
||||
// next process the gonoproxy settings
|
||||
if len(g.noProxy) == 0 {
|
||||
goPrivate := os.Getenv("GOPRIVATE")
|
||||
goNoProxy := os.Getenv("GONOPROXY")
|
||||
// we only use the env var if it was not set explicitly
|
||||
if goPrivate != "" {
|
||||
g.noProxy = append(g.noProxy, strings.Split(goPrivate, ",")...)
|
||||
}
|
||||
|
||||
// next process the goprivate settings; we always add those
|
||||
if goNoProxy != "" {
|
||||
g.noProxy = append(g.noProxy, strings.Split(goNoProxy, ",")...)
|
||||
}
|
||||
}
|
||||
|
||||
if g.localModCacheDir == "" {
|
||||
goPath := os.Getenv("GOPATH")
|
||||
|
||||
if goPath == "" {
|
||||
homeDir, err := homedir.Dir()
|
||||
if err != nil {
|
||||
log.Debug("unable to determine user home dir: %v", err)
|
||||
} else {
|
||||
goPath = path.Join(homeDir, "go")
|
||||
}
|
||||
}
|
||||
if goPath != "" {
|
||||
g.localModCacheDir = path.Join(goPath, "pkg", "mod")
|
||||
}
|
||||
}
|
||||
return g
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package golang
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_Options(t *testing.T) {
|
||||
type opts struct {
|
||||
local bool
|
||||
cacheDir string
|
||||
remote bool
|
||||
proxy string
|
||||
noProxy string
|
||||
}
|
||||
|
||||
homedirCacheDisabled := homedir.DisableCache
|
||||
homedir.DisableCache = true
|
||||
t.Cleanup(func() {
|
||||
homedir.DisableCache = homedirCacheDisabled
|
||||
})
|
||||
|
||||
allEnv := map[string]string{
|
||||
"HOME": "/usr/home",
|
||||
"GOPATH": "",
|
||||
"GOPROXY": "",
|
||||
"GOPRIVATE": "",
|
||||
"GONOPROXY": "",
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
env map[string]string
|
||||
opts opts
|
||||
expected GoCatalogerOpts
|
||||
}{
|
||||
{
|
||||
name: "set via env defaults",
|
||||
env: map[string]string{
|
||||
"GOPATH": "/go",
|
||||
"GOPROXY": "https://my.proxy",
|
||||
"GOPRIVATE": "my.private",
|
||||
"GONOPROXY": "no.proxy",
|
||||
},
|
||||
opts: opts{},
|
||||
expected: GoCatalogerOpts{
|
||||
searchLocalModCacheLicenses: false,
|
||||
localModCacheDir: "/go/pkg/mod",
|
||||
searchRemoteLicenses: false,
|
||||
proxies: []string{"https://my.proxy"},
|
||||
noProxy: []string{"my.private", "no.proxy"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "set via configuration",
|
||||
env: map[string]string{
|
||||
"GOPATH": "/go",
|
||||
"GOPROXY": "https://my.proxy",
|
||||
"GOPRIVATE": "my.private",
|
||||
"GONOPROXY": "no.proxy",
|
||||
},
|
||||
opts: opts{
|
||||
local: true,
|
||||
cacheDir: "/go-cache",
|
||||
remote: true,
|
||||
proxy: "https://alt.proxy,direct",
|
||||
noProxy: "alt.no.proxy",
|
||||
},
|
||||
expected: GoCatalogerOpts{
|
||||
searchLocalModCacheLicenses: true,
|
||||
localModCacheDir: "/go-cache",
|
||||
searchRemoteLicenses: true,
|
||||
proxies: []string{"https://alt.proxy", "direct"},
|
||||
noProxy: []string{"alt.no.proxy"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
for k, v := range allEnv {
|
||||
t.Setenv(k, v)
|
||||
}
|
||||
for k, v := range test.env {
|
||||
t.Setenv(k, v)
|
||||
}
|
||||
got := NewGoCatalogerOpts().
|
||||
WithSearchLocalModCacheLicenses(test.opts.local).
|
||||
WithLocalModCacheDir(test.opts.cacheDir).
|
||||
WithSearchRemoteLicenses(test.opts.remote).
|
||||
WithProxy(test.opts.proxy).
|
||||
WithNoProxy(test.opts.noProxy)
|
||||
|
||||
assert.Equal(t, test.expected, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,15 @@ func (c *goBinaryCataloger) parseGoBinary(resolver source.FileResolver, _ *gener
|
||||
|
||||
func (c *goBinaryCataloger) makeGoMainPackage(resolver source.FileResolver, mod *debug.BuildInfo, arch string, location source.Location) pkg.Package {
|
||||
gbs := getBuildSettings(mod.Settings)
|
||||
main := c.newGoBinaryPackage(resolver, &mod.Main, mod.Main.Path, mod.GoVersion, arch, gbs, location)
|
||||
main := c.newGoBinaryPackage(
|
||||
resolver,
|
||||
&mod.Main,
|
||||
mod.Main.Path,
|
||||
mod.GoVersion,
|
||||
arch,
|
||||
gbs,
|
||||
location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
)
|
||||
if main.Version == devel {
|
||||
if version, ok := gbs["vcs.revision"]; ok {
|
||||
if timestamp, ok := gbs["vcs.time"]; ok {
|
||||
@@ -204,7 +212,15 @@ func (c *goBinaryCataloger) buildGoPkgInfo(resolver source.FileResolver, locatio
|
||||
if dep == nil {
|
||||
continue
|
||||
}
|
||||
p := c.newGoBinaryPackage(resolver, dep, mod.Main.Path, mod.GoVersion, arch, nil, location)
|
||||
p := c.newGoBinaryPackage(
|
||||
resolver,
|
||||
dep,
|
||||
mod.Main.Path,
|
||||
mod.GoVersion,
|
||||
arch,
|
||||
nil,
|
||||
location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
)
|
||||
if pkg.IsValid(&p) {
|
||||
pkgs = append(pkgs, p)
|
||||
}
|
||||
|
||||
@@ -136,12 +136,12 @@ func TestBuildGoPkgInfo(t *testing.T) {
|
||||
Version: "(devel)",
|
||||
PURL: "pkg:golang/github.com/anchore/syft@(devel)",
|
||||
Locations: source.NewLocationSet(
|
||||
source.Location{
|
||||
Coordinates: source.Coordinates{
|
||||
source.NewLocationFromCoordinates(
|
||||
source.Coordinates{
|
||||
RealPath: "/a-path",
|
||||
FileSystemID: "layer-id",
|
||||
},
|
||||
},
|
||||
).WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
MetadataType: pkg.GolangBinMetadataType,
|
||||
Metadata: pkg.GolangBinMetadata{
|
||||
@@ -183,12 +183,12 @@ func TestBuildGoPkgInfo(t *testing.T) {
|
||||
Language: pkg.Go,
|
||||
Type: pkg.GoModulePkg,
|
||||
Locations: source.NewLocationSet(
|
||||
source.Location{
|
||||
Coordinates: source.Coordinates{
|
||||
source.NewLocationFromCoordinates(
|
||||
source.Coordinates{
|
||||
RealPath: "/a-path",
|
||||
FileSystemID: "layer-id",
|
||||
},
|
||||
},
|
||||
).WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
MetadataType: pkg.GolangBinMetadataType,
|
||||
Metadata: pkg.GolangBinMetadata{},
|
||||
@@ -226,12 +226,12 @@ func TestBuildGoPkgInfo(t *testing.T) {
|
||||
Language: pkg.Go,
|
||||
Type: pkg.GoModulePkg,
|
||||
Locations: source.NewLocationSet(
|
||||
source.Location{
|
||||
Coordinates: source.Coordinates{
|
||||
source.NewLocationFromCoordinates(
|
||||
source.Coordinates{
|
||||
RealPath: "/a-path",
|
||||
FileSystemID: "layer-id",
|
||||
},
|
||||
},
|
||||
).WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
MetadataType: pkg.GolangBinMetadataType,
|
||||
Metadata: pkg.GolangBinMetadata{
|
||||
@@ -262,12 +262,12 @@ func TestBuildGoPkgInfo(t *testing.T) {
|
||||
Language: pkg.Go,
|
||||
Type: pkg.GoModulePkg,
|
||||
Locations: source.NewLocationSet(
|
||||
source.Location{
|
||||
Coordinates: source.Coordinates{
|
||||
source.NewLocationFromCoordinates(
|
||||
source.Coordinates{
|
||||
RealPath: "/a-path",
|
||||
FileSystemID: "layer-id",
|
||||
},
|
||||
},
|
||||
).WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
MetadataType: pkg.GolangBinMetadataType,
|
||||
Metadata: pkg.GolangBinMetadata{
|
||||
@@ -320,12 +320,12 @@ func TestBuildGoPkgInfo(t *testing.T) {
|
||||
Version: "v0.0.0-20221014195457-41bc6bb41035",
|
||||
PURL: "pkg:golang/github.com/anchore/syft@v0.0.0-20221014195457-41bc6bb41035",
|
||||
Locations: source.NewLocationSet(
|
||||
source.Location{
|
||||
Coordinates: source.Coordinates{
|
||||
source.NewLocationFromCoordinates(
|
||||
source.Coordinates{
|
||||
RealPath: "/a-path",
|
||||
FileSystemID: "layer-id",
|
||||
},
|
||||
},
|
||||
).WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
MetadataType: pkg.GolangBinMetadataType,
|
||||
Metadata: pkg.GolangBinMetadata{
|
||||
@@ -375,12 +375,12 @@ func TestBuildGoPkgInfo(t *testing.T) {
|
||||
Language: pkg.Go,
|
||||
Type: pkg.GoModulePkg,
|
||||
Locations: source.NewLocationSet(
|
||||
source.Location{
|
||||
Coordinates: source.Coordinates{
|
||||
source.NewLocationFromCoordinates(
|
||||
source.Coordinates{
|
||||
RealPath: "/a-path",
|
||||
FileSystemID: "layer-id",
|
||||
},
|
||||
},
|
||||
).WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
MetadataType: pkg.GolangBinMetadataType,
|
||||
Metadata: pkg.GolangBinMetadata{
|
||||
@@ -397,12 +397,12 @@ func TestBuildGoPkgInfo(t *testing.T) {
|
||||
Language: pkg.Go,
|
||||
Type: pkg.GoModulePkg,
|
||||
Locations: source.NewLocationSet(
|
||||
source.Location{
|
||||
Coordinates: source.Coordinates{
|
||||
source.NewLocationFromCoordinates(
|
||||
source.Coordinates{
|
||||
RealPath: "/a-path",
|
||||
FileSystemID: "layer-id",
|
||||
},
|
||||
},
|
||||
).WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
MetadataType: pkg.GolangBinMetadataType,
|
||||
Metadata: pkg.GolangBinMetadata{
|
||||
@@ -452,12 +452,12 @@ func TestBuildGoPkgInfo(t *testing.T) {
|
||||
Language: pkg.Go,
|
||||
Type: pkg.GoModulePkg,
|
||||
Locations: source.NewLocationSet(
|
||||
source.Location{
|
||||
Coordinates: source.Coordinates{
|
||||
source.NewLocationFromCoordinates(
|
||||
source.Coordinates{
|
||||
RealPath: "/a-path",
|
||||
FileSystemID: "layer-id",
|
||||
},
|
||||
},
|
||||
).WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
MetadataType: pkg.GolangBinMetadataType,
|
||||
Metadata: pkg.GolangBinMetadata{
|
||||
@@ -473,12 +473,12 @@ func TestBuildGoPkgInfo(t *testing.T) {
|
||||
Language: pkg.Go,
|
||||
Type: pkg.GoModulePkg,
|
||||
Locations: source.NewLocationSet(
|
||||
source.Location{
|
||||
Coordinates: source.Coordinates{
|
||||
source.NewLocationFromCoordinates(
|
||||
source.Coordinates{
|
||||
RealPath: "/a-path",
|
||||
FileSystemID: "layer-id",
|
||||
},
|
||||
},
|
||||
).WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
MetadataType: pkg.GolangBinMetadataType,
|
||||
Metadata: pkg.GolangBinMetadata{
|
||||
@@ -502,15 +502,15 @@ func TestBuildGoPkgInfo(t *testing.T) {
|
||||
}
|
||||
p.SetID()
|
||||
}
|
||||
location := source.Location{
|
||||
Coordinates: source.Coordinates{
|
||||
location := source.NewLocationFromCoordinates(
|
||||
source.Coordinates{
|
||||
RealPath: "/a-path",
|
||||
FileSystemID: "layer-id",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
c := goBinaryCataloger{}
|
||||
pkgs := c.buildGoPkgInfo(source.NewMockResolverForPaths(), location, test.mod, test.arch)
|
||||
pkgs := c.buildGoPkgInfo(source.EmptyResolver{}, location, test.mod, test.arch)
|
||||
assert.Equal(t, test.expected, pkgs)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ func (c *goModCataloger) parseGoModFile(resolver source.FileResolver, _ *generic
|
||||
Name: m.Mod.Path,
|
||||
Version: m.Mod.Version,
|
||||
Licenses: licenses,
|
||||
Locations: source.NewLocationSet(reader.Location),
|
||||
Locations: source.NewLocationSet(reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
|
||||
PURL: packageURL(m.Mod.Path, m.Mod.Version),
|
||||
Language: pkg.Go,
|
||||
Type: pkg.GoModulePkg,
|
||||
@@ -73,7 +73,7 @@ func (c *goModCataloger) parseGoModFile(resolver source.FileResolver, _ *generic
|
||||
Name: m.New.Path,
|
||||
Version: m.New.Version,
|
||||
Licenses: licenses,
|
||||
Locations: source.NewLocationSet(reader.Location),
|
||||
Locations: source.NewLocationSet(reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
|
||||
PURL: packageURL(m.New.Path, m.New.Version),
|
||||
Language: pkg.Go,
|
||||
Type: pkg.GoModulePkg,
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
package golang
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"path"
|
||||
"time"
|
||||
)
|
||||
|
||||
type subFS struct {
|
||||
root string
|
||||
f fs.FS
|
||||
}
|
||||
|
||||
func getSubFS(f fs.FS, root string) fs.FS {
|
||||
if s, ok := f.(fs.SubFS); ok {
|
||||
s, err := s.Sub(root)
|
||||
if err != nil {
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
return newSubFS(f, root)
|
||||
}
|
||||
|
||||
func newSubFS(f fs.FS, root string) fs.FS {
|
||||
return subFS{
|
||||
root: root,
|
||||
f: f,
|
||||
}
|
||||
}
|
||||
|
||||
func (s subFS) Open(name string) (fs.File, error) {
|
||||
if name == "." {
|
||||
return rootFile{
|
||||
s: s,
|
||||
}, nil
|
||||
}
|
||||
return s.f.Open(path.Join(s.root, name))
|
||||
}
|
||||
|
||||
type rootFile struct {
|
||||
s subFS
|
||||
}
|
||||
|
||||
func (r rootFile) Name() string {
|
||||
return "."
|
||||
}
|
||||
|
||||
func (r rootFile) Size() int64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (r rootFile) Mode() fs.FileMode {
|
||||
return fs.ModePerm
|
||||
}
|
||||
|
||||
func (r rootFile) ModTime() time.Time {
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
func (r rootFile) IsDir() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (r rootFile) Sys() any {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r rootFile) ReadDir(_ int) ([]fs.DirEntry, error) {
|
||||
return fs.ReadDir(r.s.f, r.s.root)
|
||||
}
|
||||
|
||||
func (r rootFile) Stat() (fs.FileInfo, error) {
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (r rootFile) Read(_ []byte) (int, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (r rootFile) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ fs.File = (*rootFile)(nil)
|
||||
var _ fs.ReadDirFile = (*rootFile)(nil)
|
||||
var _ fs.FileInfo = (*rootFile)(nil)
|
||||
|
||||
type subFsFileInfo struct {
|
||||
fi fs.FileInfo
|
||||
}
|
||||
|
||||
func (s subFsFileInfo) Name() string {
|
||||
return s.fi.Name()
|
||||
}
|
||||
|
||||
func (s subFsFileInfo) Size() int64 {
|
||||
return s.fi.Size()
|
||||
}
|
||||
|
||||
func (s subFsFileInfo) Mode() fs.FileMode {
|
||||
return s.fi.Mode()
|
||||
}
|
||||
|
||||
func (s subFsFileInfo) ModTime() time.Time {
|
||||
return s.fi.ModTime()
|
||||
}
|
||||
|
||||
func (s subFsFileInfo) IsDir() bool {
|
||||
return s.fi.IsDir()
|
||||
}
|
||||
|
||||
func (s subFsFileInfo) Sys() any {
|
||||
return s.fi.Sys()
|
||||
}
|
||||
|
||||
var _ fs.FileInfo = (*subFsFileInfo)(nil)
|
||||
@@ -0,0 +1,27 @@
|
||||
package golang
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_NewSubFS(t *testing.T) {
|
||||
f := os.DirFS("test-fixtures/zip-fs")
|
||||
f = newSubFS(f, "github.com/someorg/somepkg@version")
|
||||
var names []string
|
||||
err := fs.WalkDir(f, ".", func(path string, d fs.DirEntry, err error) error {
|
||||
names = append(names, path)
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
expected := []string{
|
||||
".",
|
||||
"a-file",
|
||||
"subdir",
|
||||
"subdir/subfile",
|
||||
}
|
||||
require.Equal(t, expected, names)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
Copyright (c) 2022
|
||||
Acme, inc.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
THIS SOFTWARE IS PROVIDED BY [Name of Organization] “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL [Name of Organisation] BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+1
@@ -0,0 +1 @@
|
||||
a file
|
||||
+1
@@ -0,0 +1 @@
|
||||
another file
|
||||
@@ -46,6 +46,14 @@ func parseCabalFreeze(_ source.FileResolver, _ *generic.Environment, reader sour
|
||||
fields := strings.Split(line, " ==")
|
||||
|
||||
pkgName, pkgVersion := fields[0], fields[1]
|
||||
pkgs = append(pkgs, newPackage(pkgName, pkgVersion, nil, reader.Location))
|
||||
pkgs = append(
|
||||
pkgs,
|
||||
newPackage(
|
||||
pkgName,
|
||||
pkgVersion,
|
||||
nil,
|
||||
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,10 +62,18 @@ func parseStackLock(_ source.FileResolver, _ *generic.Environment, reader source
|
||||
|
||||
for _, pack := range lockFile.Packages {
|
||||
pkgName, pkgVersion, pkgHash := parseStackPackageEncoding(pack.Completed.Hackage)
|
||||
pkgs = append(pkgs, newPackage(pkgName, pkgVersion, &pkg.HackageMetadata{
|
||||
PkgHash: pkgHash,
|
||||
SnapshotURL: snapshotURL,
|
||||
}, reader.Location))
|
||||
pkgs = append(
|
||||
pkgs,
|
||||
newPackage(
|
||||
pkgName,
|
||||
pkgVersion,
|
||||
&pkg.HackageMetadata{
|
||||
PkgHash: pkgHash,
|
||||
SnapshotURL: snapshotURL,
|
||||
},
|
||||
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
return pkgs, nil, nil
|
||||
|
||||
@@ -34,9 +34,17 @@ func parseStackYaml(_ source.FileResolver, _ *generic.Environment, reader source
|
||||
var pkgs []pkg.Package
|
||||
for _, dep := range stackFile.ExtraDeps {
|
||||
pkgName, pkgVersion, pkgHash := parseStackPackageEncoding(dep)
|
||||
pkgs = append(pkgs, newPackage(pkgName, pkgVersion, &pkg.HackageMetadata{
|
||||
PkgHash: pkgHash,
|
||||
}, reader.Location))
|
||||
pkgs = append(
|
||||
pkgs,
|
||||
newPackage(
|
||||
pkgName,
|
||||
pkgVersion,
|
||||
&pkg.HackageMetadata{
|
||||
PkgHash: pkgHash,
|
||||
},
|
||||
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
return pkgs, nil, nil
|
||||
|
||||
@@ -186,11 +186,13 @@ func (j *archiveParser) discoverMainPackage() (*pkg.Package, error) {
|
||||
}
|
||||
|
||||
return &pkg.Package{
|
||||
Name: selectName(manifest, j.fileInfo),
|
||||
Version: selectVersion(manifest, j.fileInfo),
|
||||
Licenses: selectLicense(manifest),
|
||||
Language: pkg.Java,
|
||||
Locations: source.NewLocationSet(j.location),
|
||||
Name: selectName(manifest, j.fileInfo),
|
||||
Version: selectVersion(manifest, j.fileInfo),
|
||||
Licenses: selectLicense(manifest),
|
||||
Language: pkg.Java,
|
||||
Locations: source.NewLocationSet(
|
||||
j.location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
Type: j.fileInfo.pkgType(),
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
@@ -380,9 +382,11 @@ func newPackageFromMavenData(pomProperties pkg.PomProperties, pomProject *pkg.Po
|
||||
|
||||
// discovered props = new package
|
||||
p := pkg.Package{
|
||||
Name: pomProperties.ArtifactID,
|
||||
Version: pomProperties.Version,
|
||||
Locations: source.NewLocationSet(location),
|
||||
Name: pomProperties.ArtifactID,
|
||||
Version: pomProperties.Version,
|
||||
Locations: source.NewLocationSet(
|
||||
location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
Language: pkg.Java,
|
||||
Type: pomProperties.PkgTypeIndicated(),
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
|
||||
@@ -49,9 +49,11 @@ func parseGradleLockfile(_ source.FileResolver, _ *generic.Environment, reader s
|
||||
// map the dependencies
|
||||
for _, dep := range dependencies {
|
||||
mappedPkg := pkg.Package{
|
||||
Name: dep.Name,
|
||||
Version: dep.Version,
|
||||
Locations: source.NewLocationSet(reader.Location),
|
||||
Name: dep.Name,
|
||||
Version: dep.Version,
|
||||
Locations: source.NewLocationSet(
|
||||
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JavaPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
|
||||
@@ -29,7 +29,11 @@ func parserPomXML(_ source.FileResolver, _ *generic.Environment, reader source.L
|
||||
|
||||
var pkgs []pkg.Package
|
||||
for _, dep := range pom.Dependencies {
|
||||
p := newPackageFromPom(pom, dep, reader.Location)
|
||||
p := newPackageFromPom(
|
||||
pom,
|
||||
dep,
|
||||
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
)
|
||||
if p.Name == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ func newPackageLockV1Package(resolver source.FileResolver, location source.Locat
|
||||
pkg.Package{
|
||||
Name: name,
|
||||
Version: version,
|
||||
Locations: source.NewLocationSet(location),
|
||||
Locations: source.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
|
||||
PURL: packageURL(name, version),
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
@@ -89,7 +89,7 @@ func newPackageLockV2Package(resolver source.FileResolver, location source.Locat
|
||||
pkg.Package{
|
||||
Name: name,
|
||||
Version: u.Version,
|
||||
Locations: source.NewLocationSet(location),
|
||||
Locations: source.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
|
||||
PURL: packageURL(name, u.Version),
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
@@ -107,7 +107,7 @@ func newPnpmPackage(resolver source.FileResolver, location source.Location, name
|
||||
pkg.Package{
|
||||
Name: name,
|
||||
Version: version,
|
||||
Locations: source.NewLocationSet(location),
|
||||
Locations: source.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
|
||||
PURL: packageURL(name, version),
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
@@ -122,7 +122,7 @@ func newYarnLockPackage(resolver source.FileResolver, location source.Location,
|
||||
pkg.Package{
|
||||
Name: name,
|
||||
Version: version,
|
||||
Locations: source.NewLocationSet(location),
|
||||
Locations: source.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
|
||||
PURL: packageURL(name, version),
|
||||
Language: pkg.JavaScript,
|
||||
Type: pkg.NpmPkg,
|
||||
|
||||
@@ -68,7 +68,10 @@ func parsePackageJSON(_ source.FileResolver, _ *generic.Environment, reader sour
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
pkgs = append(pkgs, newPackageJSONPackage(p, reader.Location))
|
||||
pkgs = append(
|
||||
pkgs,
|
||||
newPackageJSONPackage(p, reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
|
||||
)
|
||||
}
|
||||
|
||||
pkg.Sort(pkgs)
|
||||
|
||||
@@ -113,7 +113,10 @@ func parsePackageLock(resolver source.FileResolver, _ *generic.Environment, read
|
||||
name = pkgMeta.Name
|
||||
}
|
||||
|
||||
pkgs = append(pkgs, newPackageLockV2Package(resolver, reader.Location, getNameFromPath(name), pkgMeta))
|
||||
pkgs = append(
|
||||
pkgs,
|
||||
newPackageLockV2Package(resolver, reader.Location, getNameFromPath(name), pkgMeta),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
Package kernel provides a concrete Cataloger implementation for linux kernel and module files.
|
||||
*/
|
||||
package kernel
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/go-multierror"
|
||||
|
||||
"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"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
var _ pkg.Cataloger = (*LinuxKernelCataloger)(nil)
|
||||
|
||||
type LinuxCatalogerConfig struct {
|
||||
CatalogModules bool
|
||||
}
|
||||
|
||||
type LinuxKernelCataloger struct {
|
||||
cfg LinuxCatalogerConfig
|
||||
}
|
||||
|
||||
func DefaultLinuxCatalogerConfig() LinuxCatalogerConfig {
|
||||
return LinuxCatalogerConfig{
|
||||
CatalogModules: true,
|
||||
}
|
||||
}
|
||||
|
||||
var kernelArchiveGlobs = []string{
|
||||
"**/kernel",
|
||||
"**/kernel-*",
|
||||
"**/vmlinux",
|
||||
"**/vmlinux-*",
|
||||
"**/vmlinuz",
|
||||
"**/vmlinuz-*",
|
||||
}
|
||||
|
||||
var kernelModuleGlobs = []string{
|
||||
"**/lib/modules/**/*.ko",
|
||||
}
|
||||
|
||||
// NewLinuxKernelCataloger returns a new kernel files cataloger object.
|
||||
func NewLinuxKernelCataloger(cfg LinuxCatalogerConfig) *LinuxKernelCataloger {
|
||||
return &LinuxKernelCataloger{
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (l LinuxKernelCataloger) Name() string {
|
||||
return "linux-kernel-cataloger"
|
||||
}
|
||||
|
||||
func (l LinuxKernelCataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
var allPackages []pkg.Package
|
||||
var allRelationships []artifact.Relationship
|
||||
var errs error
|
||||
|
||||
kernelPackages, kernelRelationships, err := generic.NewCataloger(l.Name()).WithParserByGlobs(parseLinuxKernelFile, kernelArchiveGlobs...).Catalog(resolver)
|
||||
if err != nil {
|
||||
errs = multierror.Append(errs, err)
|
||||
}
|
||||
|
||||
allRelationships = append(allRelationships, kernelRelationships...)
|
||||
allPackages = append(allPackages, kernelPackages...)
|
||||
|
||||
if l.cfg.CatalogModules {
|
||||
modulePackages, moduleRelationships, err := generic.NewCataloger(l.Name()).WithParserByGlobs(parseLinuxKernelModuleFile, kernelModuleGlobs...).Catalog(resolver)
|
||||
if err != nil {
|
||||
errs = multierror.Append(errs, err)
|
||||
}
|
||||
|
||||
allPackages = append(allPackages, modulePackages...)
|
||||
|
||||
moduleToKernelRelationships := createKernelToModuleRelationships(kernelPackages, modulePackages)
|
||||
allRelationships = append(allRelationships, moduleRelationships...)
|
||||
allRelationships = append(allRelationships, moduleToKernelRelationships...)
|
||||
}
|
||||
|
||||
return allPackages, allRelationships, errs
|
||||
}
|
||||
|
||||
func createKernelToModuleRelationships(kernelPackages, modulePackages []pkg.Package) []artifact.Relationship {
|
||||
// organize kernel and module packages by kernel version
|
||||
kernelPackagesByVersion := make(map[string][]*pkg.Package)
|
||||
for idx, p := range kernelPackages {
|
||||
kernelPackagesByVersion[p.Version] = append(kernelPackagesByVersion[p.Version], &kernelPackages[idx])
|
||||
}
|
||||
|
||||
modulesByKernelVersion := make(map[string][]*pkg.Package)
|
||||
for idx, p := range modulePackages {
|
||||
m, ok := p.Metadata.(pkg.LinuxKernelModuleMetadata)
|
||||
if !ok {
|
||||
log.Debug("linux-kernel-module package found without metadata: %s@%s", p.Name, p.Version)
|
||||
continue
|
||||
}
|
||||
modulesByKernelVersion[m.KernelVersion] = append(modulesByKernelVersion[m.KernelVersion], &modulePackages[idx])
|
||||
}
|
||||
|
||||
// create relationships between kernel and modules: [module] --(depends on)--> [kernel]
|
||||
// since we try to use singular directions for relationships, we'll use "dependency of" here instead:
|
||||
// [kernel] --(dependency of)--> [module]
|
||||
var moduleToKernelRelationships []artifact.Relationship
|
||||
for kernelVersion, modules := range modulesByKernelVersion {
|
||||
kps, ok := kernelPackagesByVersion[kernelVersion]
|
||||
if !ok {
|
||||
// it's ok if there is a module that has no installed kernel...
|
||||
continue
|
||||
}
|
||||
|
||||
// we don't know which kernel is the "right" one, so we'll create a relationship for each one
|
||||
for _, kp := range kps {
|
||||
for _, mp := range modules {
|
||||
moduleToKernelRelationships = append(moduleToKernelRelationships, artifact.Relationship{
|
||||
// note: relationships should have Package objects, not pointers
|
||||
From: *kp,
|
||||
To: *mp,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return moduleToKernelRelationships
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package kernel
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func Test_KernelCataloger(t *testing.T) {
|
||||
kernelPkg := pkg.Package{
|
||||
Name: "linux-kernel",
|
||||
Version: "6.0.7-301.fc37.x86_64",
|
||||
FoundBy: "linux-kernel-cataloger",
|
||||
Locations: source.NewLocationSet(
|
||||
source.NewVirtualLocation(
|
||||
"/lib/modules/6.0.7-301.fc37.x86_64/vmlinuz",
|
||||
"/lib/modules/6.0.7-301.fc37.x86_64/vmlinuz",
|
||||
),
|
||||
),
|
||||
Type: pkg.LinuxKernelPkg,
|
||||
PURL: "pkg:generic/linux-kernel@6.0.7-301.fc37.x86_64",
|
||||
MetadataType: pkg.LinuxKernelMetadataType,
|
||||
Metadata: pkg.LinuxKernelMetadata{
|
||||
Name: "",
|
||||
Architecture: "x86",
|
||||
Version: "6.0.7-301.fc37.x86_64",
|
||||
ExtendedVersion: "6.0.7-301.fc37.x86_64 (mockbuild@bkernel01.iad2.fedoraproject.org) #1 SMP PREEMPT_DYNAMIC Fri Nov 4 18:35:48 UTC 2022",
|
||||
BuildTime: "",
|
||||
Author: "",
|
||||
Format: "bzImage",
|
||||
RWRootFS: false,
|
||||
SwapDevice: 0,
|
||||
RootDevice: 0,
|
||||
VideoMode: "Video mode 65535",
|
||||
},
|
||||
}
|
||||
|
||||
kernelModulePkg := pkg.Package{
|
||||
Name: "ttynull",
|
||||
Version: "",
|
||||
FoundBy: "linux-kernel-cataloger",
|
||||
Locations: source.NewLocationSet(
|
||||
source.NewVirtualLocation("/lib/modules/6.0.7-301.fc37.x86_64/kernel/drivers/tty/ttynull.ko",
|
||||
"/lib/modules/6.0.7-301.fc37.x86_64/kernel/drivers/tty/ttynull.ko",
|
||||
),
|
||||
),
|
||||
Licenses: []string{
|
||||
"GPL v2",
|
||||
},
|
||||
Type: pkg.LinuxKernelModulePkg,
|
||||
PURL: "pkg:generic/ttynull",
|
||||
MetadataType: pkg.LinuxKernelModuleMetadataType,
|
||||
Metadata: pkg.LinuxKernelModuleMetadata{
|
||||
Name: "ttynull",
|
||||
Version: "",
|
||||
SourceVersion: "",
|
||||
License: "GPL v2",
|
||||
Path: "/lib/modules/6.0.7-301.fc37.x86_64/kernel/drivers/tty/ttynull.ko",
|
||||
Description: "",
|
||||
KernelVersion: "6.0.7-301.fc37.x86_64",
|
||||
VersionMagic: "6.0.7-301.fc37.x86_64 SMP preempt mod_unload ",
|
||||
Parameters: map[string]pkg.LinuxKernelModuleParameter{},
|
||||
},
|
||||
}
|
||||
|
||||
expectedPkgs := []pkg.Package{
|
||||
kernelPkg,
|
||||
kernelModulePkg,
|
||||
}
|
||||
expectedRelationships := []artifact.Relationship{
|
||||
{
|
||||
From: kernelPkg,
|
||||
To: kernelModulePkg,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
}
|
||||
|
||||
pkgtest.NewCatalogTester().
|
||||
WithImageResolver(t, "image-kernel-and-modules").
|
||||
IgnoreLocationLayer().
|
||||
Expects(expectedPkgs, expectedRelationships).
|
||||
TestCataloger(t,
|
||||
NewLinuxKernelCataloger(
|
||||
LinuxCatalogerConfig{
|
||||
CatalogModules: true,
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package kernel
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
const linuxKernelPackageName = "linux-kernel"
|
||||
|
||||
func newLinuxKernelPackage(metadata pkg.LinuxKernelMetadata, locations ...source.Location) pkg.Package {
|
||||
p := pkg.Package{
|
||||
Name: linuxKernelPackageName,
|
||||
Version: metadata.Version,
|
||||
Locations: source.NewLocationSet(locations...),
|
||||
PURL: packageURL(linuxKernelPackageName, metadata.Version),
|
||||
Type: pkg.LinuxKernelPkg,
|
||||
MetadataType: pkg.LinuxKernelMetadataType,
|
||||
Metadata: metadata,
|
||||
}
|
||||
|
||||
p.SetID()
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func newLinuxKernelModulePackage(metadata pkg.LinuxKernelModuleMetadata, locations ...source.Location) pkg.Package {
|
||||
var licenses []string
|
||||
if metadata.License != "" {
|
||||
licenses = []string{metadata.License}
|
||||
} else {
|
||||
licenses = []string{}
|
||||
}
|
||||
|
||||
p := pkg.Package{
|
||||
Name: metadata.Name,
|
||||
Version: metadata.Version,
|
||||
Locations: source.NewLocationSet(locations...),
|
||||
Licenses: licenses,
|
||||
PURL: packageURL(metadata.Name, metadata.Version),
|
||||
Type: pkg.LinuxKernelModulePkg,
|
||||
MetadataType: pkg.LinuxKernelModuleMetadataType,
|
||||
Metadata: metadata,
|
||||
}
|
||||
|
||||
p.SetID()
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
// packageURL returns the PURL for the specific Kernel package (see https://github.com/package-url/purl-spec)
|
||||
func packageURL(name, version string) string {
|
||||
var namespace string
|
||||
|
||||
fields := strings.SplitN(name, "/", 2)
|
||||
if len(fields) > 1 {
|
||||
namespace = fields[0]
|
||||
name = fields[1]
|
||||
}
|
||||
|
||||
return packageurl.NewPackageURL(
|
||||
packageurl.TypeGeneric,
|
||||
namespace,
|
||||
name,
|
||||
version,
|
||||
nil,
|
||||
"",
|
||||
).ToString()
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package kernel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/deitch/magic/pkg/magic"
|
||||
|
||||
"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"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/unionreader"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
const linuxKernelMagicName = "Linux kernel"
|
||||
|
||||
func parseLinuxKernelFile(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
unionReader, err := unionreader.GetUnionReader(reader)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to get union reader for file: %w", err)
|
||||
}
|
||||
magicType, err := magic.GetType(unionReader)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to get magic type for file: %w", err)
|
||||
}
|
||||
if len(magicType) < 1 || magicType[0] != linuxKernelMagicName {
|
||||
return nil, nil, nil
|
||||
}
|
||||
metadata := parseLinuxKernelMetadata(magicType)
|
||||
if metadata.Version == "" {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
return []pkg.Package{
|
||||
newLinuxKernelPackage(
|
||||
metadata,
|
||||
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
}, nil, nil
|
||||
}
|
||||
|
||||
func parseLinuxKernelMetadata(magicType []string) (p pkg.LinuxKernelMetadata) {
|
||||
// Linux kernel x86 boot executable bzImage,
|
||||
// version 5.10.121-linuxkit (root@buildkitsandbox) #1 SMP Fri Dec 2 10:35:42 UTC 2022,
|
||||
// RO-rootFS,
|
||||
// swap_dev 0XA,
|
||||
// Normal VGA
|
||||
for _, t := range magicType {
|
||||
switch {
|
||||
case strings.HasPrefix(t, "x86 "):
|
||||
p.Architecture = "x86"
|
||||
case strings.Contains(t, "ARM64 "):
|
||||
p.Architecture = "arm64"
|
||||
case strings.Contains(t, "ARM "):
|
||||
p.Architecture = "arm"
|
||||
case t == "bzImage":
|
||||
p.Format = "bzImage"
|
||||
case t == "zImage":
|
||||
p.Format = "zImage"
|
||||
case strings.HasPrefix(t, "version "):
|
||||
p.ExtendedVersion = strings.TrimPrefix(t, "version ")
|
||||
fields := strings.Fields(p.ExtendedVersion)
|
||||
if len(fields) > 0 {
|
||||
p.Version = fields[0]
|
||||
}
|
||||
case strings.Contains(t, "rootFS") && strings.HasPrefix(t, "RW-"):
|
||||
p.RWRootFS = true
|
||||
case strings.HasPrefix(t, "swap_dev "):
|
||||
swapDevStr := strings.TrimPrefix(t, "swap_dev ")
|
||||
swapDev, err := strconv.ParseInt(swapDevStr, 16, 32)
|
||||
if err != nil {
|
||||
log.Warnf("unable to parse swap device: %s", err)
|
||||
continue
|
||||
}
|
||||
p.SwapDevice = int(swapDev)
|
||||
case strings.HasPrefix(t, "root_dev "):
|
||||
rootDevStr := strings.TrimPrefix(t, "root_dev ")
|
||||
rootDev, err := strconv.ParseInt(rootDevStr, 16, 32)
|
||||
if err != nil {
|
||||
log.Warnf("unable to parse root device: %s", err)
|
||||
continue
|
||||
}
|
||||
p.SwapDevice = int(rootDev)
|
||||
case strings.Contains(t, "VGA") || strings.Contains(t, "Video"):
|
||||
p.VideoMode = t
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package kernel
|
||||
|
||||
import (
|
||||
"debug/elf"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/internal/unionreader"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
const modinfoName = ".modinfo"
|
||||
|
||||
func parseLinuxKernelModuleFile(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||
unionReader, err := unionreader.GetUnionReader(reader)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to get union reader for file: %w", err)
|
||||
}
|
||||
metadata, err := parseLinuxKernelModuleMetadata(unionReader)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to parse kernel module metadata: %w", err)
|
||||
}
|
||||
if metadata == nil || metadata.KernelVersion == "" {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
metadata.Path = reader.Location.RealPath
|
||||
|
||||
return []pkg.Package{
|
||||
newLinuxKernelModulePackage(
|
||||
*metadata,
|
||||
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
}, nil, nil
|
||||
}
|
||||
|
||||
func parseLinuxKernelModuleMetadata(r unionreader.UnionReader) (p *pkg.LinuxKernelModuleMetadata, err error) {
|
||||
// filename: /lib/modules/5.15.0-1031-aws/kernel/zfs/zzstd.ko
|
||||
// version: 1.4.5a
|
||||
// license: Dual BSD/GPL
|
||||
// description: ZSTD Compression for ZFS
|
||||
// srcversion: F1F818A6E016499AB7F826E
|
||||
// depends: spl
|
||||
// retpoline: Y
|
||||
// name: zzstd
|
||||
// vermagic: 5.15.0-1031-aws SMP mod_unload modversions
|
||||
// sig_id: PKCS#7
|
||||
// signer: Build time autogenerated kernel key
|
||||
// sig_key: 49:A9:55:87:90:5B:33:41:AF:C0:A7:BE:2A:71:6C:D2:CA:34:E0:AE
|
||||
// sig_hashalgo: sha512
|
||||
//
|
||||
// OR
|
||||
//
|
||||
// filename: /home/ubuntu/eve/rootfs/lib/modules/5.10.121-linuxkit/kernel/drivers/net/wireless/realtek/rtl8821cu/8821cu.ko
|
||||
// version: v5.4.1_28754.20180921_COEX20180712-3232
|
||||
// author: Realtek Semiconductor Corp.
|
||||
// description: Realtek Wireless Lan Driver
|
||||
// license: GPL
|
||||
// srcversion: 960CCC648A0E0369171A2C9
|
||||
// depends: cfg80211
|
||||
// retpoline: Y
|
||||
// name: 8821cu
|
||||
// vermagic: 5.10.121-linuxkit SMP mod_unload
|
||||
p = &pkg.LinuxKernelModuleMetadata{
|
||||
Parameters: make(map[string]pkg.LinuxKernelModuleParameter),
|
||||
}
|
||||
f, err := elf.NewFile(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
modinfo := f.Section(modinfoName)
|
||||
if modinfo == nil {
|
||||
return nil, fmt.Errorf("no section %s", modinfoName)
|
||||
}
|
||||
b, err := modinfo.Data()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading secion %s: %w", modinfoName, err)
|
||||
}
|
||||
var (
|
||||
entry []byte
|
||||
)
|
||||
for _, b2 := range b {
|
||||
if b2 == 0 {
|
||||
if err := addLinuxKernelModuleEntry(p, entry); err != nil {
|
||||
return nil, fmt.Errorf("error parsing entry %s: %w", string(entry), err)
|
||||
}
|
||||
entry = []byte{}
|
||||
continue
|
||||
}
|
||||
entry = append(entry, b2)
|
||||
}
|
||||
if err := addLinuxKernelModuleEntry(p, entry); err != nil {
|
||||
return nil, fmt.Errorf("error parsing entry %s: %w", string(entry), err)
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func addLinuxKernelModuleEntry(k *pkg.LinuxKernelModuleMetadata, entry []byte) error {
|
||||
if len(entry) == 0 {
|
||||
return nil
|
||||
}
|
||||
var key, value string
|
||||
parts := strings.SplitN(string(entry), "=", 2)
|
||||
if len(parts) > 0 {
|
||||
key = parts[0]
|
||||
}
|
||||
if len(parts) > 1 {
|
||||
value = parts[1]
|
||||
}
|
||||
|
||||
switch key {
|
||||
case "version":
|
||||
k.Version = value
|
||||
case "license":
|
||||
k.License = value
|
||||
case "author":
|
||||
k.Author = value
|
||||
case "name":
|
||||
k.Name = value
|
||||
case "vermagic":
|
||||
k.VersionMagic = value
|
||||
fields := strings.Fields(value)
|
||||
if len(fields) > 0 {
|
||||
k.KernelVersion = fields[0]
|
||||
}
|
||||
case "srcversion":
|
||||
k.SourceVersion = value
|
||||
case "description":
|
||||
k.Description = value
|
||||
case "parm":
|
||||
parts := strings.SplitN(value, ":", 2)
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("invalid parm entry: %s", value)
|
||||
}
|
||||
if m, ok := k.Parameters[parts[0]]; !ok {
|
||||
k.Parameters[parts[0]] = pkg.LinuxKernelModuleParameter{Description: parts[1]}
|
||||
} else {
|
||||
m.Description = parts[1]
|
||||
}
|
||||
case "parmtype":
|
||||
parts := strings.SplitN(value, ":", 2)
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("invalid parmtype entry: %s", value)
|
||||
}
|
||||
if m, ok := k.Parameters[parts[0]]; !ok {
|
||||
k.Parameters[parts[0]] = pkg.LinuxKernelModuleParameter{Type: parts[1]}
|
||||
} else {
|
||||
m.Type = parts[1]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
all:
|
||||
|
||||
# we need a way to determine if CI should bust the test cache based on the source material
|
||||
.PHONY: cache.fingerprint
|
||||
cache.fingerprint:
|
||||
find Makefile **/Dockerfile -type f -exec sha256sum {} \; | sort | tee /dev/stderr | tee cache.fingerprint
|
||||
sha256sum cache.fingerprint
|
||||
@@ -0,0 +1,19 @@
|
||||
FROM fedora:37@sha256:3f987b7657e944cf87a129cc262982d4f80e38bd98f7db313ccaf90ca7069dd2
|
||||
|
||||
RUN dnf install 'dnf-command(download)' cpio xz -y
|
||||
# https://dl.fedoraproject.org/pub/fedora/linux/releases/37/Everything/x86_64/os/Packages/k/kernel-6.0.7-301.fc37.x86_64.rpm
|
||||
# https://dl.fedoraproject.org/pub/fedora/linux/releases/37/Everything/x86_64/os/Packages/k/kernel-modules-6.0.7-301.fc37.x86_64.rpm
|
||||
RUN dnf download kernel-core-6.0.7-301.fc37 kernel-modules-6.0.7-301.fc37 -y
|
||||
|
||||
RUN rpm2cpio kernel-core-*.rpm | cpio -t && \
|
||||
rpm2cpio kernel-core-*.rpm | cpio -idmv ./lib/modules/6.0.7-301.fc37.x86_64/vmlinuz
|
||||
|
||||
RUN rpm2cpio kernel-modules-*.rpm | cpio -t && \
|
||||
rpm2cpio kernel-modules-*.rpm | cpio -idmv ./lib/modules/6.0.7-301.fc37.x86_64/kernel/drivers/tty/ttynull.ko.xz
|
||||
|
||||
RUN unxz /lib/modules/6.0.7-301.fc37.x86_64/kernel/drivers/tty/ttynull.ko.xz
|
||||
|
||||
FROM scratch
|
||||
|
||||
COPY --from=0 /lib/modules/6.0.7-301.fc37.x86_64/vmlinuz /lib/modules/6.0.7-301.fc37.x86_64/vmlinuz
|
||||
COPY --from=0 /lib/modules/6.0.7-301.fc37.x86_64/kernel/drivers/tty/ttynull.ko /lib/modules/6.0.7-301.fc37.x86_64/kernel/drivers/tty/ttynull.ko
|
||||
@@ -56,7 +56,7 @@ func (c *StoreCataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, [
|
||||
continue
|
||||
}
|
||||
|
||||
p := newNixStorePackage(*storePath, location)
|
||||
p := newNixStorePackage(*storePath, location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation))
|
||||
pkgs = append(pkgs, p)
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,13 @@ func parseComposerLock(_ source.FileResolver, _ *generic.Environment, reader sou
|
||||
return nil, nil, fmt.Errorf("failed to parse composer.lock file: %w", err)
|
||||
}
|
||||
for _, m := range lock.Packages {
|
||||
pkgs = append(pkgs, newComposerLockPackage(m, reader.Location))
|
||||
pkgs = append(
|
||||
pkgs,
|
||||
newComposerLockPackage(
|
||||
m,
|
||||
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,12 @@ func parseInstalledJSON(_ source.FileResolver, _ *generic.Environment, reader so
|
||||
return nil, nil, fmt.Errorf("failed to parse installed.json file: %w", err)
|
||||
}
|
||||
for _, pkgMeta := range lock.Packages {
|
||||
pkgs = append(pkgs, newComposerLockPackage(pkgMeta, reader.Location))
|
||||
pkgs = append(
|
||||
pkgs,
|
||||
newComposerLockPackage(pkgMeta,
|
||||
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,10 +37,12 @@ func parsePortageContents(resolver source.FileResolver, _ *generic.Environment,
|
||||
}
|
||||
|
||||
p := pkg.Package{
|
||||
Name: name,
|
||||
Version: version,
|
||||
PURL: packageURL(name, version),
|
||||
Locations: source.NewLocationSet(),
|
||||
Name: name,
|
||||
Version: version,
|
||||
PURL: packageURL(name, version),
|
||||
Locations: source.NewLocationSet(
|
||||
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
Type: pkg.PortagePkg,
|
||||
MetadataType: pkg.PortageMetadataType,
|
||||
Metadata: pkg.PortageMetadata{
|
||||
@@ -117,7 +119,7 @@ func addLicenses(resolver source.FileResolver, dbLocation source.Location, p *pk
|
||||
licenses := findings.ToSlice()
|
||||
sort.Strings(licenses)
|
||||
p.Licenses = licenses
|
||||
p.Locations.Add(*location)
|
||||
p.Locations.Add(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation))
|
||||
}
|
||||
|
||||
func addSize(resolver source.FileResolver, dbLocation source.Location, p *pkg.Package) {
|
||||
@@ -150,5 +152,5 @@ func addSize(resolver source.FileResolver, dbLocation source.Location, p *pkg.Pa
|
||||
}
|
||||
|
||||
p.Metadata = entry
|
||||
p.Locations.Add(*location)
|
||||
p.Locations.Add(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation))
|
||||
}
|
||||
|
||||
@@ -39,7 +39,14 @@ func parsePoetryLock(_ source.FileResolver, _ *generic.Environment, reader sourc
|
||||
|
||||
var pkgs []pkg.Package
|
||||
for _, p := range metadata.Packages {
|
||||
pkgs = append(pkgs, newPackageForIndex(p.Name, p.Version, reader.Location))
|
||||
pkgs = append(
|
||||
pkgs,
|
||||
newPackageForIndex(
|
||||
p.Name,
|
||||
p.Version,
|
||||
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
return pkgs, nil, nil
|
||||
|
||||
@@ -61,7 +61,14 @@ func parseRequirementsTxt(_ source.FileResolver, _ *generic.Environment, reader
|
||||
log.WithFields("path", reader.RealPath).Debugf("found empty package in requirements.txt line: %q", line)
|
||||
continue
|
||||
}
|
||||
packages = append(packages, newPackageForIndex(name, version, reader.Location))
|
||||
packages = append(
|
||||
packages,
|
||||
newPackageForIndex(
|
||||
name,
|
||||
version,
|
||||
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
|
||||
@@ -53,7 +53,14 @@ func parseSetup(_ source.FileResolver, _ *generic.Environment, reader source.Loc
|
||||
continue
|
||||
}
|
||||
|
||||
packages = append(packages, newPackageForIndex(name, version, reader.Location))
|
||||
packages = append(
|
||||
packages,
|
||||
newPackageForIndex(
|
||||
name,
|
||||
version,
|
||||
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ func fetchInstalledFiles(resolver source.FileResolver, metadataLocation source.L
|
||||
installedFilesRef := resolver.RelativeFileByPath(metadataLocation, installedFilesPath)
|
||||
|
||||
if installedFilesRef != nil {
|
||||
sources = append(sources, *installedFilesRef)
|
||||
sources = append(sources, installedFilesRef.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation))
|
||||
|
||||
installedFilesContents, err := resolver.FileContentsByLocation(*installedFilesRef)
|
||||
if err != nil {
|
||||
@@ -78,7 +78,7 @@ func fetchRecordFiles(resolver source.FileResolver, metadataLocation source.Loca
|
||||
recordRef := resolver.RelativeFileByPath(metadataLocation, recordPath)
|
||||
|
||||
if recordRef != nil {
|
||||
sources = append(sources, *recordRef)
|
||||
sources = append(sources, recordRef.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation))
|
||||
|
||||
recordContents, err := resolver.FileContentsByLocation(*recordRef)
|
||||
if err != nil {
|
||||
@@ -105,7 +105,7 @@ func fetchTopLevelPackages(resolver source.FileResolver, metadataLocation source
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
sources = append(sources, *topLevelLocation)
|
||||
sources = append(sources, topLevelLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation))
|
||||
|
||||
topLevelContents, err := resolver.FileContentsByLocation(*topLevelLocation)
|
||||
if err != nil {
|
||||
@@ -134,7 +134,7 @@ func fetchDirectURLData(resolver source.FileResolver, metadataLocation source.Lo
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
sources = append(sources, *directURLLocation)
|
||||
sources = append(sources, directURLLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation))
|
||||
|
||||
directURLContents, err := resolver.FileContentsByLocation(*directURLLocation)
|
||||
if err != nil {
|
||||
@@ -161,7 +161,9 @@ func fetchDirectURLData(resolver source.FileResolver, metadataLocation source.Lo
|
||||
|
||||
// assembleEggOrWheelMetadata discovers and accumulates python package metadata from multiple file sources and returns a single metadata object as well as a list of files where the metadata was derived from.
|
||||
func assembleEggOrWheelMetadata(resolver source.FileResolver, metadataLocation source.Location) (*pkg.PythonPackageMetadata, []source.Location, error) {
|
||||
var sources = []source.Location{metadataLocation}
|
||||
var sources = []source.Location{
|
||||
metadataLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
}
|
||||
|
||||
metadataContents, err := resolver.FileContentsByLocation(metadataLocation)
|
||||
if err != nil {
|
||||
|
||||
@@ -13,12 +13,12 @@ import (
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func newPackage(dbLocation source.Location, metadata pkg.RpmMetadata, distro *linux.Release) pkg.Package {
|
||||
func newPackage(location source.Location, metadata pkg.RpmMetadata, distro *linux.Release) pkg.Package {
|
||||
p := pkg.Package{
|
||||
Name: metadata.Name,
|
||||
Version: toELVersion(metadata),
|
||||
PURL: packageURL(metadata, distro),
|
||||
Locations: source.NewLocationSet(dbLocation),
|
||||
Locations: source.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
|
||||
Type: pkg.RpmPkg,
|
||||
MetadataType: pkg.RpmMetadataType,
|
||||
Metadata: metadata,
|
||||
|
||||
@@ -44,7 +44,7 @@ func parseGemFileLockEntries(_ source.FileResolver, _ *generic.Environment, read
|
||||
newGemfileLockPackage(
|
||||
candidate[0],
|
||||
strings.Trim(candidate[1], "()"),
|
||||
reader.Location,
|
||||
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -95,7 +95,10 @@ func parseGemSpecEntries(_ source.FileResolver, _ *generic.Environment, reader s
|
||||
return nil, nil, fmt.Errorf("unable to decode gem metadata: %w", err)
|
||||
}
|
||||
|
||||
pkgs = append(pkgs, newGemspecPackage(metadata, reader.Location))
|
||||
pkgs = append(
|
||||
pkgs,
|
||||
newGemspecPackage(metadata, reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
|
||||
)
|
||||
}
|
||||
|
||||
return pkgs, nil, nil
|
||||
|
||||
@@ -31,7 +31,7 @@ func newPackagesFromAudit(location source.Location, versionInfo rustaudit.Versio
|
||||
|
||||
for _, dep := range versionInfo.Packages {
|
||||
dep := dep
|
||||
p := newPackageFromAudit(&dep, location)
|
||||
p := newPackageFromAudit(&dep, location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation))
|
||||
if pkg.IsValid(&p) && dep.Kind == rustaudit.Runtime {
|
||||
pkgs = append(pkgs, p)
|
||||
}
|
||||
|
||||
@@ -36,7 +36,13 @@ func parseCargoLock(_ source.FileResolver, _ *generic.Environment, reader source
|
||||
if p.Dependencies == nil {
|
||||
p.Dependencies = make([]string, 0)
|
||||
}
|
||||
pkgs = append(pkgs, newPackageFromCargoMetadata(p, reader.Location))
|
||||
pkgs = append(
|
||||
pkgs,
|
||||
newPackageFromCargoMetadata(
|
||||
p,
|
||||
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
return pkgs, nil, nil
|
||||
|
||||
@@ -47,7 +47,9 @@ func parseSBOM(_ source.FileResolver, _ *generic.Environment, reader source.Loca
|
||||
// Why not keep the original list of locations? Since the "locations" field is meant to capture
|
||||
// where there is evidence of this file, and the catalogers have not run against any file other than,
|
||||
// the SBOM, this is the only location that is relevant for this cataloger.
|
||||
p.Locations = source.NewLocationSet(reader.Location)
|
||||
p.Locations = source.NewLocationSet(
|
||||
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
)
|
||||
p.FoundBy = catalogerName
|
||||
|
||||
pkgs = append(pkgs, p)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user