feat(spdx): add support for SPDX 2.3 (#4058)

Co-authored-by: knqyf263 <knqyf263@gmail.com>
This commit is contained in:
Idan Frimark
2023-04-23 23:36:04 +03:00
committed by GitHub
parent 107752df65
commit 48e021ea6b
12 changed files with 526 additions and 456 deletions

View File

@@ -50,6 +50,7 @@ jobs:
uses: aquaproj/aqua-installer@v2.0.2
with:
aqua_version: v1.25.0
aqua_opts: ""
- name: Check if CLI references are up-to-date
run: |

View File

@@ -3,7 +3,8 @@
# https://aquaproj.github.io/
registries:
- type: standard
ref: v3.106.0 # renovate: depName=aquaproj/aqua-registry
ref: v3.157.0 # renovate: depName=aquaproj/aqua-registry
packages:
- name: tinygo-org/tinygo@v0.26.0
- name: tinygo-org/tinygo@v0.27.0
- name: WebAssembly/binaryen@version_112
- name: magefile/mage@v1.14.0

7
go.mod
View File

@@ -79,7 +79,7 @@ require (
github.com/secure-systems-lab/go-securesystemslib v0.5.0
github.com/sigstore/rekor v1.1.0
github.com/sosedoff/gitkit v0.3.0
github.com/spdx/tools-golang v0.3.1-0.20230104082527-d6f58551be3f
github.com/spdx/tools-golang v0.5.0
github.com/spf13/cast v1.5.0
github.com/spf13/cobra v1.6.1
github.com/spf13/pflag v1.0.5
@@ -135,6 +135,7 @@ require (
github.com/agnivade/levenshtein v1.1.1 // indirect
github.com/alecthomas/chroma v0.10.0 // indirect
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect
github.com/apparentlymart/go-cidr v1.1.0 // indirect
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
@@ -395,7 +396,3 @@ require (
// v1.2.0 is taken from github.com/open-policy-agent/opa v0.42.0
// v1.2.0 incompatible with github.com/docker/docker v23.0.0-rc.1+incompatible
replace oras.land/oras-go => oras.land/oras-go v1.1.1
// v0.3.1-0.20230104082527-d6f58551be3f is taken from github.com/moby/buildkit v0.11.0
// spdx logic write on v0.3.0 and incompatible with v0.3.1-0.20230104082527-d6f58551be3f
replace github.com/spdx/tools-golang => github.com/spdx/tools-golang v0.3.0

6
go.sum
View File

@@ -307,6 +307,8 @@ github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZp
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis/v2 v2.30.1 h1:HM1rlQjq1bm9yQcsawJqSZBJ9AYgxvjkMsNtddh90+g=
github.com/alicebob/miniredis/v2 v2.30.1/go.mod h1:b25qWj4fCEsBeAAR2mlb0ufImGC6uH3VlUfb/HS5zKg=
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 h1:aM1rlcoLz8y5B2r4tTLMiVTrMtpfY0O8EScKJxaSaEc=
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
@@ -1557,8 +1559,8 @@ github.com/sosedoff/gitkit v0.3.0 h1:TfINVRNUM+GcFa+LGhZ3RcWN86Im1M6i8qs0IsgMy90
github.com/sosedoff/gitkit v0.3.0/go.mod h1:V3EpGZ0nvCBhXerPsbDeqtyReNb48cwP9KtkUYTKT5I=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb/go.mod h1:uKWaldnbMnjsSAXRurWqqrdyZen1R7kxl8TkmWk2OyM=
github.com/spdx/tools-golang v0.3.0 h1:rtm+DHk3aAt74Fh0Wgucb4pCxjXV8SqHCPEb2iBd30k=
github.com/spdx/tools-golang v0.3.0/go.mod h1:RO4Y3IFROJnz+43JKm1YOrbtgQNljW4gAPpA/sY2eqo=
github.com/spdx/tools-golang v0.5.0 h1:/fqihV2Jna7fmow65dHpgKNsilgLK7ICpd2tkCnPEyY=
github.com/spdx/tools-golang v0.5.0/go.mod h1:kkGlrSXXfHwuSzHQZJRV3aKu9ZXCq/MSf2+xyiJH1lM=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=

View File

@@ -15,13 +15,14 @@ import (
"time"
cdx "github.com/CycloneDX/cyclonedx-go"
"github.com/spdx/tools-golang/jsonloader"
spdxjson "github.com/spdx/tools-golang/json"
"github.com/spdx/tools-golang/spdx"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/aquasecurity/trivy-db/pkg/db"
"github.com/aquasecurity/trivy-db/pkg/metadata"
"github.com/aquasecurity/trivy/pkg/commands"
"github.com/aquasecurity/trivy/pkg/dbtest"
"github.com/aquasecurity/trivy/pkg/types"
@@ -162,12 +163,12 @@ func readCycloneDX(t *testing.T, filePath string) *cdx.BOM {
return bom
}
func readSpdxJson(t *testing.T, filePath string) *spdx.Document2_2 {
func readSpdxJson(t *testing.T, filePath string) *spdx.Document {
f, err := os.Open(filePath)
require.NoError(t, err)
defer f.Close()
bom, err := jsonloader.Load2_2(f)
bom, err := spdxjson.Read(f)
require.NoError(t, err)
sort.Slice(bom.Relationships, func(i, j int) bool {
@@ -179,7 +180,7 @@ func readSpdxJson(t *testing.T, filePath string) *spdx.Document2_2 {
// We don't compare values which change each time an SBOM is generated
bom.CreationInfo.Created = ""
bom.CreationInfo.DocumentNamespace = ""
bom.DocumentNamespace = ""
return bom
}

View File

@@ -1,117 +1,107 @@
{
"SPDXID": "SPDXRef-DOCUMENT",
"creationInfo": {
"created": "2023-03-29T19:07:18Z",
"creators": [
"Tool: trivy-dev",
"Organization: aquasecurity"
]
},
"dataLicense": "CC0-1.0",
"documentDescribes": [
"SPDXRef-Filesystem-6e0ac6a0fab50ab4"
],
"documentNamespace": "http://aquasecurity.github.io/trivy/filesystem/testdata/fixtures/fs/conda-d872c7e3-4c6c-4fa1-a9b6-3e69dc71ff3b",
"files": [
{
"SPDXID": "SPDXRef-File-600e5e0110a84891",
"checksums": [
{
"algorithm": "SHA1",
"checksumValue": "237db0da53131e4548cb1181337fa0f420299e1f"
}
],
"fileName": "miniconda3/envs/testenv/conda-meta/openssl-1.1.1q-h7f8727e_0.json"
},
{
"SPDXID": "SPDXRef-File-7eb62e2a3edddc0a",
"checksums": [
{
"algorithm": "SHA1",
"checksumValue": "a6a2db7668f1ad541d704369fc66c96a4415aa24"
}
],
"fileName": "miniconda3/envs/testenv/conda-meta/pip-22.2.2-py38h06a4308_0.json"
}
],
"name": "testdata/fixtures/fs/conda",
"packages": [
{
"SPDXID": "SPDXRef-Application-ee5ef1aa4ac89125",
"downloadLocation": "NONE",
"filesAnalyzed": false,
"name": "conda-pkg",
"sourceInfo": "Conda"
},
{
"SPDXID": "SPDXRef-Filesystem-6e0ac6a0fab50ab4",
"attributionTexts": [
"SchemaVersion: 2"
],
"downloadLocation": "NONE",
"filesAnalyzed": false,
"name": "testdata/fixtures/fs/conda"
},
{
"SPDXID": "SPDXRef-Package-6b677e82217fb5bd",
"downloadLocation": "NONE",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceLocator": "pkg:conda/pip@22.2.2",
"referenceType": "purl"
}
],
"filesAnalyzed": false,
"hasFiles": [
"SPDXRef-File-7eb62e2a3edddc0a"
],
"licenseConcluded": "MIT",
"licenseDeclared": "MIT",
"name": "pip",
"versionInfo": "22.2.2"
},
{
"SPDXID": "SPDXRef-Package-b1088cb4090e3a55",
"downloadLocation": "NONE",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceLocator": "pkg:conda/openssl@1.1.1q",
"referenceType": "purl"
}
],
"filesAnalyzed": false,
"hasFiles": [
"SPDXRef-File-600e5e0110a84891"
],
"licenseConcluded": "OpenSSL",
"licenseDeclared": "OpenSSL",
"name": "openssl",
"versionInfo": "1.1.1q"
}
],
"relationships": [
{
"relatedSpdxElement": "SPDXRef-Filesystem-6e0ac6a0fab50ab4",
"relationshipType": "DESCRIBES",
"spdxElementId": "SPDXRef-DOCUMENT"
},
{
"relatedSpdxElement": "SPDXRef-Application-ee5ef1aa4ac89125",
"relationshipType": "CONTAINS",
"spdxElementId": "SPDXRef-Filesystem-6e0ac6a0fab50ab4"
},
{
"relatedSpdxElement": "SPDXRef-Package-b1088cb4090e3a55",
"relationshipType": "CONTAINS",
"spdxElementId": "SPDXRef-Application-ee5ef1aa4ac89125"
},
{
"relatedSpdxElement": "SPDXRef-Package-6b677e82217fb5bd",
"relationshipType": "CONTAINS",
"spdxElementId": "SPDXRef-Application-ee5ef1aa4ac89125"
}
],
"spdxVersion": "SPDX-2.2"
"spdxVersion": "SPDX-2.3",
"dataLicense": "CC0-1.0",
"SPDXID": "SPDXRef-DOCUMENT",
"name": "testdata/fixtures/fs/conda",
"creationInfo": {
"licenseListVersion": "",
"creators": [
"Organization: aquasecurity",
"Tool: trivy-dev"
],
"created": "2023-04-16T11:00:02Z"
},
"packages": [
{
"name": "conda-pkg",
"SPDXID": "SPDXRef-Application-ee5ef1aa4ac89125",
"downloadLocation": "NONE",
"sourceInfo": "Conda"
},
{
"name": "openssl",
"SPDXID": "SPDXRef-Package-b1088cb4090e3a55",
"versionInfo": "1.1.1q",
"downloadLocation": "NONE",
"licenseConcluded": "OpenSSL",
"licenseDeclared": "OpenSSL",
"copyrightText": "",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:conda/openssl@1.1.1q"
}
],
"files": [
{
"fileName": "miniconda3/envs/testenv/conda-meta/openssl-1.1.1q-h7f8727e_0.json",
"SPDXID": "SPDXRef-File-600e5e0110a84891",
"checksums": [
{
"algorithm": "SHA1",
"checksumValue": "237db0da53131e4548cb1181337fa0f420299e1f"
}
]
}
]
},
{
"name": "pip",
"SPDXID": "SPDXRef-Package-6b677e82217fb5bd",
"versionInfo": "22.2.2",
"downloadLocation": "NONE",
"licenseConcluded": "MIT",
"licenseDeclared": "MIT",
"externalRefs": [
{
"referenceCategory": "PACKAGE-MANAGER",
"referenceType": "purl",
"referenceLocator": "pkg:conda/pip@22.2.2"
}
],
"files": [
{
"fileName": "miniconda3/envs/testenv/conda-meta/pip-22.2.2-py38h06a4308_0.json",
"SPDXID": "SPDXRef-File-7eb62e2a3edddc0a",
"checksums": [
{
"algorithm": "SHA1",
"checksumValue": "a6a2db7668f1ad541d704369fc66c96a4415aa24"
}
]
}
]
},
{
"name": "testdata/fixtures/fs/conda",
"SPDXID": "SPDXRef-Filesystem-6e0ac6a0fab50ab4",
"downloadLocation": "NONE",
"attributionTexts": [
"SchemaVersion: 2"
]
}
],
"relationships": [
{
"spdxElementId": "SPDXRef-DOCUMENT",
"relatedSpdxElement": "SPDXRef-Filesystem-6e0ac6a0fab50ab4",
"relationshipType": "DESCRIBES"
},
{
"spdxElementId": "SPDXRef-Filesystem-6e0ac6a0fab50ab4",
"relatedSpdxElement": "SPDXRef-Application-ee5ef1aa4ac89125",
"relationshipType": "CONTAINS"
},
{
"spdxElementId": "SPDXRef-Application-ee5ef1aa4ac89125",
"relatedSpdxElement": "SPDXRef-Package-b1088cb4090e3a55",
"relationshipType": "CONTAINS"
},
{
"spdxElementId": "SPDXRef-Application-ee5ef1aa4ac89125",
"relatedSpdxElement": "SPDXRef-Package-6b677e82217fb5bd",
"relationshipType": "CONTAINS"
}
]
}

View File

@@ -3,8 +3,8 @@ package spdx
import (
"io"
"github.com/spdx/tools-golang/jsonsaver"
"github.com/spdx/tools-golang/tvsaver"
"github.com/spdx/tools-golang/json"
"github.com/spdx/tools-golang/tagvalue"
"golang.org/x/xerrors"
"github.com/aquasecurity/trivy/pkg/sbom/spdx"
@@ -34,11 +34,11 @@ func (w Writer) Write(report types.Report) error {
}
if w.format == "spdx-json" {
if err := jsonsaver.Save2_2(spdxDoc, w.output); err != nil {
if err := json.Write(spdxDoc, w.output); err != nil {
return xerrors.Errorf("failed to save spdx json: %w", err)
}
} else {
if err := tvsaver.Save2_2(spdxDoc, w.output); err != nil {
if err := tagvalue.Write(spdxDoc, w.output); err != nil {
return xerrors.Errorf("failed to save spdx tag-value: %w", err)
}
}

View File

@@ -2,6 +2,7 @@ package spdx
import (
"fmt"
"sort"
"strconv"
"strings"
"time"
@@ -10,6 +11,8 @@ import (
"github.com/mitchellh/hashstructure/v2"
"github.com/samber/lo"
"github.com/spdx/tools-golang/spdx"
"github.com/spdx/tools-golang/spdx/v2/common"
"golang.org/x/exp/maps"
"golang.org/x/xerrors"
"k8s.io/utils/clock"
@@ -24,13 +27,11 @@ import (
)
const (
SPDXVersion = "SPDX-2.2"
DataLicense = "CC0-1.0"
SPDXIdentifier = "DOCUMENT"
DocumentNamespace = "http://aquasecurity.github.io/trivy"
CreatorOrganization = "aquasecurity"
CreatorTool = "trivy"
noneField = "NONE"
DocumentSPDXIdentifier = "DOCUMENT"
DocumentNamespace = "http://aquasecurity.github.io/trivy"
CreatorOrganization = "aquasecurity"
CreatorTool = "trivy"
noneField = "NONE"
)
const (
@@ -51,9 +52,9 @@ const (
PropertyLayerDiffID = "LayerDiffID"
PropertyLayerDigest = "LayerDigest"
RelationShipContains = "CONTAINS"
RelationShipDescribe = "DESCRIBES"
RelationShipDependsOn = "DEPENDS_ON"
RelationShipContains = common.TypeRelationshipContains
RelationShipDescribe = common.TypeRelationshipDescribe
RelationShipDependsOn = common.TypeRelationshipDependsOn
ElementOperatingSystem = "OperatingSystem"
ElementApplication = "Application"
@@ -66,7 +67,7 @@ var (
)
type Marshaler struct {
format spdx.Document2_1
format spdx.Document
clock clock.Clock
newUUID newUUID
hasher Hash
@@ -99,7 +100,7 @@ func WithHasher(hasher Hash) marshalOption {
func NewMarshaler(version string, opts ...marshalOption) *Marshaler {
m := &Marshaler{
format: spdx.Document2_1{},
format: spdx.Document{},
clock: clock.RealClock{},
newUUID: uuid.New,
hasher: hashstructure.Hash,
@@ -113,9 +114,9 @@ func NewMarshaler(version string, opts ...marshalOption) *Marshaler {
return m
}
func (m *Marshaler) Marshal(r types.Report) (*spdx.Document2_2, error) {
var relationShips []*spdx.Relationship2_2
packages := make(map[spdx.ElementID]*spdx.Package2_2)
func (m *Marshaler) Marshal(r types.Report) (*spdx.Document, error) {
var relationShips []*spdx.Relationship
packages := make(map[spdx.ElementID]*spdx.Package)
pkgDownloadLocation := getPackageDownloadLocation(r.ArtifactType, r.ArtifactName)
// Root package contains OS, OS packages, language-specific packages and so on.
@@ -125,7 +126,7 @@ func (m *Marshaler) Marshal(r types.Report) (*spdx.Document2_2, error) {
}
packages[rootPkg.PackageSPDXIdentifier] = rootPkg
relationShips = append(relationShips,
relationShip(SPDXIdentifier, rootPkg.PackageSPDXIdentifier, RelationShipDescribe),
relationShip(DocumentSPDXIdentifier, rootPkg.PackageSPDXIdentifier, RelationShipDescribe),
)
for _, result := range r.Results {
@@ -150,57 +151,76 @@ func (m *Marshaler) Marshal(r types.Report) (*spdx.Document2_2, error) {
}
}
return &spdx.Document2_2{
CreationInfo: &spdx.CreationInfo2_2{
SPDXVersion: SPDXVersion,
DataLicense: DataLicense,
SPDXIdentifier: SPDXIdentifier,
DocumentName: r.ArtifactName,
DocumentNamespace: getDocumentNamespace(r, m),
CreatorOrganizations: []string{CreatorOrganization},
CreatorTools: []string{fmt.Sprintf("%s-%s", CreatorTool, m.appVersion)},
Created: m.clock.Now().UTC().Format(time.RFC3339),
return &spdx.Document{
SPDXVersion: spdx.Version,
DataLicense: spdx.DataLicense,
SPDXIdentifier: DocumentSPDXIdentifier,
DocumentName: r.ArtifactName,
DocumentNamespace: getDocumentNamespace(r, m),
CreationInfo: &spdx.CreationInfo{
Creators: []common.Creator{
{
Creator: CreatorOrganization,
CreatorType: "Organization",
},
{
Creator: fmt.Sprintf("%s-%s", CreatorTool, m.appVersion),
CreatorType: "Tool",
},
},
Created: m.clock.Now().UTC().Format(time.RFC3339),
},
Packages: packages,
Packages: toPackages(packages),
Relationships: relationShips,
}, nil
}
func (m *Marshaler) resultToSpdxPackage(result types.Result, os *ftypes.OS, pkgDownloadLocation string) (spdx.Package2_2, error) {
func toPackages(packages map[spdx.ElementID]*spdx.Package) []*spdx.Package {
ret := maps.Values(packages)
sort.Slice(ret, func(i, j int) bool {
if ret[i].PackageName != ret[j].PackageName {
return ret[i].PackageName < ret[j].PackageName
}
return ret[i].PackageSPDXIdentifier < ret[j].PackageSPDXIdentifier
})
return ret
}
func (m *Marshaler) resultToSpdxPackage(result types.Result, os *ftypes.OS, pkgDownloadLocation string) (spdx.Package, error) {
switch result.Class {
case types.ClassOSPkg:
osPkg, err := m.osPackage(os, pkgDownloadLocation)
if err != nil {
return spdx.Package2_2{}, xerrors.Errorf("failed to parse operating system package: %w", err)
return spdx.Package{}, xerrors.Errorf("failed to parse operating system package: %w", err)
}
return osPkg, nil
case types.ClassLangPkg:
langPkg, err := m.langPackage(result.Target, result.Type, pkgDownloadLocation)
if err != nil {
return spdx.Package2_2{}, xerrors.Errorf("failed to parse application package: %w", err)
return spdx.Package{}, xerrors.Errorf("failed to parse application package: %w", err)
}
return langPkg, nil
default:
// unsupported packages
return spdx.Package2_2{}, nil
return spdx.Package{}, nil
}
}
func (m *Marshaler) parseFile(filePath string, digest digest.Digest) (spdx.File2_2, error) {
func (m *Marshaler) parseFile(filePath string, digest digest.Digest) (spdx.File, error) {
pkgID, err := calcPkgID(m.hasher, filePath)
if err != nil {
return spdx.File2_2{}, xerrors.Errorf("failed to get %s package ID: %w", filePath, err)
return spdx.File{}, xerrors.Errorf("failed to get %s package ID: %w", filePath, err)
}
file := spdx.File2_2{
file := spdx.File{
FileSPDXIdentifier: spdx.ElementID(fmt.Sprintf("File-%s", pkgID)),
FileName: filePath,
FileChecksums: digestToSpdxFileChecksum(digest),
Checksums: digestToSpdxFileChecksum(digest),
}
return file, nil
}
func (m *Marshaler) rootPackage(r types.Report, pkgDownloadLocation string) (*spdx.Package2_2, error) {
var externalReferences []*spdx.PackageExternalReference2_2
func (m *Marshaler) rootPackage(r types.Report, pkgDownloadLocation string) (*spdx.Package, error) {
var externalReferences []*spdx.PackageExternalReference
attributionTexts := []string{attributionText(PropertySchemaVersion, strconv.Itoa(r.SchemaVersion))}
// When the target is a container image, add PURL to the external references of the root package.
@@ -232,7 +252,7 @@ func (m *Marshaler) rootPackage(r types.Report, pkgDownloadLocation string) (*sp
return nil, xerrors.Errorf("failed to get %s package ID: %w", err)
}
return &spdx.Package2_2{
return &spdx.Package{
PackageName: r.ArtifactName,
PackageSPDXIdentifier: elementID(camelCase(string(r.ArtifactType)), pkgID),
PackageDownloadLocation: pkgDownloadLocation,
@@ -241,17 +261,17 @@ func (m *Marshaler) rootPackage(r types.Report, pkgDownloadLocation string) (*sp
}, nil
}
func (m *Marshaler) osPackage(osFound *ftypes.OS, pkgDownloadLocation string) (spdx.Package2_2, error) {
func (m *Marshaler) osPackage(osFound *ftypes.OS, pkgDownloadLocation string) (spdx.Package, error) {
if osFound == nil {
return spdx.Package2_2{}, nil
return spdx.Package{}, nil
}
pkgID, err := calcPkgID(m.hasher, osFound)
if err != nil {
return spdx.Package2_2{}, xerrors.Errorf("failed to get os metadata package ID: %w", err)
return spdx.Package{}, xerrors.Errorf("failed to get os metadata package ID: %w", err)
}
return spdx.Package2_2{
return spdx.Package{
PackageName: osFound.Family,
PackageVersion: osFound.Name,
PackageSPDXIdentifier: elementID(ElementOperatingSystem, pkgID),
@@ -259,13 +279,13 @@ func (m *Marshaler) osPackage(osFound *ftypes.OS, pkgDownloadLocation string) (s
}, nil
}
func (m *Marshaler) langPackage(target, appType, pkgDownloadLocation string) (spdx.Package2_2, error) {
func (m *Marshaler) langPackage(target, appType, pkgDownloadLocation string) (spdx.Package, error) {
pkgID, err := calcPkgID(m.hasher, fmt.Sprintf("%s-%s", target, appType))
if err != nil {
return spdx.Package2_2{}, xerrors.Errorf("failed to get %s package ID: %w", target, err)
return spdx.Package{}, xerrors.Errorf("failed to get %s package ID: %w", target, err)
}
return spdx.Package2_2{
return spdx.Package{
PackageName: appType,
PackageSourceInfo: target, // TODO: Files seems better
PackageSPDXIdentifier: elementID(ElementApplication, pkgID),
@@ -273,12 +293,12 @@ func (m *Marshaler) langPackage(target, appType, pkgDownloadLocation string) (sp
}, nil
}
func (m *Marshaler) pkgToSpdxPackage(t, pkgDownloadLocation string, class types.ResultClass, metadata types.Metadata, pkg ftypes.Package) (spdx.Package2_2, error) {
func (m *Marshaler) pkgToSpdxPackage(t, pkgDownloadLocation string, class types.ResultClass, metadata types.Metadata, pkg ftypes.Package) (spdx.Package, error) {
license := GetLicense(pkg)
pkgID, err := calcPkgID(m.hasher, pkg)
if err != nil {
return spdx.Package2_2{}, xerrors.Errorf("failed to get %s package ID: %w", pkg.Name, err)
return spdx.Package{}, xerrors.Errorf("failed to get %s package ID: %w", pkg.Name, err)
}
var pkgSrcInfo string
@@ -288,9 +308,9 @@ func (m *Marshaler) pkgToSpdxPackage(t, pkgDownloadLocation string, class types.
packageURL, err := purl.NewPackageURL(t, metadata, pkg)
if err != nil {
return spdx.Package2_2{}, xerrors.Errorf("failed to parse purl (%s): %w", pkg.Name, err)
return spdx.Package{}, xerrors.Errorf("failed to parse purl (%s): %w", pkg.Name, err)
}
pkgExtRefs := []*spdx.PackageExternalReference2_2{purlExternalReference(packageURL.String())}
pkgExtRefs := []*spdx.PackageExternalReference{purlExternalReference(packageURL.String())}
var attrTexts []string
attrTexts = appendAttributionText(attrTexts, PropertyPkgID, pkg.ID)
@@ -299,10 +319,10 @@ func (m *Marshaler) pkgToSpdxPackage(t, pkgDownloadLocation string, class types.
files, err := m.pkgFiles(pkg)
if err != nil {
return spdx.Package2_2{}, xerrors.Errorf("package file error: %w", err)
return spdx.Package{}, xerrors.Errorf("package file error: %w", err)
}
return spdx.Package2_2{
return spdx.Package{
PackageName: pkg.Name,
PackageVersion: utils.FormatVersion(pkg),
PackageSPDXIdentifier: elementID(ElementPackage, pkgID),
@@ -321,7 +341,7 @@ func (m *Marshaler) pkgToSpdxPackage(t, pkgDownloadLocation string, class types.
}, nil
}
func (m *Marshaler) pkgFiles(pkg ftypes.Package) (map[spdx.ElementID]*spdx.File2_2, error) {
func (m *Marshaler) pkgFiles(pkg ftypes.Package) ([]*spdx.File, error) {
if pkg.FilePath == "" {
return nil, nil
}
@@ -330,8 +350,8 @@ func (m *Marshaler) pkgFiles(pkg ftypes.Package) (map[spdx.ElementID]*spdx.File2
if err != nil {
return nil, xerrors.Errorf("failed to parse file: %w")
}
return map[spdx.ElementID]*spdx.File2_2{
file.FileSPDXIdentifier: &file,
return []*spdx.File{
&file,
}, nil
}
@@ -339,10 +359,10 @@ func elementID(elementType, pkgID string) spdx.ElementID {
return spdx.ElementID(fmt.Sprintf("%s-%s", elementType, pkgID))
}
func relationShip(refA, refB spdx.ElementID, operator string) *spdx.Relationship2_2 {
ref := spdx.Relationship2_2{
RefA: spdx.MakeDocElementID("", string(refA)),
RefB: spdx.MakeDocElementID("", string(refB)),
func relationShip(refA, refB spdx.ElementID, operator string) *spdx.Relationship {
ref := spdx.Relationship{
RefA: common.MakeDocElementID("", string(refA)),
RefB: common.MakeDocElementID("", string(refB)),
Relationship: operator,
}
return &ref
@@ -359,8 +379,8 @@ func attributionText(key, value string) string {
return fmt.Sprintf("%s: %s", key, value)
}
func purlExternalReference(packageURL string) *spdx.PackageExternalReference2_2 {
return &spdx.PackageExternalReference2_2{
func purlExternalReference(packageURL string) *spdx.PackageExternalReference {
return &spdx.PackageExternalReference{
Category: CategoryPackageManager,
RefType: RefTypePurl,
Locator: packageURL,
@@ -443,7 +463,7 @@ func getPackageDownloadLocation(t ftypes.ArtifactType, artifactName string) stri
return location
}
func digestToSpdxFileChecksum(d digest.Digest) map[spdx.ChecksumAlgorithm]spdx.Checksum {
func digestToSpdxFileChecksum(d digest.Digest) []common.Checksum {
if d == "" {
return nil
}
@@ -458,8 +478,8 @@ func digestToSpdxFileChecksum(d digest.Digest) map[spdx.ChecksumAlgorithm]spdx.C
return nil
}
return map[spdx.ChecksumAlgorithm]spdx.Checksum{
alg: {
return []spdx.Checksum{
{
Algorithm: alg,
Value: d.Encoded(),
},

View File

@@ -10,6 +10,7 @@ import (
"github.com/google/uuid"
"github.com/mitchellh/hashstructure/v2"
"github.com/spdx/tools-golang/spdx"
"github.com/spdx/tools-golang/spdx/v2/common"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
fake "k8s.io/utils/clock/testing"
@@ -25,7 +26,7 @@ func TestMarshaler_Marshal(t *testing.T) {
testCases := []struct {
name string
inputReport types.Report
wantSBOM *spdx.Document2_2
wantSBOM *spdx.Document
}{
{
name: "happy path for container scan",
@@ -97,23 +98,95 @@ func TestMarshaler_Marshal(t *testing.T) {
},
},
},
wantSBOM: &spdx.Document2_2{
CreationInfo: &spdx.CreationInfo2_2{
SPDXVersion: "SPDX-2.2",
DataLicense: "CC0-1.0",
SPDXIdentifier: "DOCUMENT",
DocumentName: "rails:latest",
DocumentNamespace: "http://aquasecurity.github.io/trivy/container_image/rails:latest-3ff14136-e09f-4df9-80ea-000000000001",
CreatorOrganizations: []string{"aquasecurity"},
CreatorTools: []string{"trivy-0.38.1"},
Created: "2021-08-25T12:20:30Z",
wantSBOM: &spdx.Document{
SPDXVersion: spdx.Version,
DataLicense: spdx.DataLicense,
SPDXIdentifier: "DOCUMENT",
DocumentName: "rails:latest",
DocumentNamespace: "http://aquasecurity.github.io/trivy/container_image/rails:latest-3ff14136-e09f-4df9-80ea-000000000001",
CreationInfo: &spdx.CreationInfo{
Creators: []common.Creator{
{
Creator: "aquasecurity",
CreatorType: "Organization",
},
{
Creator: fmt.Sprintf("trivy-0.38.1"),
CreatorType: "Tool",
},
},
Created: "2021-08-25T12:20:30Z",
},
Packages: map[spdx.ElementID]*spdx.Package2_2{
spdx.ElementID("ContainerImage-9396d894cd0cb6cb"): {
Packages: []*spdx.Package{
{
PackageSPDXIdentifier: spdx.ElementID("Package-eb0263038c3b445b"),
PackageDownloadLocation: "NONE",
PackageName: "actioncontroller",
PackageVersion: "7.0.1",
PackageLicenseConcluded: "NONE",
PackageLicenseDeclared: "NONE",
PackageExternalReferences: []*spdx.PackageExternalReference{
{
Category: tspdx.CategoryPackageManager,
RefType: tspdx.RefTypePurl,
Locator: "pkg:gem/actioncontroller@7.0.1",
},
},
},
{
PackageSPDXIdentifier: spdx.ElementID("Package-826226d056ff30c0"),
PackageDownloadLocation: "NONE",
PackageName: "actionpack",
PackageVersion: "7.0.1",
PackageLicenseConcluded: "NONE",
PackageLicenseDeclared: "NONE",
PackageExternalReferences: []*spdx.PackageExternalReference{
{
Category: tspdx.CategoryPackageManager,
RefType: tspdx.RefTypePurl,
Locator: "pkg:gem/actionpack@7.0.1",
},
},
},
{
PackageSPDXIdentifier: spdx.ElementID("Package-fd0dc3cf913d5bc3"),
PackageDownloadLocation: "NONE",
PackageName: "binutils",
PackageVersion: "2.30-93.el8",
PackageLicenseConcluded: "GPL-3.0-or-later",
PackageLicenseDeclared: "GPL-3.0-or-later",
PackageExternalReferences: []*spdx.PackageExternalReference{
{
Category: tspdx.CategoryPackageManager,
RefType: tspdx.RefTypePurl,
Locator: "pkg:rpm/centos/binutils@2.30-93.el8?arch=aarch64&distro=centos-8.3.2011",
},
},
PackageSourceInfo: "built package from: binutils 2.30-93.el8",
},
{
PackageSPDXIdentifier: spdx.ElementID("Application-73c871d73f3c8248"),
PackageDownloadLocation: "NONE",
PackageName: "bundler",
PackageSourceInfo: "app/subproject/Gemfile.lock",
},
{
PackageSPDXIdentifier: spdx.ElementID("Application-c3fac92c1ac0a9fa"),
PackageDownloadLocation: "NONE",
PackageName: "bundler",
PackageSourceInfo: "app/Gemfile.lock",
},
{
PackageSPDXIdentifier: spdx.ElementID("OperatingSystem-197f9a00ebcb51f0"),
PackageDownloadLocation: "NONE",
PackageName: "centos",
PackageVersion: "8.3.2011",
},
{
PackageSPDXIdentifier: spdx.ElementID("ContainerImage-9396d894cd0cb6cb"),
PackageDownloadLocation: "NONE",
PackageName: "rails:latest",
PackageExternalReferences: []*spdx.PackageExternalReference2_2{
PackageExternalReferences: []*spdx.PackageExternalReference{
{
Category: tspdx.CategoryPackageManager,
RefType: tspdx.RefTypePurl,
@@ -129,73 +202,8 @@ func TestMarshaler_Marshal(t *testing.T) {
"RepoTag: rails:latest",
},
},
spdx.ElementID("Application-73c871d73f3c8248"): {
PackageSPDXIdentifier: spdx.ElementID("Application-73c871d73f3c8248"),
PackageDownloadLocation: "NONE",
PackageName: "bundler",
PackageSourceInfo: "app/subproject/Gemfile.lock",
},
spdx.ElementID("Application-c3fac92c1ac0a9fa"): {
PackageSPDXIdentifier: spdx.ElementID("Application-c3fac92c1ac0a9fa"),
PackageDownloadLocation: "NONE",
PackageName: "bundler",
PackageSourceInfo: "app/Gemfile.lock",
},
spdx.ElementID("OperatingSystem-197f9a00ebcb51f0"): {
PackageSPDXIdentifier: spdx.ElementID("OperatingSystem-197f9a00ebcb51f0"),
PackageDownloadLocation: "NONE",
PackageName: "centos",
PackageVersion: "8.3.2011",
},
spdx.ElementID("Package-eb0263038c3b445b"): {
PackageSPDXIdentifier: spdx.ElementID("Package-eb0263038c3b445b"),
PackageDownloadLocation: "NONE",
PackageName: "actioncontroller",
PackageVersion: "7.0.1",
PackageLicenseConcluded: "NONE",
PackageLicenseDeclared: "NONE",
PackageExternalReferences: []*spdx.PackageExternalReference2_2{
{
Category: tspdx.CategoryPackageManager,
RefType: tspdx.RefTypePurl,
Locator: "pkg:gem/actioncontroller@7.0.1",
},
},
},
spdx.ElementID("Package-826226d056ff30c0"): {
PackageSPDXIdentifier: spdx.ElementID("Package-826226d056ff30c0"),
PackageDownloadLocation: "NONE",
PackageName: "actionpack",
PackageVersion: "7.0.1",
PackageLicenseConcluded: "NONE",
PackageLicenseDeclared: "NONE",
PackageExternalReferences: []*spdx.PackageExternalReference2_2{
{
Category: tspdx.CategoryPackageManager,
RefType: tspdx.RefTypePurl,
Locator: "pkg:gem/actionpack@7.0.1",
},
},
},
spdx.ElementID("Package-fd0dc3cf913d5bc3"): {
PackageSPDXIdentifier: spdx.ElementID("Package-fd0dc3cf913d5bc3"),
PackageDownloadLocation: "NONE",
PackageName: "binutils",
PackageVersion: "2.30-93.el8",
PackageLicenseConcluded: "GPL-3.0-or-later",
PackageLicenseDeclared: "GPL-3.0-or-later",
PackageExternalReferences: []*spdx.PackageExternalReference2_2{
{
Category: tspdx.CategoryPackageManager,
RefType: tspdx.RefTypePurl,
Locator: "pkg:rpm/centos/binutils@2.30-93.el8?arch=aarch64&distro=centos-8.3.2011",
},
},
PackageSourceInfo: "built package from: binutils 2.30-93.el8",
},
},
Relationships: []*spdx.Relationship2_2{
Relationships: []*spdx.Relationship{
{
RefA: spdx.DocElementID{ElementRefID: "DOCUMENT"},
RefB: spdx.DocElementID{ElementRefID: "ContainerImage-9396d894cd0cb6cb"},
@@ -237,10 +245,9 @@ func TestMarshaler_Marshal(t *testing.T) {
Relationship: "CONTAINS",
},
},
UnpackagedFiles: nil,
OtherLicenses: nil,
Annotations: nil,
Reviews: nil,
OtherLicenses: nil,
Annotations: nil,
Reviews: nil,
},
},
{
@@ -311,19 +318,109 @@ func TestMarshaler_Marshal(t *testing.T) {
},
},
},
wantSBOM: &spdx.Document2_2{
CreationInfo: &spdx.CreationInfo2_2{
SPDXVersion: "SPDX-2.2",
DataLicense: "CC0-1.0",
SPDXIdentifier: "DOCUMENT",
DocumentName: "centos:latest",
DocumentNamespace: "http://aquasecurity.github.io/trivy/container_image/centos:latest-3ff14136-e09f-4df9-80ea-000000000001",
CreatorOrganizations: []string{"aquasecurity"},
CreatorTools: []string{"trivy-0.38.1"},
Created: "2021-08-25T12:20:30Z",
wantSBOM: &spdx.Document{
SPDXVersion: spdx.Version,
DataLicense: spdx.DataLicense,
SPDXIdentifier: "DOCUMENT",
DocumentName: "centos:latest",
DocumentNamespace: "http://aquasecurity.github.io/trivy/container_image/centos:latest-3ff14136-e09f-4df9-80ea-000000000001",
CreationInfo: &spdx.CreationInfo{
Creators: []common.Creator{
{
Creator: "aquasecurity",
CreatorType: "Organization",
},
{
Creator: fmt.Sprintf("trivy-0.38.1"),
CreatorType: "Tool",
},
},
Created: "2021-08-25T12:20:30Z",
},
Packages: map[spdx.ElementID]*spdx.Package2_2{
spdx.ElementID("ContainerImage-413bfede37ad01fc"): {
Packages: []*spdx.Package{
{
PackageSPDXIdentifier: spdx.ElementID("Package-d8dccb186bafaf37"),
PackageDownloadLocation: "NONE",
PackageName: "acl",
PackageVersion: "1:2.2.53-1.el8",
PackageLicenseConcluded: "GPL-2.0-or-later",
PackageLicenseDeclared: "GPL-2.0-or-later",
PackageExternalReferences: []*spdx.PackageExternalReference{
{
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",
},
},
PackageSourceInfo: "built package from: acl 1:2.2.53-1.el8",
},
{
PackageSPDXIdentifier: spdx.ElementID("Package-13fe667a0805e6b7"),
PackageDownloadLocation: "NONE",
PackageName: "actionpack",
PackageVersion: "7.0.1",
PackageLicenseConcluded: "NONE",
PackageLicenseDeclared: "NONE",
PackageExternalReferences: []*spdx.PackageExternalReference{
{
Category: tspdx.CategoryPackageManager,
RefType: tspdx.RefTypePurl,
Locator: "pkg:gem/actionpack@7.0.1",
},
},
PackageAttributionTexts: []string{
"LayerDiffID: sha256:ccb64cf0b7ba2e50741d0b64cae324eb5de3b1e2f580bbf177e721b67df38488",
},
Files: []*spdx.File{
{
FileSPDXIdentifier: "File-fa42187221d0d0a8",
FileName: "tools/project-doe/specifications/actionpack.gemspec",
Checksums: []spdx.Checksum{
{
Algorithm: spdx.SHA1,
Value: "413f98442c83808042b5d1d2611a346b999bdca5",
},
},
},
},
},
{
PackageSPDXIdentifier: spdx.ElementID("Package-d5443dbcbba0dbd4"),
PackageDownloadLocation: "NONE",
PackageName: "actionpack",
PackageVersion: "7.0.1",
PackageLicenseConcluded: "NONE",
PackageLicenseDeclared: "NONE",
PackageExternalReferences: []*spdx.PackageExternalReference{
{
Category: tspdx.CategoryPackageManager,
RefType: tspdx.RefTypePurl,
Locator: "pkg:gem/actionpack@7.0.1",
},
},
PackageAttributionTexts: []string{
"LayerDiffID: sha256:ccb64cf0b7ba2e50741d0b64cae324eb5de3b1e2f580bbf177e721b67df38488",
},
Files: []*spdx.File{
{
FileSPDXIdentifier: "File-6a540784b0dc6d55",
FileName: "tools/project-john/specifications/actionpack.gemspec",
Checksums: []spdx.Checksum{
{
Algorithm: spdx.SHA1,
Value: "d2f9f9aed5161f6e4116a3f9573f41cd832f137c",
},
},
},
},
},
{
PackageSPDXIdentifier: spdx.ElementID("OperatingSystem-197f9a00ebcb51f0"),
PackageDownloadLocation: "NONE",
PackageName: "centos",
PackageVersion: "8.3.2011",
},
{
PackageName: "centos:latest",
PackageSPDXIdentifier: "ContainerImage-413bfede37ad01fc",
PackageDownloadLocation: "NONE",
@@ -334,96 +431,14 @@ func TestMarshaler_Marshal(t *testing.T) {
"RepoTag: centos:latest",
},
},
spdx.ElementID("Application-441a648f2aeeee72"): {
{
PackageSPDXIdentifier: spdx.ElementID("Application-441a648f2aeeee72"),
PackageDownloadLocation: "NONE",
PackageName: "gemspec",
PackageSourceInfo: "Ruby",
},
spdx.ElementID("OperatingSystem-197f9a00ebcb51f0"): {
PackageSPDXIdentifier: spdx.ElementID("OperatingSystem-197f9a00ebcb51f0"),
PackageDownloadLocation: "NONE",
PackageName: "centos",
PackageVersion: "8.3.2011",
},
spdx.ElementID("Package-d8dccb186bafaf37"): {
PackageSPDXIdentifier: spdx.ElementID("Package-d8dccb186bafaf37"),
PackageDownloadLocation: "NONE",
PackageName: "acl",
PackageVersion: "1:2.2.53-1.el8",
PackageLicenseConcluded: "GPL-2.0-or-later",
PackageLicenseDeclared: "GPL-2.0-or-later",
PackageExternalReferences: []*spdx.PackageExternalReference2_2{
{
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",
},
},
PackageSourceInfo: "built package from: acl 1:2.2.53-1.el8",
},
spdx.ElementID("Package-13fe667a0805e6b7"): {
PackageSPDXIdentifier: spdx.ElementID("Package-13fe667a0805e6b7"),
PackageDownloadLocation: "NONE",
PackageName: "actionpack",
PackageVersion: "7.0.1",
PackageLicenseConcluded: "NONE",
PackageLicenseDeclared: "NONE",
PackageExternalReferences: []*spdx.PackageExternalReference2_2{
{
Category: tspdx.CategoryPackageManager,
RefType: tspdx.RefTypePurl,
Locator: "pkg:gem/actionpack@7.0.1",
},
},
PackageAttributionTexts: []string{
"LayerDiffID: sha256:ccb64cf0b7ba2e50741d0b64cae324eb5de3b1e2f580bbf177e721b67df38488",
},
Files: map[spdx.ElementID]*spdx.File2_2{
"File-fa42187221d0d0a8": {
FileSPDXIdentifier: "File-fa42187221d0d0a8",
FileName: "tools/project-doe/specifications/actionpack.gemspec",
FileChecksums: map[spdx.ChecksumAlgorithm]spdx.Checksum{
spdx.SHA1: {
Algorithm: spdx.SHA1,
Value: "413f98442c83808042b5d1d2611a346b999bdca5",
},
},
},
},
},
spdx.ElementID("Package-d5443dbcbba0dbd4"): {
PackageSPDXIdentifier: spdx.ElementID("Package-d5443dbcbba0dbd4"),
PackageDownloadLocation: "NONE",
PackageName: "actionpack",
PackageVersion: "7.0.1",
PackageLicenseConcluded: "NONE",
PackageLicenseDeclared: "NONE",
PackageExternalReferences: []*spdx.PackageExternalReference2_2{
{
Category: tspdx.CategoryPackageManager,
RefType: tspdx.RefTypePurl,
Locator: "pkg:gem/actionpack@7.0.1",
},
},
PackageAttributionTexts: []string{
"LayerDiffID: sha256:ccb64cf0b7ba2e50741d0b64cae324eb5de3b1e2f580bbf177e721b67df38488",
},
Files: map[spdx.ElementID]*spdx.File2_2{
"File-6a540784b0dc6d55": {
FileSPDXIdentifier: "File-6a540784b0dc6d55",
FileName: "tools/project-john/specifications/actionpack.gemspec",
FileChecksums: map[spdx.ChecksumAlgorithm]spdx.Checksum{
spdx.SHA1: {
Algorithm: spdx.SHA1,
Value: "d2f9f9aed5161f6e4116a3f9573f41cd832f137c",
},
},
},
},
},
},
Relationships: []*spdx.Relationship2_2{
Relationships: []*spdx.Relationship{
{
RefA: spdx.DocElementID{ElementRefID: "DOCUMENT"},
RefB: spdx.DocElementID{ElementRefID: "ContainerImage-413bfede37ad01fc"},
@@ -456,10 +471,9 @@ func TestMarshaler_Marshal(t *testing.T) {
},
},
UnpackagedFiles: nil,
OtherLicenses: nil,
Annotations: nil,
Reviews: nil,
OtherLicenses: nil,
Annotations: nil,
Reviews: nil,
},
},
{
@@ -482,40 +496,34 @@ func TestMarshaler_Marshal(t *testing.T) {
},
},
},
wantSBOM: &spdx.Document2_2{
CreationInfo: &spdx.CreationInfo2_2{
SPDXVersion: "SPDX-2.2",
DataLicense: "CC0-1.0",
SPDXIdentifier: "DOCUMENT",
DocumentName: "masahiro331/CVE-2021-41098",
DocumentNamespace: "http://aquasecurity.github.io/trivy/filesystem/masahiro331/CVE-2021-41098-3ff14136-e09f-4df9-80ea-000000000001",
CreatorOrganizations: []string{"aquasecurity"},
CreatorTools: []string{"trivy-0.38.1"},
Created: "2021-08-25T12:20:30Z",
},
Packages: map[spdx.ElementID]*spdx.Package2_2{
spdx.ElementID("Filesystem-5af0f1f08c20909a"): {
PackageSPDXIdentifier: spdx.ElementID("Filesystem-5af0f1f08c20909a"),
PackageDownloadLocation: "NONE",
PackageName: "masahiro331/CVE-2021-41098",
PackageAttributionTexts: []string{
"SchemaVersion: 2",
wantSBOM: &spdx.Document{
SPDXVersion: spdx.Version,
DataLicense: spdx.DataLicense,
SPDXIdentifier: "DOCUMENT",
DocumentName: "masahiro331/CVE-2021-41098",
DocumentNamespace: "http://aquasecurity.github.io/trivy/filesystem/masahiro331/CVE-2021-41098-3ff14136-e09f-4df9-80ea-000000000001",
CreationInfo: &spdx.CreationInfo{
Creators: []common.Creator{
{
Creator: "aquasecurity",
CreatorType: "Organization",
},
{
Creator: fmt.Sprintf("trivy-0.38.1"),
CreatorType: "Tool",
},
},
spdx.ElementID("Application-9dd4a4ba7077cc5a"): {
PackageSPDXIdentifier: spdx.ElementID("Application-9dd4a4ba7077cc5a"),
PackageDownloadLocation: "NONE",
PackageName: "bundler",
PackageSourceInfo: "Gemfile.lock",
},
spdx.ElementID("Package-3da61e86d0530402"): {
Created: "2021-08-25T12:20:30Z",
},
Packages: []*spdx.Package{
{
PackageSPDXIdentifier: spdx.ElementID("Package-3da61e86d0530402"),
PackageDownloadLocation: "NONE",
PackageName: "actioncable",
PackageVersion: "6.1.4.1",
PackageLicenseConcluded: "NONE",
PackageLicenseDeclared: "NONE",
PackageExternalReferences: []*spdx.PackageExternalReference2_2{
PackageExternalReferences: []*spdx.PackageExternalReference{
{
Category: tspdx.CategoryPackageManager,
RefType: tspdx.RefTypePurl,
@@ -523,8 +531,22 @@ func TestMarshaler_Marshal(t *testing.T) {
},
},
},
{
PackageSPDXIdentifier: spdx.ElementID("Application-9dd4a4ba7077cc5a"),
PackageDownloadLocation: "NONE",
PackageName: "bundler",
PackageSourceInfo: "Gemfile.lock",
},
{
PackageSPDXIdentifier: spdx.ElementID("Filesystem-5af0f1f08c20909a"),
PackageDownloadLocation: "NONE",
PackageName: "masahiro331/CVE-2021-41098",
PackageAttributionTexts: []string{
"SchemaVersion: 2",
},
},
},
Relationships: []*spdx.Relationship2_2{
Relationships: []*spdx.Relationship{
{
RefA: spdx.DocElementID{ElementRefID: "DOCUMENT"},
RefB: spdx.DocElementID{ElementRefID: "Filesystem-5af0f1f08c20909a"},
@@ -568,19 +590,27 @@ func TestMarshaler_Marshal(t *testing.T) {
},
},
},
wantSBOM: &spdx.Document2_2{
CreationInfo: &spdx.CreationInfo2_2{
SPDXVersion: "SPDX-2.2",
DataLicense: "CC0-1.0",
SPDXIdentifier: "DOCUMENT",
DocumentName: "http://test-aggregate",
DocumentNamespace: "http://aquasecurity.github.io/trivy/repository/test-aggregate-3ff14136-e09f-4df9-80ea-000000000001",
CreatorOrganizations: []string{"aquasecurity"},
CreatorTools: []string{"trivy-0.38.1"},
Created: "2021-08-25T12:20:30Z",
wantSBOM: &spdx.Document{
SPDXVersion: spdx.Version,
DataLicense: spdx.DataLicense,
SPDXIdentifier: "DOCUMENT",
DocumentName: "http://test-aggregate",
DocumentNamespace: "http://aquasecurity.github.io/trivy/repository/test-aggregate-3ff14136-e09f-4df9-80ea-000000000001",
CreationInfo: &spdx.CreationInfo{
Creators: []common.Creator{
{
Creator: "aquasecurity",
CreatorType: "Organization",
},
{
Creator: fmt.Sprintf("trivy-0.38.1"),
CreatorType: "Tool",
},
},
Created: "2021-08-25T12:20:30Z",
},
Packages: map[spdx.ElementID]*spdx.Package2_2{
spdx.ElementID("Repository-1a78857c1a6a759e"): {
Packages: []*spdx.Package{
{
PackageName: "http://test-aggregate",
PackageSPDXIdentifier: "Repository-1a78857c1a6a759e",
PackageDownloadLocation: "git+http://test-aggregate",
@@ -588,20 +618,20 @@ func TestMarshaler_Marshal(t *testing.T) {
"SchemaVersion: 2",
},
},
spdx.ElementID("Application-24f8a80152e2c0fc"): {
{
PackageSPDXIdentifier: "Application-24f8a80152e2c0fc",
PackageDownloadLocation: "git+http://test-aggregate",
PackageName: "node-pkg",
PackageSourceInfo: "Node.js",
},
spdx.ElementID("Package-daedb173cfd43058"): {
{
PackageSPDXIdentifier: spdx.ElementID("Package-daedb173cfd43058"),
PackageDownloadLocation: "git+http://test-aggregate",
PackageName: "ruby-typeprof",
PackageVersion: "0.20.1",
PackageLicenseConcluded: "MIT",
PackageLicenseDeclared: "MIT",
PackageExternalReferences: []*spdx.PackageExternalReference2_2{
PackageExternalReferences: []*spdx.PackageExternalReference{
{
Category: tspdx.CategoryPackageManager,
RefType: tspdx.RefTypePurl,
@@ -611,15 +641,15 @@ func TestMarshaler_Marshal(t *testing.T) {
PackageAttributionTexts: []string{
"LayerDiffID: sha256:661c3fd3cc16b34c070f3620ca6b03b6adac150f9a7e5d0e3c707a159990f88e",
},
Files: map[spdx.ElementID]*spdx.File2_2{
"File-a52825a3e5bc6dfe": {
Files: []*spdx.File{
{
FileName: "usr/local/lib/ruby/gems/3.1.0/gems/typeprof-0.21.1/vscode/package.json",
FileSPDXIdentifier: "File-a52825a3e5bc6dfe",
},
},
},
},
Relationships: []*spdx.Relationship2_2{
Relationships: []*spdx.Relationship{
{
RefA: spdx.DocElementID{ElementRefID: "DOCUMENT"},
RefB: spdx.DocElementID{ElementRefID: "Repository-1a78857c1a6a759e"},
@@ -646,19 +676,28 @@ func TestMarshaler_Marshal(t *testing.T) {
ArtifactType: ftypes.ArtifactFilesystem,
Results: types.Results{},
},
wantSBOM: &spdx.Document2_2{
CreationInfo: &spdx.CreationInfo2_2{
SPDXVersion: "SPDX-2.2",
DataLicense: "CC0-1.0",
SPDXIdentifier: "DOCUMENT",
DocumentName: "empty/path",
DocumentNamespace: "http://aquasecurity.github.io/trivy/filesystem/empty/path-3ff14136-e09f-4df9-80ea-000000000001",
CreatorOrganizations: []string{"aquasecurity"},
CreatorTools: []string{"trivy-0.38.1"},
Created: "2021-08-25T12:20:30Z",
wantSBOM: &spdx.Document{
SPDXVersion: spdx.Version,
DataLicense: spdx.DataLicense,
SPDXIdentifier: "DOCUMENT",
DocumentName: "empty/path",
DocumentNamespace: "http://aquasecurity.github.io/trivy/filesystem/empty/path-3ff14136-e09f-4df9-80ea-000000000001",
CreationInfo: &spdx.CreationInfo{
Creators: []common.Creator{
{
Creator: "aquasecurity",
CreatorType: "Organization",
},
{
Creator: fmt.Sprintf("trivy-0.38.1"),
CreatorType: "Tool",
},
},
Created: "2021-08-25T12:20:30Z",
},
Packages: map[spdx.ElementID]*spdx.Package2_2{
spdx.ElementID("Filesystem-70f34983067dba86"): {
Packages: []*spdx.Package{
{
PackageName: "empty/path",
PackageSPDXIdentifier: "Filesystem-70f34983067dba86",
PackageDownloadLocation: "NONE",
@@ -667,7 +706,7 @@ func TestMarshaler_Marshal(t *testing.T) {
},
},
},
Relationships: []*spdx.Relationship2_2{
Relationships: []*spdx.Relationship{
{
RefA: spdx.DocElementID{ElementRefID: "DOCUMENT"},
RefB: spdx.DocElementID{ElementRefID: "Filesystem-70f34983067dba86"},

View File

@@ -8,10 +8,10 @@ import (
version "github.com/knqyf263/go-rpm-version"
"github.com/package-url/packageurl-go"
"github.com/samber/lo"
"github.com/spdx/tools-golang/jsonloader"
"github.com/spdx/tools-golang/json"
"github.com/spdx/tools-golang/spdx"
"github.com/spdx/tools-golang/tvloader"
"github.com/spdx/tools-golang/spdx/v2/common"
"github.com/spdx/tools-golang/tagvalue"
"golang.org/x/xerrors"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
@@ -36,7 +36,7 @@ type TVDecoder struct {
}
func (tv *TVDecoder) Decode(v interface{}) error {
spdxDocument, err := tvloader.Load2_2(tv.r)
spdxDocument, err := tagvalue.Read(tv.r)
if err != nil {
return xerrors.Errorf("failed to load tag-value spdx: %w", err)
}
@@ -54,7 +54,7 @@ func (tv *TVDecoder) Decode(v interface{}) error {
}
func (s *SPDX) UnmarshalJSON(b []byte) error {
spdxDocument, err := jsonloader.Load2_2(bytes.NewReader(b))
spdxDocument, err := json.Read(bytes.NewReader(b))
if err != nil {
return xerrors.Errorf("failed to load spdx json: %w", err)
}
@@ -65,9 +65,10 @@ func (s *SPDX) UnmarshalJSON(b []byte) error {
return nil
}
func (s *SPDX) unmarshal(spdxDocument *spdx.Document2_2) error {
func (s *SPDX) unmarshal(spdxDocument *spdx.Document) error {
var osPkgs []ftypes.Package
apps := map[spdx.ElementID]*ftypes.Application{}
apps := map[common.ElementID]*ftypes.Application{}
packageSPDXIdentifierMap := createPackageSPDXIdentifierMap(spdxDocument.Packages)
// Package relationships would be as belows:
// - Root (container image, filesystem, etc.)
@@ -81,16 +82,26 @@ func (s *SPDX) unmarshal(spdxDocument *spdx.Document2_2) error {
// - Python package A
// - Python package B
for _, rel := range spdxDocument.Relationships {
pkgA := lo.FromPtr(spdxDocument.Packages[rel.RefA.ElementRefID])
pkgB := lo.FromPtr(spdxDocument.Packages[rel.RefB.ElementRefID])
if rel.Relationship == common.TypeRelationshipDescribe || rel.Relationship == "DESCRIBE" {
// Skip the DESCRIBES relationship.
continue
}
pkgA := packageSPDXIdentifierMap[string(rel.RefA.ElementRefID)]
pkgB := packageSPDXIdentifierMap[string(rel.RefB.ElementRefID)]
if pkgA == nil || pkgB == nil {
// Skip the missing pkg relationship.
continue
}
switch {
// Relationship: root package => OS
case isOperatingSystem(pkgB.PackageSPDXIdentifier):
s.SBOM.OS = parseOS(pkgB)
s.SBOM.OS = parseOS(*pkgB)
// Relationship: OS => OS package
case isOperatingSystem(pkgA.PackageSPDXIdentifier):
pkg, err := parsePkg(pkgB)
pkg, err := parsePkg(*pkgB)
if err != nil {
return xerrors.Errorf("failed to parse os package: %w", err)
}
@@ -102,11 +113,11 @@ func (s *SPDX) unmarshal(spdxDocument *spdx.Document2_2) error {
case isApplication(pkgA.PackageSPDXIdentifier):
app, ok := apps[pkgA.PackageSPDXIdentifier]
if !ok {
app = initApplication(pkgA)
app = initApplication(*pkgA)
apps[pkgA.PackageSPDXIdentifier] = app
}
lib, err := parsePkg(pkgB)
lib, err := parsePkg(*pkgB)
if err != nil {
return xerrors.Errorf("failed to parse language-specific package: %w", err)
}
@@ -129,6 +140,14 @@ func (s *SPDX) unmarshal(spdxDocument *spdx.Document2_2) error {
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 isOperatingSystem(elementID spdx.ElementID) bool {
return strings.HasPrefix(string(elementID), ElementOperatingSystem)
}
@@ -137,7 +156,7 @@ func isApplication(elementID spdx.ElementID) bool {
return strings.HasPrefix(string(elementID), ElementApplication)
}
func initApplication(pkg spdx.Package2_2) *ftypes.Application {
func initApplication(pkg spdx.Package) *ftypes.Application {
app := &ftypes.Application{
Type: pkg.PackageName,
FilePath: pkg.PackageSourceInfo,
@@ -149,14 +168,14 @@ func initApplication(pkg spdx.Package2_2) *ftypes.Application {
return app
}
func parseOS(pkg spdx.Package2_2) ftypes.OS {
func parseOS(pkg spdx.Package) ftypes.OS {
return ftypes.OS{
Family: pkg.PackageName,
Name: pkg.PackageVersion,
}
}
func parsePkg(spdxPkg spdx.Package2_2) (*ftypes.Package, error) {
func parsePkg(spdxPkg spdx.Package) (*ftypes.Package, error) {
pkg, pkgType, err := parseExternalReferences(spdxPkg.PackageExternalReferences)
if err != nil {
return nil, xerrors.Errorf("external references error: %w", err)
@@ -185,7 +204,7 @@ func parsePkg(spdxPkg spdx.Package2_2) (*ftypes.Package, error) {
return pkg, nil
}
func parseExternalReferences(refs []*spdx.PackageExternalReference2_2) (*ftypes.Package, string, error) {
func parseExternalReferences(refs []*spdx.PackageExternalReference) (*ftypes.Package, string, error) {
for _, ref := range refs {
// Extract the package information from PURL
if ref.RefType == RefTypePurl && ref.Category == CategoryPackageManager {

View File

@@ -171,7 +171,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) {
return
}
// Not compare the CycloneDX field
// Not compare the SPDX field
v.SPDX = nil
sort.Slice(v.Applications, func(i, j int) bool {

View File

@@ -12,7 +12,7 @@ type SBOM struct {
Applications []types.Application
CycloneDX *types.CycloneDX
SPDX *stypes.Document2_2
SPDX *stypes.Document
}
type SBOMSource = string