From 84db73eac17e22413980c36e562f635720f22ad7 Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Tue, 29 Oct 2024 13:00:17 +0600 Subject: [PATCH 1/5] fix(spdx): use `Annotation` instead of `AttributionTexts` --- pkg/sbom/spdx/marshal.go | 35 +++++-- pkg/sbom/spdx/marshal_test.go | 184 ++++++++++++++++++---------------- 2 files changed, 123 insertions(+), 96 deletions(-) diff --git a/pkg/sbom/spdx/marshal.go b/pkg/sbom/spdx/marshal.go index 809ce95d7e4c..8a023635281e 100644 --- a/pkg/sbom/spdx/marshal.go +++ b/pkg/sbom/spdx/marshal.go @@ -50,6 +50,8 @@ const ( PackageSupplierNoAssertion = "NOASSERTION" PackageSupplierOrganization = "Organization" + PackageAnnotatorToolField = "Tool" + RelationShipContains = common.TypeRelationshipContains RelationShipDescribe = common.TypeRelationshipDescribe RelationShipDependsOn = common.TypeRelationshipDependsOn @@ -80,6 +82,7 @@ type Marshaler struct { format spdx.Document hasher Hash appVersion string // Trivy version. It needed for `creator` field + timeNow string } type Hash func(v any, format hashstructure.Format, opts *hashstructure.HashOptions) (uint64, error) @@ -122,6 +125,9 @@ func (m *Marshaler) Marshal(ctx context.Context, bom *core.BOM) (*spdx.Document, packages []*spdx.Package ) + // Lock time to use same time for all spdx fields + m.timeNow = clock.Now(ctx).UTC().Format(time.RFC3339) + root := bom.Root() pkgDownloadLocation := m.packageDownloadLocation(root) @@ -216,7 +222,7 @@ func (m *Marshaler) Marshal(ctx context.Context, bom *core.BOM) (*spdx.Document, CreatorType: "Tool", }, }, - Created: clock.Now(ctx).UTC().Format(time.RFC3339), + Created: m.timeNow, }, Packages: packages, Relationships: relationShips, @@ -258,17 +264,25 @@ func (m *Marshaler) rootSPDXPackage(root *core.Component, pkgDownloadLocation st PackageName: root.Name, PackageSPDXIdentifier: elementID(camelCase(string(root.Type)), pkgID), PackageDownloadLocation: pkgDownloadLocation, - PackageAttributionTexts: m.spdxAttributionTexts(root), + Annotations: m.spdxAnnotations(root), PackageExternalReferences: externalReferences, PrimaryPackagePurpose: pkgPurpose, }, nil } -func (m *Marshaler) appendAttributionText(attributionTexts []string, key, value string) []string { +func (m *Marshaler) appendAnnotation(annotations []spdx.Annotation, key, value string) []spdx.Annotation { if value == "" { - return attributionTexts + return annotations } - return append(attributionTexts, fmt.Sprintf("%s: %s", key, value)) + return append(annotations, spdx.Annotation{ + AnnotationDate: m.timeNow, + AnnotationType: spdx.CategoryOther, + Annotator: spdx.Annotator{ + Annotator: fmt.Sprintf("%s-%s", CreatorTool, m.appVersion), + AnnotatorType: PackageAnnotatorToolField, + }, + AnnotationComment: fmt.Sprintf("%s: %s", key, value), + }) } func (m *Marshaler) purlExternalReference(packageURL string) *spdx.PackageExternalReference { @@ -343,7 +357,7 @@ func (m *Marshaler) spdxPackage(c *core.Component, pkgDownloadLocation string) ( PrimaryPackagePurpose: purpose, PackageDownloadLocation: pkgDownloadLocation, PackageExternalReferences: pkgExtRefs, - PackageAttributionTexts: m.spdxAttributionTexts(c), + Annotations: m.spdxAnnotations(c), PackageSourceInfo: sourceInfo, PackageSupplier: supplier, PackageChecksums: m.spdxChecksums(digests), @@ -365,16 +379,15 @@ func spdxPkgName(component *core.Component) string { } return component.Name } - -func (m *Marshaler) spdxAttributionTexts(c *core.Component) []string { - var texts []string +func (m *Marshaler) spdxAnnotations(c *core.Component) []spdx.Annotation { + var annotations []spdx.Annotation for _, p := range c.Properties { // Add properties that are not in other fields. if !slices.Contains(duplicateProperties, p.Name) { - texts = m.appendAttributionText(texts, p.Name, p.Value) + annotations = m.appendAnnotation(annotations, p.Name, p.Value) } } - return texts + return annotations } func (m *Marshaler) spdxLicense(c *core.Component) string { diff --git a/pkg/sbom/spdx/marshal_test.go b/pkg/sbom/spdx/marshal_test.go index 74e9bee0a45d..4d9d33c013a7 100644 --- a/pkg/sbom/spdx/marshal_test.go +++ b/pkg/sbom/spdx/marshal_test.go @@ -25,6 +25,20 @@ import ( "github.com/aquasecurity/trivy/pkg/uuid" ) +func annotation(t *testing.T, comment string) spdx.Annotation { + t.Helper() + + return spdx.Annotation{ + AnnotationDate: "2021-08-25T12:20:30Z", + AnnotationType: spdx.CategoryOther, + Annotator: spdx.Annotator{ + Annotator: "trivy-0.56.2", + AnnotatorType: tspdx.PackageAnnotatorToolField, + }, + AnnotationComment: comment, + } +} + func TestMarshaler_Marshal(t *testing.T) { testCases := []struct { name string @@ -164,7 +178,7 @@ func TestMarshaler_Marshal(t *testing.T) { CreatorType: "Organization", }, { - Creator: "trivy-0.38.1", + Creator: "trivy-0.56.2", CreatorType: "Tool", }, }, @@ -176,9 +190,9 @@ func TestMarshaler_Marshal(t *testing.T) { PackageDownloadLocation: "NONE", PackageName: "app/Gemfile.lock", PrimaryPackagePurpose: tspdx.PackagePurposeApplication, - PackageAttributionTexts: []string{ - "Class: lang-pkgs", - "Type: bundler", + Annotations: []spdx.Annotation{ + annotation(t, "Class: lang-pkgs"), + annotation(t, "Type: bundler"), }, }, { @@ -186,9 +200,9 @@ func TestMarshaler_Marshal(t *testing.T) { PackageDownloadLocation: "NONE", PackageName: "app/subproject/Gemfile.lock", PrimaryPackagePurpose: tspdx.PackagePurposeApplication, - PackageAttributionTexts: []string{ - "Class: lang-pkgs", - "Type: bundler", + Annotations: []spdx.Annotation{ + annotation(t, "Class: lang-pkgs"), + annotation(t, "Type: bundler"), }, }, { @@ -202,14 +216,14 @@ func TestMarshaler_Marshal(t *testing.T) { Locator: "pkg:oci/rails@sha256%3Aa27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177?arch=arm64&repository_url=index.docker.io%2Flibrary%2Frails", }, }, - PackageAttributionTexts: []string{ - "DiffID: sha256:d871dadfb37b53ef1ca45be04fc527562b91989991a8f545345ae3be0b93f92a", - "ImageID: sha256:5d0da3dc976460b72c77d94c8a1ad043720b0416bfc16c52c45d4847e53fadb6", - "Labels:vendor: aquasecurity", - "RepoDigest: rails@sha256:a27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177", - "RepoTag: rails:latest", - "SchemaVersion: 2", - "Size: 1024", + Annotations: []spdx.Annotation{ + annotation(t, "DiffID: sha256:d871dadfb37b53ef1ca45be04fc527562b91989991a8f545345ae3be0b93f92a"), + annotation(t, "ImageID: sha256:5d0da3dc976460b72c77d94c8a1ad043720b0416bfc16c52c45d4847e53fadb6"), + annotation(t, "Labels:vendor: aquasecurity"), + annotation(t, "RepoDigest: rails@sha256:a27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177"), + annotation(t, "RepoTag: rails:latest"), + annotation(t, "SchemaVersion: 2"), + annotation(t, "Size: 1024"), }, PrimaryPackagePurpose: tspdx.PackagePurposeContainer, }, @@ -220,8 +234,8 @@ func TestMarshaler_Marshal(t *testing.T) { PackageVersion: "7.0.1", PackageLicenseConcluded: "NOASSERTION", PackageLicenseDeclared: "NOASSERTION", - PackageAttributionTexts: []string{ - "PkgType: bundler", + Annotations: []spdx.Annotation{ + annotation(t, "PkgType: bundler"), }, PackageExternalReferences: []*spdx.PackageExternalReference{ { @@ -241,8 +255,8 @@ func TestMarshaler_Marshal(t *testing.T) { PackageVersion: "7.0.1", PackageLicenseConcluded: "NOASSERTION", PackageLicenseDeclared: "NOASSERTION", - PackageAttributionTexts: []string{ - "PkgType: bundler", + Annotations: []spdx.Annotation{ + annotation(t, "PkgType: bundler"), }, PackageExternalReferences: []*spdx.PackageExternalReference{ { @@ -262,8 +276,8 @@ func TestMarshaler_Marshal(t *testing.T) { PackageVersion: "7.0.1", PackageLicenseConcluded: "NOASSERTION", PackageLicenseDeclared: "NOASSERTION", - PackageAttributionTexts: []string{ - "PkgType: bundler", + Annotations: []spdx.Annotation{ + annotation(t, "PkgType: bundler"), }, PackageExternalReferences: []*spdx.PackageExternalReference{ { @@ -283,8 +297,8 @@ func TestMarshaler_Marshal(t *testing.T) { PackageVersion: "2.30-93.el8", PackageLicenseConcluded: "GPL-3.0-or-later", PackageLicenseDeclared: "GPL-3.0-or-later", - PackageAttributionTexts: []string{ - "PkgType: centos", + Annotations: []spdx.Annotation{ + annotation(t, "PkgType: centos"), }, PackageSupplier: &spdx.Supplier{ SupplierType: tspdx.PackageSupplierOrganization, @@ -312,9 +326,9 @@ func TestMarshaler_Marshal(t *testing.T) { PackageName: "centos", PackageVersion: "8.3.2011", PrimaryPackagePurpose: tspdx.PackagePurposeOS, - PackageAttributionTexts: []string{ - "Class: os-pkgs", - "Type: centos", + Annotations: []spdx.Annotation{ + annotation(t, "Class: os-pkgs"), + annotation(t, "Type: centos"), }, }, }, @@ -486,7 +500,7 @@ func TestMarshaler_Marshal(t *testing.T) { CreatorType: "Organization", }, { - Creator: "trivy-0.38.1", + Creator: "trivy-0.56.2", CreatorType: "Tool", }, }, @@ -497,11 +511,11 @@ func TestMarshaler_Marshal(t *testing.T) { PackageName: "centos:latest", PackageSPDXIdentifier: "ContainerImage-413bfede37ad01fc", PackageDownloadLocation: "NONE", - PackageAttributionTexts: []string{ - "ImageID: sha256:5d0da3dc976460b72c77d94c8a1ad043720b0416bfc16c52c45d4847e53fadb6", - "RepoTag: centos:latest", - "SchemaVersion: 2", - "Size: 1024", + Annotations: []spdx.Annotation{ + annotation(t, "ImageID: sha256:5d0da3dc976460b72c77d94c8a1ad043720b0416bfc16c52c45d4847e53fadb6"), + annotation(t, "RepoTag: centos:latest"), + annotation(t, "SchemaVersion: 2"), + annotation(t, "Size: 1024"), }, PrimaryPackagePurpose: tspdx.PackagePurposeContainer, }, @@ -512,8 +526,8 @@ func TestMarshaler_Marshal(t *testing.T) { PackageVersion: "1:2.2.53-1.el8", PackageLicenseConcluded: "GPL-2.0-or-later", PackageLicenseDeclared: "GPL-2.0-or-later", - PackageAttributionTexts: []string{ - "PkgType: centos", + Annotations: []spdx.Annotation{ + annotation(t, "PkgType: centos"), }, PackageExternalReferences: []*spdx.PackageExternalReference{ { @@ -546,9 +560,9 @@ func TestMarshaler_Marshal(t *testing.T) { Locator: "pkg:gem/actionpack@7.0.1", }, }, - PackageAttributionTexts: []string{ - "LayerDiffID: sha256:ccb64cf0b7ba2e50741d0b64cae324eb5de3b1e2f580bbf177e721b67df38488", - "PkgType: gemspec", + Annotations: []spdx.Annotation{ + annotation(t, "LayerDiffID: sha256:ccb64cf0b7ba2e50741d0b64cae324eb5de3b1e2f580bbf177e721b67df38488"), + annotation(t, "PkgType: gemspec"), }, PrimaryPackagePurpose: tspdx.PackagePurposeLibrary, PackageSupplier: &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion}, @@ -571,9 +585,9 @@ func TestMarshaler_Marshal(t *testing.T) { Locator: "pkg:gem/actionpack@7.0.1", }, }, - PackageAttributionTexts: []string{ - "LayerDiffID: sha256:ccb64cf0b7ba2e50741d0b64cae324eb5de3b1e2f580bbf177e721b67df38488", - "PkgType: gemspec", + Annotations: []spdx.Annotation{ + annotation(t, "LayerDiffID: sha256:ccb64cf0b7ba2e50741d0b64cae324eb5de3b1e2f580bbf177e721b67df38488"), + annotation(t, "PkgType: gemspec"), }, PrimaryPackagePurpose: tspdx.PackagePurposeLibrary, PackageSupplier: &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion}, @@ -588,9 +602,9 @@ func TestMarshaler_Marshal(t *testing.T) { PackageName: "centos", PackageVersion: "8.3.2011", PrimaryPackagePurpose: tspdx.PackagePurposeOS, - PackageAttributionTexts: []string{ - "Class: os-pkgs", - "Type: centos", + Annotations: []spdx.Annotation{ + annotation(t, "Class: os-pkgs"), + annotation(t, "Type: centos"), }, }, }, @@ -719,7 +733,7 @@ func TestMarshaler_Marshal(t *testing.T) { CreatorType: "Organization", }, { - Creator: "trivy-0.38.1", + Creator: "trivy-0.56.2", CreatorType: "Tool", }, }, @@ -731,9 +745,9 @@ func TestMarshaler_Marshal(t *testing.T) { PackageDownloadLocation: "NONE", PackageName: "Gemfile.lock", PrimaryPackagePurpose: tspdx.PackagePurposeApplication, - PackageAttributionTexts: []string{ - "Class: lang-pkgs", - "Type: bundler", + Annotations: []spdx.Annotation{ + annotation(t, "Class: lang-pkgs"), + annotation(t, "Type: bundler"), }, }, { @@ -741,9 +755,9 @@ func TestMarshaler_Marshal(t *testing.T) { PackageDownloadLocation: "NONE", PackageName: "pom.xml", PrimaryPackagePurpose: tspdx.PackagePurposeApplication, - PackageAttributionTexts: []string{ - "Class: lang-pkgs", - "Type: pom", + Annotations: []spdx.Annotation{ + annotation(t, "Class: lang-pkgs"), + annotation(t, "Type: pom"), }, }, { @@ -763,8 +777,8 @@ func TestMarshaler_Marshal(t *testing.T) { PrimaryPackagePurpose: tspdx.PackagePurposeLibrary, PackageSupplier: &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion}, PackageSourceInfo: "package found in: Gemfile.lock", - PackageAttributionTexts: []string{ - "PkgType: bundler", + Annotations: []spdx.Annotation{ + annotation(t, "PkgType: bundler"), }, }, { @@ -784,17 +798,17 @@ func TestMarshaler_Marshal(t *testing.T) { PrimaryPackagePurpose: tspdx.PackagePurposeLibrary, PackageSupplier: &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion}, PackageSourceInfo: "package found in: pom.xml", - PackageAttributionTexts: []string{ - "PkgID: com.example:example:1.0.0", - "PkgType: pom", + Annotations: []spdx.Annotation{ + annotation(t, "PkgID: com.example:example:1.0.0"), + annotation(t, "PkgType: pom"), }, }, { PackageSPDXIdentifier: spdx.ElementID("Filesystem-5af0f1f08c20909a"), PackageDownloadLocation: "NONE", PackageName: "masahiro331/CVE-2021-41098", - PackageAttributionTexts: []string{ - "SchemaVersion: 2", + Annotations: []spdx.Annotation{ + annotation(t, "SchemaVersion: 2"), }, PrimaryPackagePurpose: tspdx.PackagePurposeSource, }, @@ -878,7 +892,7 @@ func TestMarshaler_Marshal(t *testing.T) { CreatorType: "Organization", }, { - Creator: "trivy-0.38.1", + Creator: "trivy-0.56.2", CreatorType: "Tool", }, }, @@ -906,16 +920,16 @@ func TestMarshaler_Marshal(t *testing.T) { }, PrimaryPackagePurpose: tspdx.PackagePurposeLibrary, PackageSupplier: &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion}, - PackageAttributionTexts: []string{ - "PkgType: jar", + Annotations: []spdx.Annotation{ + annotation(t, "PkgType: jar"), }, }, { PackageSPDXIdentifier: spdx.ElementID("Filesystem-121e7e7a43f02ab"), PackageDownloadLocation: "NONE", PackageName: "log4j-core-2.17.0.jar", - PackageAttributionTexts: []string{ - "SchemaVersion: 2", + Annotations: []spdx.Annotation{ + annotation(t, "SchemaVersion: 2"), }, PrimaryPackagePurpose: tspdx.PackagePurposeSource, }, @@ -980,7 +994,7 @@ func TestMarshaler_Marshal(t *testing.T) { CreatorType: "Organization", }, { - Creator: "trivy-0.38.1", + Creator: "trivy-0.56.2", CreatorType: "Tool", }, }, @@ -1001,9 +1015,9 @@ func TestMarshaler_Marshal(t *testing.T) { Locator: "pkg:npm/ruby-typeprof@0.20.1", }, }, - PackageAttributionTexts: []string{ - "LayerDiffID: sha256:661c3fd3cc16b34c070f3620ca6b03b6adac150f9a7e5d0e3c707a159990f88e", - "PkgType: node-pkg", + Annotations: []spdx.Annotation{ + annotation(t, "LayerDiffID: sha256:661c3fd3cc16b34c070f3620ca6b03b6adac150f9a7e5d0e3c707a159990f88e"), + annotation(t, "PkgType: node-pkg"), }, PrimaryPackagePurpose: tspdx.PackagePurposeLibrary, PackageSupplier: &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion}, @@ -1016,8 +1030,8 @@ func TestMarshaler_Marshal(t *testing.T) { PackageSPDXIdentifier: "Repository-1a78857c1a6a759e", PackageName: "http://test-aggregate", PackageDownloadLocation: "git+http://test-aggregate", - PackageAttributionTexts: []string{ - "SchemaVersion: 2", + Annotations: []spdx.Annotation{ + annotation(t, "SchemaVersion: 2"), }, PrimaryPackagePurpose: tspdx.PackagePurposeSource, }, @@ -1075,7 +1089,7 @@ func TestMarshaler_Marshal(t *testing.T) { CreatorType: "Organization", }, { - Creator: "trivy-0.38.1", + Creator: "trivy-0.56.2", CreatorType: "Tool", }, }, @@ -1086,8 +1100,8 @@ func TestMarshaler_Marshal(t *testing.T) { PackageName: "empty/path", PackageSPDXIdentifier: "Filesystem-70f34983067dba86", PackageDownloadLocation: "NONE", - PackageAttributionTexts: []string{ - "SchemaVersion: 2", + Annotations: []spdx.Annotation{ + annotation(t, "SchemaVersion: 2"), }, PrimaryPackagePurpose: tspdx.PackagePurposeSource, }, @@ -1137,7 +1151,7 @@ func TestMarshaler_Marshal(t *testing.T) { CreatorType: "Organization", }, { - Creator: "trivy-0.38.1", + Creator: "trivy-0.56.2", CreatorType: "Tool", }, }, @@ -1148,8 +1162,8 @@ func TestMarshaler_Marshal(t *testing.T) { PackageName: "secret", PackageSPDXIdentifier: "Filesystem-5c08d34162a2c5d3", PackageDownloadLocation: "NONE", - PackageAttributionTexts: []string{ - "SchemaVersion: 2", + Annotations: []spdx.Annotation{ + annotation(t, "SchemaVersion: 2"), }, PrimaryPackagePurpose: tspdx.PackagePurposeSource, }, @@ -1209,7 +1223,7 @@ func TestMarshaler_Marshal(t *testing.T) { CreatorType: "Organization", }, { - Creator: "trivy-0.38.1", + Creator: "trivy-0.56.2", CreatorType: "Tool", }, }, @@ -1221,9 +1235,9 @@ func TestMarshaler_Marshal(t *testing.T) { PackageDownloadLocation: "NONE", PackageName: "/usr/local/bin/test", PrimaryPackagePurpose: tspdx.PackagePurposeApplication, - PackageAttributionTexts: []string{ - "Class: lang-pkgs", - "Type: gobinary", + Annotations: []spdx.Annotation{ + annotation(t, "Class: lang-pkgs"), + annotation(t, "Type: gobinary"), }, }, { @@ -1235,8 +1249,8 @@ func TestMarshaler_Marshal(t *testing.T) { PrimaryPackagePurpose: tspdx.PackagePurposeLibrary, PackageSupplier: &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion}, PackageSourceInfo: "package found in: /usr/local/bin/test", - PackageAttributionTexts: []string{ - "PkgType: gobinary", + Annotations: []spdx.Annotation{ + annotation(t, "PkgType: gobinary"), }, }, { @@ -1256,16 +1270,16 @@ func TestMarshaler_Marshal(t *testing.T) { PrimaryPackagePurpose: tspdx.PackagePurposeLibrary, PackageSupplier: &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion}, PackageSourceInfo: "package found in: /usr/local/bin/test", - PackageAttributionTexts: []string{ - "PkgType: gobinary", + Annotations: []spdx.Annotation{ + annotation(t, "PkgType: gobinary"), }, }, { PackageName: "go-artifact", PackageSPDXIdentifier: "Filesystem-e340f27468b382be", PackageDownloadLocation: "NONE", - PackageAttributionTexts: []string{ - "SchemaVersion: 2", + Annotations: []spdx.Annotation{ + annotation(t, "SchemaVersion: 2"), }, PrimaryPackagePurpose: tspdx.PackagePurposeSource, }, @@ -1326,7 +1340,7 @@ func TestMarshaler_Marshal(t *testing.T) { ctx := clock.With(context.Background(), time.Date(2021, 8, 25, 12, 20, 30, 5, time.UTC)) uuid.SetFakeUUID(t, "3ff14136-e09f-4df9-80ea-%012d") - marshaler := tspdx.NewMarshaler("0.38.1", tspdx.WithHasher(hasher)) + marshaler := tspdx.NewMarshaler("0.56.2", tspdx.WithHasher(hasher)) spdxDoc, err := marshaler.MarshalReport(ctx, tc.inputReport) require.NoError(t, err) From 414518df62e8caf641277f1e68b27312fb34b5d9 Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Tue, 29 Oct 2024 13:00:29 +0600 Subject: [PATCH 2/5] test(unit): update tests --- pkg/sbom/spdx/testdata/happy/bom.json | 98 +++++++++++++++++++++------ pkg/sbom/spdx/unmarshal.go | 18 ++++- 2 files changed, 94 insertions(+), 22 deletions(-) diff --git a/pkg/sbom/spdx/testdata/happy/bom.json b/pkg/sbom/spdx/testdata/happy/bom.json index 823c0471351f..88af63b3b71a 100644 --- a/pkg/sbom/spdx/testdata/happy/bom.json +++ b/pkg/sbom/spdx/testdata/happy/bom.json @@ -37,13 +37,43 @@ }, { "SPDXID": "SPDXRef-ContainerImage-b5d81cde5f95c8fc", - "attributionTexts": [ - "SchemaVersion: 2", - "ImageID: sha256:49193a2310dbad4c02382da87ac624a80a92387a4f7536235f9ba590e5bcd7b5", - "DiffID: sha256:dd565ff850e7003356e2b252758f9bdc1ff2803f61e995e24c7844f6297f8fc3", - "DiffID: sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", - "RepoTag: maven-test-project:latest", - "RepoTag: tmp-test:latest" + "annotations": [ + { + "annotator": "Tool: trivy-dev", + "annotationDate": "2024-10-29T06:50:38Z", + "annotationType": "OTHER", + "comment": "SchemaVersion: 2" + }, + { + "annotator": "Tool: trivy-dev", + "annotationDate": "2024-10-29T06:50:38Z", + "annotationType": "OTHER", + "comment": "ImageID: sha256:49193a2310dbad4c02382da87ac624a80a92387a4f7536235f9ba590e5bcd7b5" + }, + { + "annotator": "Tool: trivy-dev", + "annotationDate": "2024-10-29T06:50:38Z", + "annotationType": "OTHER", + "comment": "DiffID: sha256:dd565ff850e7003356e2b252758f9bdc1ff2803f61e995e24c7844f6297f8fc3" + }, + { + "annotator": "Tool: trivy-dev", + "annotationDate": "2024-10-29T06:50:38Z", + "annotationType": "OTHER", + "comment": "DiffID: sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1" + }, + { + "annotator": "Tool: trivy-dev", + "annotationDate": "2024-10-29T06:50:38Z", + "annotationType": "OTHER", + "comment": "RepoTag: maven-test-project:latest" + }, + { + "annotator": "Tool: trivy-dev", + "annotationDate": "2024-10-29T06:50:38Z", + "annotationType": "OTHER", + "comment": "RepoTag: tmp-test:latest" + } ], "filesAnalyzed": false, "name": "meven-test-project" @@ -56,8 +86,13 @@ }, { "SPDXID": "SPDXRef-Package-2906575950df652b", - "attributionTexts": [ - "LayerDiffID: sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1" + "annotations": [ + { + "annotator": "Tool: trivy-dev", + "annotationDate": "2024-10-29T06:50:38Z", + "annotationType": "OTHER", + "comment": "LayerDiffID: sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1" + } ], "externalRefs": [ { @@ -74,8 +109,13 @@ }, { "SPDXID": "SPDXRef-Package-2a53baa495b9ddaf", - "attributionTexts": [ - "LayerDiffID: sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1" + "annotations": [ + { + "annotator": "Tool: trivy-dev", + "annotationDate": "2024-10-29T06:50:38Z", + "annotationType": "OTHER", + "comment": "LayerDiffID: sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1" + } ], "externalRefs": [ { @@ -92,8 +132,13 @@ }, { "SPDXID": "SPDXRef-Package-5e2e255ac76747ef", - "attributionTexts": [ - "LayerDiffID: sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1" + "annotations": [ + { + "annotator": "Tool: trivy-dev", + "annotationDate": "2024-10-29T06:50:38Z", + "annotationType": "OTHER", + "comment": "LayerDiffID: sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1" + } ], "externalRefs": [ { @@ -110,8 +155,13 @@ }, { "SPDXID": "SPDXRef-Package-5f1dbaff8de5eb06", - "attributionTexts": [ - "LayerDiffID: sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1" + "annotations": [ + { + "annotator": "Tool: trivy-dev", + "annotationDate": "2024-10-29T06:50:38Z", + "annotationType": "OTHER", + "comment": "LayerDiffID: sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1" + } ], "externalRefs": [ { @@ -128,8 +178,13 @@ }, { "SPDXID": "SPDXRef-Package-84ebffe38343d949", - "attributionTexts": [ - "LayerDiffID: sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1" + "annotations": [ + { + "annotator": "Tool: trivy-dev", + "annotationDate": "2024-10-29T06:50:38Z", + "annotationType": "OTHER", + "comment": "LayerDiffID: sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1" + } ], "externalRefs": [ { @@ -146,8 +201,13 @@ }, { "SPDXID": "SPDXRef-Package-b7ebaf0233f1ef7b", - "attributionTexts": [ - "LayerDiffID: sha256:dd565ff850e7003356e2b252758f9bdc1ff2803f61e995e24c7844f6297f8fc3" + "annotations": [ + { + "annotator": "Tool: trivy-dev", + "annotationDate": "2024-10-29T06:50:38Z", + "annotationType": "OTHER", + "comment": "LayerDiffID: sha256:dd565ff850e7003356e2b252758f9bdc1ff2803f61e995e24c7844f6297f8fc3" + } ], "externalRefs": [ { diff --git a/pkg/sbom/spdx/unmarshal.go b/pkg/sbom/spdx/unmarshal.go index d2ba05b44999..c7ed6b0905dd 100644 --- a/pkg/sbom/spdx/unmarshal.go +++ b/pkg/sbom/spdx/unmarshal.go @@ -194,9 +194,21 @@ func (s *SPDX) parsePackage(spdxPkg spdx.Package) (*core.Component, error) { } } - // Attributions - for _, attr := range spdxPkg.PackageAttributionTexts { - k, v, ok := strings.Cut(attr, ": ") + // Trivy stores properties in Annotations + // But previous versions stored properties in AttributionTexts + // So we need to check both cases to maintain backward compatibility + var props []string + if len(spdxPkg.Annotations) > 0 { + for _, annotation := range spdxPkg.Annotations { + props = append(props, annotation.AnnotationComment) + } + } else if len(spdxPkg.PackageAttributionTexts) > 0 { + for _, attr := range spdxPkg.PackageAttributionTexts { + props = append(props, attr) + } + } + for _, prop := range props { + k, v, ok := strings.Cut(prop, ": ") if !ok { continue } From 30018da8752282de7767328cdce0a2149202bc03 Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Tue, 29 Oct 2024 13:00:36 +0600 Subject: [PATCH 3/5] test(integration): update tests --- integration/testdata/conda-spdx.json.golden | 39 +++++--- integration/testdata/julia-spdx.json.golden | 93 ++++++++++++++----- .../testdata/license-cyclonedx.json.golden | 3 + 3 files changed, 99 insertions(+), 36 deletions(-) diff --git a/integration/testdata/conda-spdx.json.golden b/integration/testdata/conda-spdx.json.golden index c14d1bf4e018..c11f52eeca1b 100644 --- a/integration/testdata/conda-spdx.json.golden +++ b/integration/testdata/conda-spdx.json.golden @@ -31,10 +31,15 @@ "referenceLocator": "pkg:conda/openssl@1.1.1q" } ], - "attributionTexts": [ - "PkgType: conda-pkg" - ], - "primaryPackagePurpose": "LIBRARY" + "primaryPackagePurpose": "LIBRARY", + "annotations": [ + { + "annotator": "Tool: trivy-dev", + "annotationDate": "2021-08-25T12:20:30Z", + "annotationType": "OTHER", + "comment": "PkgType: conda-pkg" + } + ] }, { "name": "pip", @@ -55,20 +60,30 @@ "referenceLocator": "pkg:conda/pip@22.2.2" } ], - "attributionTexts": [ - "PkgType: conda-pkg" - ], - "primaryPackagePurpose": "LIBRARY" + "primaryPackagePurpose": "LIBRARY", + "annotations": [ + { + "annotator": "Tool: trivy-dev", + "annotationDate": "2021-08-25T12:20:30Z", + "annotationType": "OTHER", + "comment": "PkgType: conda-pkg" + } + ] }, { "name": "testdata/fixtures/repo/conda", "SPDXID": "SPDXRef-Filesystem-2e2426fd0f2580ef", "downloadLocation": "NONE", "filesAnalyzed": false, - "attributionTexts": [ - "SchemaVersion: 2" - ], - "primaryPackagePurpose": "SOURCE" + "primaryPackagePurpose": "SOURCE", + "annotations": [ + { + "annotator": "Tool: trivy-dev", + "annotationDate": "2021-08-25T12:20:30Z", + "annotationType": "OTHER", + "comment": "SchemaVersion: 2" + } + ] } ], "files": [ diff --git a/integration/testdata/julia-spdx.json.golden b/integration/testdata/julia-spdx.json.golden index 89a4edb3f287..a770765dd849 100644 --- a/integration/testdata/julia-spdx.json.golden +++ b/integration/testdata/julia-spdx.json.golden @@ -17,11 +17,21 @@ "SPDXID": "SPDXRef-Application-18fc3597717a3e56", "downloadLocation": "NONE", "filesAnalyzed": false, - "attributionTexts": [ - "Class: lang-pkgs", - "Type: julia" - ], - "primaryPackagePurpose": "APPLICATION" + "primaryPackagePurpose": "APPLICATION", + "annotations": [ + { + "annotator": "Tool: trivy-dev", + "annotationDate": "2021-08-25T12:20:30Z", + "annotationType": "OTHER", + "comment": "Class: lang-pkgs" + }, + { + "annotator": "Tool: trivy-dev", + "annotationDate": "2021-08-25T12:20:30Z", + "annotationType": "OTHER", + "comment": "Type: julia" + } + ] }, { "name": "A", @@ -40,11 +50,21 @@ "referenceLocator": "pkg:julia/A@1.9.0?uuid=ead4f63c-334e-11e9-00e6-e7f0a5f21b60" } ], - "attributionTexts": [ - "PkgID: ead4f63c-334e-11e9-00e6-e7f0a5f21b60", - "PkgType: julia" - ], - "primaryPackagePurpose": "LIBRARY" + "primaryPackagePurpose": "LIBRARY", + "annotations": [ + { + "annotator": "Tool: trivy-dev", + "annotationDate": "2021-08-25T12:20:30Z", + "annotationType": "OTHER", + "comment": "PkgID: ead4f63c-334e-11e9-00e6-e7f0a5f21b60" + }, + { + "annotator": "Tool: trivy-dev", + "annotationDate": "2021-08-25T12:20:30Z", + "annotationType": "OTHER", + "comment": "PkgType: julia" + } + ] }, { "name": "B", @@ -63,11 +83,21 @@ "referenceLocator": "pkg:julia/B@1.9.0?uuid=f41f7b98-334e-11e9-1257-49272045fb24" } ], - "attributionTexts": [ - "PkgID: f41f7b98-334e-11e9-1257-49272045fb24", - "PkgType: julia" - ], - "primaryPackagePurpose": "LIBRARY" + "primaryPackagePurpose": "LIBRARY", + "annotations": [ + { + "annotator": "Tool: trivy-dev", + "annotationDate": "2021-08-25T12:20:30Z", + "annotationType": "OTHER", + "comment": "PkgID: f41f7b98-334e-11e9-1257-49272045fb24" + }, + { + "annotator": "Tool: trivy-dev", + "annotationDate": "2021-08-25T12:20:30Z", + "annotationType": "OTHER", + "comment": "PkgType: julia" + } + ] }, { "name": "B", @@ -86,21 +116,36 @@ "referenceLocator": "pkg:julia/B@1.9.0?uuid=edca9bc6-334e-11e9-3554-9595dbb4349c" } ], - "attributionTexts": [ - "PkgID: edca9bc6-334e-11e9-3554-9595dbb4349c", - "PkgType: julia" - ], - "primaryPackagePurpose": "LIBRARY" + "primaryPackagePurpose": "LIBRARY", + "annotations": [ + { + "annotator": "Tool: trivy-dev", + "annotationDate": "2021-08-25T12:20:30Z", + "annotationType": "OTHER", + "comment": "PkgID: edca9bc6-334e-11e9-3554-9595dbb4349c" + }, + { + "annotator": "Tool: trivy-dev", + "annotationDate": "2021-08-25T12:20:30Z", + "annotationType": "OTHER", + "comment": "PkgType: julia" + } + ] }, { "name": "testdata/fixtures/repo/julia", "SPDXID": "SPDXRef-Filesystem-1be792dd0077c431", "downloadLocation": "NONE", "filesAnalyzed": false, - "attributionTexts": [ - "SchemaVersion: 2" - ], - "primaryPackagePurpose": "SOURCE" + "primaryPackagePurpose": "SOURCE", + "annotations": [ + { + "annotator": "Tool: trivy-dev", + "annotationDate": "2021-08-25T12:20:30Z", + "annotationType": "OTHER", + "comment": "SchemaVersion: 2" + } + ] } ], "relationships": [ diff --git a/integration/testdata/license-cyclonedx.json.golden b/integration/testdata/license-cyclonedx.json.golden index 95f8acd896b1..e87b3245d37b 100644 --- a/integration/testdata/license-cyclonedx.json.golden +++ b/integration/testdata/license-cyclonedx.json.golden @@ -34,6 +34,7 @@ "PkgName": "org.eclipse.sisu:org.eclipse.sisu.plexus", "FilePath": "", "Name": "EPL-1.0", + "Text": "", "Confidence": 1, "Link": "" }, @@ -43,6 +44,7 @@ "PkgName": "org.ow2.asm:asm", "FilePath": "", "Name": "BSD-3-Clause", + "Text": "", "Confidence": 1, "Link": "" }, @@ -52,6 +54,7 @@ "PkgName": "org.slf4j:slf4j-api", "FilePath": "", "Name": "MIT License", + "Text": "", "Confidence": 1, "Link": "" } From ef2a73da97714e0f7319bba03cbc4addb34743ce Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Tue, 29 Oct 2024 13:11:13 +0600 Subject: [PATCH 4/5] refactor: don't save timeNow in Marshaller --- pkg/sbom/spdx/marshal.go | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/pkg/sbom/spdx/marshal.go b/pkg/sbom/spdx/marshal.go index 8a023635281e..51f9144f682d 100644 --- a/pkg/sbom/spdx/marshal.go +++ b/pkg/sbom/spdx/marshal.go @@ -82,7 +82,6 @@ type Marshaler struct { format spdx.Document hasher Hash appVersion string // Trivy version. It needed for `creator` field - timeNow string } type Hash func(v any, format hashstructure.Format, opts *hashstructure.HashOptions) (uint64, error) @@ -126,7 +125,7 @@ func (m *Marshaler) Marshal(ctx context.Context, bom *core.BOM) (*spdx.Document, ) // Lock time to use same time for all spdx fields - m.timeNow = clock.Now(ctx).UTC().Format(time.RFC3339) + timeNow := clock.Now(ctx).UTC().Format(time.RFC3339) root := bom.Root() pkgDownloadLocation := m.packageDownloadLocation(root) @@ -135,7 +134,7 @@ func (m *Marshaler) Marshal(ctx context.Context, bom *core.BOM) (*spdx.Document, packageIDs := make(map[uuid.UUID]spdx.ElementID) // Root package contains OS, OS packages, language-specific packages and so on. - rootPkg, err := m.rootSPDXPackage(root, pkgDownloadLocation) + rootPkg, err := m.rootSPDXPackage(root, timeNow, pkgDownloadLocation) if err != nil { return nil, xerrors.Errorf("failed to generate a root package: %w", err) } @@ -150,7 +149,7 @@ func (m *Marshaler) Marshal(ctx context.Context, bom *core.BOM) (*spdx.Document, if c.Root { continue } - spdxPackage, err := m.spdxPackage(c, pkgDownloadLocation) + spdxPackage, err := m.spdxPackage(c, timeNow, pkgDownloadLocation) if err != nil { return nil, xerrors.Errorf("spdx package error: %w", err) } @@ -222,7 +221,7 @@ func (m *Marshaler) Marshal(ctx context.Context, bom *core.BOM) (*spdx.Document, CreatorType: "Tool", }, }, - Created: m.timeNow, + Created: timeNow, }, Packages: packages, Relationships: relationShips, @@ -243,7 +242,7 @@ func (m *Marshaler) packageDownloadLocation(root *core.Component) string { return location } -func (m *Marshaler) rootSPDXPackage(root *core.Component, pkgDownloadLocation string) (*spdx.Package, error) { +func (m *Marshaler) rootSPDXPackage(root *core.Component, timeNow, pkgDownloadLocation string) (*spdx.Package, error) { var externalReferences []*spdx.PackageExternalReference // When the target is a container image, add PURL to the external references of the root package. if root.PkgIdentifier.PURL != nil { @@ -264,18 +263,18 @@ func (m *Marshaler) rootSPDXPackage(root *core.Component, pkgDownloadLocation st PackageName: root.Name, PackageSPDXIdentifier: elementID(camelCase(string(root.Type)), pkgID), PackageDownloadLocation: pkgDownloadLocation, - Annotations: m.spdxAnnotations(root), + Annotations: m.spdxAnnotations(root, timeNow), PackageExternalReferences: externalReferences, PrimaryPackagePurpose: pkgPurpose, }, nil } -func (m *Marshaler) appendAnnotation(annotations []spdx.Annotation, key, value string) []spdx.Annotation { +func (m *Marshaler) appendAnnotation(annotations []spdx.Annotation, timeNow, key, value string) []spdx.Annotation { if value == "" { return annotations } return append(annotations, spdx.Annotation{ - AnnotationDate: m.timeNow, + AnnotationDate: timeNow, AnnotationType: spdx.CategoryOther, Annotator: spdx.Annotator{ Annotator: fmt.Sprintf("%s-%s", CreatorTool, m.appVersion), @@ -301,7 +300,7 @@ func (m *Marshaler) advisoryExternalReference(primaryURL string) *spdx.PackageEx } } -func (m *Marshaler) spdxPackage(c *core.Component, pkgDownloadLocation string) (spdx.Package, error) { +func (m *Marshaler) spdxPackage(c *core.Component, timeNow, pkgDownloadLocation string) (spdx.Package, error) { pkgID, err := calcPkgID(m.hasher, c) if err != nil { return spdx.Package{}, xerrors.Errorf("failed to get os metadata package ID: %w", err) @@ -357,7 +356,7 @@ func (m *Marshaler) spdxPackage(c *core.Component, pkgDownloadLocation string) ( PrimaryPackagePurpose: purpose, PackageDownloadLocation: pkgDownloadLocation, PackageExternalReferences: pkgExtRefs, - Annotations: m.spdxAnnotations(c), + Annotations: m.spdxAnnotations(c, timeNow), PackageSourceInfo: sourceInfo, PackageSupplier: supplier, PackageChecksums: m.spdxChecksums(digests), @@ -379,12 +378,12 @@ func spdxPkgName(component *core.Component) string { } return component.Name } -func (m *Marshaler) spdxAnnotations(c *core.Component) []spdx.Annotation { +func (m *Marshaler) spdxAnnotations(c *core.Component, timeNow string) []spdx.Annotation { var annotations []spdx.Annotation for _, p := range c.Properties { // Add properties that are not in other fields. if !slices.Contains(duplicateProperties, p.Name) { - annotations = m.appendAnnotation(annotations, p.Name, p.Value) + annotations = m.appendAnnotation(annotations, timeNow, p.Name, p.Value) } } return annotations From 6c1592db28ad1a900592def65eba599b7216e38f Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Tue, 29 Oct 2024 13:15:36 +0600 Subject: [PATCH 5/5] chore: remove unneeded changes --- integration/testdata/license-cyclonedx.json.golden | 3 --- 1 file changed, 3 deletions(-) diff --git a/integration/testdata/license-cyclonedx.json.golden b/integration/testdata/license-cyclonedx.json.golden index e87b3245d37b..95f8acd896b1 100644 --- a/integration/testdata/license-cyclonedx.json.golden +++ b/integration/testdata/license-cyclonedx.json.golden @@ -34,7 +34,6 @@ "PkgName": "org.eclipse.sisu:org.eclipse.sisu.plexus", "FilePath": "", "Name": "EPL-1.0", - "Text": "", "Confidence": 1, "Link": "" }, @@ -44,7 +43,6 @@ "PkgName": "org.ow2.asm:asm", "FilePath": "", "Name": "BSD-3-Clause", - "Text": "", "Confidence": 1, "Link": "" }, @@ -54,7 +52,6 @@ "PkgName": "org.slf4j:slf4j-api", "FilePath": "", "Name": "MIT License", - "Text": "", "Confidence": 1, "Link": "" }