feat(docker): add support for scanning Bitnami components (#5062)

* feat(bitnami): add support for scanning Bitnami components

Signed-off-by: juan131 <jariza@vmware.com>

* chore(deps): bump packageurl-go

TypeBitnami is not included in v0.1.1

* feat(spdx): handle orphan packages

* fix: update Elastic SPDX

Signed-off-by: juan131 <jariza@vmware.com>

* Update pkg/fanal/analyzer/sbom/sbom.go

Co-authored-by: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com>

* fix: remove useless else

Signed-off-by: juan131 <jariza@vmware.com>

* call AnalysisResult.Sort()

Signed-off-by: knqyf263 <knqyf263@gmail.com>

* delete app packages

Signed-off-by: knqyf263 <knqyf263@gmail.com>

* fix: set the component path to packages

Signed-off-by: knqyf263 <knqyf263@gmail.com>

* docs: add a comment about continue

Signed-off-by: knqyf263 <knqyf263@gmail.com>

* chore: bump trivy-db

Signed-off-by: knqyf263 <knqyf263@gmail.com>

* docs: add Bitnami

Signed-off-by: knqyf263 <knqyf263@gmail.com>

---------

Signed-off-by: juan131 <jariza@vmware.com>
Signed-off-by: knqyf263 <knqyf263@gmail.com>
Co-authored-by: knqyf263 <knqyf263@gmail.com>
Co-authored-by: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com>
This commit is contained in:
Juan Ariza Toledano
2023-08-31 22:18:05 +02:00
committed by GitHub
parent 9628b1cbf3
commit 7acc5e8312
20 changed files with 652 additions and 374 deletions

View File

@@ -74,6 +74,7 @@ jobs:
elixir
dart
swift
bitnami
os
lang

View File

@@ -1,4 +1,4 @@
# Google Distroless
# Google Distroless Images
Trivy supports the following scanners for OS packages.
| Scanner | Supported |

View File

@@ -26,7 +26,13 @@ Trivy supports operating systems for
| [Photon OS](photon.md) | 1.0, 2.0, 3.0, 4.0 | tndf/yum/rpm |
| [Debian GNU/Linux](debian.md) | 7, 8, 9, 10, 11, 12 | apt/dpkg |
| [Ubuntu](ubuntu.md) | All versions supported by Canonical | apt/dpkg |
## Supported container images
| Container image | Supported Versions | Package Managers |
|-----------------------------------------------|-------------------------------------|------------------|
| [Google Distroless](google-distroless.md)[^2] | Any | apt/dpkg |
| [Bitnami](bitnami.md) | Any | - |
Each page gives more details.

4
go.mod
View File

@@ -23,7 +23,7 @@ require (
github.com/aquasecurity/table v1.8.0
github.com/aquasecurity/testdocker v0.0.0-20230111101738-e741bda259da
github.com/aquasecurity/tml v0.6.1
github.com/aquasecurity/trivy-db v0.0.0-20230828105148-2c9c4da5a321
github.com/aquasecurity/trivy-db v0.0.0-20230831170347-f732860d4917
github.com/aquasecurity/trivy-java-db v0.0.0-20230209231723-7cddb1406728
github.com/aquasecurity/trivy-kubernetes v0.5.7-0.20230830063136-fe986af3f10f
github.com/aws/aws-sdk-go v1.44.273
@@ -74,7 +74,7 @@ require (
github.com/opencontainers/image-spec v1.1.0-rc4
github.com/openvex/go-vex v0.2.5
github.com/owenrumney/go-sarif/v2 v2.2.0
github.com/package-url/packageurl-go v0.1.1
github.com/package-url/packageurl-go v0.1.2-0.20230812223828-f8bb31c1f10b
github.com/samber/lo v1.38.1
github.com/saracen/walker v0.1.3
github.com/secure-systems-lab/go-securesystemslib v0.7.0

8
go.sum
View File

@@ -344,8 +344,8 @@ github.com/aquasecurity/testdocker v0.0.0-20230111101738-e741bda259da h1:pj/adfN
github.com/aquasecurity/testdocker v0.0.0-20230111101738-e741bda259da/go.mod h1:852lbQLpK2nCwlR4ZLYIccxYCfoQao6q9Nl6tjz54v8=
github.com/aquasecurity/tml v0.6.1 h1:y2ZlGSfrhnn7t4ZJ/0rotuH+v5Jgv6BDDO5jB6A9gwo=
github.com/aquasecurity/tml v0.6.1/go.mod h1:OnYMWY5lvI9ejU7yH9LCberWaaTBW7hBFsITiIMY2yY=
github.com/aquasecurity/trivy-db v0.0.0-20230828105148-2c9c4da5a321 h1:oAXkM8x6jMal+6p2XB78+ntPs5LGjxZhtWHdOy4crlg=
github.com/aquasecurity/trivy-db v0.0.0-20230828105148-2c9c4da5a321/go.mod h1:WJ5Qnk5ZNGWvks07GOZe2IOsuXrPfSC5c8hYGOGfrsU=
github.com/aquasecurity/trivy-db v0.0.0-20230831170347-f732860d4917 h1:MQd7h7yUyA8UlUzhjNMzpUX0NpD7jfxmRfSKwp/Ji3E=
github.com/aquasecurity/trivy-db v0.0.0-20230831170347-f732860d4917/go.mod h1:WJ5Qnk5ZNGWvks07GOZe2IOsuXrPfSC5c8hYGOGfrsU=
github.com/aquasecurity/trivy-java-db v0.0.0-20230209231723-7cddb1406728 h1:0eS+V7SXHgqoT99tV1mtMW6HL4HdoB9qGLMCb1fZp8A=
github.com/aquasecurity/trivy-java-db v0.0.0-20230209231723-7cddb1406728/go.mod h1:Ldya37FLi0e/5Cjq2T5Bty7cFkzUDwTcPeQua+2M8i8=
github.com/aquasecurity/trivy-kubernetes v0.5.7-0.20230830063136-fe986af3f10f h1:KOB3oGBjP+usI88PzDehhJ0AUWoKUCs7wBspcxBAF00=
@@ -1459,8 +1459,8 @@ github.com/owenrumney/go-sarif/v2 v2.2.0 h1:1DmZaijK0HBZCR1fgcDSGa7VzYkU9NDmbZ7q
github.com/owenrumney/go-sarif/v2 v2.2.0/go.mod h1:MSqMMx9WqlBSY7pXoOZWgEsVB4FDNfhcaXDA1j6Sr+w=
github.com/owenrumney/squealer v1.1.1 h1:e+fg29IxdNARSc4s7CbYnqVSepm9eOqErLNNNR5XbAs=
github.com/owenrumney/squealer v1.1.1/go.mod h1:Q5ekVoyFSG2FlnCVIBGsyk/FSMA/ATv8PtwKIVX7t/o=
github.com/package-url/packageurl-go v0.1.1 h1:KTRE0bK3sKbFKAk3yy63DpeskU7Cvs/x/Da5l+RtzyU=
github.com/package-url/packageurl-go v0.1.1/go.mod h1:uQd4a7Rh3ZsVg5j0lNyAfyxIeGde9yrlhjF78GzeW0c=
github.com/package-url/packageurl-go v0.1.2-0.20230812223828-f8bb31c1f10b h1:mUXbYcE4/ZAh9uto21SUH+FL/RGmD0OGYci9JX66jDc=
github.com/package-url/packageurl-go v0.1.2-0.20230812223828-f8bb31c1f10b/go.mod h1:uQd4a7Rh3ZsVg5j0lNyAfyxIeGde9yrlhjF78GzeW0c=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=

File diff suppressed because it is too large Load Diff

View File

@@ -76,7 +76,6 @@ nav:
- CentOS: docs/coverage/os/centos.md
- Chainguard: docs/coverage/os/chainguard.md
- Debian: docs/coverage/os/debian.md
- Google Distroless: docs/coverage/os/google-distroless.md
- Oracle Linux: docs/coverage/os/oracle.md
- Photon OS: docs/coverage/os/photon.md
- Red Hat: docs/coverage/os/rhel.md
@@ -84,6 +83,8 @@ nav:
- SUSE: docs/coverage/os/suse.md
- Ubuntu: docs/coverage/os/ubuntu.md
- Wolfi: docs/coverage/os/wolfi.md
- Google Distroless (Images): docs/coverage/os/google-distroless.md
- Bitnami (Images): docs/coverage/os/bitnami.md
- Language:
- Overview: docs/coverage/language/index.md
- C/C++: docs/coverage/language/c.md

View File

@@ -65,6 +65,9 @@ func NewDriver(libType string) (Driver, bool) {
// https://www.swift.org/package-manager/#importing-dependencies
ecosystem = vulnerability.Swift
comparer = compare.GenericComparer{}
case ftypes.Bitnami:
ecosystem = vulnerability.Bitnami
comparer = compare.GenericComparer{}
case ftypes.Cocoapods:
// CocoaPods uses RubyGems version specifiers
// https://guides.cocoapods.org/making/making-a-cocoapod.html#cocoapods-versioning-specifics

View File

@@ -194,7 +194,10 @@ func (r *AnalysisResult) Sort() {
// Language-specific packages
sort.Slice(r.Applications, func(i, j int) bool {
return r.Applications[i].FilePath < r.Applications[j].FilePath
if r.Applications[i].FilePath != r.Applications[j].FilePath {
return r.Applications[i].FilePath < r.Applications[j].FilePath
}
return r.Applications[i].Type < r.Applications[j].Type
})
for _, app := range r.Applications {

View File

@@ -9,7 +9,9 @@ import (
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/fanal/analyzer"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/sbom"
"github.com/aquasecurity/trivy/pkg/types"
)
func init() {
@@ -39,26 +41,12 @@ func (a sbomAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (
return nil, xerrors.Errorf("SBOM decode error: %w", err)
}
// For Bitnami images
// Bitnami images
// SPDX files are located under the /opt/bitnami/<component> directory
// and named with the pattern .spdx-<component>.spdx
// ref: https://github.com/bitnami/vulndb#how-to-consume-this-cve-feed
if strings.HasPrefix(input.FilePath, "opt/bitnami/") {
dir, file := path.Split(input.FilePath)
bin := strings.TrimPrefix(file, ".spdx-")
bin = strings.TrimSuffix(bin, ".spdx")
binPath := path.Join(input.FilePath, "../bin", bin)
for i, app := range bom.Applications {
// Replace the SBOM path with the binary path
bom.Applications[i].FilePath = binPath
for j, pkg := range app.Libraries {
if pkg.FilePath == "" {
continue
}
// Set the absolute path since SBOM in Bitnami images contain a relative path
// e.g. modules/apm/elastic-apm-agent-1.36.0.jar
// => opt/bitnami/elasticsearch/modules/apm/elastic-apm-agent-1.36.0.jar
bom.Applications[i].Libraries[j].FilePath = path.Join(dir, pkg.FilePath)
}
}
handleBitnamiImages(path.Dir(input.FilePath), bom)
}
return &analyzer.AnalysisResult{
@@ -83,3 +71,22 @@ func (a sbomAnalyzer) Type() analyzer.Type {
func (a sbomAnalyzer) Version() int {
return version
}
func handleBitnamiImages(componentPath string, bom types.SBOM) {
for i, app := range bom.Applications {
if app.Type == ftypes.Bitnami {
// Set the component dir path to the application
bom.Applications[i].FilePath = componentPath
// Either Application.FilePath or Application.Libraries[].FilePath should be set
continue
}
for j, pkg := range app.Libraries {
// Set the absolute path since SBOM in Bitnami images contain a relative path
// e.g. modules/apm/elastic-apm-agent-1.36.0.jar
// => opt/bitnami/elasticsearch/modules/apm/elastic-apm-agent-1.36.0.jar
// If the file path is empty, the file path will be set to the component dir path.
bom.Applications[i].Libraries[j].FilePath = path.Join(componentPath, pkg.FilePath)
}
}
}

View File

@@ -2,38 +2,92 @@ package sbom
import (
"context"
"os"
"testing"
"github.com/aquasecurity/trivy/pkg/fanal/analyzer"
"github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"os"
"testing"
)
func Test_sbomAnalyzer_Analyze(t *testing.T) {
tests := []struct {
name string
file string
want *analyzer.AnalysisResult
wantErr require.ErrorAssertionFunc
name string
file string
filePath string
want *analyzer.AnalysisResult
wantErr require.ErrorAssertionFunc
}{
{
name: "valid spdx file",
file: "testdata/spdx.json",
name: "valid elasticsearch spdx file",
file: "testdata/elasticsearch.spdx.json",
filePath: "opt/bitnami/elasticsearch/.spdx-elasticsearch.spdx",
want: &analyzer.AnalysisResult{
Applications: []types.Application{
{
Type: types.Jar,
FilePath: "opt/bitnami/bin/elasticsearch",
Type: types.Jar,
Libraries: types.Packages{
{
FilePath: "opt/bitnami/modules/apm/elastic-apm-agent-1.36.0.jar",
Name: "co.elastic.apm:apm-agent",
Version: "1.36.0",
Ref: "pkg:maven/co.elastic.apm/apm-agent@1.36.0",
FilePath: "opt/bitnami/elasticsearch",
},
{
Name: "co.elastic.apm:apm-agent-cached-lookup-key",
Version: "1.36.0",
Ref: "pkg:maven/co.elastic.apm/apm-agent-cached-lookup-key@1.36.0",
FilePath: "opt/bitnami/elasticsearch",
},
{
Name: "co.elastic.apm:apm-agent-common",
Version: "1.36.0",
Ref: "pkg:maven/co.elastic.apm/apm-agent-common@1.36.0",
FilePath: "opt/bitnami/elasticsearch",
},
{
Name: "co.elastic.apm:apm-agent-core",
Version: "1.36.0",
Ref: "pkg:maven/co.elastic.apm/apm-agent-core@1.36.0",
FilePath: "opt/bitnami/elasticsearch",
},
},
},
{
Type: types.Bitnami,
FilePath: "opt/bitnami/elasticsearch",
Libraries: types.Packages{
{
Name: "elasticsearch",
Version: "8.9.1",
Ref: "pkg:bitnami/elasticsearch@8.9.1?arch=arm64",
Arch: "arm64",
Licenses: []string{"Elastic-2.0"},
},
},
},
},
},
wantErr: require.NoError,
},
{
name: "valid elasticsearch cdx file",
file: "testdata/cdx.json",
filePath: "opt/bitnami/elasticsearch/.spdx-elasticsearch.cdx",
want: &analyzer.AnalysisResult{
Applications: []types.Application{
{
Type: types.Jar,
Libraries: types.Packages{
{
FilePath: "opt/bitnami/elasticsearch/modules/apm/elastic-apm-agent-1.36.0.jar",
Name: "co.elastic.apm:apm-agent",
Version: "1.36.0",
Ref: "pkg:maven/co.elastic.apm/apm-agent@1.36.0",
},
{
FilePath: "opt/bitnami/modules/apm/elastic-apm-agent-1.36.0.jar",
FilePath: "opt/bitnami/elasticsearch/modules/apm/elastic-apm-agent-1.36.0.jar",
Name: "co.elastic.apm:apm-agent-cached-lookup-key",
Version: "1.36.0",
Ref: "pkg:maven/co.elastic.apm/apm-agent-cached-lookup-key@1.36.0",
@@ -45,25 +99,38 @@ func Test_sbomAnalyzer_Analyze(t *testing.T) {
wantErr: require.NoError,
},
{
name: "valid cdx file",
file: "testdata/cdx.json",
name: "valid postgresql spdx file",
file: "testdata/postgresql.spdx.json",
filePath: "opt/bitnami/postgresql/.spdx-postgresql.spdx",
want: &analyzer.AnalysisResult{
Applications: []types.Application{
{
Type: types.Jar,
FilePath: "opt/bitnami/bin/elasticsearch",
Type: types.Bitnami,
FilePath: "opt/bitnami/postgresql",
Libraries: types.Packages{
{
FilePath: "opt/bitnami/modules/apm/elastic-apm-agent-1.36.0.jar",
Name: "co.elastic.apm:apm-agent",
Version: "1.36.0",
Ref: "pkg:maven/co.elastic.apm/apm-agent@1.36.0",
Name: "gdal",
Version: "3.7.1",
Ref: "pkg:bitnami/gdal@3.7.1",
Licenses: []string{"MIT"},
},
{
FilePath: "opt/bitnami/modules/apm/elastic-apm-agent-1.36.0.jar",
Name: "co.elastic.apm:apm-agent-cached-lookup-key",
Version: "1.36.0",
Ref: "pkg:maven/co.elastic.apm/apm-agent-cached-lookup-key@1.36.0",
Name: "geos",
Version: "3.8.3",
Ref: "pkg:bitnami/geos@3.8.3",
Licenses: []string{"LGPL-2.1-only"},
},
{
Name: "postgresql",
Version: "15.3.0",
Ref: "pkg:bitnami/postgresql@15.3.0",
Licenses: []string{"PostgreSQL"},
},
{
Name: "proj",
Version: "6.3.2",
Ref: "pkg:bitnami/proj@6.3.2",
Licenses: []string{"MIT"},
},
},
},
@@ -72,10 +139,11 @@ func Test_sbomAnalyzer_Analyze(t *testing.T) {
wantErr: require.NoError,
},
{
name: "invalid spdx file",
file: "testdata/invalid_spdx.json",
want: nil,
wantErr: require.Error,
name: "invalid spdx file",
file: "testdata/invalid_spdx.json",
filePath: "opt/bitnami/elasticsearch/.spdx-elasticsearch.spdx",
want: nil,
wantErr: require.Error,
},
}
for _, tt := range tests {
@@ -86,10 +154,14 @@ func Test_sbomAnalyzer_Analyze(t *testing.T) {
a := sbomAnalyzer{}
got, err := a.Analyze(context.Background(), analyzer.AnalysisInput{
FilePath: "opt/bitnami/.spdx-elasticsearch.spdx",
FilePath: tt.filePath,
Content: f,
})
tt.wantErr(t, err)
if got != nil {
got.Sort()
}
assert.Equal(t, tt.want, got)
})
}

View File

@@ -0,0 +1,150 @@
{
"SPDXID": "SPDXRef-elasticsearch",
"spdxVersion": "SPDX-2.3",
"creationInfo": {
"created": "2023-08-18T20:09:40.708Z",
"creators": [
"Organization: VMware, Inc."
]
},
"name": "SPDX document for Elasticsearch 8.9.1",
"dataLicense": "CC0-1.0",
"documentDescribes": [
"SPDXRef-elasticsearch"
],
"documentNamespace": "elasticsearch-8.9.1",
"packages": [
{
"SPDXID": "SPDXRef-elasticsearch",
"name": "Elasticsearch",
"versionInfo": "8.9.1",
"downloadLocation": "https://github.com/elastic/elasticsearch/archive/v8.9.1.tar.gz",
"licenseConcluded": "Elastic-2.0",
"licenseDeclared": "Elastic-2.0",
"filesAnalyzed": false,
"externalRefs": [
{
"referenceCategory": "SECURITY",
"referenceType": "cpe23Type",
"referenceLocator": "cpe:2.3:*:elastic:elasticsearch:8.9.1:*:*:*:*:*:*:*"
},
{
"referenceCategory": "PACKAGE_MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:bitnami/elasticsearch@8.9.1?arch=arm64"
}
],
"copyrightText": "NOASSERTION"
},
{
"name": "jar",
"SPDXID": "SPDXRef-Application-150e605f5f17224d",
"downloadLocation": "NONE",
"sourceInfo": "Java",
"copyrightText": "NOASSERTION",
"licenseConcluded": "NOASSERTION",
"licenseDeclared": "NOASSERTION",
"filesAnalyzed": false
},
{
"name": "co.elastic.apm:apm-agent",
"SPDXID": "SPDXRef-Package-f0db45781e6813a1",
"versionInfo": "1.36.0",
"supplier": "NOASSERTION",
"downloadLocation": "NONE",
"licenseConcluded": "NONE",
"licenseDeclared": "NONE",
"copyrightText": "NOASSERTION",
"externalRefs": [
{
"referenceCategory": "PACKAGE_MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:maven/co.elastic.apm/apm-agent@1.36.0"
}
],
"filesAnalyzed": false
},
{
"name": "co.elastic.apm:apm-agent-cached-lookup-key",
"SPDXID": "SPDXRef-Package-efe22bf5916f985f",
"versionInfo": "1.36.0",
"supplier": "NOASSERTION",
"downloadLocation": "NONE",
"licenseConcluded": "NONE",
"licenseDeclared": "NONE",
"copyrightText": "NOASSERTION",
"externalRefs": [
{
"referenceCategory": "PACKAGE_MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:maven/co.elastic.apm/apm-agent-cached-lookup-key@1.36.0"
}
],
"filesAnalyzed": false
},
{
"name": "co.elastic.apm:apm-agent-common",
"SPDXID": "SPDXRef-Package-33d86d2d11abe114",
"versionInfo": "1.36.0",
"supplier": "NOASSERTION",
"downloadLocation": "NONE",
"licenseConcluded": "NONE",
"licenseDeclared": "NONE",
"copyrightText": "NOASSERTION",
"externalRefs": [
{
"referenceCategory": "PACKAGE_MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:maven/co.elastic.apm/apm-agent-common@1.36.0"
}
],
"filesAnalyzed": false
},
{
"name": "co.elastic.apm:apm-agent-core",
"SPDXID": "SPDXRef-Package-b905fcf69ca61281",
"versionInfo": "1.36.0",
"supplier": "NOASSERTION",
"downloadLocation": "NONE",
"licenseConcluded": "NONE",
"licenseDeclared": "NONE",
"copyrightText": "NOASSERTION",
"externalRefs": [
{
"referenceCategory": "PACKAGE_MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:maven/co.elastic.apm/apm-agent-core@1.36.0"
}
],
"filesAnalyzed": false
}
],
"files": [],
"relationships": [
{
"spdxElementId": "SPDXRef-elasticsearch",
"relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-Application-150e605f5f17224d"
},
{
"spdxElementId": "SPDXRef-Application-150e605f5f17224d",
"relatedSpdxElement": "SPDXRef-Package-f0db45781e6813a1",
"relationshipType": "CONTAINS"
},
{
"spdxElementId": "SPDXRef-Application-150e605f5f17224d",
"relatedSpdxElement": "SPDXRef-Package-efe22bf5916f985f",
"relationshipType": "CONTAINS"
},
{
"spdxElementId": "SPDXRef-Application-150e605f5f17224d",
"relatedSpdxElement": "SPDXRef-Package-33d86d2d11abe114",
"relationshipType": "CONTAINS"
},
{
"spdxElementId": "SPDXRef-Application-150e605f5f17224d",
"relatedSpdxElement": "SPDXRef-Package-b905fcf69ca61281",
"relationshipType": "CONTAINS"
}
]
}

View File

@@ -0,0 +1,120 @@
{
"SPDXID": "SPDXRef-postgresql",
"spdxVersion": "SPDX-2.3",
"creationInfo": {
"created": "2023-07-13T19:24:23.609Z",
"creators": [
"Organization: VMware, Inc."
]
},
"name": "SPDX document for PostgreSQL 15.3.0",
"dataLicense": "CC0-1.0",
"documentDescribes": [
"SPDXRef-postgresql"
],
"documentNamespace": "postgresql-15.3.0",
"packages": [
{
"SPDXID": "SPDXRef-postgresql",
"name": "PostgreSQL",
"versionInfo": "15.3.0",
"downloadLocation": "https://ftp.postgresql.org/pub/source/v15.3/postgresql-15.3.tar.gz",
"licenseConcluded": "PostgreSQL",
"licenseDeclared": "PostgreSQL",
"filesAnalyzed": false,
"externalRefs": [
{
"referenceCategory": "SECURITY",
"referenceType": "cpe23Type",
"referenceLocator": "cpe:2.3:*:postgresql:postgresql:15.3.0:*:*:*:*:*:*:*"
},
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:bitnami/postgresql@15.3.0"
}
]
},
{
"SPDXID": "SPDXRef-geos",
"name": "GEOS",
"versionInfo": "3.8.3",
"downloadLocation": "https://github.com/libgeos/geos/archive/3.8.3.tar.gz",
"licenseConcluded": "LGPL-2.1-only",
"licenseDeclared": "LGPL-2.1-only",
"filesAnalyzed": false,
"externalRefs": [
{
"referenceCategory": "SECURITY",
"referenceType": "cpe23Type",
"referenceLocator": "cpe:2.3:*:libgeos:geos:3.8.3:*:*:*:*:*:*:*"
},
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:bitnami/geos@3.8.3"
}
]
},
{
"SPDXID": "SPDXRef-proj",
"name": "Proj",
"versionInfo": "6.3.2",
"downloadLocation": "https://github.com/OSGeo/PROJ/archive/6.3.2.tar.gz",
"licenseConcluded": "MIT",
"licenseDeclared": "MIT",
"filesAnalyzed": false,
"externalRefs": [
{
"referenceCategory": "SECURITY",
"referenceType": "cpe23Type",
"referenceLocator": "cpe:2.3:*:proj:proj:6.3.2:*:*:*:*:*:*:*"
},
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:bitnami/proj@6.3.2"
}
]
},
{
"SPDXID": "SPDXRef-gdal",
"name": "GDAL",
"versionInfo": "3.7.1",
"downloadLocation": "https://github.com/OSGeo/gdal/releases/download/v3.7.1/gdal-3.7.1.tar.gz",
"licenseConcluded": "MIT",
"licenseDeclared": "MIT",
"filesAnalyzed": false,
"externalRefs": [
{
"referenceCategory": "SECURITY",
"referenceType": "cpe23Type",
"referenceLocator": "cpe:2.3:*:osgeo:gdal:3.7.1:*:*:*:*:*:*:*"
},
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:bitnami/gdal@3.7.1"
}
]
}
],
"files": [],
"relationships": [
{
"spdxElementId": "SPDXRef-postgresql",
"relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-geos"
},
{
"spdxElementId": "SPDXRef-postgresql",
"relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-proj"
},
{
"spdxElementId": "SPDXRef-postgresql",
"relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-gdal"
}
]
}

View File

@@ -1,97 +0,0 @@
{
"SPDXID": "SPDXRef-elasticsearch",
"spdxVersion": "SPDX-2.3",
"creationInfo": {
"created": "2023-05-17T15:59:30.511Z",
"creators": [
"Organization: VMware, Inc."
]
},
"name": "SPDX document for Elasticsearch 8.7.1",
"dataLicense": "CC0-1.0",
"documentDescribes": [
"SPDXRef-elasticsearch"
],
"documentNamespace": "elasticsearch-8.7.1",
"packages": [
{
"SPDXID": "SPDXRef-elasticsearch",
"name": "Elasticsearch",
"versionInfo": "8.7.1",
"downloadLocation": "https://github.com/elastic/elasticsearch/archive/v8.7.1.tar.gz",
"licenseConcluded": "Elastic-2.0",
"licenseDeclared": "Elastic-2.0",
"filesAnalyzed": false,
"externalRefs": [
{
"referenceCategory": "SECURITY",
"referenceType": "cpe23Type",
"referenceLocator": "cpe:2.3:*:elasticsearch:elasticsearch:8.7.1:*:*:*:*:*:*:*"
}
]
},
{
"name": "co.elastic.apm:apm-agent",
"SPDXID": "SPDXRef-Package-d6465ccdd5385c16",
"versionInfo": "1.36.0",
"supplier": "NOASSERTION",
"downloadLocation": "NONE",
"licenseConcluded": "NONE",
"licenseDeclared": "NONE",
"copyrightText": "",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:maven/co.elastic.apm/apm-agent@1.36.0"
}
],
"primaryPackagePurpose": "LIBRARY",
"files": [
{
"fileName":"modules/apm/elastic-apm-agent-1.36.0.jar",
"SPDXID": "SPDXRef-File-4d457bf4ff3526ea",
"checksums": [
{
"algorithm": "SHA1",
"checksumValue": "d2a9ad9b159eb650d25add9395c4f4198f200066"
}
],
"copyrightText": ""
}
]
},
{
"name": "co.elastic.apm:apm-agent-cached-lookup-key",
"SPDXID": "SPDXRef-Package-8e3a2cf58d7bd790",
"versionInfo": "1.36.0",
"supplier": "NOASSERTION",
"downloadLocation": "NONE",
"licenseConcluded": "NONE",
"licenseDeclared": "NONE",
"copyrightText": "",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:maven/co.elastic.apm/apm-agent-cached-lookup-key@1.36.0"
}
],
"primaryPackagePurpose": "LIBRARY",
"files": [
{
"fileName": "modules/apm/elastic-apm-agent-1.36.0.jar",
"SPDXID": "SPDXRef-File-4d457bf4ff3526ea",
"checksums": [
{
"algorithm": "SHA1",
"checksumValue": "d2a9ad9b159eb650d25add9395c4f4198f200066"
}
],
"copyrightText": ""
}
]
}
],
"files": []
}

View File

@@ -34,6 +34,7 @@ const (
Swift = "swift"
Pub = "pub"
Hex = "hex"
Bitnami = "bitnami"
// Config files
YAML = "yaml"

View File

@@ -129,6 +129,8 @@ func (p *PackageURL) PackageType() string {
return ftypes.Conan
case TypeDart: // TODO: replace with packageurl.TypeDart once they add it.
return ftypes.Pub
case packageurl.TypeBitnami:
return ftypes.Bitnami
}
return p.Type
}

View File

@@ -293,7 +293,7 @@ func TestMarshaler_CoreComponent(t *testing.T) {
},
},
{
BOMRef: "pkg:oci/kube-apiserver@sha256:18e61c783b41758dd391ab901366ec3546b26fae00eef7e223d1f94da808e02f?repository_url=k8s.gcr.io%2Fkube-apiserver&arch=",
BOMRef: "pkg:oci/kube-apiserver@sha256%3A18e61c783b41758dd391ab901366ec3546b26fae00eef7e223d1f94da808e02f?arch=&repository_url=k8s.gcr.io%2Fkube-apiserver",
Hashes: &[]cdx.Hash{
{
Algorithm: "SHA-256",
@@ -303,7 +303,7 @@ func TestMarshaler_CoreComponent(t *testing.T) {
Type: "container",
Name: "k8s.gcr.io/kube-apiserver",
Version: "sha256:18e61c783b41758dd391ab901366ec3546b26fae00eef7e223d1f94da808e02f",
PackageURL: "pkg:oci/kube-apiserver@sha256:18e61c783b41758dd391ab901366ec3546b26fae00eef7e223d1f94da808e02f?repository_url=k8s.gcr.io%2Fkube-apiserver&arch=",
PackageURL: "pkg:oci/kube-apiserver@sha256%3A18e61c783b41758dd391ab901366ec3546b26fae00eef7e223d1f94da808e02f?arch=&repository_url=k8s.gcr.io%2Fkube-apiserver",
Properties: &[]cdx.Property{
{
Name: "aquasecurity:trivy:PkgID",
@@ -326,7 +326,7 @@ func TestMarshaler_CoreComponent(t *testing.T) {
},
{
Ref: "3ff14136-e09f-4df9-80ea-000000000003",
Dependencies: &[]string{"pkg:oci/kube-apiserver@sha256:18e61c783b41758dd391ab901366ec3546b26fae00eef7e223d1f94da808e02f?repository_url=k8s.gcr.io%2Fkube-apiserver&arch="},
Dependencies: &[]string{"pkg:oci/kube-apiserver@sha256%3A18e61c783b41758dd391ab901366ec3546b26fae00eef7e223d1f94da808e02f?arch=&repository_url=k8s.gcr.io%2Fkube-apiserver"},
},
{
Ref: "3ff14136-e09f-4df9-80ea-000000000004",
@@ -355,7 +355,7 @@ func TestMarshaler_CoreComponent(t *testing.T) {
Dependencies: &noDepRefs,
},
{
Ref: "pkg:oci/kube-apiserver@sha256:18e61c783b41758dd391ab901366ec3546b26fae00eef7e223d1f94da808e02f?repository_url=k8s.gcr.io%2Fkube-apiserver&arch=",
Ref: "pkg:oci/kube-apiserver@sha256%3A18e61c783b41758dd391ab901366ec3546b26fae00eef7e223d1f94da808e02f?arch=&repository_url=k8s.gcr.io%2Fkube-apiserver",
Dependencies: &noDepRefs,
},
},

View File

@@ -191,8 +191,8 @@ func TestMarshaler_Marshal(t *testing.T) {
},
Component: &cdx.Component{
Type: cdx.ComponentTypeContainer,
BOMRef: "pkg:oci/rails@sha256:a27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177?repository_url=index.docker.io%2Flibrary%2Frails&arch=arm64",
PackageURL: "pkg:oci/rails@sha256:a27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177?repository_url=index.docker.io%2Flibrary%2Frails&arch=arm64",
BOMRef: "pkg:oci/rails@sha256%3Aa27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177?arch=arm64&repository_url=index.docker.io%2Flibrary%2Frails",
PackageURL: "pkg:oci/rails@sha256%3Aa27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177?arch=arm64&repository_url=index.docker.io%2Flibrary%2Frails",
Name: "rails:latest",
Properties: &[]cdx.Property{
{
@@ -464,7 +464,7 @@ func TestMarshaler_Marshal(t *testing.T) {
Dependencies: lo.ToPtr([]string{}),
},
{
Ref: "pkg:oci/rails@sha256:a27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177?repository_url=index.docker.io%2Flibrary%2Frails&arch=arm64",
Ref: "pkg:oci/rails@sha256%3Aa27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177?arch=arm64&repository_url=index.docker.io%2Flibrary%2Frails",
Dependencies: &[]string{
"3ff14136-e09f-4df9-80ea-000000000002",
"3ff14136-e09f-4df9-80ea-000000000003",
@@ -823,7 +823,7 @@ func TestMarshaler_Marshal(t *testing.T) {
},
},
{
BOMRef: "pkg:rpm/centos/acl@2.2.53-1.el8?arch=aarch64&epoch=1&distro=centos-8.3.2011",
BOMRef: "pkg:rpm/centos/acl@2.2.53-1.el8?arch=aarch64&distro=centos-8.3.2011&epoch=1",
Type: cdx.ComponentTypeLibrary,
Name: "acl",
Version: "2.2.53-1.el8",
@@ -834,7 +834,7 @@ func TestMarshaler_Marshal(t *testing.T) {
},
},
},
PackageURL: "pkg:rpm/centos/acl@2.2.53-1.el8?arch=aarch64&epoch=1&distro=centos-8.3.2011",
PackageURL: "pkg:rpm/centos/acl@2.2.53-1.el8?arch=aarch64&distro=centos-8.3.2011&epoch=1",
Properties: &[]cdx.Property{
{
Name: "aquasecurity:trivy:PkgID",
@@ -923,7 +923,7 @@ func TestMarshaler_Marshal(t *testing.T) {
{
Ref: "3ff14136-e09f-4df9-80ea-000000000003",
Dependencies: &[]string{
"pkg:rpm/centos/acl@2.2.53-1.el8?arch=aarch64&epoch=1&distro=centos-8.3.2011",
"pkg:rpm/centos/acl@2.2.53-1.el8?arch=aarch64&distro=centos-8.3.2011&epoch=1",
// Trivy is unable to identify the direct OS packages as of today.
"pkg:rpm/centos/glibc@2.28-151.el8?arch=aarch64&distro=centos-8.3.2011",
},
@@ -937,7 +937,7 @@ func TestMarshaler_Marshal(t *testing.T) {
Dependencies: lo.ToPtr([]string{}),
},
{
Ref: "pkg:rpm/centos/acl@2.2.53-1.el8?arch=aarch64&epoch=1&distro=centos-8.3.2011",
Ref: "pkg:rpm/centos/acl@2.2.53-1.el8?arch=aarch64&distro=centos-8.3.2011&epoch=1",
Dependencies: &[]string{
"pkg:rpm/centos/glibc@2.28-151.el8?arch=aarch64&distro=centos-8.3.2011",
},

View File

@@ -209,7 +209,7 @@ func TestMarshaler_Marshal(t *testing.T) {
{
Category: tspdx.CategoryPackageManager,
RefType: tspdx.RefTypePurl,
Locator: "pkg:oci/rails@sha256:a27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177?repository_url=index.docker.io%2Flibrary%2Frails&arch=arm64",
Locator: "pkg:oci/rails@sha256%3Aa27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177?arch=arm64&repository_url=index.docker.io%2Flibrary%2Frails",
},
},
PackageAttributionTexts: []string{
@@ -370,7 +370,7 @@ func TestMarshaler_Marshal(t *testing.T) {
{
Category: tspdx.CategoryPackageManager,
RefType: tspdx.RefTypePurl,
Locator: "pkg:rpm/centos/acl@2.2.53-1.el8?arch=aarch64&epoch=1&distro=centos-8.3.2011",
Locator: "pkg:rpm/centos/acl@2.2.53-1.el8?arch=aarch64&distro=centos-8.3.2011&epoch=1",
},
},
PackageSourceInfo: "built package from: acl 1:2.2.53-1.el8",

View File

@@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"io"
"sort"
"strings"
version "github.com/knqyf263/go-rpm-version"
@@ -73,6 +74,9 @@ func (s *SPDX) unmarshal(spdxDocument *spdx.Document) error {
packageSPDXIdentifierMap := createPackageSPDXIdentifierMap(spdxDocument.Packages)
packageFilePaths := getPackageFilePaths(spdxDocument)
// Hold packages that are not processed by relationships
orphanPkgs := createPackageSPDXIdentifierMap(spdxDocument.Packages)
relationships := lo.Filter(spdxDocument.Relationships, func(rel *spdx.Relationship, _ int) bool {
// Skip the DESCRIBES relationship.
return rel.Relationship != common.TypeRelationshipDescribe && rel.Relationship != "DESCRIBE"
@@ -90,8 +94,8 @@ func (s *SPDX) unmarshal(spdxDocument *spdx.Document) error {
// - Python package A
// - Python package B
for _, rel := range relationships {
pkgA := packageSPDXIdentifierMap[string(rel.RefA.ElementRefID)]
pkgB := packageSPDXIdentifierMap[string(rel.RefB.ElementRefID)]
pkgA := packageSPDXIdentifierMap[rel.RefA.ElementRefID]
pkgB := packageSPDXIdentifierMap[rel.RefB.ElementRefID]
if pkgA == nil || pkgB == nil {
// Skip the missing pkg relationship.
@@ -102,6 +106,7 @@ func (s *SPDX) unmarshal(spdxDocument *spdx.Document) error {
// Relationship: root package => OS
case isOperatingSystem(pkgB.PackageSPDXIdentifier):
s.SBOM.OS = parseOS(*pkgB)
delete(orphanPkgs, pkgB.PackageSPDXIdentifier)
// Relationship: OS => OS package
case isOperatingSystem(pkgA.PackageSPDXIdentifier):
pkg, _, err := parsePkg(*pkgB, packageFilePaths)
@@ -111,6 +116,7 @@ func (s *SPDX) unmarshal(spdxDocument *spdx.Document) error {
return xerrors.Errorf("failed to parse os package: %w", err)
}
osPkgs = append(osPkgs, *pkg)
delete(orphanPkgs, pkgB.PackageSPDXIdentifier)
// Relationship: root package => application
case isApplication(pkgB.PackageSPDXIdentifier):
// pass
@@ -129,6 +135,10 @@ func (s *SPDX) unmarshal(spdxDocument *spdx.Document) error {
return xerrors.Errorf("failed to parse language-specific package: %w", err)
}
app.Libraries = append(app.Libraries, *lib)
// They are no longer orphan packages
delete(orphanPkgs, pkgA.PackageSPDXIdentifier)
delete(orphanPkgs, pkgB.PackageSPDXIdentifier)
}
}
@@ -143,10 +153,8 @@ func (s *SPDX) unmarshal(spdxDocument *spdx.Document) error {
}
// Fallback for when there are no effective relationships.
if len(relationships) == 0 {
if err := s.parsePackages(spdxDocument); err != nil {
return err
}
if err := s.parsePackages(orphanPkgs); err != nil {
return err
}
// Keep the original document
@@ -156,13 +164,13 @@ func (s *SPDX) unmarshal(spdxDocument *spdx.Document) error {
// parsePackages processes the packages and categorizes them into OS packages and application packages.
// Note that all language-specific packages are treated as a single application.
func (s *SPDX) parsePackages(spdxDocument *spdx.Document) error {
func (s *SPDX) parsePackages(pkgs map[common.ElementID]*spdx.Package) error {
var (
osPkgs []ftypes.Package
app ftypes.Application
apps = map[string]ftypes.Application{}
)
for _, p := range spdxDocument.Packages {
for _, p := range pkgs {
pkg, pkgType, err := parsePkg(*p, nil)
if errors.Is(err, errUnknownPackageFormat) {
continue
@@ -174,27 +182,28 @@ func (s *SPDX) parsePackages(spdxDocument *spdx.Document) error {
osPkgs = append(osPkgs, *pkg)
default:
// Language-specific packages
if app.Type == "" {
app, ok := apps[pkgType]
if !ok {
app.Type = pkgType
}
app.Libraries = append(app.Libraries, *pkg)
apps[pkgType] = app
}
}
if len(osPkgs) > 0 {
s.Packages = []ftypes.PackageInfo{{Packages: osPkgs}}
}
if len(app.Libraries) > 0 {
for _, app := range apps {
sort.Sort(app.Libraries)
s.SBOM.Applications = append(s.SBOM.Applications, app)
}
return nil
}
func createPackageSPDXIdentifierMap(packages []*spdx.Package) map[string]*spdx.Package {
ret := make(map[string]*spdx.Package)
for _, info := range packages {
ret[string(info.PackageSPDXIdentifier)] = info
}
return ret
func createPackageSPDXIdentifierMap(packages []*spdx.Package) map[common.ElementID]*spdx.Package {
return lo.SliceToMap(packages, func(pkg *spdx.Package) (common.ElementID, *spdx.Package) {
return pkg.PackageSPDXIdentifier, pkg
})
}
func createFileSPDXIdentifierMap(files []*spdx.File) map[string]*spdx.File {
@@ -218,14 +227,14 @@ func isFile(elementID spdx.ElementID) bool {
}
func initApplication(pkg spdx.Package) *ftypes.Application {
app := &ftypes.Application{
Type: pkg.PackageName,
FilePath: pkg.PackageSourceInfo,
}
if pkg.PackageName == ftypes.NodePkg || pkg.PackageName == ftypes.PythonPkg ||
pkg.PackageName == ftypes.GemSpec || pkg.PackageName == ftypes.Jar || pkg.PackageName == ftypes.CondaPkg {
app := &ftypes.Application{Type: pkg.PackageName}
switch pkg.PackageName {
case ftypes.NodePkg, ftypes.PythonPkg, ftypes.GemSpec, ftypes.Jar, ftypes.CondaPkg:
app.FilePath = ""
default:
app.FilePath = pkg.PackageSourceInfo
}
return app
}