mirror of
https://github.com/anchore/syft.git
synced 2026-10-12 06:07:20 +02:00
Compare commits
31
Commits
@@ -10,11 +10,11 @@ CHRONICLE_CMD = $(TEMP_DIR)/chronicle
|
||||
GLOW_CMD = $(TEMP_DIR)/glow
|
||||
|
||||
# Tool versions #################################
|
||||
GOLANGCILINT_VERSION := v1.53.3
|
||||
GOLANGCILINT_VERSION := v1.54.1
|
||||
GOSIMPORTS_VERSION := v0.3.8
|
||||
BOUNCER_VERSION := v0.4.0
|
||||
CHRONICLE_VERSION := v0.6.0
|
||||
GORELEASER_VERSION := v1.19.2
|
||||
CHRONICLE_VERSION := v0.7.0
|
||||
GORELEASER_VERSION := v1.20.0
|
||||
YAJSV_VERSION := v1.4.1
|
||||
COSIGN_VERSION := v2.1.1
|
||||
QUILL_VERSION := v0.2.0
|
||||
|
||||
@@ -482,6 +482,10 @@ default-image-pull-source: ""
|
||||
# - "./out/**/*.json"
|
||||
exclude: []
|
||||
|
||||
# allows users to exclude synthetic binary packages from the sbom
|
||||
# these packages are removed if an overlap with a non-synthetic package is found
|
||||
exclude-binary-overlap-by-ownership: true
|
||||
|
||||
# os and/or architecture to use when referencing container images (e.g. "windows/armv6" or "arm64")
|
||||
# same as --platform; SYFT_PLATFORM env var
|
||||
platform: ""
|
||||
|
||||
@@ -37,13 +37,13 @@ require (
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/spf13/viper v1.16.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
github.com/vifraa/gopom v0.2.2
|
||||
github.com/vifraa/gopom v1.0.0
|
||||
github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651
|
||||
github.com/wagoodman/go-progress v0.0.0-20230301185719-21920a456ad5
|
||||
github.com/xeipuuv/gojsonschema v1.2.0
|
||||
golang.org/x/mod v0.12.0
|
||||
golang.org/x/net v0.12.0
|
||||
golang.org/x/term v0.10.0
|
||||
golang.org/x/net v0.14.0
|
||||
golang.org/x/term v0.11.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
@@ -51,14 +51,14 @@ require (
|
||||
github.com/CycloneDX/cyclonedx-go v0.7.1
|
||||
github.com/Masterminds/semver v1.5.0
|
||||
github.com/Masterminds/sprig/v3 v3.2.3
|
||||
github.com/anchore/bubbly v0.0.0-20230712165553-812110ab0a10
|
||||
github.com/anchore/bubbly v0.0.0-20230801194016-acdb4981b461
|
||||
github.com/anchore/clio v0.0.0-20230602170917-e747e60c4aa0
|
||||
github.com/anchore/go-logger v0.0.0-20230531193951-db5ae83e7dbe
|
||||
github.com/anchore/stereoscope v0.0.0-20230724160817-d515761c6ca2
|
||||
github.com/anchore/stereoscope v0.0.0-20230727211946-d1f3d766295e
|
||||
github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46
|
||||
github.com/charmbracelet/bubbletea v0.24.2
|
||||
github.com/charmbracelet/lipgloss v0.7.1
|
||||
github.com/dave/jennifer v1.6.1
|
||||
github.com/dave/jennifer v1.7.0
|
||||
github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da
|
||||
github.com/docker/distribution v2.8.2+incompatible
|
||||
github.com/docker/docker v24.0.5+incompatible
|
||||
@@ -66,7 +66,7 @@ require (
|
||||
github.com/gkampitakis/go-snaps v0.4.7
|
||||
github.com/go-git/go-billy/v5 v5.4.1
|
||||
github.com/go-git/go-git/v5 v5.8.1
|
||||
github.com/google/go-containerregistry v0.15.2
|
||||
github.com/google/go-containerregistry v0.16.1
|
||||
github.com/google/licensecheck v0.3.1
|
||||
github.com/invopop/jsonschema v0.7.0
|
||||
github.com/knqyf263/go-rpmdb v0.0.0-20230301153543-ba94b245509b
|
||||
@@ -77,7 +77,7 @@ require (
|
||||
github.com/zyedidia/generic v1.2.2-0.20230320175451-4410d2372cb1
|
||||
golang.org/x/exp v0.0.0-20230202163644-54bba9f4231b
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
modernc.org/sqlite v1.24.0
|
||||
modernc.org/sqlite v1.25.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -101,7 +101,7 @@ require (
|
||||
github.com/containerd/containerd v1.7.0 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/docker/cli v23.0.5+incompatible // indirect
|
||||
github.com/docker/cli v24.0.0+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.7.0 // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
@@ -178,10 +178,10 @@ require (
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
|
||||
go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.org/x/sys v0.10.0 // indirect
|
||||
golang.org/x/text v0.11.0 // indirect
|
||||
golang.org/x/tools v0.8.0 // indirect
|
||||
golang.org/x/sync v0.2.0 // indirect
|
||||
golang.org/x/sys v0.11.0 // indirect
|
||||
golang.org/x/text v0.12.0 // indirect
|
||||
golang.org/x/tools v0.9.1 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
|
||||
google.golang.org/grpc v1.55.0 // indirect
|
||||
@@ -191,9 +191,9 @@ require (
|
||||
lukechampine.com/uint128 v1.2.0 // indirect
|
||||
modernc.org/cc/v3 v3.40.0 // indirect
|
||||
modernc.org/ccgo/v3 v3.16.13 // indirect
|
||||
modernc.org/libc v1.22.5 // indirect
|
||||
modernc.org/libc v1.24.1 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.5.0 // indirect
|
||||
modernc.org/memory v1.6.0 // indirect
|
||||
modernc.org/opt v0.1.3 // indirect
|
||||
modernc.org/strutil v1.1.3 // indirect
|
||||
modernc.org/token v1.0.1 // indirect
|
||||
@@ -204,7 +204,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.11.0 // indirect
|
||||
golang.org/x/crypto v0.12.0 // indirect
|
||||
)
|
||||
|
||||
retract (
|
||||
|
||||
@@ -88,8 +88,8 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/anchore/bubbly v0.0.0-20230712165553-812110ab0a10 h1:Wrqt9fd8ygEMyFtxncZU7RgW2qBu5CL1x876xIyUlPU=
|
||||
github.com/anchore/bubbly v0.0.0-20230712165553-812110ab0a10/go.mod h1:Ger02eh5NpPm2IqkPAy396HU1KlK3BhOeCljDYXySSk=
|
||||
github.com/anchore/bubbly v0.0.0-20230801194016-acdb4981b461 h1:xGu4/uMWucwWV0YV3fpFIQZ6KVfS/Wfhmma8t0s0vRo=
|
||||
github.com/anchore/bubbly v0.0.0-20230801194016-acdb4981b461/go.mod h1:Ger02eh5NpPm2IqkPAy396HU1KlK3BhOeCljDYXySSk=
|
||||
github.com/anchore/clio v0.0.0-20230602170917-e747e60c4aa0 h1:g0UqRW60JDrf5fb40RUyIwwcfQ3nAJqGj4aUCVTwFE4=
|
||||
github.com/anchore/clio v0.0.0-20230602170917-e747e60c4aa0/go.mod h1:0IQVIROfgRX4WZFMfgsbNZmMgLKqW/KgByyJDYvWiDE=
|
||||
github.com/anchore/fangs v0.0.0-20230531202914-48a718c6b4ba h1:tJ186HK8e0Lf+hhNWX4fJrq14yj3mw8JQkkLhA0nFhE=
|
||||
@@ -106,8 +106,8 @@ github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b h1:e1bmaoJfZV
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E=
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501 h1:AV7qjwMcM4r8wFhJq3jLRztew3ywIyPTRapl2T1s9o8=
|
||||
github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501/go.mod h1:Blo6OgJNiYF41ufcgHKkbCKF2MDOMlrqhXv/ij6ocR4=
|
||||
github.com/anchore/stereoscope v0.0.0-20230724160817-d515761c6ca2 h1:qIxlZFFZIl5YNHwxsVQ6UJG8QtkR3W0W9ozaI9JQiPk=
|
||||
github.com/anchore/stereoscope v0.0.0-20230724160817-d515761c6ca2/go.mod h1:0LsgHgXO4QFnk2hsYwtqd3fR18PIZXlFLIl2qb9tu3g=
|
||||
github.com/anchore/stereoscope v0.0.0-20230727211946-d1f3d766295e h1:S6IhYpsBCpvphlHA1tN0glSG/kjVvFzC6OJuU2qW5Pc=
|
||||
github.com/anchore/stereoscope v0.0.0-20230727211946-d1f3d766295e/go.mod h1:0LsgHgXO4QFnk2hsYwtqd3fR18PIZXlFLIl2qb9tu3g=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||
@@ -179,16 +179,16 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma
|
||||
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/dave/jennifer v1.6.1 h1:T4T/67t6RAA5AIV6+NP8Uk/BIsXgDoqEowgycdQQLuk=
|
||||
github.com/dave/jennifer v1.6.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc=
|
||||
github.com/dave/jennifer v1.7.0 h1:uRbSBH9UTS64yXbh4FrMHfgfY762RD+C7bUPKODpSJE=
|
||||
github.com/dave/jennifer v1.7.0/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc=
|
||||
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.5+incompatible h1:ufWmAOuD3Vmr7JP2G5K3cyuNC4YZWiAsuDEvFVVDafE=
|
||||
github.com/docker/cli v23.0.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/cli v24.0.0+incompatible h1:0+1VshNwBQzQAx9lOl+OYCTCEAD8fKs/qeXMx3O0wqM=
|
||||
github.com/docker/cli v24.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
|
||||
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v24.0.5+incompatible h1:WmgcE4fxyI6EEXxBRxsHnZXrO1pQ3smi0k/jho4HLeY=
|
||||
@@ -325,8 +325,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-containerregistry v0.15.2 h1:MMkSh+tjSdnmJZO7ljvEqV1DjfekB6VUEAZgy3a+TQE=
|
||||
github.com/google/go-containerregistry v0.15.2/go.mod h1:wWK+LnOv4jXMM23IT/F1wdYftGWGr47Is8CG+pmHK1Q=
|
||||
github.com/google/go-containerregistry v0.16.1 h1:rUEt426sR6nyrL3gt+18ibRcvYpKYdpsa5ZW7MA08dQ=
|
||||
github.com/google/go-containerregistry v0.16.1/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY1hLbf8eeGapA+vbFDCtQ=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/licensecheck v0.3.1 h1:QoxgoDkaeC4nFrtGN1jV7IPmDCHFNIVh54e5hSt6sPs=
|
||||
github.com/google/licensecheck v0.3.1/go.mod h1:ORkR35t/JjW+emNKtfJDII0zlciG9JgbT7SmsohlHmY=
|
||||
@@ -687,8 +687,8 @@ github.com/vbatts/go-mtree v0.5.3 h1:S/jYlfG8rZ+a0bhZd+RANXejy7M4Js8fq9U+XoWTd5w
|
||||
github.com/vbatts/go-mtree v0.5.3/go.mod h1:eXsdoPMdL2jcJx6HweWi9lYQxBsTp4lNhqqAjgkZUg8=
|
||||
github.com/vbatts/tar-split v0.11.3 h1:hLFqsOLQ1SsppQNTMpkpPXClLDfC2A3Zgy9OUU+RVck=
|
||||
github.com/vbatts/tar-split v0.11.3/go.mod h1:9QlHN18E+fEH7RdG+QAJJcuya3rqT7eXSTY7wGrAokY=
|
||||
github.com/vifraa/gopom v0.2.2 h1:zrqoCUVIplcsETouv3xxHPvfI/WV1GUPrdX2+Diahzo=
|
||||
github.com/vifraa/gopom v0.2.2/go.mod h1:oPa1dcrGrtlO37WPDBm5SqHAT+wTgF8An1Q71Z6Vv4o=
|
||||
github.com/vifraa/gopom v1.0.0 h1:L9XlKbyvid8PAIK8nr0lihMApJQg/12OBvMA28BcWh0=
|
||||
github.com/vifraa/gopom v1.0.0/go.mod h1:oPa1dcrGrtlO37WPDBm5SqHAT+wTgF8An1Q71Z6Vv4o=
|
||||
github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651 h1:jIVmlAFIqV3d+DOxazTR9v+zgj8+VYuQBzPgBZvWBHA=
|
||||
github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651/go.mod h1:b26F2tHLqaoRQf8DywqzVaV1MQ9yvjb0OMcNl7Nxu20=
|
||||
github.com/wagoodman/go-progress v0.0.0-20230301185719-21920a456ad5 h1:lwgTsTy18nYqASnH58qyfRW/ldj7Gt2zzBvgYPzdA4s=
|
||||
@@ -750,8 +750,8 @@ golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0
|
||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
|
||||
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
|
||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
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=
|
||||
@@ -843,8 +843,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
||||
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.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
|
||||
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
|
||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -874,8 +874,9 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
|
||||
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -957,16 +958,16 @@ 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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||
golang.org/x/sys v0.11.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-20210927222741-03fcf44c2211/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.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
|
||||
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
|
||||
golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
|
||||
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -979,8 +980,8 @@ 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.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -1043,8 +1044,8 @@ 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.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y=
|
||||
golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4=
|
||||
golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo=
|
||||
golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -1243,16 +1244,16 @@ modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
|
||||
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
|
||||
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
|
||||
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
|
||||
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
|
||||
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
|
||||
modernc.org/libc v1.24.1 h1:uvJSeCKL/AgzBo2yYIPPTy82v21KgGnizcGYfBHaNuM=
|
||||
modernc.org/libc v1.24.1/go.mod h1:FmfO1RLrU3MHJfyi9eYYmZBfi/R+tqZ6+hQ3yQQUkak=
|
||||
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
||||
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
|
||||
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||
modernc.org/memory v1.6.0 h1:i6mzavxrE9a30whzMfwf7XWVODx2r5OYXvU46cirX7o=
|
||||
modernc.org/memory v1.6.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sqlite v1.24.0 h1:EsClRIWHGhLTCX44p+Ri/JLD+vFGo0QGjasg2/F9TlI=
|
||||
modernc.org/sqlite v1.24.0/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
|
||||
modernc.org/sqlite v1.25.0 h1:AFweiwPNd/b3BoKnBOfFm+Y260guGMF+0UFk0savqeA=
|
||||
modernc.org/sqlite v1.25.0/go.mod h1:FL3pVXie73rg3Rii6V/u5BoHlSoyeZeIgKZEgHARyCU=
|
||||
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
|
||||
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
|
||||
modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY=
|
||||
|
||||
@@ -42,31 +42,32 @@ type Application struct {
|
||||
ConfigPath string `yaml:"configPath,omitempty" json:"configPath" mapstructure:"config"`
|
||||
Verbosity uint `yaml:"verbosity,omitempty" json:"verbosity" mapstructure:"verbosity"`
|
||||
// -q, indicates to not show any status output to stderr (ETUI or logging UI)
|
||||
Quiet bool `yaml:"quiet" json:"quiet" mapstructure:"quiet"`
|
||||
Outputs []string `yaml:"output" json:"output" mapstructure:"output"` // -o, the format to use for output
|
||||
OutputTemplatePath string `yaml:"output-template-path" json:"output-template-path" mapstructure:"output-template-path"` // -t template file to use for output
|
||||
File string `yaml:"file" json:"file" mapstructure:"file"` // --file, the file to write report output to
|
||||
CheckForAppUpdate bool `yaml:"check-for-app-update" json:"check-for-app-update" mapstructure:"check-for-app-update"` // whether to check for an application update on start up or not
|
||||
Dev development `yaml:"dev" json:"dev" mapstructure:"dev"`
|
||||
Log logging `yaml:"log" json:"log" mapstructure:"log"` // all logging-related options
|
||||
Catalogers []string `yaml:"catalogers" json:"catalogers" mapstructure:"catalogers"`
|
||||
Package pkg `yaml:"package" json:"package" mapstructure:"package"`
|
||||
Golang golang `yaml:"golang" json:"golang" mapstructure:"golang"`
|
||||
LinuxKernel linuxKernel `yaml:"linux-kernel" json:"linux-kernel" mapstructure:"linux-kernel"`
|
||||
Python python `yaml:"python" json:"python" mapstructure:"python"`
|
||||
Attest attest `yaml:"attest" json:"attest" mapstructure:"attest"`
|
||||
FileMetadata FileMetadata `yaml:"file-metadata" json:"file-metadata" mapstructure:"file-metadata"`
|
||||
FileClassification fileClassification `yaml:"file-classification" json:"file-classification" mapstructure:"file-classification"`
|
||||
FileContents fileContents `yaml:"file-contents" json:"file-contents" mapstructure:"file-contents"`
|
||||
Secrets secrets `yaml:"secrets" json:"secrets" mapstructure:"secrets"`
|
||||
Registry registry `yaml:"registry" json:"registry" mapstructure:"registry"`
|
||||
Exclusions []string `yaml:"exclude" json:"exclude" mapstructure:"exclude"`
|
||||
Platform string `yaml:"platform" json:"platform" mapstructure:"platform"`
|
||||
Name string `yaml:"name" json:"name" mapstructure:"name"`
|
||||
Source sourceCfg `yaml:"source" json:"source" mapstructure:"source"`
|
||||
Parallelism int `yaml:"parallelism" json:"parallelism" mapstructure:"parallelism"` // the number of catalog workers to run in parallel
|
||||
DefaultImagePullSource string `yaml:"default-image-pull-source" json:"default-image-pull-source" mapstructure:"default-image-pull-source"` // specify default image pull source
|
||||
BasePath string `yaml:"base-path" json:"base-path" mapstructure:"base-path"` // specify base path for all file paths
|
||||
Quiet bool `yaml:"quiet" json:"quiet" mapstructure:"quiet"`
|
||||
Outputs []string `yaml:"output" json:"output" mapstructure:"output"` // -o, the format to use for output
|
||||
OutputTemplatePath string `yaml:"output-template-path" json:"output-template-path" mapstructure:"output-template-path"` // -t template file to use for output
|
||||
File string `yaml:"file" json:"file" mapstructure:"file"` // --file, the file to write report output to
|
||||
CheckForAppUpdate bool `yaml:"check-for-app-update" json:"check-for-app-update" mapstructure:"check-for-app-update"` // whether to check for an application update on start up or not
|
||||
Dev development `yaml:"dev" json:"dev" mapstructure:"dev"`
|
||||
Log logging `yaml:"log" json:"log" mapstructure:"log"` // all logging-related options
|
||||
Catalogers []string `yaml:"catalogers" json:"catalogers" mapstructure:"catalogers"`
|
||||
Package pkg `yaml:"package" json:"package" mapstructure:"package"`
|
||||
Golang golang `yaml:"golang" json:"golang" mapstructure:"golang"`
|
||||
LinuxKernel linuxKernel `yaml:"linux-kernel" json:"linux-kernel" mapstructure:"linux-kernel"`
|
||||
Python python `yaml:"python" json:"python" mapstructure:"python"`
|
||||
Attest attest `yaml:"attest" json:"attest" mapstructure:"attest"`
|
||||
FileMetadata FileMetadata `yaml:"file-metadata" json:"file-metadata" mapstructure:"file-metadata"`
|
||||
FileClassification fileClassification `yaml:"file-classification" json:"file-classification" mapstructure:"file-classification"`
|
||||
FileContents fileContents `yaml:"file-contents" json:"file-contents" mapstructure:"file-contents"`
|
||||
Secrets secrets `yaml:"secrets" json:"secrets" mapstructure:"secrets"`
|
||||
Registry registry `yaml:"registry" json:"registry" mapstructure:"registry"`
|
||||
Exclusions []string `yaml:"exclude" json:"exclude" mapstructure:"exclude"`
|
||||
Platform string `yaml:"platform" json:"platform" mapstructure:"platform"`
|
||||
Name string `yaml:"name" json:"name" mapstructure:"name"`
|
||||
Source sourceCfg `yaml:"source" json:"source" mapstructure:"source"`
|
||||
Parallelism int `yaml:"parallelism" json:"parallelism" mapstructure:"parallelism"` // the number of catalog workers to run in parallel
|
||||
DefaultImagePullSource string `yaml:"default-image-pull-source" json:"default-image-pull-source" mapstructure:"default-image-pull-source"` // specify default image pull source
|
||||
BasePath string `yaml:"base-path" json:"base-path" mapstructure:"base-path"` // specify base path for all file paths
|
||||
ExcludeBinaryOverlapByOwnership bool `yaml:"exclude-binary-overlap-by-ownership" json:"exclude-binary-overlap-by-ownership" mapstructure:"exclude-binary-overlap-by-ownership"` // exclude synthetic binary packages owned by os package files
|
||||
}
|
||||
|
||||
func (cfg Application) ToCatalogerConfig() cataloger.Config {
|
||||
@@ -76,8 +77,9 @@ func (cfg Application) ToCatalogerConfig() cataloger.Config {
|
||||
IncludeUnindexedArchives: cfg.Package.SearchUnindexedArchives,
|
||||
Scope: cfg.Package.Cataloger.ScopeOpt,
|
||||
},
|
||||
Catalogers: cfg.Catalogers,
|
||||
Parallelism: cfg.Parallelism,
|
||||
Catalogers: cfg.Catalogers,
|
||||
Parallelism: cfg.Parallelism,
|
||||
ExcludeBinaryOverlapByOwnership: cfg.ExcludeBinaryOverlapByOwnership,
|
||||
Golang: golangCataloger.NewGoCatalogerOpts().
|
||||
WithSearchLocalModCacheLicenses(cfg.Golang.SearchLocalModCacheLicenses).
|
||||
WithLocalModCacheDir(cfg.Golang.LocalModCacheDir).
|
||||
@@ -221,6 +223,7 @@ func loadDefaultValues(v *viper.Viper) {
|
||||
v.SetDefault("catalogers", nil)
|
||||
v.SetDefault("parallelism", 1)
|
||||
v.SetDefault("default-image-pull-source", "")
|
||||
v.SetDefault("exclude-binary-overlap-by-ownership", true)
|
||||
|
||||
// for each field in the configuration struct, see if the field implements the defaultValueLoader interface and invoke it if it does
|
||||
value := reflect.ValueOf(Application{})
|
||||
|
||||
@@ -210,24 +210,33 @@ func collectRelationships(bom *cyclonedx.BOM, s *sbom.SBOM, idMap map[string]int
|
||||
return
|
||||
}
|
||||
for _, d := range *bom.Dependencies {
|
||||
to, fromExists := idMap[d.Ref].(artifact.Identifiable)
|
||||
if !fromExists {
|
||||
continue
|
||||
}
|
||||
|
||||
if d.Dependencies == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
toPtr, toExists := idMap[d.Ref]
|
||||
if !toExists {
|
||||
continue
|
||||
}
|
||||
to, ok := common.PtrToStruct(toPtr).(artifact.Identifiable)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, t := range *d.Dependencies {
|
||||
from, toExists := idMap[t].(artifact.Identifiable)
|
||||
if !toExists {
|
||||
fromPtr, fromExists := idMap[t]
|
||||
if !fromExists {
|
||||
continue
|
||||
}
|
||||
from, ok := common.PtrToStruct(fromPtr).(artifact.Identifiable)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
s.Relationships = append(s.Relationships, artifact.Relationship{
|
||||
From: from,
|
||||
To: to,
|
||||
Type: artifact.DependencyOfRelationship, // FIXME this information is lost
|
||||
// match assumptions in encoding, that this is the only type of relationship captured:
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,10 @@ import (
|
||||
|
||||
"github.com/CycloneDX/cyclonedx-go"
|
||||
"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/sbom"
|
||||
)
|
||||
|
||||
@@ -336,3 +338,101 @@ func Test_missingComponentsDecode(t *testing.T) {
|
||||
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func Test_decodeDependencies(t *testing.T) {
|
||||
c1 := cyclonedx.Component{
|
||||
Name: "c1",
|
||||
}
|
||||
|
||||
c2 := cyclonedx.Component{
|
||||
Name: "c2",
|
||||
}
|
||||
|
||||
c3 := cyclonedx.Component{
|
||||
Name: "c3",
|
||||
}
|
||||
|
||||
for _, c := range []*cyclonedx.Component{&c1, &c2, &c3} {
|
||||
c.BOMRef = c.Name
|
||||
}
|
||||
|
||||
setTypes := func(typ cyclonedx.ComponentType, components ...cyclonedx.Component) *[]cyclonedx.Component {
|
||||
var out []cyclonedx.Component
|
||||
for _, c := range components {
|
||||
c.Type = typ
|
||||
out = append(out, c)
|
||||
}
|
||||
return &out
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
sbom cyclonedx.BOM
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "dependencies decoded as dependencyOf relationships",
|
||||
sbom: cyclonedx.BOM{
|
||||
Components: setTypes(cyclonedx.ComponentTypeLibrary,
|
||||
c1,
|
||||
c2,
|
||||
c3,
|
||||
),
|
||||
Dependencies: &[]cyclonedx.Dependency{
|
||||
{
|
||||
Ref: c1.BOMRef,
|
||||
Dependencies: &[]string{
|
||||
c2.BOMRef,
|
||||
c3.BOMRef,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []string{c2.Name, c3.Name},
|
||||
},
|
||||
{
|
||||
name: "dependencies skipped with unhandled components",
|
||||
sbom: cyclonedx.BOM{
|
||||
Components: setTypes("",
|
||||
c1,
|
||||
c2,
|
||||
c3,
|
||||
),
|
||||
Dependencies: &[]cyclonedx.Dependency{
|
||||
{
|
||||
Ref: c1.BOMRef,
|
||||
Dependencies: &[]string{
|
||||
c2.BOMRef,
|
||||
c3.BOMRef,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
s, err := ToSyftModel(&test.sbom)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, s)
|
||||
|
||||
var deps []string
|
||||
if s != nil {
|
||||
for _, r := range s.Relationships {
|
||||
if r.Type != artifact.DependencyOfRelationship {
|
||||
continue
|
||||
}
|
||||
if p, ok := r.To.(pkg.Package); !ok || p.Name != c1.Name {
|
||||
continue
|
||||
}
|
||||
if p, ok := r.From.(pkg.Package); ok {
|
||||
deps = append(deps, p.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
require.Equal(t, test.expected, deps)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,27 +143,29 @@ func toDependencies(relationships []artifact.Relationship) []cyclonedx.Dependenc
|
||||
for _, r := range relationships {
|
||||
exists := isExpressiblePackageRelationship(r.Type)
|
||||
if !exists {
|
||||
log.Debugf("unable to convert relationship from CycloneDX 1.4 JSON, dropping: %+v", r)
|
||||
log.Debugf("unable to convert relationship type to CycloneDX JSON, dropping: %#v", r)
|
||||
continue
|
||||
}
|
||||
|
||||
// we only capture package-to-package relationships for now
|
||||
fromPkg, ok := r.From.(*pkg.Package)
|
||||
fromPkg, ok := r.From.(pkg.Package)
|
||||
if !ok {
|
||||
log.Tracef("unable to convert relationship fromPkg to CycloneDX JSON, dropping: %#v", r)
|
||||
continue
|
||||
}
|
||||
|
||||
toPkg, ok := r.To.(*pkg.Package)
|
||||
toPkg, ok := r.To.(pkg.Package)
|
||||
if !ok {
|
||||
log.Tracef("unable to convert relationship toPkg to CycloneDX JSON, dropping: %#v", r)
|
||||
continue
|
||||
}
|
||||
|
||||
// ind dep
|
||||
|
||||
innerDeps := []string{}
|
||||
innerDeps = append(innerDeps, deriveBomRef(*fromPkg))
|
||||
innerDeps = append(innerDeps, deriveBomRef(fromPkg))
|
||||
result = append(result, cyclonedx.Dependency{
|
||||
Ref: deriveBomRef(*toPkg),
|
||||
Ref: deriveBomRef(toPkg),
|
||||
Dependencies: &innerDeps,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,7 +3,13 @@ package cyclonedxhelpers
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/CycloneDX/cyclonedx-go"
|
||||
"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/sbom"
|
||||
)
|
||||
|
||||
func Test_formatCPE(t *testing.T) {
|
||||
@@ -32,3 +38,98 @@ func Test_formatCPE(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_relationships(t *testing.T) {
|
||||
p1 := pkg.Package{
|
||||
Name: "p1",
|
||||
}
|
||||
p1.SetID()
|
||||
|
||||
p2 := pkg.Package{
|
||||
Name: "p2",
|
||||
}
|
||||
p2.SetID()
|
||||
|
||||
p3 := pkg.Package{
|
||||
Name: "p3",
|
||||
}
|
||||
p3.SetID()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
sbom sbom.SBOM
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "package dependencyOf relationships output as dependencies",
|
||||
sbom: sbom.SBOM{
|
||||
Artifacts: sbom.Artifacts{
|
||||
Packages: pkg.NewCollection(p1, p2, p3),
|
||||
},
|
||||
Relationships: []artifact.Relationship{
|
||||
{
|
||||
From: p2,
|
||||
To: p1,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
{
|
||||
From: p3,
|
||||
To: p1,
|
||||
Type: artifact.DependencyOfRelationship,
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []string{p2.Name, p3.Name},
|
||||
},
|
||||
{
|
||||
name: "package contains relationships not output",
|
||||
sbom: sbom.SBOM{
|
||||
Artifacts: sbom.Artifacts{
|
||||
Packages: pkg.NewCollection(p1, p2, p3),
|
||||
},
|
||||
Relationships: []artifact.Relationship{
|
||||
{
|
||||
From: p2,
|
||||
To: p1,
|
||||
Type: artifact.ContainsRelationship,
|
||||
},
|
||||
{
|
||||
From: p3,
|
||||
To: p1,
|
||||
Type: artifact.ContainsRelationship,
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
cdx := ToFormatModel(test.sbom)
|
||||
got := cdx.Dependencies
|
||||
|
||||
var deps []string
|
||||
if got != nil {
|
||||
for _, r := range *got {
|
||||
for _, d := range *r.Dependencies {
|
||||
c := findComponent(cdx, d)
|
||||
require.NotNil(t, c)
|
||||
deps = append(deps, c.Name)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
require.Equal(t, test.expected, deps)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func findComponent(cdx *cyclonedx.BOM, bomRef string) *cyclonedx.Component {
|
||||
for _, c := range *cdx.Components {
|
||||
if c.BOMRef == bomRef {
|
||||
return &c
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package spdxhelpers
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/internal/spdxlicense"
|
||||
@@ -27,29 +29,18 @@ func License(p pkg.Package) (concluded, declared string) {
|
||||
// https://spdx.github.io/spdx-spec/v2.3/SPDX-license-expressions/
|
||||
pc, pd := parseLicenses(p.Licenses.ToSlice())
|
||||
|
||||
for i, v := range pc {
|
||||
if strings.HasPrefix(v, spdxlicense.LicenseRefPrefix) {
|
||||
pc[i] = SanitizeElementID(v)
|
||||
}
|
||||
}
|
||||
|
||||
for i, v := range pd {
|
||||
if strings.HasPrefix(v, spdxlicense.LicenseRefPrefix) {
|
||||
pd[i] = SanitizeElementID(v)
|
||||
}
|
||||
}
|
||||
|
||||
return joinLicenses(pc), joinLicenses(pd)
|
||||
}
|
||||
|
||||
func joinLicenses(licenses []string) string {
|
||||
func joinLicenses(licenses []spdxLicense) string {
|
||||
if len(licenses) == 0 {
|
||||
return NOASSERTION
|
||||
}
|
||||
|
||||
var newLicenses []string
|
||||
|
||||
for _, v := range licenses {
|
||||
for _, l := range licenses {
|
||||
v := l.id
|
||||
// check if license does not start or end with parens
|
||||
if !strings.HasPrefix(v, "(") && !strings.HasSuffix(v, ")") {
|
||||
// if license contains AND, OR, or WITH, then wrap in parens
|
||||
@@ -66,14 +57,31 @@ func joinLicenses(licenses []string) string {
|
||||
return strings.Join(newLicenses, " AND ")
|
||||
}
|
||||
|
||||
func parseLicenses(raw []pkg.License) (concluded, declared []string) {
|
||||
type spdxLicense struct {
|
||||
id string
|
||||
value string
|
||||
}
|
||||
|
||||
func parseLicenses(raw []pkg.License) (concluded, declared []spdxLicense) {
|
||||
for _, l := range raw {
|
||||
var candidate string
|
||||
if l.Value == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
candidate := spdxLicense{}
|
||||
if l.SPDXExpression != "" {
|
||||
candidate = l.SPDXExpression
|
||||
candidate.id = l.SPDXExpression
|
||||
} else {
|
||||
// we did not find a valid SPDX license ID so treat as separate license
|
||||
candidate = spdxlicense.LicenseRefPrefix + l.Value
|
||||
if len(l.Value) <= 64 {
|
||||
// if the license text is less than the size of the hash,
|
||||
// just use it directly so the id is more readable
|
||||
candidate.id = spdxlicense.LicenseRefPrefix + SanitizeElementID(l.Value)
|
||||
} else {
|
||||
hash := sha256.Sum256([]byte(l.Value))
|
||||
candidate.id = fmt.Sprintf("%s%x", spdxlicense.LicenseRefPrefix, hash)
|
||||
}
|
||||
candidate.value = l.Value
|
||||
}
|
||||
|
||||
switch l.Type {
|
||||
@@ -83,5 +91,6 @@ func parseLicenses(raw []pkg.License) (concluded, declared []string) {
|
||||
declared = append(declared, candidate)
|
||||
}
|
||||
}
|
||||
|
||||
return concluded, declared
|
||||
}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
package spdxhelpers
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/spdx/tools-golang/spdx"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/internal/spdxlicense"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
)
|
||||
|
||||
func Test_License(t *testing.T) {
|
||||
@@ -103,6 +108,77 @@ func Test_License(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_otherLicenses(t *testing.T) {
|
||||
pkg1 := pkg.Package{
|
||||
Name: "first-pkg",
|
||||
Version: "1.1",
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicense("MIT"),
|
||||
),
|
||||
}
|
||||
pkg2 := pkg.Package{
|
||||
Name: "second-pkg",
|
||||
Version: "2.2",
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicense("non spdx license"),
|
||||
),
|
||||
}
|
||||
bigText := `
|
||||
Apache License
|
||||
Version 2.0, January 2004`
|
||||
pkg3 := pkg.Package{
|
||||
Name: "third-pkg",
|
||||
Version: "3.3",
|
||||
Licenses: pkg.NewLicenseSet(
|
||||
pkg.NewLicense(bigText),
|
||||
),
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
packages []pkg.Package
|
||||
expected []*spdx.OtherLicense
|
||||
}{
|
||||
{
|
||||
name: "no other licenses when all valid spdx expressions",
|
||||
packages: []pkg.Package{pkg1},
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "other licenses includes original text",
|
||||
packages: []pkg.Package{pkg2},
|
||||
expected: []*spdx.OtherLicense{
|
||||
{
|
||||
LicenseIdentifier: "LicenseRef-non-spdx-license",
|
||||
ExtractedText: "non spdx license",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "big licenses get hashed",
|
||||
packages: []pkg.Package{pkg3},
|
||||
expected: []*spdx.OtherLicense{
|
||||
{
|
||||
LicenseIdentifier: "LicenseRef-e9a1e42833d3e456f147052f4d312101bd171a0798893169fe596ca6b55c049e",
|
||||
ExtractedText: bigText,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
s := sbom.SBOM{
|
||||
Artifacts: sbom.Artifacts{
|
||||
Packages: pkg.NewCollection(test.packages...),
|
||||
},
|
||||
}
|
||||
got := ToFormatModel(s)
|
||||
require.Equal(t, test.expected, got.OtherLicenses)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_joinLicenses(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -122,7 +198,18 @@ func Test_joinLicenses(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equalf(t, tt.want, joinLicenses(tt.args), "joinLicenses(%v)", tt.args)
|
||||
assert.Equalf(t, tt.want, joinLicenses(toSpdxLicenses(tt.args)), "joinLicenses(%v)", tt.args)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func toSpdxLicenses(ids []string) (licenses []spdxLicense) {
|
||||
for _, l := range ids {
|
||||
license := spdxLicense{id: l}
|
||||
if strings.HasPrefix(l, spdxlicense.LicenseRefPrefix) {
|
||||
license.value = l
|
||||
}
|
||||
licenses = append(licenses, license)
|
||||
}
|
||||
return licenses
|
||||
}
|
||||
|
||||
@@ -240,9 +240,12 @@ func toRootPackage(s source.Description) *spdx.Package {
|
||||
PackageSPDXIdentifier: spdx.ElementID(SanitizeElementID(fmt.Sprintf("DocumentRoot-%s-%s", prefix, name))),
|
||||
PackageVersion: version,
|
||||
PackageChecksums: checksums,
|
||||
PackageSupplier: nil,
|
||||
PackageExternalReferences: nil,
|
||||
PrimaryPackagePurpose: purpose,
|
||||
PackageSupplier: &spdx.Supplier{
|
||||
Supplier: NOASSERTION,
|
||||
},
|
||||
PackageDownloadLocation: NOASSERTION,
|
||||
}
|
||||
|
||||
if purl != nil {
|
||||
@@ -357,7 +360,7 @@ func toPackages(catalog *pkg.Collection, sbom sbom.SBOM) (results []*spdx.Packag
|
||||
// 7.6: Package Originator: may have single result for either Person or Organization,
|
||||
// or NOASSERTION
|
||||
// Cardinality: optional, one
|
||||
PackageSupplier: nil,
|
||||
PackageSupplier: toPackageSupplier(p),
|
||||
|
||||
PackageOriginator: toPackageOriginator(p),
|
||||
|
||||
@@ -514,6 +517,21 @@ func toPackageOriginator(p pkg.Package) *spdx.Originator {
|
||||
}
|
||||
}
|
||||
|
||||
func toPackageSupplier(p pkg.Package) *spdx.Supplier {
|
||||
// this uses the Originator function for now until
|
||||
// a better distinction can be made for supplier
|
||||
kind, supplier := Originator(p)
|
||||
if kind == "" || supplier == "" {
|
||||
return &spdx.Supplier{
|
||||
Supplier: NOASSERTION,
|
||||
}
|
||||
}
|
||||
return &spdx.Supplier{
|
||||
Supplier: supplier,
|
||||
SupplierType: kind,
|
||||
}
|
||||
}
|
||||
|
||||
func formatSPDXExternalRefs(p pkg.Package) (refs []*spdx.PackageExternalReference) {
|
||||
for _, ref := range ExternalRefs(p) {
|
||||
refs = append(refs, &spdx.PackageExternalReference{
|
||||
@@ -686,32 +704,31 @@ func toFileTypes(metadata *file.Metadata) (ty []string) {
|
||||
// other licenses are for licenses from the pkg.Package that do not have an SPDXExpression
|
||||
// field. The spdxexpression field is only filled given a validated Value field.
|
||||
func toOtherLicenses(catalog *pkg.Collection) []*spdx.OtherLicense {
|
||||
licenses := map[string]bool{}
|
||||
for _, p := range catalog.Sorted() {
|
||||
licenses := map[string]spdxLicense{}
|
||||
|
||||
for p := range catalog.Enumerate() {
|
||||
declaredLicenses, concludedLicenses := parseLicenses(p.Licenses.ToSlice())
|
||||
for _, license := range declaredLicenses {
|
||||
if strings.HasPrefix(license, spdxlicense.LicenseRefPrefix) {
|
||||
licenses[license] = true
|
||||
for _, l := range declaredLicenses {
|
||||
if l.value != "" {
|
||||
licenses[l.id] = l
|
||||
}
|
||||
}
|
||||
for _, license := range concludedLicenses {
|
||||
if strings.HasPrefix(license, spdxlicense.LicenseRefPrefix) {
|
||||
licenses[license] = true
|
||||
for _, l := range concludedLicenses {
|
||||
if l.value != "" {
|
||||
licenses[l.id] = l
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var result []*spdx.OtherLicense
|
||||
|
||||
sorted := maps.Keys(licenses)
|
||||
slices.Sort(sorted)
|
||||
for _, license := range sorted {
|
||||
// separate the found value from the prefix
|
||||
// this only contains licenses that are not found on the SPDX License List
|
||||
name := strings.TrimPrefix(license, spdxlicense.LicenseRefPrefix)
|
||||
ids := maps.Keys(licenses)
|
||||
slices.Sort(ids)
|
||||
for _, id := range ids {
|
||||
license := licenses[id]
|
||||
result = append(result, &spdx.OtherLicense{
|
||||
LicenseIdentifier: SanitizeElementID(license),
|
||||
ExtractedText: name,
|
||||
LicenseIdentifier: license.id,
|
||||
ExtractedText: license.value,
|
||||
})
|
||||
}
|
||||
return result
|
||||
|
||||
@@ -51,12 +51,14 @@ func Test_toFormatModel(t *testing.T) {
|
||||
SPDXVersion: spdx.Version,
|
||||
DataLicense: spdx.DataLicense,
|
||||
DocumentName: "alpine",
|
||||
|
||||
Packages: []*spdx.Package{
|
||||
{
|
||||
PackageSPDXIdentifier: "Package-pkg-1-pkg-1",
|
||||
PackageName: "pkg-1",
|
||||
PackageVersion: "version-1",
|
||||
PackageSupplier: &spdx.Supplier{
|
||||
Supplier: "NOASSERTION",
|
||||
},
|
||||
},
|
||||
{
|
||||
PackageSPDXIdentifier: "DocumentRoot-Image-alpine",
|
||||
@@ -71,6 +73,9 @@ func Test_toFormatModel(t *testing.T) {
|
||||
Locator: "pkg:oci/alpine@sha256:d34db33f?arch=&tag=latest",
|
||||
},
|
||||
},
|
||||
PackageSupplier: &spdx.Supplier{
|
||||
Supplier: "NOASSERTION",
|
||||
},
|
||||
},
|
||||
},
|
||||
Relationships: []*spdx.Relationship{
|
||||
@@ -122,12 +127,18 @@ func Test_toFormatModel(t *testing.T) {
|
||||
PackageSPDXIdentifier: "Package-pkg-1-pkg-1",
|
||||
PackageName: "pkg-1",
|
||||
PackageVersion: "version-1",
|
||||
PackageSupplier: &spdx.Supplier{
|
||||
Supplier: "NOASSERTION",
|
||||
},
|
||||
},
|
||||
{
|
||||
PackageSPDXIdentifier: "DocumentRoot-Directory-some-directory",
|
||||
PackageName: "some/directory",
|
||||
PackageVersion: "",
|
||||
PrimaryPackagePurpose: "FILE",
|
||||
PackageSupplier: &spdx.Supplier{
|
||||
Supplier: "NOASSERTION",
|
||||
},
|
||||
},
|
||||
},
|
||||
Relationships: []*spdx.Relationship{
|
||||
@@ -180,12 +191,14 @@ func Test_toFormatModel(t *testing.T) {
|
||||
SPDXVersion: spdx.Version,
|
||||
DataLicense: spdx.DataLicense,
|
||||
DocumentName: "path/to/some.file",
|
||||
|
||||
Packages: []*spdx.Package{
|
||||
{
|
||||
PackageSPDXIdentifier: "Package-pkg-1-pkg-1",
|
||||
PackageName: "pkg-1",
|
||||
PackageVersion: "version-1",
|
||||
PackageSupplier: &spdx.Supplier{
|
||||
Supplier: "NOASSERTION",
|
||||
},
|
||||
},
|
||||
{
|
||||
PackageSPDXIdentifier: "DocumentRoot-File-path-to-some.file",
|
||||
@@ -193,6 +206,9 @@ func Test_toFormatModel(t *testing.T) {
|
||||
PackageVersion: "sha256:d34db33f",
|
||||
PrimaryPackagePurpose: "FILE",
|
||||
PackageChecksums: []spdx.Checksum{{Algorithm: "SHA256", Value: "d34db33f"}},
|
||||
PackageSupplier: &spdx.Supplier{
|
||||
Supplier: "NOASSERTION",
|
||||
},
|
||||
},
|
||||
},
|
||||
Relationships: []*spdx.Relationship{
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/anchore/syft/internal/log"
|
||||
"github.com/anchore/syft/internal/spdxlicense"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/cpe"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
@@ -285,6 +286,16 @@ func collectSyftPackages(s *sbom.SBOM, spdxIDMap map[string]any, packages []*spd
|
||||
}
|
||||
|
||||
func collectSyftFiles(s *sbom.SBOM, spdxIDMap map[string]any, doc *spdx.Document) {
|
||||
for _, p := range doc.Packages {
|
||||
for _, f := range p.Files {
|
||||
l := toSyftLocation(f)
|
||||
spdxIDMap[string(f.FileSPDXIdentifier)] = l
|
||||
|
||||
s.Artifacts.FileMetadata[l.Coordinates] = toFileMetadata(f)
|
||||
s.Artifacts.FileDigests[l.Coordinates] = toFileDigests(f)
|
||||
}
|
||||
}
|
||||
|
||||
for _, f := range doc.Files {
|
||||
l := toSyftLocation(f)
|
||||
spdxIDMap[string(f.FileSPDXIdentifier)] = l
|
||||
@@ -331,7 +342,14 @@ func toFileMetadata(f *spdx.File) (meta file.Metadata) {
|
||||
}
|
||||
|
||||
func toSyftRelationships(spdxIDMap map[string]any, doc *spdx.Document) []artifact.Relationship {
|
||||
var out []artifact.Relationship
|
||||
out := collectDocRelationships(spdxIDMap, doc)
|
||||
|
||||
out = append(out, collectPackageFileRelationships(spdxIDMap, doc)...)
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func collectDocRelationships(spdxIDMap map[string]any, doc *spdx.Document) (out []artifact.Relationship) {
|
||||
for _, r := range doc.Relationships {
|
||||
// FIXME what to do with r.RefA.DocumentRefID and r.RefA.SpecialID
|
||||
if r.RefA.DocumentRefID != "" && requireAndTrimPrefix(r.RefA.DocumentRefID, "DocumentRef-") != string(doc.SPDXIdentifier) {
|
||||
@@ -385,6 +403,30 @@ func toSyftRelationships(spdxIDMap map[string]any, doc *spdx.Document) []artifac
|
||||
return out
|
||||
}
|
||||
|
||||
// collectPackageFileRelationships add relationships for direct files
|
||||
func collectPackageFileRelationships(spdxIDMap map[string]any, doc *spdx.Document) (out []artifact.Relationship) {
|
||||
for _, p := range doc.Packages {
|
||||
a := spdxIDMap[string(p.PackageSPDXIdentifier)]
|
||||
from, fromOk := a.(pkg.Package)
|
||||
if !fromOk {
|
||||
continue
|
||||
}
|
||||
for _, f := range p.Files {
|
||||
b := spdxIDMap[string(f.FileSPDXIdentifier)]
|
||||
to, toLocationOk := b.(file.Location)
|
||||
if !toLocationOk {
|
||||
continue
|
||||
}
|
||||
out = append(out, artifact.Relationship{
|
||||
From: from,
|
||||
To: to,
|
||||
Type: artifact.ContainsRelationship,
|
||||
})
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func toSyftCoordinates(f *spdx.File) file.Coordinates {
|
||||
const layerIDPrefix = "layerID: "
|
||||
var fileSystemID string
|
||||
@@ -467,11 +509,11 @@ func toSyftPackage(p *spdx.Package) pkg.Package {
|
||||
}
|
||||
|
||||
func purlValue(purl packageurl.PackageURL) string {
|
||||
p := purl.String()
|
||||
if p == "pkg:/" {
|
||||
val := purl.String()
|
||||
if _, err := packageurl.FromString(val); err != nil {
|
||||
return ""
|
||||
}
|
||||
return p
|
||||
return val
|
||||
}
|
||||
|
||||
func parseSPDXLicenses(p *spdx.Package) []pkg.License {
|
||||
@@ -495,10 +537,7 @@ func parseSPDXLicenses(p *spdx.Package) []pkg.License {
|
||||
}
|
||||
|
||||
func cleanSPDXID(id string) string {
|
||||
if strings.HasPrefix(id, "LicenseRef-") {
|
||||
return strings.TrimPrefix(id, "LicenseRef-")
|
||||
}
|
||||
return id
|
||||
return strings.TrimPrefix(id, spdxlicense.LicenseRefPrefix)
|
||||
}
|
||||
|
||||
//nolint:funlen
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/packageurl-go"
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
@@ -552,3 +553,132 @@ func Test_convertToAndFromFormat(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_purlValue(t *testing.T) {
|
||||
tests := []struct {
|
||||
purl packageurl.PackageURL
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
purl: packageurl.PackageURL{},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
purl: packageurl.PackageURL{
|
||||
Name: "name",
|
||||
Version: "version",
|
||||
},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
purl: packageurl.PackageURL{
|
||||
Type: "typ",
|
||||
Version: "version",
|
||||
},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
purl: packageurl.PackageURL{
|
||||
Type: "typ",
|
||||
Name: "name",
|
||||
Version: "version",
|
||||
},
|
||||
expected: "pkg:typ/name@version",
|
||||
},
|
||||
{
|
||||
purl: packageurl.PackageURL{
|
||||
Type: "typ",
|
||||
Name: "name",
|
||||
Version: "version",
|
||||
Qualifiers: packageurl.Qualifiers{
|
||||
{
|
||||
Key: "q",
|
||||
Value: "v",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: "pkg:typ/name@version?q=v",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.purl.String(), func(t *testing.T) {
|
||||
got := purlValue(test.purl)
|
||||
require.Equal(t, test.expected, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_directPackageFiles(t *testing.T) {
|
||||
doc := &spdx.Document{
|
||||
SPDXVersion: "SPDX-2.3",
|
||||
Packages: []*spdx.Package{
|
||||
{
|
||||
PackageName: "some-package",
|
||||
PackageSPDXIdentifier: "1",
|
||||
PackageVersion: "1.0.5",
|
||||
Files: []*spdx.File{
|
||||
{
|
||||
FileName: "some-file",
|
||||
FileSPDXIdentifier: "2",
|
||||
Checksums: []spdx.Checksum{
|
||||
{
|
||||
Algorithm: "SHA1",
|
||||
Value: "a8d733c64f9123",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
got, err := ToSyftModel(doc)
|
||||
require.NoError(t, err)
|
||||
|
||||
p := pkg.Package{
|
||||
Name: "some-package",
|
||||
Version: "1.0.5",
|
||||
MetadataType: pkg.UnknownMetadataType,
|
||||
}
|
||||
p.SetID()
|
||||
f := file.Location{
|
||||
LocationData: file.LocationData{
|
||||
Coordinates: file.Coordinates{
|
||||
RealPath: "some-file",
|
||||
FileSystemID: "",
|
||||
},
|
||||
VirtualPath: "some-file",
|
||||
},
|
||||
LocationMetadata: file.LocationMetadata{
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
}
|
||||
s := &sbom.SBOM{
|
||||
Artifacts: sbom.Artifacts{
|
||||
Packages: pkg.NewCollection(p),
|
||||
FileMetadata: map[file.Coordinates]file.Metadata{
|
||||
f.Coordinates: {},
|
||||
},
|
||||
FileDigests: map[file.Coordinates][]file.Digest{
|
||||
f.Coordinates: {
|
||||
{
|
||||
Algorithm: "sha1",
|
||||
Value: "a8d733c64f9123",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Relationships: []artifact.Relationship{
|
||||
{
|
||||
From: p,
|
||||
To: f,
|
||||
Type: artifact.ContainsRelationship,
|
||||
},
|
||||
},
|
||||
Source: source.Description{},
|
||||
Descriptor: sbom.Descriptor{},
|
||||
}
|
||||
|
||||
require.Equal(t, s, got)
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"name": "package-1",
|
||||
"SPDXID": "SPDXRef-Package-python-package-1-9265397e5e15168a",
|
||||
"versionInfo": "1.0.1",
|
||||
"supplier": "NOASSERTION",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
"filesAnalyzed": false,
|
||||
"sourceInfo": "acquired package info from installed python package manifest file: /some/path/pkg1",
|
||||
@@ -40,6 +41,7 @@
|
||||
"name": "package-2",
|
||||
"SPDXID": "SPDXRef-Package-deb-package-2-db4abfe497c180d3",
|
||||
"versionInfo": "2.0.1",
|
||||
"supplier": "NOASSERTION",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
"filesAnalyzed": false,
|
||||
"sourceInfo": "acquired package info from DPKG DB: /some/path/pkg1",
|
||||
@@ -62,7 +64,8 @@
|
||||
{
|
||||
"name": "some/path",
|
||||
"SPDXID": "SPDXRef-DocumentRoot-Directory-some-path",
|
||||
"downloadLocation": "",
|
||||
"supplier": "NOASSERTION",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
"filesAnalyzed": false,
|
||||
"primaryPackagePurpose": "FILE"
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"name": "package-1",
|
||||
"SPDXID": "SPDXRef-Package-python-package-1-125840abc1c66dd7",
|
||||
"versionInfo": "1.0.1",
|
||||
"supplier": "NOASSERTION",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
"filesAnalyzed": false,
|
||||
"sourceInfo": "acquired package info from installed python package manifest file: /somefile-1.txt",
|
||||
@@ -40,6 +41,7 @@
|
||||
"name": "package-2",
|
||||
"SPDXID": "SPDXRef-Package-deb-package-2-958443e2d9304af4",
|
||||
"versionInfo": "2.0.1",
|
||||
"supplier": "NOASSERTION",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
"filesAnalyzed": false,
|
||||
"sourceInfo": "acquired package info from DPKG DB: /somefile-2.txt",
|
||||
@@ -63,7 +65,8 @@
|
||||
"name": "user-image-input",
|
||||
"SPDXID": "SPDXRef-DocumentRoot-Image-user-image-input",
|
||||
"versionInfo": "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368",
|
||||
"downloadLocation": "",
|
||||
"supplier": "NOASSERTION",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
"filesAnalyzed": false,
|
||||
"checksums": [
|
||||
{
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"name": "package-1",
|
||||
"SPDXID": "SPDXRef-Package-python-package-1-125840abc1c66dd7",
|
||||
"versionInfo": "1.0.1",
|
||||
"supplier": "NOASSERTION",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
"filesAnalyzed": false,
|
||||
"sourceInfo": "acquired package info from installed python package manifest file: /somefile-1.txt",
|
||||
@@ -40,6 +41,7 @@
|
||||
"name": "package-2",
|
||||
"SPDXID": "SPDXRef-Package-deb-package-2-958443e2d9304af4",
|
||||
"versionInfo": "2.0.1",
|
||||
"supplier": "NOASSERTION",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
"filesAnalyzed": false,
|
||||
"sourceInfo": "acquired package info from DPKG DB: /somefile-2.txt",
|
||||
@@ -63,7 +65,8 @@
|
||||
"name": "user-image-input",
|
||||
"SPDXID": "SPDXRef-DocumentRoot-Image-user-image-input",
|
||||
"versionInfo": "sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368",
|
||||
"downloadLocation": "",
|
||||
"supplier": "NOASSERTION",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
"filesAnalyzed": false,
|
||||
"checksums": [
|
||||
{
|
||||
|
||||
Binary file not shown.
@@ -2,9 +2,13 @@ package spdxtagvalue
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
)
|
||||
|
||||
// TODO: this is a temporary coverage see below
|
||||
@@ -34,3 +38,59 @@ func TestSPDXTagValueDecoder(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_packageDirectFiles(t *testing.T) {
|
||||
contents := `
|
||||
SPDXVersion: SPDX-2.2
|
||||
DataLicense: CC0-1.0
|
||||
SPDXID: SPDXRef-DOCUMENT
|
||||
DocumentName: Some-SBOM
|
||||
DocumentNamespace: https://example.org/some/namespace
|
||||
Creator: Organization: Some-organization
|
||||
Creator: Tool: Some-tool Version: 1.0
|
||||
Created: 2021-12-29T17:02:21Z
|
||||
PackageName: Some-package
|
||||
PackageVersion: 5.1.2
|
||||
SPDXID: SPDXRef-Package-43c51b08-cc7e-406d-8ad9-34aa292d1157
|
||||
PackageSupplier: Organization: Some-organization
|
||||
PackageDownloadLocation: https://example.org/download/location
|
||||
FilesAnalyzed: true
|
||||
PackageLicenseInfoFromFiles: NOASSERTION
|
||||
PackageVerificationCode: 23460C5559C8D4DE3F6504E0E84E844CAC8B1D95
|
||||
PackageLicenseConcluded: NOASSERTION
|
||||
PackageLicenseDeclared: NOASSERTION
|
||||
PackageCopyrightText: NOASSERTION
|
||||
PackageChecksum: SHA1: 23460C5559C8D4DE3F6504E0E84E844CAC8B1D95
|
||||
FileName: Some-file-name
|
||||
SPDXID: SPDXRef-99545d55-933d-4e08-9eb5-9d826111cb79
|
||||
FileContributor: Some-file-contributor
|
||||
FileType: BINARY
|
||||
FileChecksum: SHA1: 23460C5559C8D4DE3F6504E0E84E844CAC8B1D95
|
||||
LicenseConcluded: NOASSERTION
|
||||
LicenseInfoInFile: NOASSERTION
|
||||
FileCopyrightText: NOASSERTION
|
||||
`
|
||||
|
||||
s, err := decoder(strings.NewReader(contents))
|
||||
require.NoError(t, err)
|
||||
|
||||
pkgs := s.Artifacts.Packages.Sorted()
|
||||
assert.Len(t, pkgs, 1)
|
||||
assert.Len(t, s.Artifacts.FileMetadata, 1)
|
||||
assert.Len(t, s.Relationships, 1)
|
||||
p := pkgs[0]
|
||||
r := s.Relationships[0]
|
||||
f := file.Location{}
|
||||
for c := range s.Artifacts.FileMetadata {
|
||||
f = file.Location{
|
||||
LocationData: file.LocationData{
|
||||
Coordinates: c,
|
||||
VirtualPath: "",
|
||||
},
|
||||
LocationMetadata: file.LocationMetadata{},
|
||||
}
|
||||
break // there should only be 1
|
||||
}
|
||||
assert.Equal(t, p.ID(), r.From.ID())
|
||||
assert.Equal(t, f.ID(), r.To.ID())
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ Created: redacted
|
||||
|
||||
PackageName: foobar/baz
|
||||
SPDXID: SPDXRef-DocumentRoot-Directory-foobar-baz
|
||||
PackageSupplier: NOASSERTION
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
PrimaryPackagePurpose: FILE
|
||||
FilesAnalyzed: false
|
||||
|
||||
@@ -19,6 +21,7 @@ FilesAnalyzed: false
|
||||
|
||||
PackageName: @at-sign
|
||||
SPDXID: SPDXRef-Package--at-sign-3732f7a5679bdec4
|
||||
PackageSupplier: NOASSERTION
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
PackageSourceInfo: acquired package info from the following paths:
|
||||
@@ -30,6 +33,7 @@ PackageCopyrightText: NOASSERTION
|
||||
|
||||
PackageName: some/slashes
|
||||
SPDXID: SPDXRef-Package-some-slashes-1345166d4801153b
|
||||
PackageSupplier: NOASSERTION
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
PackageSourceInfo: acquired package info from the following paths:
|
||||
@@ -41,6 +45,7 @@ PackageCopyrightText: NOASSERTION
|
||||
|
||||
PackageName: under_scores
|
||||
SPDXID: SPDXRef-Package-under-scores-290d5c77210978c1
|
||||
PackageSupplier: NOASSERTION
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
PackageSourceInfo: acquired package info from the following paths:
|
||||
|
||||
@@ -51,6 +51,8 @@ LicenseConcluded: NOASSERTION
|
||||
PackageName: user-image-input
|
||||
SPDXID: SPDXRef-DocumentRoot-Image-user-image-input
|
||||
PackageVersion: sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368
|
||||
PackageSupplier: NOASSERTION
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
PrimaryPackagePurpose: CONTAINER
|
||||
FilesAnalyzed: false
|
||||
PackageChecksum: SHA256: 2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368
|
||||
@@ -61,6 +63,7 @@ ExternalRef: PACKAGE-MANAGER purl pkg:oci/user-image-input@sha256:2731251dc34951
|
||||
PackageName: package-2
|
||||
SPDXID: SPDXRef-Package-deb-package-2-958443e2d9304af4
|
||||
PackageVersion: 2.0.1
|
||||
PackageSupplier: NOASSERTION
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
PackageSourceInfo: acquired package info from DPKG DB: /somefile-2.txt
|
||||
@@ -75,6 +78,7 @@ ExternalRef: PACKAGE-MANAGER purl pkg:deb/debian/package-2@2.0.1
|
||||
PackageName: package-1
|
||||
SPDXID: SPDXRef-Package-python-package-1-125840abc1c66dd7
|
||||
PackageVersion: 1.0.1
|
||||
PackageSupplier: NOASSERTION
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
PackageSourceInfo: acquired package info from installed python package manifest file: /somefile-1.txt
|
||||
|
||||
+4
@@ -12,6 +12,8 @@ Created: redacted
|
||||
|
||||
PackageName: some/path
|
||||
SPDXID: SPDXRef-DocumentRoot-Directory-some-path
|
||||
PackageSupplier: NOASSERTION
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
PrimaryPackagePurpose: FILE
|
||||
FilesAnalyzed: false
|
||||
|
||||
@@ -20,6 +22,7 @@ FilesAnalyzed: false
|
||||
PackageName: package-2
|
||||
SPDXID: SPDXRef-Package-deb-package-2-db4abfe497c180d3
|
||||
PackageVersion: 2.0.1
|
||||
PackageSupplier: NOASSERTION
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
PackageSourceInfo: acquired package info from DPKG DB: /some/path/pkg1
|
||||
@@ -34,6 +37,7 @@ ExternalRef: PACKAGE-MANAGER purl pkg:deb/debian/package-2@2.0.1
|
||||
PackageName: package-1
|
||||
SPDXID: SPDXRef-Package-python-package-1-9265397e5e15168a
|
||||
PackageVersion: 1.0.1
|
||||
PackageSupplier: NOASSERTION
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
PackageSourceInfo: acquired package info from installed python package manifest file: /some/path/pkg1
|
||||
|
||||
@@ -13,6 +13,8 @@ Created: redacted
|
||||
PackageName: user-image-input
|
||||
SPDXID: SPDXRef-DocumentRoot-Image-user-image-input
|
||||
PackageVersion: sha256:2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368
|
||||
PackageSupplier: NOASSERTION
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
PrimaryPackagePurpose: CONTAINER
|
||||
FilesAnalyzed: false
|
||||
PackageChecksum: SHA256: 2731251dc34951c0e50fcc643b4c5f74922dad1a5d98f302b504cf46cd5d9368
|
||||
@@ -23,6 +25,7 @@ ExternalRef: PACKAGE-MANAGER purl pkg:oci/user-image-input@sha256:2731251dc34951
|
||||
PackageName: package-2
|
||||
SPDXID: SPDXRef-Package-deb-package-2-958443e2d9304af4
|
||||
PackageVersion: 2.0.1
|
||||
PackageSupplier: NOASSERTION
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
PackageSourceInfo: acquired package info from DPKG DB: /somefile-2.txt
|
||||
@@ -37,6 +40,7 @@ ExternalRef: PACKAGE-MANAGER purl pkg:deb/debian/package-2@2.0.1
|
||||
PackageName: package-1
|
||||
SPDXID: SPDXRef-Package-python-package-1-125840abc1c66dd7
|
||||
PackageVersion: 1.0.1
|
||||
PackageSupplier: NOASSERTION
|
||||
PackageDownloadLocation: NOASSERTION
|
||||
FilesAnalyzed: false
|
||||
PackageSourceInfo: acquired package info from installed python package manifest file: /somefile-1.txt
|
||||
|
||||
BIN
Binary file not shown.
+23
-1
@@ -74,11 +74,33 @@ func CatalogPackages(src source.Source, cfg cataloger.Config) (*pkg.Collection,
|
||||
|
||||
catalog, relationships, err := cataloger.Catalog(resolver, release, cfg.Parallelism, catalogers...)
|
||||
|
||||
relationships = append(relationships, newSourceRelationshipsFromCatalog(src, catalog)...)
|
||||
// apply exclusions to the package catalog
|
||||
// default config value for this is true
|
||||
// https://github.com/anchore/syft/issues/931
|
||||
if cfg.ExcludeBinaryOverlapByOwnership {
|
||||
for _, r := range relationships {
|
||||
if cataloger.ExcludeBinaryByFileOwnershipOverlap(r, catalog) {
|
||||
catalog.Delete(r.To.ID())
|
||||
relationships = removeRelationshipsByID(relationships, r.To.ID())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// no need to consider source relationships for os -> binary exclusions
|
||||
relationships = append(relationships, newSourceRelationshipsFromCatalog(src, catalog)...)
|
||||
return catalog, relationships, release, err
|
||||
}
|
||||
|
||||
func removeRelationshipsByID(relationships []artifact.Relationship, id artifact.ID) []artifact.Relationship {
|
||||
var filtered []artifact.Relationship
|
||||
for _, r := range relationships {
|
||||
if r.To.ID() != id && r.From.ID() != id {
|
||||
filtered = append(filtered, r)
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
func newSourceRelationshipsFromCatalog(src source.Source, c *pkg.Collection) []artifact.Relationship {
|
||||
relationships := make([]artifact.Relationship, 0) // Should we pre-allocate this by giving catalog a Len() method?
|
||||
for p := range c.Enumerate() {
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package syft
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func Test_removeRelationshipsByID(t *testing.T) {
|
||||
p1 := pkg.Package{}
|
||||
p1.OverrideID("1")
|
||||
|
||||
p2 := pkg.Package{}
|
||||
p2.OverrideID("2")
|
||||
|
||||
p3 := pkg.Package{}
|
||||
p3.OverrideID("3")
|
||||
|
||||
rel := func(pkgs ...pkg.Package) (out []artifact.Relationship) {
|
||||
for _, p := range pkgs {
|
||||
out = append(out, artifact.Relationship{
|
||||
From: p,
|
||||
To: p,
|
||||
Type: artifact.OwnershipByFileOverlapRelationship,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
relationships := rel(p1, p2, p3)
|
||||
|
||||
for _, r := range relationships {
|
||||
if r.From.ID() == "1" || r.From.ID() == "2" {
|
||||
relationships = removeRelationshipsByID(relationships, r.From.ID())
|
||||
}
|
||||
}
|
||||
|
||||
require.Equal(t, rel(p3), relationships)
|
||||
}
|
||||
@@ -643,6 +643,30 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) {
|
||||
Metadata: metadata("consul-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "positive-nginx-1.25.1",
|
||||
fixtureDir: "test-fixtures/classifiers/positive/nginx-1.25.1",
|
||||
expected: pkg.Package{
|
||||
Name: "nginx",
|
||||
Version: "1.25.1",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/nginx@1.25.1",
|
||||
Locations: locations("nginx"),
|
||||
Metadata: metadata("nginx-binary"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "positive-nginx-openresty-1.21.4.2",
|
||||
fixtureDir: "test-fixtures/classifiers/positive/nginx-openresty-1.21.4.2",
|
||||
expected: pkg.Package{
|
||||
Name: "nginx",
|
||||
Version: "1.21.4",
|
||||
Type: "binary",
|
||||
PURL: "pkg:generic/nginx@1.21.4",
|
||||
Locations: locations("nginx"),
|
||||
Metadata: metadata("nginx-binary"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
@@ -254,6 +254,21 @@ var defaultClassifiers = []classifier{
|
||||
PURL: mustPURL("pkg:golang/github.com/hashicorp/consul@version"),
|
||||
CPEs: singleCPE("cpe:2.3:a:hashicorp:consul:*:*:*:*:*:*:*:*"),
|
||||
},
|
||||
{
|
||||
Class: "nginx-binary",
|
||||
FileGlob: "**/nginx",
|
||||
EvidenceMatcher: fileContentsVersionMatcher(
|
||||
// [NUL]nginx version: nginx/1.25.1 - fetches '1.25.1'
|
||||
// [NUL]nginx version: openresty/1.21.4.1 - fetches '1.21.4' as this is the nginx version part
|
||||
`(?m)(\x00|\?)nginx version: [^\/]+\/(?P<version>[0-9]+\.[0-9]+\.[0-9]+(?:\+\d+)?(?:-\d+)?)`,
|
||||
),
|
||||
Package: "nginx",
|
||||
PURL: mustPURL("pkg:generic/nginx@version"),
|
||||
CPEs: []cpe.CPE{
|
||||
cpe.Must("cpe:2.3:a:f5:nginx:*:*:*:*:*:*:*:*"),
|
||||
cpe.Must("cpe:2.3:a:nginx:nginx:*:*:*:*:*:*:*:*"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// in both binaries and shared libraries, the version pattern is [NUL]3.11.2[NUL]
|
||||
|
||||
Binary file not shown.
BIN
Binary file not shown.
@@ -807,6 +807,7 @@
|
||||
"promise-probe": "cpe:2.3:a:promise-probe_project:promise-probe:*:*:*:*:*:node.js:*:*",
|
||||
"promisehelpers": "cpe:2.3:a:yola:promisehelpers:*:*:*:*:*:node.js:*:*",
|
||||
"property-expr": "cpe:2.3:a:property-expr_project:property-expr:*:*:*:*:*:node.js:*:*",
|
||||
"proxy": "cpe:2.3:a:proxy_project:proxy:*:*:*:*:*:node.js:*:*",
|
||||
"proxy.js": "cpe:2.3:a:proxy.js_project:proxy.js:*:*:*:*:*:node.js:*:*",
|
||||
"ps-kill": "cpe:2.3:a:ps-kill_project:ps-kill:*:*:*:*:*:node.js:*:*",
|
||||
"ps-visitor": "cpe:2.3:a:ps-visitor_project:ps-visitor:*:*:*:*:*:node.js:*:*",
|
||||
@@ -1144,6 +1145,7 @@
|
||||
"papercrop": "cpe:2.3:a:papercrop_project:papercrop:*:*:*:*:*:ruby:*:*",
|
||||
"paranoid2": "cpe:2.3:a:anjlab:paranoid2:*:*:*:*:*:ruby:*:*",
|
||||
"paratrooper-pingdom": "cpe:2.3:a:tobias_maier:paratrooper-pingdom:*:*:-:*:-:ruby:*:*",
|
||||
"pdf_info": "cpe:2.3:a:newspaperclub:pdf_info:*:*:*:*:*:ruby:*:*",
|
||||
"pdfkit": "cpe:2.3:a:pdfkit_project:pdfkit:*:*:*:*:*:ruby:*:*",
|
||||
"point-cli": "cpe:2.3:a:point-cli_project:point-cli:*:*:*:*:*:ruby:*:*",
|
||||
"private_address_check": "cpe:2.3:a:private_address_check_project:private_address_check:*:*:*:*:*:ruby:*:*",
|
||||
|
||||
@@ -354,11 +354,12 @@ func TestGeneratePackageCPEs(t *testing.T) {
|
||||
{
|
||||
name: "cloudbees jenkins package identified via groupId",
|
||||
p: pkg.Package{
|
||||
Name: "name",
|
||||
Version: "3.2",
|
||||
FoundBy: "some-analyzer",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JenkinsPluginPkg,
|
||||
Name: "name",
|
||||
Version: "3.2",
|
||||
FoundBy: "some-analyzer",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JenkinsPluginPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PomProperties: &pkg.PomProperties{
|
||||
GroupID: "com.cloudbees.jenkins.plugins",
|
||||
@@ -368,16 +369,18 @@ func TestGeneratePackageCPEs(t *testing.T) {
|
||||
expected: []string{
|
||||
"cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:jenkins:name:3.2:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:cloudbees:name:3.2:*:*:*:*:*:*:*",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "jenkins.io package identified via groupId prefix",
|
||||
p: pkg.Package{
|
||||
Name: "name",
|
||||
Version: "3.2",
|
||||
FoundBy: "some-analyzer",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JenkinsPluginPkg,
|
||||
Name: "name",
|
||||
Version: "3.2",
|
||||
FoundBy: "some-analyzer",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JenkinsPluginPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PomProperties: &pkg.PomProperties{
|
||||
GroupID: "io.jenkins.plugins.name.something",
|
||||
@@ -389,16 +392,19 @@ func TestGeneratePackageCPEs(t *testing.T) {
|
||||
"cpe:2.3:a:name:something:3.2:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:something:name:3.2:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:something:something:3.2:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:jenkins:name:3.2:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:jenkins:something:3.2:*:*:*:*:*:*:*",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "jenkins.io package identified via groupId",
|
||||
p: pkg.Package{
|
||||
Name: "name",
|
||||
Version: "3.2",
|
||||
FoundBy: "some-analyzer",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JenkinsPluginPkg,
|
||||
Name: "name",
|
||||
Version: "3.2",
|
||||
FoundBy: "some-analyzer",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JenkinsPluginPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PomProperties: &pkg.PomProperties{
|
||||
GroupID: "io.jenkins.plugins",
|
||||
@@ -407,16 +413,18 @@ func TestGeneratePackageCPEs(t *testing.T) {
|
||||
},
|
||||
expected: []string{
|
||||
"cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:jenkins:name:3.2:*:*:*:*:*:*:*",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "jenkins-ci.io package identified via groupId",
|
||||
p: pkg.Package{
|
||||
Name: "name",
|
||||
Version: "3.2",
|
||||
FoundBy: "some-analyzer",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JenkinsPluginPkg,
|
||||
Name: "name",
|
||||
Version: "3.2",
|
||||
FoundBy: "some-analyzer",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JenkinsPluginPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PomProperties: &pkg.PomProperties{
|
||||
GroupID: "io.jenkins-ci.plugins",
|
||||
@@ -425,16 +433,20 @@ func TestGeneratePackageCPEs(t *testing.T) {
|
||||
},
|
||||
expected: []string{
|
||||
"cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:jenkins-ci:name:3.2:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:jenkins:name:3.2:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:jenkins_ci:name:3.2:*:*:*:*:*:*:*",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "jenkins-ci.org package identified via groupId",
|
||||
p: pkg.Package{
|
||||
Name: "name",
|
||||
Version: "3.2",
|
||||
FoundBy: "some-analyzer",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JenkinsPluginPkg,
|
||||
Name: "name",
|
||||
Version: "3.2",
|
||||
FoundBy: "some-analyzer",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JenkinsPluginPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PomProperties: &pkg.PomProperties{
|
||||
GroupID: "org.jenkins-ci.plugins",
|
||||
@@ -443,6 +455,9 @@ func TestGeneratePackageCPEs(t *testing.T) {
|
||||
},
|
||||
expected: []string{
|
||||
"cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:jenkins-ci:name:3.2:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:jenkins:name:3.2:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:jenkins_ci:name:3.2:*:*:*:*:*:*:*",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
|
||||
"github.com/scylladb/go-set/strset"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
@@ -13,14 +12,6 @@ var (
|
||||
forbiddenProductGroupIDFields = strset.New("plugin", "plugins", "client")
|
||||
forbiddenVendorGroupIDFields = strset.New("plugin", "plugins")
|
||||
|
||||
domains = []string{
|
||||
"com",
|
||||
"org",
|
||||
"net",
|
||||
"io",
|
||||
"be",
|
||||
}
|
||||
|
||||
primaryJavaManifestGroupIDFields = []string{
|
||||
"Extension-Name",
|
||||
"Specification-Vendor",
|
||||
@@ -66,7 +57,7 @@ func vendorsFromJavaManifestNames(p pkg.Package) fieldCandidateSet {
|
||||
for _, name := range javaManifestNameFields {
|
||||
if metadata.Manifest.Main != nil {
|
||||
if value, exists := metadata.Manifest.Main[name]; exists {
|
||||
if !startsWithTopLevelDomain(value) {
|
||||
if !looksLikeGroupID(value) {
|
||||
vendors.add(fieldCandidate{
|
||||
value: normalizePersonName(value),
|
||||
disallowSubSelections: true,
|
||||
@@ -80,7 +71,7 @@ func vendorsFromJavaManifestNames(p pkg.Package) fieldCandidateSet {
|
||||
continue
|
||||
}
|
||||
if value, exists := section[name]; exists {
|
||||
if !startsWithTopLevelDomain(value) {
|
||||
if !looksLikeGroupID(value) {
|
||||
vendors.add(fieldCandidate{
|
||||
value: normalizePersonName(value),
|
||||
disallowSubSelections: true,
|
||||
@@ -144,7 +135,7 @@ func productsFromArtifactAndGroupIDs(artifactID string, groupIDs []string) []str
|
||||
continue
|
||||
}
|
||||
|
||||
// umbrella projects tend to have sub components that either start or end with the project name. We expect
|
||||
// umbrella projects tend to have subcomponents that either start or end with the project name. We expect
|
||||
// to identify fields that may represent the umbrella project, and not fields that indicate auxiliary
|
||||
// information about the package.
|
||||
couldBeProjectName := strings.HasPrefix(artifactID, field) || strings.HasSuffix(artifactID, field)
|
||||
@@ -168,7 +159,7 @@ func artifactIDFromJavaPackage(p pkg.Package) string {
|
||||
}
|
||||
|
||||
artifactID := strings.TrimSpace(metadata.PomProperties.ArtifactID)
|
||||
if startsWithTopLevelDomain(artifactID) && len(strings.Split(artifactID, ".")) > 1 {
|
||||
if looksLikeGroupID(artifactID) {
|
||||
// there is a strong indication that the artifact ID is really a group ID, don't use it
|
||||
return ""
|
||||
}
|
||||
@@ -181,13 +172,13 @@ func GroupIDsFromJavaPackage(p pkg.Package) (groupIDs []string) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return GroupIDsFromJavaMetadata(metadata)
|
||||
return GroupIDsFromJavaMetadata(p.Name, metadata)
|
||||
}
|
||||
|
||||
func GroupIDsFromJavaMetadata(metadata pkg.JavaMetadata) (groupIDs []string) {
|
||||
func GroupIDsFromJavaMetadata(pkgName string, metadata pkg.JavaMetadata) (groupIDs []string) {
|
||||
groupIDs = append(groupIDs, groupIDsFromPomProperties(metadata.PomProperties)...)
|
||||
groupIDs = append(groupIDs, groupIDsFromPomProject(metadata.PomProject)...)
|
||||
groupIDs = append(groupIDs, groupIDsFromJavaManifest(metadata.Manifest)...)
|
||||
groupIDs = append(groupIDs, groupIDsFromJavaManifest(pkgName, metadata.Manifest)...)
|
||||
|
||||
return groupIDs
|
||||
}
|
||||
@@ -197,16 +188,10 @@ func groupIDsFromPomProperties(properties *pkg.PomProperties) (groupIDs []string
|
||||
return nil
|
||||
}
|
||||
|
||||
if startsWithTopLevelDomain(properties.GroupID) {
|
||||
if properties.GroupID != "" {
|
||||
groupIDs = append(groupIDs, cleanGroupID(properties.GroupID))
|
||||
}
|
||||
|
||||
// sometimes the publisher puts the group ID in the artifact ID field unintentionally
|
||||
if startsWithTopLevelDomain(properties.ArtifactID) && len(strings.Split(properties.ArtifactID, ".")) > 1 {
|
||||
// there is a strong indication that the artifact ID is really a group ID
|
||||
groupIDs = append(groupIDs, cleanGroupID(properties.ArtifactID))
|
||||
}
|
||||
|
||||
return groupIDs
|
||||
}
|
||||
|
||||
@@ -215,33 +200,22 @@ func groupIDsFromPomProject(project *pkg.PomProject) (groupIDs []string) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// extract the project info...
|
||||
groupIDs = addGroupIDsFromGroupIDsAndArtifactID(project.GroupID, project.ArtifactID)
|
||||
|
||||
if project.Parent == nil {
|
||||
return groupIDs
|
||||
if project.GroupID != "" {
|
||||
groupIDs = append(groupIDs, project.GroupID)
|
||||
}
|
||||
|
||||
// extract the parent project info...
|
||||
groupIDs = append(groupIDs, addGroupIDsFromGroupIDsAndArtifactID(project.Parent.GroupID, project.Parent.ArtifactID)...)
|
||||
if project.Parent != nil && project.Parent.GroupID != "" {
|
||||
groupIDs = append(groupIDs, project.Parent.GroupID)
|
||||
}
|
||||
|
||||
return groupIDs
|
||||
}
|
||||
|
||||
func addGroupIDsFromGroupIDsAndArtifactID(groupID, artifactID string) (groupIDs []string) {
|
||||
if startsWithTopLevelDomain(groupID) {
|
||||
groupIDs = append(groupIDs, cleanGroupID(groupID))
|
||||
func groupIDsFromJavaManifest(pkgName string, manifest *pkg.JavaManifest) []string {
|
||||
if groupID, ok := defaultArtifactIDToGroupID[pkgName]; ok {
|
||||
return []string{groupID}
|
||||
}
|
||||
|
||||
// sometimes the publisher puts the group ID in the artifact ID field unintentionally
|
||||
if startsWithTopLevelDomain(artifactID) && len(strings.Split(artifactID, ".")) > 1 {
|
||||
// there is a strong indication that the artifact ID is really a group ID
|
||||
groupIDs = append(groupIDs, cleanGroupID(artifactID))
|
||||
}
|
||||
return groupIDs
|
||||
}
|
||||
|
||||
func groupIDsFromJavaManifest(manifest *pkg.JavaManifest) []string {
|
||||
if manifest == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -268,13 +242,13 @@ func getManifestFieldGroupIDs(manifest *pkg.JavaManifest, fields []string) (grou
|
||||
|
||||
for _, name := range fields {
|
||||
if value, exists := manifest.Main[name]; exists {
|
||||
if startsWithTopLevelDomain(value) {
|
||||
if looksLikeGroupID(value) {
|
||||
groupIDs = append(groupIDs, cleanGroupID(value))
|
||||
}
|
||||
}
|
||||
for _, section := range manifest.NamedSections {
|
||||
if value, exists := section[name]; exists {
|
||||
if startsWithTopLevelDomain(value) {
|
||||
if looksLikeGroupID(value) {
|
||||
groupIDs = append(groupIDs, cleanGroupID(value))
|
||||
}
|
||||
}
|
||||
@@ -295,6 +269,6 @@ func removeOSCIDirectives(groupID string) string {
|
||||
return strings.Split(groupID, ";")[0]
|
||||
}
|
||||
|
||||
func startsWithTopLevelDomain(value string) bool {
|
||||
return internal.HasAnyOfPrefixes(value, domains...)
|
||||
func looksLikeGroupID(value string) bool {
|
||||
return strings.Contains(value, ".")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package cpe
|
||||
|
||||
var defaultArtifactIDToGroupID = map[string]string{
|
||||
"ant": "org.apache.ant",
|
||||
"ant-antlr": "org.apache.ant",
|
||||
"ant-antunit": "org.apache.ant",
|
||||
"ant-apache-bcel": "org.apache.ant",
|
||||
"ant-apache-bsf": "org.apache.ant",
|
||||
"ant-apache-log4j": "org.apache.ant",
|
||||
"ant-apache-oro": "org.apache.ant",
|
||||
"ant-apache-regexp": "org.apache.ant",
|
||||
"ant-apache-resolver": "org.apache.ant",
|
||||
"ant-apache-xalan2": "org.apache.ant",
|
||||
"ant-commons-logging": "org.apache.ant",
|
||||
"ant-commons-net": "org.apache.ant",
|
||||
"ant-compress": "org.apache.ant",
|
||||
"ant-dotnet": "org.apache.ant",
|
||||
"ant-imageio": "org.apache.ant",
|
||||
"ant-jai": "org.apache.ant",
|
||||
"ant-jakartamail": "org.apache.ant",
|
||||
"ant-javamail": "org.apache.ant",
|
||||
"ant-jdepend": "org.apache.ant",
|
||||
"ant-jmf": "org.apache.ant",
|
||||
"ant-jsch": "org.apache.ant",
|
||||
"ant-junit": "org.apache.ant",
|
||||
"ant-junit4": "org.apache.ant",
|
||||
"ant-junitlauncher": "org.apache.ant",
|
||||
"ant-launcher": "org.apache.ant",
|
||||
"ant-netrexx": "org.apache.ant",
|
||||
"ant-nodeps": "org.apache.ant",
|
||||
"ant-parent": "org.apache.ant",
|
||||
"ant-starteam": "org.apache.ant",
|
||||
"ant-stylebook": "org.apache.ant",
|
||||
"ant-swing": "org.apache.ant",
|
||||
"ant-testutil": "org.apache.ant",
|
||||
"ant-trax": "org.apache.ant",
|
||||
"ant-weblogic": "org.apache.ant",
|
||||
"ant-xz": "org.apache.ant",
|
||||
"spring": "org.springframework",
|
||||
"spring-amqp": "org.springframework.amqp",
|
||||
"spring-batch-core": "org.springframework.batch",
|
||||
"spring-beans": "org.springframework",
|
||||
"spring-boot": "org.springframework.boot",
|
||||
"spring-boot-starter-web": "org.springframework.boot",
|
||||
"spring-boot-starter-webflux": "org.springframework.boot",
|
||||
"spring-cloud-function-context": "org.springframework.cloud",
|
||||
"spring-cloud-function-parent": "org.springframework.cloud",
|
||||
"spring-cloud-gateway": "org.springframework.cloud",
|
||||
"spring-cloud-openfeign-core": "org.springframework.cloud",
|
||||
"spring-cloud-task-dependencies": "org.springframework.cloud",
|
||||
"spring-core": "org.springframework",
|
||||
"spring-data-jpa": "org.springframework.data",
|
||||
"spring-data-mongodb": "org.springframework.data",
|
||||
"spring-data-rest-core": "org.springframework.data",
|
||||
"spring-expression": "org.springframework",
|
||||
"spring-integration-zip": "org.springframework.integration",
|
||||
"spring-oxm": "org.springframework",
|
||||
"spring-security-core": "org.springframework.security",
|
||||
"spring-security-config": "org.springframework.security",
|
||||
"spring-security-oauth": "org.springframework.security.oauth",
|
||||
"spring-security-oauth-parent": "org.springframework.security.oauth",
|
||||
"spring-security-oauth2-client": "org.springframework.security",
|
||||
"spring-session-core": "org.springframework.session",
|
||||
"spring-vault-core": "org.springframework.vault",
|
||||
"spring-web": "org.springframework",
|
||||
"spring-webflow": "org.springframework.webflow",
|
||||
"spring-webflux": "org.springframework",
|
||||
"spring-webmvc": "org.springframework",
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
@@ -427,3 +428,38 @@ func Test_vendorsFromJavaManifestNames(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_groupIDsFromJavaManifest(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
manifest pkg.JavaManifest
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "spring-security-core",
|
||||
manifest: pkg.JavaManifest{},
|
||||
expected: []string{"org.springframework.security"},
|
||||
},
|
||||
{
|
||||
name: "spring-web",
|
||||
manifest: pkg.JavaManifest{},
|
||||
expected: []string{"org.springframework"},
|
||||
},
|
||||
{
|
||||
name: "spring-foo",
|
||||
manifest: pkg.JavaManifest{
|
||||
Main: map[string]string{
|
||||
"Implementation-Vendor": "org.foo",
|
||||
},
|
||||
},
|
||||
expected: []string{"org.foo"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
got := groupIDsFromJavaManifest(test.name, &test.manifest)
|
||||
require.Equal(t, test.expected, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,22 +8,23 @@ import (
|
||||
)
|
||||
|
||||
// TODO: these field naming vs helper function naming schemes are inconsistent.
|
||||
|
||||
type Config struct {
|
||||
Search SearchConfig
|
||||
Golang golang.GoCatalogerOpts
|
||||
LinuxKernel kernel.LinuxCatalogerConfig
|
||||
Python python.CatalogerConfig
|
||||
Catalogers []string
|
||||
Parallelism int
|
||||
Search SearchConfig
|
||||
Golang golang.GoCatalogerOpts
|
||||
LinuxKernel kernel.LinuxCatalogerConfig
|
||||
Python python.CatalogerConfig
|
||||
Catalogers []string
|
||||
Parallelism int
|
||||
ExcludeBinaryOverlapByOwnership bool
|
||||
}
|
||||
|
||||
func DefaultConfig() Config {
|
||||
return Config{
|
||||
Search: DefaultSearchConfig(),
|
||||
Parallelism: 1,
|
||||
LinuxKernel: kernel.DefaultLinuxCatalogerConfig(),
|
||||
Python: python.DefaultCatalogerConfig(),
|
||||
Search: DefaultSearchConfig(),
|
||||
Parallelism: 1,
|
||||
LinuxKernel: kernel.DefaultLinuxCatalogerConfig(),
|
||||
Python: python.DefaultCatalogerConfig(),
|
||||
ExcludeBinaryOverlapByOwnership: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,5 +14,5 @@ func NewDpkgdbCataloger() *generic.Cataloger {
|
||||
return generic.NewCataloger(catalogerName).
|
||||
// note: these globs have been intentionally split up in order to improve search performance,
|
||||
// please do NOT combine into: "**/var/lib/dpkg/{status,status.d/*}"
|
||||
WithParserByGlobs(parseDpkgDB, "**/var/lib/dpkg/status", "**/var/lib/dpkg/status.d/*")
|
||||
WithParserByGlobs(parseDpkgDB, "**/var/lib/dpkg/status", "**/var/lib/dpkg/status.d/*", "**/lib/opkg/info/*.control", "**/lib/opkg/status")
|
||||
}
|
||||
|
||||
@@ -168,6 +168,8 @@ func TestCataloger_Globs(t *testing.T) {
|
||||
expected: []string{
|
||||
"var/lib/dpkg/status",
|
||||
"var/lib/dpkg/status.d/pkg-1.0",
|
||||
"usr/lib/opkg/status",
|
||||
"usr/lib/opkg/info/pkg-1.0.control",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
bogus package
|
||||
@@ -0,0 +1 @@
|
||||
bogus status
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
// PackageURL returns the PURL for the specific java package (see https://github.com/package-url/purl-spec)
|
||||
func packageURL(name, version string, metadata pkg.JavaMetadata) string {
|
||||
var groupID = name
|
||||
groupIDs := cpe.GroupIDsFromJavaMetadata(metadata)
|
||||
groupIDs := cpe.GroupIDsFromJavaMetadata(name, metadata)
|
||||
if len(groupIDs) > 0 {
|
||||
groupID = groupIDs[0]
|
||||
}
|
||||
|
||||
@@ -57,7 +57,16 @@ func parseGradleLockfile(_ file.Resolver, _ *generic.Environment, reader file.Lo
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JavaPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PomProject: &pkg.PomProject{
|
||||
GroupID: dep.Group,
|
||||
ArtifactID: dep.Name,
|
||||
Version: dep.Version,
|
||||
Name: dep.Name,
|
||||
},
|
||||
},
|
||||
}
|
||||
mappedPkg.SetID()
|
||||
pkgs = append(pkgs, mappedPkg)
|
||||
}
|
||||
|
||||
|
||||
@@ -16,12 +16,25 @@ func Test_parserGradleLockfile(t *testing.T) {
|
||||
{
|
||||
input: "test-fixtures/gradle/gradle.lockfile",
|
||||
expected: []pkg.Package{
|
||||
{
|
||||
Name: "commons-text",
|
||||
Version: "1.8",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JavaPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PomProject: &pkg.PomProject{GroupID: "org.apache.commons", ArtifactID: "commons-text", Version: "1.8", Name: "commons-text"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "hamcrest-core",
|
||||
Version: "1.3",
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JavaPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PomProject: &pkg.PomProject{GroupID: "org.hamcrest", ArtifactID: "hamcrest-core", Version: "1.3", Name: "hamcrest-core"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "joda-time",
|
||||
@@ -29,6 +42,9 @@ func Test_parserGradleLockfile(t *testing.T) {
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JavaPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PomProject: &pkg.PomProject{GroupID: "joda-time", ArtifactID: "joda-time", Version: "2.2", Name: "joda-time"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "junit",
|
||||
@@ -36,6 +52,9 @@ func Test_parserGradleLockfile(t *testing.T) {
|
||||
Language: pkg.Java,
|
||||
Type: pkg.JavaPkg,
|
||||
MetadataType: pkg.JavaMetadataType,
|
||||
Metadata: pkg.JavaMetadata{
|
||||
PomProject: &pkg.PomProject{GroupID: "junit", ArtifactID: "junit", Version: "4.12", Name: "junit"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -28,17 +28,19 @@ func parserPomXML(_ file.Resolver, _ *generic.Environment, reader file.LocationR
|
||||
}
|
||||
|
||||
var pkgs []pkg.Package
|
||||
for _, dep := range pom.Dependencies {
|
||||
p := newPackageFromPom(
|
||||
pom,
|
||||
dep,
|
||||
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
)
|
||||
if p.Name == "" {
|
||||
continue
|
||||
}
|
||||
if pom.Dependencies != nil {
|
||||
for _, dep := range *pom.Dependencies {
|
||||
p := newPackageFromPom(
|
||||
pom,
|
||||
dep,
|
||||
reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
|
||||
)
|
||||
if p.Name == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
pkgs = append(pkgs, p)
|
||||
pkgs = append(pkgs, p)
|
||||
}
|
||||
}
|
||||
|
||||
return pkgs, nil, nil
|
||||
@@ -53,15 +55,18 @@ func parsePomXMLProject(path string, reader io.Reader) (*pkg.PomProject, error)
|
||||
}
|
||||
|
||||
func newPomProject(path string, p gopom.Project) *pkg.PomProject {
|
||||
artifactID := safeString(p.ArtifactID)
|
||||
name := safeString(p.Name)
|
||||
projectURL := safeString(p.URL)
|
||||
return &pkg.PomProject{
|
||||
Path: path,
|
||||
Parent: pomParent(p, p.Parent),
|
||||
GroupID: resolveProperty(p, p.GroupID),
|
||||
ArtifactID: p.ArtifactID,
|
||||
ArtifactID: artifactID,
|
||||
Version: resolveProperty(p, p.Version),
|
||||
Name: p.Name,
|
||||
Name: name,
|
||||
Description: cleanDescription(p.Description),
|
||||
URL: p.URL,
|
||||
URL: projectURL,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +79,7 @@ func newPackageFromPom(pom gopom.Project, dep gopom.Dependency, locations ...fil
|
||||
},
|
||||
}
|
||||
|
||||
name := dep.ArtifactID
|
||||
name := safeString(dep.ArtifactID)
|
||||
version := resolveProperty(pom, dep.Version)
|
||||
|
||||
p := pkg.Package{
|
||||
@@ -104,19 +109,29 @@ func decodePomXML(content io.Reader) (project gopom.Project, err error) {
|
||||
return project, nil
|
||||
}
|
||||
|
||||
func pomParent(pom gopom.Project, parent gopom.Parent) (result *pkg.PomParent) {
|
||||
if parent.ArtifactID != "" || parent.GroupID != "" || parent.Version != "" {
|
||||
result = &pkg.PomParent{
|
||||
GroupID: resolveProperty(pom, parent.GroupID),
|
||||
ArtifactID: parent.ArtifactID,
|
||||
Version: resolveProperty(pom, parent.Version),
|
||||
}
|
||||
func pomParent(pom gopom.Project, parent *gopom.Parent) (result *pkg.PomParent) {
|
||||
if parent == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
artifactID := safeString(parent.ArtifactID)
|
||||
result = &pkg.PomParent{
|
||||
GroupID: resolveProperty(pom, parent.GroupID),
|
||||
ArtifactID: artifactID,
|
||||
Version: resolveProperty(pom, parent.Version),
|
||||
}
|
||||
|
||||
if result.GroupID == "" && result.ArtifactID == "" && result.Version == "" {
|
||||
return nil
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func cleanDescription(original string) (cleaned string) {
|
||||
descriptionLines := strings.Split(original, "\n")
|
||||
func cleanDescription(original *string) (cleaned string) {
|
||||
if original == nil {
|
||||
return ""
|
||||
}
|
||||
descriptionLines := strings.Split(*original, "\n")
|
||||
for _, line := range descriptionLines {
|
||||
line = strings.TrimSpace(line)
|
||||
if len(line) == 0 {
|
||||
@@ -130,12 +145,17 @@ func cleanDescription(original string) (cleaned string) {
|
||||
// resolveProperty emulates some maven property resolution logic by looking in the project's variables
|
||||
// as well as supporting the project expressions like ${project.parent.groupId}.
|
||||
// If no match is found, the entire expression including ${} is returned
|
||||
func resolveProperty(pom gopom.Project, property string) string {
|
||||
return propertyMatcher.ReplaceAllStringFunc(property, func(match string) string {
|
||||
//
|
||||
//nolint:gocognit
|
||||
func resolveProperty(pom gopom.Project, property *string) string {
|
||||
propertyCase := safeString(property)
|
||||
return propertyMatcher.ReplaceAllStringFunc(propertyCase, func(match string) string {
|
||||
propertyName := strings.TrimSpace(match[2 : len(match)-1])
|
||||
if value, ok := pom.Properties.Entries[propertyName]; ok {
|
||||
entries := pomProperties(pom)
|
||||
if value, ok := entries[propertyName]; ok {
|
||||
return value
|
||||
}
|
||||
|
||||
// if we don't find anything directly in the pom properties,
|
||||
// see if we have a project.x expression and process this based
|
||||
// on the xml tags in gopom
|
||||
@@ -151,9 +171,15 @@ func resolveProperty(pom gopom.Project, property string) string {
|
||||
part := parts[partNum]
|
||||
for fieldNum := 0; fieldNum < pomValueType.NumField(); fieldNum++ {
|
||||
f := pomValueType.Field(fieldNum)
|
||||
if part == f.Tag.Get("xml") {
|
||||
tag := f.Tag.Get("xml")
|
||||
tag = strings.TrimSuffix(tag, ",omitempty")
|
||||
if part == tag {
|
||||
pomValue = pomValue.Field(fieldNum)
|
||||
pomValueType = pomValue.Type()
|
||||
if pomValueType.Kind() == reflect.Ptr {
|
||||
pomValue = pomValue.Elem()
|
||||
pomValueType = pomValue.Type()
|
||||
}
|
||||
if partNum == numParts-1 {
|
||||
return fmt.Sprintf("%v", pomValue.Interface())
|
||||
}
|
||||
@@ -165,3 +191,17 @@ func resolveProperty(pom gopom.Project, property string) string {
|
||||
return match
|
||||
})
|
||||
}
|
||||
|
||||
func pomProperties(p gopom.Project) map[string]string {
|
||||
if p.Properties != nil {
|
||||
return p.Properties.Entries
|
||||
}
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
func safeString(s *string) string {
|
||||
if s == nil {
|
||||
return ""
|
||||
}
|
||||
return *s
|
||||
}
|
||||
|
||||
@@ -272,13 +272,13 @@ func Test_parsePomXMLProject(t *testing.T) {
|
||||
func Test_pomParent(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input gopom.Parent
|
||||
input *gopom.Parent
|
||||
expected *pkg.PomParent
|
||||
}{
|
||||
{
|
||||
name: "only group ID",
|
||||
input: gopom.Parent{
|
||||
GroupID: "org.something",
|
||||
input: &gopom.Parent{
|
||||
GroupID: stringPointer("org.something"),
|
||||
},
|
||||
expected: &pkg.PomParent{
|
||||
GroupID: "org.something",
|
||||
@@ -286,8 +286,8 @@ func Test_pomParent(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "only artifact ID",
|
||||
input: gopom.Parent{
|
||||
ArtifactID: "something",
|
||||
input: &gopom.Parent{
|
||||
ArtifactID: stringPointer("something"),
|
||||
},
|
||||
expected: &pkg.PomParent{
|
||||
ArtifactID: "something",
|
||||
@@ -295,22 +295,27 @@ func Test_pomParent(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "only Version",
|
||||
input: gopom.Parent{
|
||||
Version: "something",
|
||||
input: &gopom.Parent{
|
||||
Version: stringPointer("something"),
|
||||
},
|
||||
expected: &pkg.PomParent{
|
||||
Version: "something",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nil",
|
||||
input: nil,
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "empty",
|
||||
input: gopom.Parent{},
|
||||
input: &gopom.Parent{},
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "unused field",
|
||||
input: gopom.Parent{
|
||||
RelativePath: "something",
|
||||
input: &gopom.Parent{
|
||||
RelativePath: stringPointer("something"),
|
||||
},
|
||||
expected: nil,
|
||||
},
|
||||
@@ -341,7 +346,7 @@ func Test_cleanDescription(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Equal(t, test.expected, cleanDescription(test.input))
|
||||
assert.Equal(t, test.expected, cleanDescription(stringPointer(test.input)))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -357,7 +362,7 @@ func Test_resolveProperty(t *testing.T) {
|
||||
name: "property",
|
||||
property: "${version.number}",
|
||||
pom: gopom.Project{
|
||||
Properties: gopom.Properties{
|
||||
Properties: &gopom.Properties{
|
||||
Entries: map[string]string{
|
||||
"version.number": "12.5.0",
|
||||
},
|
||||
@@ -369,7 +374,7 @@ func Test_resolveProperty(t *testing.T) {
|
||||
name: "groupId",
|
||||
property: "${project.groupId}",
|
||||
pom: gopom.Project{
|
||||
GroupID: "org.some.group",
|
||||
GroupID: stringPointer("org.some.group"),
|
||||
},
|
||||
expected: "org.some.group",
|
||||
},
|
||||
@@ -377,8 +382,8 @@ func Test_resolveProperty(t *testing.T) {
|
||||
name: "parent groupId",
|
||||
property: "${project.parent.groupId}",
|
||||
pom: gopom.Project{
|
||||
Parent: gopom.Parent{
|
||||
GroupID: "org.some.parent",
|
||||
Parent: &gopom.Parent{
|
||||
GroupID: stringPointer("org.some.parent"),
|
||||
},
|
||||
},
|
||||
expected: "org.some.parent",
|
||||
@@ -387,8 +392,12 @@ func Test_resolveProperty(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
resolved := resolveProperty(test.pom, test.property)
|
||||
resolved := resolveProperty(test.pom, stringPointer(test.property))
|
||||
assert.Equal(t, test.expected, resolved)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func stringPointer(s string) *string {
|
||||
return &s
|
||||
}
|
||||
|
||||
@@ -5,3 +5,4 @@ joda-time:joda-time:2.2=compileClasspath,runtimeClasspath,testCompileClasspath,t
|
||||
junit:junit:4.12=testCompileClasspath,testRuntimeClasspath
|
||||
org.hamcrest:hamcrest-core:1.3=testCompileClasspath,testRuntimeClasspath
|
||||
empty=annotationProcessor,testAnnotationProcessor
|
||||
org.apache.commons:commons-text:1.8=compileClasspath
|
||||
@@ -0,0 +1,51 @@
|
||||
package cataloger
|
||||
|
||||
import (
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
var (
|
||||
osCatalogerTypes = []pkg.Type{
|
||||
pkg.AlpmPkg,
|
||||
pkg.ApkPkg,
|
||||
pkg.DebPkg,
|
||||
pkg.NixPkg,
|
||||
pkg.PortagePkg,
|
||||
pkg.RpmPkg,
|
||||
}
|
||||
binaryCatalogerTypes = []pkg.Type{
|
||||
pkg.BinaryPkg,
|
||||
}
|
||||
)
|
||||
|
||||
// ExcludeBinaryByFileOwnershipOverlap will remove packages from a collection given the following properties are true
|
||||
// 1) the relationship between packages is OwnershipByFileOverlap
|
||||
// 2) the parent is an "os" package
|
||||
// 3) the child is a synthetic package generated by the binary cataloger
|
||||
// 4) the package names are identical
|
||||
// This was implemented as a way to help resolve: https://github.com/anchore/syft/issues/931
|
||||
func ExcludeBinaryByFileOwnershipOverlap(r artifact.Relationship, c *pkg.Collection) bool {
|
||||
if artifact.OwnershipByFileOverlapRelationship != r.Type {
|
||||
return false
|
||||
}
|
||||
|
||||
parent := c.Package(r.From.ID())
|
||||
if parent == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
parentInExclusion := slices.Contains(osCatalogerTypes, parent.Type)
|
||||
if !parentInExclusion {
|
||||
return false
|
||||
}
|
||||
|
||||
child := c.Package(r.To.ID())
|
||||
if child == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return slices.Contains(binaryCatalogerTypes, child.Type)
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package cataloger
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/artifact"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func TestExclude(t *testing.T) {
|
||||
packageA := pkg.Package{Name: "package-a", Type: pkg.ApkPkg}
|
||||
packageB := pkg.Package{Name: "package-a", Type: pkg.PythonPkg}
|
||||
packageC := pkg.Package{Name: "package-a", Type: pkg.BinaryPkg}
|
||||
packageD := pkg.Package{Name: "package-d", Type: pkg.BinaryPkg}
|
||||
for _, p := range []*pkg.Package{&packageA, &packageB, &packageC, &packageD} {
|
||||
p := p
|
||||
p.SetID()
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
relationship artifact.Relationship
|
||||
packages *pkg.Collection
|
||||
shouldExclude bool
|
||||
}{
|
||||
{
|
||||
name: "no exclusions from os -> python",
|
||||
relationship: artifact.Relationship{
|
||||
Type: artifact.OwnershipByFileOverlapRelationship,
|
||||
From: packageA,
|
||||
To: packageB,
|
||||
},
|
||||
packages: pkg.NewCollection(packageA, packageB),
|
||||
shouldExclude: false,
|
||||
},
|
||||
{
|
||||
name: "exclusions from os -> binary",
|
||||
relationship: artifact.Relationship{
|
||||
Type: artifact.OwnershipByFileOverlapRelationship,
|
||||
From: packageA,
|
||||
To: packageC,
|
||||
},
|
||||
packages: pkg.NewCollection(packageA, packageC),
|
||||
shouldExclude: true,
|
||||
},
|
||||
{
|
||||
name: "no exclusions from python -> binary",
|
||||
relationship: artifact.Relationship{
|
||||
Type: artifact.OwnershipByFileOverlapRelationship,
|
||||
From: packageB,
|
||||
To: packageC,
|
||||
},
|
||||
packages: pkg.NewCollection(packageB, packageC),
|
||||
shouldExclude: false,
|
||||
},
|
||||
{
|
||||
name: "no exclusions for different package names",
|
||||
relationship: artifact.Relationship{
|
||||
Type: artifact.OwnershipByFileOverlapRelationship,
|
||||
From: packageA,
|
||||
To: packageD,
|
||||
},
|
||||
packages: pkg.NewCollection(packageA, packageD),
|
||||
shouldExclude: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
if !ExcludeBinaryByFileOwnershipOverlap(test.relationship, test.packages) && test.shouldExclude {
|
||||
t.Errorf("expected to exclude relationship %+v", test.relationship)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,11 @@ import (
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||
)
|
||||
|
||||
const (
|
||||
dbCatalogerName = "rpm-db-cataloger"
|
||||
fileCatalogerName = "rpm-file-cataloger"
|
||||
)
|
||||
|
||||
// NewRpmDBCataloger returns a new RPM DB cataloger object.
|
||||
func NewRpmDBCataloger() *generic.Cataloger {
|
||||
// check if a sqlite driver is available
|
||||
@@ -18,14 +23,14 @@ func NewRpmDBCataloger() *generic.Cataloger {
|
||||
log.Warnf("sqlite driver is not available, newer RPM databases might not be cataloged")
|
||||
}
|
||||
|
||||
return generic.NewCataloger("rpm-db-cataloger").
|
||||
return generic.NewCataloger(dbCatalogerName).
|
||||
WithParserByGlobs(parseRpmDB, pkg.RpmDBGlob).
|
||||
WithParserByGlobs(parseRpmManifest, pkg.RpmManifestGlob)
|
||||
}
|
||||
|
||||
// NewFileCataloger returns a new RPM file cataloger object.
|
||||
func NewFileCataloger() *generic.Cataloger {
|
||||
return generic.NewCataloger("rpm-file-cataloger").
|
||||
return generic.NewCataloger(fileCatalogerName).
|
||||
WithParserByGlobs(parseRpm, "**/*.rpm")
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,9 @@ func Test_DBCataloger_Globs(t *testing.T) {
|
||||
"var/lib/rpm/Packages.db",
|
||||
"var/lib/rpm/rpmdb.sqlite",
|
||||
"var/lib/rpmmanifest/container-manifest-2",
|
||||
"usr/lib/sysimage/rpm/Packages",
|
||||
"usr/lib/sysimage/rpm/Packages.db",
|
||||
"usr/lib/sysimage/rpm/rpmdb.sqlite",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
// Packages is the legacy Berkely db based format
|
||||
// Packages.db is the "ndb" format used in SUSE
|
||||
// rpmdb.sqlite is the sqlite format used in fedora + derivates
|
||||
const RpmDBGlob = "**/{var/lib,usr/share}/rpm/{Packages,Packages.db,rpmdb.sqlite}"
|
||||
const RpmDBGlob = "**/{var/lib,usr/share,usr/lib/sysimage}/rpm/{Packages,Packages.db,rpmdb.sqlite}"
|
||||
|
||||
// Used in CBL-Mariner distroless images
|
||||
const RpmManifestGlob = "**/var/lib/rpmmanifest/container-manifest-2"
|
||||
|
||||
@@ -109,8 +109,6 @@ func (s StereoscopeImageSource) Describe() Description {
|
||||
} else {
|
||||
if ref, ok := ref.(reference.Named); ok {
|
||||
nameIfUnset(ref.Name())
|
||||
} else {
|
||||
nameIfUnset(s.metadata.UserInput)
|
||||
}
|
||||
|
||||
if ref, ok := ref.(reference.NamedTagged); ok {
|
||||
@@ -122,6 +120,7 @@ func (s StereoscopeImageSource) Describe() Description {
|
||||
}
|
||||
}
|
||||
|
||||
nameIfUnset(s.metadata.UserInput)
|
||||
versionIfUnset(s.metadata.ManifestDigest)
|
||||
|
||||
return Description{
|
||||
|
||||
@@ -241,3 +241,31 @@ func Test_StereoscopeImageSource_ID(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Describe(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
source StereoscopeImageSource
|
||||
expected Description
|
||||
}{
|
||||
{
|
||||
name: "name from user input",
|
||||
source: StereoscopeImageSource{
|
||||
id: "some-id",
|
||||
metadata: StereoscopeImageSourceMetadata{
|
||||
UserInput: "user input",
|
||||
},
|
||||
},
|
||||
expected: Description{
|
||||
ID: "some-id",
|
||||
Name: "user input",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
got := test.source.Describe()
|
||||
got.Metadata = nil // might want to test this, but do not to determine if the user input is userd
|
||||
require.Equal(t, test.expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ func TestPackagesCmdFlags(t *testing.T) {
|
||||
name: "squashed-scope-flag-hidden-packages",
|
||||
args: []string{"packages", "-o", "json", "-s", "squashed", hiddenPackagesImage},
|
||||
assertions: []traitAssertion{
|
||||
assertPackageCount(163),
|
||||
assertPackageCount(162),
|
||||
assertNotInOutput("vsftpd"), // hidden package
|
||||
assertSuccessfulReturnCode,
|
||||
},
|
||||
@@ -113,7 +113,7 @@ func TestPackagesCmdFlags(t *testing.T) {
|
||||
name: "all-layers-scope-flag",
|
||||
args: []string{"packages", "-o", "json", "-s", "all-layers", hiddenPackagesImage},
|
||||
assertions: []traitAssertion{
|
||||
assertPackageCount(164), // packages are now deduplicated for this case
|
||||
assertPackageCount(163), // packages are now deduplicated for this case
|
||||
assertInOutput("all-layers"),
|
||||
assertInOutput("vsftpd"), // hidden package
|
||||
assertSuccessfulReturnCode,
|
||||
@@ -126,7 +126,7 @@ func TestPackagesCmdFlags(t *testing.T) {
|
||||
"SYFT_PACKAGE_CATALOGER_SCOPE": "all-layers",
|
||||
},
|
||||
assertions: []traitAssertion{
|
||||
assertPackageCount(164), // packages are now deduplicated for this case
|
||||
assertPackageCount(163), // packages are now deduplicated for this case
|
||||
assertInOutput("all-layers"),
|
||||
assertInOutput("vsftpd"), // hidden package
|
||||
assertSuccessfulReturnCode,
|
||||
|
||||
@@ -85,7 +85,11 @@ func TestSpdxValidationTooling(t *testing.T) {
|
||||
|
||||
validateCmd := exec.Command("make", "validate", fileArg, mountArg, imageArg)
|
||||
validateCmd.Dir = filepath.Join(cwd, "test-fixtures", "image-java-spdx-tools")
|
||||
runAndShow(t, validateCmd)
|
||||
|
||||
stdout, stderr, err := runCommand(validateCmd, map[string]string{})
|
||||
if err != nil {
|
||||
t.Fatalf("invalid SPDX document:%v\nSTDOUT:\n%s\nSTDERR:\n%s", err, stdout, stderr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
FROM openjdk:11
|
||||
|
||||
RUN wget https://github.com/spdx/tools-java/releases/download/v1.1.3/tools-java-1.1.3.zip && \
|
||||
RUN wget --no-verbose https://github.com/spdx/tools-java/releases/download/v1.1.3/tools-java-1.1.3.zip && \
|
||||
unzip tools-java-1.1.3.zip && \
|
||||
rm tools-java-1.1.3.zip
|
||||
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -17,8 +15,6 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
)
|
||||
|
||||
@@ -32,30 +28,6 @@ func logOutputOnFailure(t testing.TB, cmd *exec.Cmd, stdout, stderr string) {
|
||||
}
|
||||
}
|
||||
|
||||
func runAndShow(t *testing.T, cmd *exec.Cmd) {
|
||||
t.Helper()
|
||||
|
||||
stderr, err := cmd.StderrPipe()
|
||||
require.NoErrorf(t, err, "could not get stderr: +v", err)
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
require.NoErrorf(t, err, "could not get stdout: +v", err)
|
||||
|
||||
err = cmd.Start()
|
||||
require.NoErrorf(t, err, "failed to start cmd: %+v", err)
|
||||
|
||||
show := func(label string, reader io.ReadCloser) {
|
||||
scanner := bufio.NewScanner(reader)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
for scanner.Scan() {
|
||||
t.Logf("%s: %s", label, scanner.Text())
|
||||
}
|
||||
}
|
||||
|
||||
show("out", stdout)
|
||||
show("err", stderr)
|
||||
}
|
||||
|
||||
func setupPKI(t *testing.T, pw string) func() {
|
||||
err := os.Setenv("COSIGN_PASSWORD", pw)
|
||||
if err != nil {
|
||||
|
||||
@@ -56880,40 +56880,6 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "875f4d287d1bdcfd",
|
||||
"name": "python",
|
||||
"version": "3.6.8",
|
||||
"type": "binary",
|
||||
"foundBy": "binary-cataloger",
|
||||
"locations": [
|
||||
{
|
||||
"path": "/usr/lib64/libpython3.6m.so.1.0",
|
||||
"layerID": "sha256:eb29745b8228e1e97c01b1d5c2554a319c00a94d8dd5746a3904222ad65a13f8"
|
||||
}
|
||||
],
|
||||
"licenses": [],
|
||||
"language": "",
|
||||
"cpes": [
|
||||
"cpe:2.3:a:python_software_foundation:python:3.6.8:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:python:python:3.6.8:*:*:*:*:*:*:*",
|
||||
"cpe:2.3:a:python:python:3.6.8:*:*:*:*:*:*:*"
|
||||
],
|
||||
"purl": "pkg:generic/python@3.6.8",
|
||||
"metadataType": "BinaryMetadata",
|
||||
"metadata": {
|
||||
"matches": [
|
||||
{
|
||||
"classifier": "python-binary-lib",
|
||||
"location": {
|
||||
"path": "/usr/lib64/libpython3.6m.so.1.0",
|
||||
"layerID": "sha256:eb29745b8228e1e97c01b1d5c2554a319c00a94d8dd5746a3904222ad65a13f8",
|
||||
"virtualPath": "/usr/lib64/libpython3.6m.so.1.0"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "e57db3737a1d260f",
|
||||
"name": "python3-dnf",
|
||||
|
||||
@@ -26,7 +26,10 @@ import (
|
||||
// encode-decode-encode loop which will detect lossy behavior in both directions.
|
||||
func TestEncodeDecodeEncodeCycleComparison(t *testing.T) {
|
||||
// use second image for relationships
|
||||
images := []string{"image-pkg-coverage", "image-owning-package"}
|
||||
images := []string{
|
||||
"image-pkg-coverage",
|
||||
"image-owning-package",
|
||||
}
|
||||
tests := []struct {
|
||||
formatOption sbom.FormatID
|
||||
redactor func(in []byte) []byte
|
||||
@@ -44,8 +47,8 @@ func TestEncodeDecodeEncodeCycleComparison(t *testing.T) {
|
||||
formatOption: cyclonedxjson.ID,
|
||||
redactor: func(in []byte) []byte {
|
||||
// unstable values
|
||||
in = regexp.MustCompile(`"(timestamp|serialNumber|bom-ref)": "[^"]+",`).ReplaceAll(in, []byte{})
|
||||
|
||||
in = regexp.MustCompile(`"(timestamp|serialNumber|bom-ref|ref)":\s*"(\n|[^"])+"`).ReplaceAll(in, []byte(`"$1": "redacted"`))
|
||||
in = regexp.MustCompile(`"(dependsOn)":\s*\[(?:\s|[^]])+]`).ReplaceAll(in, []byte(`"$1": []`))
|
||||
return in
|
||||
},
|
||||
json: true,
|
||||
@@ -54,7 +57,7 @@ func TestEncodeDecodeEncodeCycleComparison(t *testing.T) {
|
||||
formatOption: cyclonedxxml.ID,
|
||||
redactor: func(in []byte) []byte {
|
||||
// unstable values
|
||||
in = regexp.MustCompile(`(serialNumber|bom-ref)="[^"]+"`).ReplaceAll(in, []byte{})
|
||||
in = regexp.MustCompile(`(serialNumber|bom-ref|ref)="[^"]+"`).ReplaceAll(in, []byte{})
|
||||
in = regexp.MustCompile(`<timestamp>[^<]+</timestamp>`).ReplaceAll(in, []byte{})
|
||||
|
||||
return in
|
||||
|
||||
@@ -22,13 +22,13 @@ func TestPackageDeduplication(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
scope: source.AllLayersScope,
|
||||
packageCount: 174, // without deduplication this would be 618
|
||||
packageCount: 172, // without deduplication this would be 618
|
||||
instanceCount: map[string]int{
|
||||
"basesystem": 1,
|
||||
"wget": 1,
|
||||
"curl": 2, // upgraded in the image
|
||||
"vsftpd": 1,
|
||||
"httpd": 2, // rpm, binary
|
||||
"httpd": 1, // rpm, - we exclude binary
|
||||
},
|
||||
locationCount: map[string]int{
|
||||
"basesystem-10.0-7.el7.centos": 4,
|
||||
@@ -37,18 +37,18 @@ func TestPackageDeduplication(t *testing.T) {
|
||||
"wget-1.14-18.el7_6.1": 3,
|
||||
"vsftpd-3.0.2-29.el7_9": 2,
|
||||
"httpd-2.4.6-97.el7.centos.5": 1,
|
||||
"httpd-2.4.6": 1, // binary
|
||||
// "httpd-2.4.6": 1, // binary
|
||||
},
|
||||
},
|
||||
{
|
||||
scope: source.SquashedScope,
|
||||
packageCount: 172,
|
||||
packageCount: 170,
|
||||
instanceCount: map[string]int{
|
||||
"basesystem": 1,
|
||||
"wget": 1,
|
||||
"curl": 1, // upgraded, but the most recent
|
||||
"vsftpd": 1,
|
||||
"httpd": 2, // rpm, binary
|
||||
"httpd": 1, // rpm, binary is now excluded by overlap
|
||||
},
|
||||
locationCount: map[string]int{
|
||||
"basesystem-10.0-7.el7.centos": 1,
|
||||
@@ -56,7 +56,7 @@ func TestPackageDeduplication(t *testing.T) {
|
||||
"wget-1.14-18.el7_6.1": 1,
|
||||
"vsftpd-3.0.2-29.el7_9": 1,
|
||||
"httpd-2.4.6-97.el7.centos.5": 1,
|
||||
"httpd-2.4.6": 1, // binary
|
||||
// "httpd-2.4.6": 1, // binary (excluded)
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -64,7 +64,6 @@ func TestPackageDeduplication(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(string(tt.scope), func(t *testing.T) {
|
||||
sbom, _ := catalogFixtureImage(t, "image-vertical-package-dups", tt.scope, nil)
|
||||
|
||||
for _, p := range sbom.Artifacts.Packages.Sorted() {
|
||||
if p.Type == pkg.BinaryPkg {
|
||||
assert.NotEmpty(t, p.Name)
|
||||
|
||||
@@ -7,11 +7,11 @@ import (
|
||||
|
||||
"github.com/anchore/syft/syft/formats/syftjson"
|
||||
syftjsonModel "github.com/anchore/syft/syft/formats/syftjson/model"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func TestPackageOwnershipRelationships(t *testing.T) {
|
||||
|
||||
// ensure that the json encoder is applying artifact ownership with an image that has expected ownership relationships
|
||||
tests := []struct {
|
||||
fixture string
|
||||
@@ -45,3 +45,43 @@ func TestPackageOwnershipRelationships(t *testing.T) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestPackageOwnershipExclusions(t *testing.T) {
|
||||
// ensure that the json encoder is excluding packages by artifact ownership with an image that has expected ownership relationships
|
||||
tests := []struct {
|
||||
name string
|
||||
fixture string
|
||||
}{
|
||||
{
|
||||
name: "busybox binary is filtered based on ownership relationship",
|
||||
fixture: "image-os-binary-overlap",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.fixture, func(t *testing.T) {
|
||||
sbom, _ := catalogFixtureImage(t, test.fixture, source.SquashedScope, nil)
|
||||
binaryPackages := make([]pkg.Package, 0)
|
||||
apkPackages := make([]pkg.Package, 0)
|
||||
for p := range sbom.Artifacts.Packages.Enumerate() {
|
||||
if p.Type == pkg.BinaryPkg && p.Name == "busybox" {
|
||||
binaryPackages = append(binaryPackages, p)
|
||||
}
|
||||
if p.Type == pkg.ApkPkg && p.Name == "busybox" {
|
||||
apkPackages = append(apkPackages, p)
|
||||
}
|
||||
}
|
||||
|
||||
if len(binaryPackages) != 0 {
|
||||
packageNames := make([]string, 0)
|
||||
for _, p := range binaryPackages {
|
||||
packageNames = append(packageNames, p.Name)
|
||||
}
|
||||
t.Errorf("expected to find no binary packages but found %d packages: %v", len(binaryPackages), packageNames)
|
||||
}
|
||||
if len(apkPackages) == 0 {
|
||||
t.Errorf("expected to find apk packages but found none")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func TestPhotonPackageRegression(t *testing.T) { // Regression: https://github.com/anchore/syft/pull/1997
|
||||
sbom, _ := catalogFixtureImage(t, "image-photon-all-layers", source.AllLayersScope, nil)
|
||||
var packages []pkg.Package
|
||||
for p := range sbom.Artifacts.Packages.Enumerate() {
|
||||
packages = append(packages, p)
|
||||
}
|
||||
|
||||
if len(packages) < 1 {
|
||||
t.Errorf("failed to find packages for photon distro; wanted > 0 got 0")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
FROM alpine:latest
|
||||
|
||||
# syft should not longer show the binary package for this image:
|
||||
# https://github.com/anchore/syft/issues/931
|
||||
# busybox 1.36.1 binary <-- not reported synthetic package overlap
|
||||
# busybox 1.36.1-r0 apk
|
||||
RUN apk update && apk add busybox
|
||||
@@ -0,0 +1 @@
|
||||
FROM photon:5.0-20230729
|
||||
Reference in New Issue
Block a user