github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/sbom/spdx/marshal_test.go (about)

     1  package spdx_test
     2  
     3  import (
     4  	"hash/fnv"
     5  	"testing"
     6  	"time"
     7  
     8  	v1 "github.com/google/go-containerregistry/pkg/v1"
     9  	"github.com/mitchellh/hashstructure/v2"
    10  	"github.com/spdx/tools-golang/spdx"
    11  	"github.com/spdx/tools-golang/spdx/v2/common"
    12  	"github.com/stretchr/testify/assert"
    13  	"github.com/stretchr/testify/require"
    14  
    15  	"github.com/devseccon/trivy/pkg/clock"
    16  	ftypes "github.com/devseccon/trivy/pkg/fanal/types"
    17  	"github.com/devseccon/trivy/pkg/report"
    18  	tspdx "github.com/devseccon/trivy/pkg/sbom/spdx"
    19  	"github.com/devseccon/trivy/pkg/types"
    20  	"github.com/devseccon/trivy/pkg/uuid"
    21  )
    22  
    23  func TestMarshaler_Marshal(t *testing.T) {
    24  	testCases := []struct {
    25  		name        string
    26  		inputReport types.Report
    27  		wantSBOM    *spdx.Document
    28  	}{
    29  		{
    30  			name: "happy path for container scan",
    31  			inputReport: types.Report{
    32  				SchemaVersion: report.SchemaVersion,
    33  				ArtifactName:  "rails:latest",
    34  				ArtifactType:  ftypes.ArtifactContainerImage,
    35  				Metadata: types.Metadata{
    36  					Size: 1024,
    37  					OS: &ftypes.OS{
    38  						Family: ftypes.CentOS,
    39  						Name:   "8.3.2011",
    40  						Eosl:   true,
    41  					},
    42  					ImageID:     "sha256:5d0da3dc976460b72c77d94c8a1ad043720b0416bfc16c52c45d4847e53fadb6",
    43  					RepoTags:    []string{"rails:latest"},
    44  					DiffIDs:     []string{"sha256:d871dadfb37b53ef1ca45be04fc527562b91989991a8f545345ae3be0b93f92a"},
    45  					RepoDigests: []string{"rails@sha256:a27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177"},
    46  					ImageConfig: v1.ConfigFile{
    47  						Architecture: "arm64",
    48  					},
    49  				},
    50  				Results: types.Results{
    51  					{
    52  						Target: "rails:latest (centos 8.3.2011)",
    53  						Class:  types.ClassOSPkg,
    54  						Type:   ftypes.CentOS,
    55  						Packages: []ftypes.Package{
    56  							{
    57  								Name:            "binutils",
    58  								Version:         "2.30",
    59  								Release:         "93.el8",
    60  								Epoch:           0,
    61  								Arch:            "aarch64",
    62  								SrcName:         "binutils",
    63  								SrcVersion:      "2.30",
    64  								SrcRelease:      "93.el8",
    65  								SrcEpoch:        0,
    66  								Modularitylabel: "",
    67  								Licenses:        []string{"GPLv3+"},
    68  								Maintainer:      "CentOS",
    69  								Digest:          "md5:7459cec61bb4d1b0ca8107e25e0dd005",
    70  							},
    71  						},
    72  					},
    73  					{
    74  						Target: "app/subproject/Gemfile.lock",
    75  						Class:  types.ClassLangPkg,
    76  						Type:   ftypes.Bundler,
    77  						Packages: []ftypes.Package{
    78  							{
    79  								Name:    "actionpack",
    80  								Version: "7.0.1",
    81  							},
    82  							{
    83  								Name:    "actioncontroller",
    84  								Version: "7.0.1",
    85  							},
    86  						},
    87  					},
    88  					{
    89  						Target: "app/Gemfile.lock",
    90  						Class:  types.ClassLangPkg,
    91  						Type:   ftypes.Bundler,
    92  						Packages: []ftypes.Package{
    93  							{
    94  								Name:    "actionpack",
    95  								Version: "7.0.1",
    96  							},
    97  						},
    98  					},
    99  				},
   100  			},
   101  			wantSBOM: &spdx.Document{
   102  				SPDXVersion:       spdx.Version,
   103  				DataLicense:       spdx.DataLicense,
   104  				SPDXIdentifier:    "DOCUMENT",
   105  				DocumentName:      "rails:latest",
   106  				DocumentNamespace: "http://aquasecurity.github.io/trivy/container_image/rails:latest-3ff14136-e09f-4df9-80ea-000000000001",
   107  				CreationInfo: &spdx.CreationInfo{
   108  					Creators: []common.Creator{
   109  						{
   110  							Creator:     "aquasecurity",
   111  							CreatorType: "Organization",
   112  						},
   113  						{
   114  							Creator:     "trivy-0.38.1",
   115  							CreatorType: "Tool",
   116  						},
   117  					},
   118  					Created: "2021-08-25T12:20:30Z",
   119  				},
   120  				Packages: []*spdx.Package{
   121  					{
   122  						PackageSPDXIdentifier:   spdx.ElementID("Package-eb0263038c3b445b"),
   123  						PackageDownloadLocation: "NONE",
   124  						PackageName:             "actioncontroller",
   125  						PackageVersion:          "7.0.1",
   126  						PackageLicenseConcluded: "NONE",
   127  						PackageLicenseDeclared:  "NONE",
   128  						PackageExternalReferences: []*spdx.PackageExternalReference{
   129  							{
   130  								Category: tspdx.CategoryPackageManager,
   131  								RefType:  tspdx.RefTypePurl,
   132  								Locator:  "pkg:gem/actioncontroller@7.0.1",
   133  							},
   134  						},
   135  						PrimaryPackagePurpose: tspdx.PackagePurposeLibrary,
   136  						PackageSupplier:       &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion},
   137  					},
   138  					{
   139  						PackageSPDXIdentifier:   spdx.ElementID("Package-826226d056ff30c0"),
   140  						PackageDownloadLocation: "NONE",
   141  						PackageName:             "actionpack",
   142  						PackageVersion:          "7.0.1",
   143  						PackageLicenseConcluded: "NONE",
   144  						PackageLicenseDeclared:  "NONE",
   145  						PackageExternalReferences: []*spdx.PackageExternalReference{
   146  							{
   147  								Category: tspdx.CategoryPackageManager,
   148  								RefType:  tspdx.RefTypePurl,
   149  								Locator:  "pkg:gem/actionpack@7.0.1",
   150  							},
   151  						},
   152  						PrimaryPackagePurpose: tspdx.PackagePurposeLibrary,
   153  						PackageSupplier:       &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion},
   154  					},
   155  					{
   156  						PackageSPDXIdentifier:   spdx.ElementID("Package-fd0dc3cf913d5bc3"),
   157  						PackageDownloadLocation: "NONE",
   158  						PackageName:             "binutils",
   159  						PackageVersion:          "2.30-93.el8",
   160  						PackageLicenseConcluded: "GPL-3.0-or-later",
   161  						PackageLicenseDeclared:  "GPL-3.0-or-later",
   162  						PackageSupplier: &spdx.Supplier{
   163  							SupplierType: tspdx.PackageSupplierOrganization,
   164  							Supplier:     "CentOS",
   165  						},
   166  						PackageExternalReferences: []*spdx.PackageExternalReference{
   167  							{
   168  								Category: tspdx.CategoryPackageManager,
   169  								RefType:  tspdx.RefTypePurl,
   170  								Locator:  "pkg:rpm/centos/binutils@2.30-93.el8?arch=aarch64&distro=centos-8.3.2011",
   171  							},
   172  						},
   173  						PackageSourceInfo:     "built package from: binutils 2.30-93.el8",
   174  						PrimaryPackagePurpose: tspdx.PackagePurposeLibrary,
   175  						PackageChecksums: []common.Checksum{
   176  							{
   177  								Algorithm: common.MD5,
   178  								Value:     "7459cec61bb4d1b0ca8107e25e0dd005",
   179  							},
   180  						},
   181  					},
   182  					{
   183  						PackageSPDXIdentifier:   spdx.ElementID("Application-73c871d73f3c8248"),
   184  						PackageDownloadLocation: "NONE",
   185  						PackageName:             "bundler",
   186  						PackageSourceInfo:       "app/subproject/Gemfile.lock",
   187  						PrimaryPackagePurpose:   tspdx.PackagePurposeApplication,
   188  					},
   189  					{
   190  						PackageSPDXIdentifier:   spdx.ElementID("Application-c3fac92c1ac0a9fa"),
   191  						PackageDownloadLocation: "NONE",
   192  						PackageName:             "bundler",
   193  						PackageSourceInfo:       "app/Gemfile.lock",
   194  						PrimaryPackagePurpose:   tspdx.PackagePurposeApplication,
   195  					},
   196  					{
   197  						PackageSPDXIdentifier:   spdx.ElementID("OperatingSystem-197f9a00ebcb51f0"),
   198  						PackageDownloadLocation: "NONE",
   199  						PackageName:             "centos",
   200  						PackageVersion:          "8.3.2011",
   201  						PrimaryPackagePurpose:   tspdx.PackagePurposeOS,
   202  					},
   203  					{
   204  						PackageSPDXIdentifier:   spdx.ElementID("ContainerImage-9396d894cd0cb6cb"),
   205  						PackageDownloadLocation: "NONE",
   206  						PackageName:             "rails:latest",
   207  						PackageExternalReferences: []*spdx.PackageExternalReference{
   208  							{
   209  								Category: tspdx.CategoryPackageManager,
   210  								RefType:  tspdx.RefTypePurl,
   211  								Locator:  "pkg:oci/rails@sha256%3Aa27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177?arch=arm64&repository_url=index.docker.io%2Flibrary%2Frails",
   212  							},
   213  						},
   214  						PackageAttributionTexts: []string{
   215  							"SchemaVersion: 2",
   216  							"ImageID: sha256:5d0da3dc976460b72c77d94c8a1ad043720b0416bfc16c52c45d4847e53fadb6",
   217  							"Size: 1024",
   218  							"RepoDigest: rails@sha256:a27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177",
   219  							"DiffID: sha256:d871dadfb37b53ef1ca45be04fc527562b91989991a8f545345ae3be0b93f92a",
   220  							"RepoTag: rails:latest",
   221  						},
   222  						PrimaryPackagePurpose: tspdx.PackagePurposeContainer,
   223  					},
   224  				},
   225  				Relationships: []*spdx.Relationship{
   226  					{
   227  						RefA:         spdx.DocElementID{ElementRefID: "DOCUMENT"},
   228  						RefB:         spdx.DocElementID{ElementRefID: "ContainerImage-9396d894cd0cb6cb"},
   229  						Relationship: "DESCRIBES",
   230  					},
   231  					{
   232  						RefA:         spdx.DocElementID{ElementRefID: "ContainerImage-9396d894cd0cb6cb"},
   233  						RefB:         spdx.DocElementID{ElementRefID: "OperatingSystem-197f9a00ebcb51f0"},
   234  						Relationship: "CONTAINS",
   235  					},
   236  					{
   237  						RefA:         spdx.DocElementID{ElementRefID: "OperatingSystem-197f9a00ebcb51f0"},
   238  						RefB:         spdx.DocElementID{ElementRefID: "Package-fd0dc3cf913d5bc3"},
   239  						Relationship: "CONTAINS",
   240  					},
   241  					{
   242  						RefA:         spdx.DocElementID{ElementRefID: "ContainerImage-9396d894cd0cb6cb"},
   243  						RefB:         spdx.DocElementID{ElementRefID: "Application-73c871d73f3c8248"},
   244  						Relationship: "CONTAINS",
   245  					},
   246  					{
   247  						RefA:         spdx.DocElementID{ElementRefID: "Application-73c871d73f3c8248"},
   248  						RefB:         spdx.DocElementID{ElementRefID: "Package-826226d056ff30c0"},
   249  						Relationship: "CONTAINS",
   250  					},
   251  					{
   252  						RefA:         spdx.DocElementID{ElementRefID: "Application-73c871d73f3c8248"},
   253  						RefB:         spdx.DocElementID{ElementRefID: "Package-eb0263038c3b445b"},
   254  						Relationship: "CONTAINS",
   255  					},
   256  					{
   257  						RefA:         spdx.DocElementID{ElementRefID: "ContainerImage-9396d894cd0cb6cb"},
   258  						RefB:         spdx.DocElementID{ElementRefID: "Application-c3fac92c1ac0a9fa"},
   259  						Relationship: "CONTAINS",
   260  					},
   261  					{
   262  						RefA:         spdx.DocElementID{ElementRefID: "Application-c3fac92c1ac0a9fa"},
   263  						RefB:         spdx.DocElementID{ElementRefID: "Package-826226d056ff30c0"},
   264  						Relationship: "CONTAINS",
   265  					},
   266  				},
   267  				OtherLicenses: nil,
   268  				Annotations:   nil,
   269  				Reviews:       nil,
   270  			},
   271  		},
   272  		{
   273  			name: "happy path for local container scan",
   274  			inputReport: types.Report{
   275  				SchemaVersion: report.SchemaVersion,
   276  				ArtifactName:  "centos:latest",
   277  				ArtifactType:  ftypes.ArtifactContainerImage,
   278  				Metadata: types.Metadata{
   279  					Size: 1024,
   280  					OS: &ftypes.OS{
   281  						Family: ftypes.CentOS,
   282  						Name:   "8.3.2011",
   283  						Eosl:   true,
   284  					},
   285  					ImageID:     "sha256:5d0da3dc976460b72c77d94c8a1ad043720b0416bfc16c52c45d4847e53fadb6",
   286  					RepoTags:    []string{"centos:latest"},
   287  					RepoDigests: []string{},
   288  					ImageConfig: v1.ConfigFile{
   289  						Architecture: "arm64",
   290  					},
   291  				},
   292  				Results: types.Results{
   293  					{
   294  						Target: "centos:latest (centos 8.3.2011)",
   295  						Class:  types.ClassOSPkg,
   296  						Type:   ftypes.CentOS,
   297  						Packages: []ftypes.Package{
   298  							{
   299  								Name:            "acl",
   300  								Version:         "2.2.53",
   301  								Release:         "1.el8",
   302  								Epoch:           1,
   303  								Arch:            "aarch64",
   304  								SrcName:         "acl",
   305  								SrcVersion:      "2.2.53",
   306  								SrcRelease:      "1.el8",
   307  								SrcEpoch:        1,
   308  								Modularitylabel: "",
   309  								Licenses:        []string{"GPLv2+"},
   310  								Digest:          "md5:483792b8b5f9eb8be7dc4407733118d0",
   311  							},
   312  						},
   313  					},
   314  					{
   315  						Target: "Ruby",
   316  						Class:  types.ClassLangPkg,
   317  						Type:   ftypes.GemSpec,
   318  						Packages: []ftypes.Package{
   319  							{
   320  								Name:    "actionpack",
   321  								Version: "7.0.1",
   322  								Layer: ftypes.Layer{
   323  									DiffID: "sha256:ccb64cf0b7ba2e50741d0b64cae324eb5de3b1e2f580bbf177e721b67df38488",
   324  								},
   325  								FilePath: "tools/project-john/specifications/actionpack.gemspec",
   326  								Digest:   "sha1:d2f9f9aed5161f6e4116a3f9573f41cd832f137c",
   327  							},
   328  							{
   329  								Name:    "actionpack",
   330  								Version: "7.0.1",
   331  								Layer: ftypes.Layer{
   332  									DiffID: "sha256:ccb64cf0b7ba2e50741d0b64cae324eb5de3b1e2f580bbf177e721b67df38488",
   333  								},
   334  								FilePath: "tools/project-doe/specifications/actionpack.gemspec",
   335  								Digest:   "sha1:413f98442c83808042b5d1d2611a346b999bdca5",
   336  							},
   337  						},
   338  					},
   339  				},
   340  			},
   341  			wantSBOM: &spdx.Document{
   342  				SPDXVersion:       spdx.Version,
   343  				DataLicense:       spdx.DataLicense,
   344  				SPDXIdentifier:    "DOCUMENT",
   345  				DocumentName:      "centos:latest",
   346  				DocumentNamespace: "http://aquasecurity.github.io/trivy/container_image/centos:latest-3ff14136-e09f-4df9-80ea-000000000001",
   347  				CreationInfo: &spdx.CreationInfo{
   348  					Creators: []common.Creator{
   349  						{
   350  							Creator:     "aquasecurity",
   351  							CreatorType: "Organization",
   352  						},
   353  						{
   354  							Creator:     "trivy-0.38.1",
   355  							CreatorType: "Tool",
   356  						},
   357  					},
   358  					Created: "2021-08-25T12:20:30Z",
   359  				},
   360  				Packages: []*spdx.Package{
   361  					{
   362  						PackageSPDXIdentifier:   spdx.ElementID("Package-d8dccb186bafaf37"),
   363  						PackageDownloadLocation: "NONE",
   364  						PackageName:             "acl",
   365  						PackageVersion:          "1:2.2.53-1.el8",
   366  						PackageLicenseConcluded: "GPL-2.0-or-later",
   367  						PackageLicenseDeclared:  "GPL-2.0-or-later",
   368  						PackageExternalReferences: []*spdx.PackageExternalReference{
   369  							{
   370  								Category: tspdx.CategoryPackageManager,
   371  								RefType:  tspdx.RefTypePurl,
   372  								Locator:  "pkg:rpm/centos/acl@2.2.53-1.el8?arch=aarch64&distro=centos-8.3.2011&epoch=1",
   373  							},
   374  						},
   375  						PackageSourceInfo:     "built package from: acl 1:2.2.53-1.el8",
   376  						PrimaryPackagePurpose: tspdx.PackagePurposeLibrary,
   377  						PackageSupplier:       &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion},
   378  						PackageChecksums: []common.Checksum{
   379  							{
   380  								Algorithm: common.MD5,
   381  								Value:     "483792b8b5f9eb8be7dc4407733118d0",
   382  							},
   383  						},
   384  					},
   385  					{
   386  						PackageSPDXIdentifier:   spdx.ElementID("Package-13fe667a0805e6b7"),
   387  						PackageDownloadLocation: "NONE",
   388  						PackageName:             "actionpack",
   389  						PackageVersion:          "7.0.1",
   390  						PackageLicenseConcluded: "NONE",
   391  						PackageLicenseDeclared:  "NONE",
   392  						PackageExternalReferences: []*spdx.PackageExternalReference{
   393  							{
   394  								Category: tspdx.CategoryPackageManager,
   395  								RefType:  tspdx.RefTypePurl,
   396  								Locator:  "pkg:gem/actionpack@7.0.1",
   397  							},
   398  						},
   399  						PackageAttributionTexts: []string{
   400  							"LayerDiffID: sha256:ccb64cf0b7ba2e50741d0b64cae324eb5de3b1e2f580bbf177e721b67df38488",
   401  						},
   402  						PrimaryPackagePurpose: tspdx.PackagePurposeLibrary,
   403  						PackageSupplier:       &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion},
   404  						FilesAnalyzed:         true,
   405  						PackageVerificationCode: &spdx.PackageVerificationCode{
   406  							Value: "688d98e7e5660b879fd1fc548af8c0df3b7d785a",
   407  						},
   408  					},
   409  					{
   410  						PackageSPDXIdentifier:   spdx.ElementID("Package-d5443dbcbba0dbd4"),
   411  						PackageDownloadLocation: "NONE",
   412  						PackageName:             "actionpack",
   413  						PackageVersion:          "7.0.1",
   414  						PackageLicenseConcluded: "NONE",
   415  						PackageLicenseDeclared:  "NONE",
   416  						PackageExternalReferences: []*spdx.PackageExternalReference{
   417  							{
   418  								Category: tspdx.CategoryPackageManager,
   419  								RefType:  tspdx.RefTypePurl,
   420  								Locator:  "pkg:gem/actionpack@7.0.1",
   421  							},
   422  						},
   423  						PackageAttributionTexts: []string{
   424  							"LayerDiffID: sha256:ccb64cf0b7ba2e50741d0b64cae324eb5de3b1e2f580bbf177e721b67df38488",
   425  						},
   426  						PrimaryPackagePurpose: tspdx.PackagePurposeLibrary,
   427  						PackageSupplier:       &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion},
   428  						FilesAnalyzed:         true,
   429  						PackageVerificationCode: &spdx.PackageVerificationCode{
   430  							Value: "c7526b18eaaeb410e82cb0da9288dd02b38ea171",
   431  						},
   432  					},
   433  					{
   434  						PackageSPDXIdentifier:   spdx.ElementID("OperatingSystem-197f9a00ebcb51f0"),
   435  						PackageDownloadLocation: "NONE",
   436  						PackageName:             "centos",
   437  						PackageVersion:          "8.3.2011",
   438  						PrimaryPackagePurpose:   tspdx.PackagePurposeOS,
   439  					},
   440  					{
   441  						PackageName:             "centos:latest",
   442  						PackageSPDXIdentifier:   "ContainerImage-413bfede37ad01fc",
   443  						PackageDownloadLocation: "NONE",
   444  						PackageAttributionTexts: []string{
   445  							"SchemaVersion: 2",
   446  							"ImageID: sha256:5d0da3dc976460b72c77d94c8a1ad043720b0416bfc16c52c45d4847e53fadb6",
   447  							"Size: 1024",
   448  							"RepoTag: centos:latest",
   449  						},
   450  						PrimaryPackagePurpose: tspdx.PackagePurposeContainer,
   451  					},
   452  					{
   453  						PackageSPDXIdentifier:   spdx.ElementID("Application-441a648f2aeeee72"),
   454  						PackageDownloadLocation: "NONE",
   455  						PackageName:             "gemspec",
   456  						PackageSourceInfo:       "Ruby",
   457  						PrimaryPackagePurpose:   tspdx.PackagePurposeApplication,
   458  					},
   459  				},
   460  				Files: []*spdx.File{
   461  					{
   462  						FileSPDXIdentifier: "File-6a540784b0dc6d55",
   463  						FileName:           "tools/project-john/specifications/actionpack.gemspec",
   464  						Checksums: []spdx.Checksum{
   465  							{
   466  								Algorithm: spdx.SHA1,
   467  								Value:     "d2f9f9aed5161f6e4116a3f9573f41cd832f137c",
   468  							},
   469  						},
   470  					},
   471  					{
   472  						FileSPDXIdentifier: "File-fa42187221d0d0a8",
   473  						FileName:           "tools/project-doe/specifications/actionpack.gemspec",
   474  						Checksums: []spdx.Checksum{
   475  							{
   476  								Algorithm: spdx.SHA1,
   477  								Value:     "413f98442c83808042b5d1d2611a346b999bdca5",
   478  							},
   479  						},
   480  					},
   481  				},
   482  				Relationships: []*spdx.Relationship{
   483  					{
   484  						RefA:         spdx.DocElementID{ElementRefID: "DOCUMENT"},
   485  						RefB:         spdx.DocElementID{ElementRefID: "ContainerImage-413bfede37ad01fc"},
   486  						Relationship: "DESCRIBES",
   487  					},
   488  					{
   489  						RefA:         spdx.DocElementID{ElementRefID: "ContainerImage-413bfede37ad01fc"},
   490  						RefB:         spdx.DocElementID{ElementRefID: "OperatingSystem-197f9a00ebcb51f0"},
   491  						Relationship: "CONTAINS",
   492  					},
   493  					{
   494  						RefA:         spdx.DocElementID{ElementRefID: "OperatingSystem-197f9a00ebcb51f0"},
   495  						RefB:         spdx.DocElementID{ElementRefID: "Package-d8dccb186bafaf37"},
   496  						Relationship: "CONTAINS",
   497  					},
   498  					{
   499  						RefA:         spdx.DocElementID{ElementRefID: "ContainerImage-413bfede37ad01fc"},
   500  						RefB:         spdx.DocElementID{ElementRefID: "Application-441a648f2aeeee72"},
   501  						Relationship: "CONTAINS",
   502  					},
   503  					{
   504  						RefA:         spdx.DocElementID{ElementRefID: "Application-441a648f2aeeee72"},
   505  						RefB:         spdx.DocElementID{ElementRefID: "Package-d5443dbcbba0dbd4"},
   506  						Relationship: "CONTAINS",
   507  					},
   508  					{
   509  						RefA:         spdx.DocElementID{ElementRefID: "Package-d5443dbcbba0dbd4"},
   510  						RefB:         spdx.DocElementID{ElementRefID: "File-6a540784b0dc6d55"},
   511  						Relationship: "CONTAINS",
   512  					},
   513  					{
   514  						RefA:         spdx.DocElementID{ElementRefID: "Application-441a648f2aeeee72"},
   515  						RefB:         spdx.DocElementID{ElementRefID: "Package-13fe667a0805e6b7"},
   516  						Relationship: "CONTAINS",
   517  					},
   518  					{
   519  						RefA:         spdx.DocElementID{ElementRefID: "Package-13fe667a0805e6b7"},
   520  						RefB:         spdx.DocElementID{ElementRefID: "File-fa42187221d0d0a8"},
   521  						Relationship: "CONTAINS",
   522  					},
   523  				},
   524  
   525  				OtherLicenses: nil,
   526  				Annotations:   nil,
   527  				Reviews:       nil,
   528  			},
   529  		},
   530  		{
   531  			name: "happy path for fs scan",
   532  			inputReport: types.Report{
   533  				SchemaVersion: report.SchemaVersion,
   534  				ArtifactName:  "masahiro331/CVE-2021-41098",
   535  				ArtifactType:  ftypes.ArtifactFilesystem,
   536  				Results: types.Results{
   537  					{
   538  						Target: "Gemfile.lock",
   539  						Class:  types.ClassLangPkg,
   540  						Type:   ftypes.Bundler,
   541  						Packages: []ftypes.Package{
   542  							{
   543  								Name:    "actioncable",
   544  								Version: "6.1.4.1",
   545  							},
   546  						},
   547  					},
   548  				},
   549  			},
   550  			wantSBOM: &spdx.Document{
   551  				SPDXVersion:       spdx.Version,
   552  				DataLicense:       spdx.DataLicense,
   553  				SPDXIdentifier:    "DOCUMENT",
   554  				DocumentName:      "masahiro331/CVE-2021-41098",
   555  				DocumentNamespace: "http://aquasecurity.github.io/trivy/filesystem/masahiro331/CVE-2021-41098-3ff14136-e09f-4df9-80ea-000000000001",
   556  				CreationInfo: &spdx.CreationInfo{
   557  					Creators: []common.Creator{
   558  						{
   559  							Creator:     "aquasecurity",
   560  							CreatorType: "Organization",
   561  						},
   562  						{
   563  							Creator:     "trivy-0.38.1",
   564  							CreatorType: "Tool",
   565  						},
   566  					},
   567  					Created: "2021-08-25T12:20:30Z",
   568  				},
   569  				Packages: []*spdx.Package{
   570  					{
   571  						PackageSPDXIdentifier:   spdx.ElementID("Package-3da61e86d0530402"),
   572  						PackageDownloadLocation: "NONE",
   573  						PackageName:             "actioncable",
   574  						PackageVersion:          "6.1.4.1",
   575  						PackageLicenseConcluded: "NONE",
   576  						PackageLicenseDeclared:  "NONE",
   577  						PackageExternalReferences: []*spdx.PackageExternalReference{
   578  							{
   579  								Category: tspdx.CategoryPackageManager,
   580  								RefType:  tspdx.RefTypePurl,
   581  								Locator:  "pkg:gem/actioncable@6.1.4.1",
   582  							},
   583  						},
   584  						PrimaryPackagePurpose: tspdx.PackagePurposeLibrary,
   585  						PackageSupplier:       &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion},
   586  					},
   587  					{
   588  						PackageSPDXIdentifier:   spdx.ElementID("Application-9dd4a4ba7077cc5a"),
   589  						PackageDownloadLocation: "NONE",
   590  						PackageName:             "bundler",
   591  						PackageSourceInfo:       "Gemfile.lock",
   592  						PrimaryPackagePurpose:   tspdx.PackagePurposeApplication,
   593  					},
   594  					{
   595  						PackageSPDXIdentifier:   spdx.ElementID("Filesystem-5af0f1f08c20909a"),
   596  						PackageDownloadLocation: "NONE",
   597  						PackageName:             "masahiro331/CVE-2021-41098",
   598  						PackageAttributionTexts: []string{
   599  							"SchemaVersion: 2",
   600  						},
   601  						PrimaryPackagePurpose: tspdx.PackagePurposeSource,
   602  					},
   603  				},
   604  				Relationships: []*spdx.Relationship{
   605  					{
   606  						RefA:         spdx.DocElementID{ElementRefID: "DOCUMENT"},
   607  						RefB:         spdx.DocElementID{ElementRefID: "Filesystem-5af0f1f08c20909a"},
   608  						Relationship: "DESCRIBES",
   609  					},
   610  					{
   611  						RefA:         spdx.DocElementID{ElementRefID: "Filesystem-5af0f1f08c20909a"},
   612  						RefB:         spdx.DocElementID{ElementRefID: "Application-9dd4a4ba7077cc5a"},
   613  						Relationship: "CONTAINS",
   614  					},
   615  					{
   616  						RefA:         spdx.DocElementID{ElementRefID: "Application-9dd4a4ba7077cc5a"},
   617  						RefB:         spdx.DocElementID{ElementRefID: "Package-3da61e86d0530402"},
   618  						Relationship: "CONTAINS",
   619  					},
   620  				},
   621  			},
   622  		},
   623  		{
   624  			name: "happy path aggregate results",
   625  			inputReport: types.Report{
   626  				SchemaVersion: report.SchemaVersion,
   627  				ArtifactName:  "http://test-aggregate",
   628  				ArtifactType:  ftypes.ArtifactRepository,
   629  				Results: types.Results{
   630  					{
   631  						Target: "Node.js",
   632  						Class:  types.ClassLangPkg,
   633  						Type:   ftypes.NodePkg,
   634  						Packages: []ftypes.Package{
   635  							{
   636  								Name:     "ruby-typeprof",
   637  								Version:  "0.20.1",
   638  								Licenses: []string{"MIT"},
   639  								Layer: ftypes.Layer{
   640  									DiffID: "sha256:661c3fd3cc16b34c070f3620ca6b03b6adac150f9a7e5d0e3c707a159990f88e",
   641  								},
   642  								FilePath: "usr/local/lib/ruby/gems/3.1.0/gems/typeprof-0.21.1/vscode/package.json",
   643  							},
   644  						},
   645  					},
   646  				},
   647  			},
   648  			wantSBOM: &spdx.Document{
   649  				SPDXVersion:       spdx.Version,
   650  				DataLicense:       spdx.DataLicense,
   651  				SPDXIdentifier:    "DOCUMENT",
   652  				DocumentName:      "http://test-aggregate",
   653  				DocumentNamespace: "http://aquasecurity.github.io/trivy/repository/test-aggregate-3ff14136-e09f-4df9-80ea-000000000001",
   654  				CreationInfo: &spdx.CreationInfo{
   655  					Creators: []common.Creator{
   656  						{
   657  							Creator:     "aquasecurity",
   658  							CreatorType: "Organization",
   659  						},
   660  						{
   661  							Creator:     "trivy-0.38.1",
   662  							CreatorType: "Tool",
   663  						},
   664  					},
   665  					Created: "2021-08-25T12:20:30Z",
   666  				},
   667  				Packages: []*spdx.Package{
   668  					{
   669  						PackageName:             "http://test-aggregate",
   670  						PackageSPDXIdentifier:   "Repository-1a78857c1a6a759e",
   671  						PackageDownloadLocation: "git+http://test-aggregate",
   672  						PackageAttributionTexts: []string{
   673  							"SchemaVersion: 2",
   674  						},
   675  						PrimaryPackagePurpose: tspdx.PackagePurposeSource,
   676  					},
   677  					{
   678  						PackageSPDXIdentifier:   "Application-24f8a80152e2c0fc",
   679  						PackageDownloadLocation: "git+http://test-aggregate",
   680  						PackageName:             "node-pkg",
   681  						PackageSourceInfo:       "Node.js",
   682  						PrimaryPackagePurpose:   tspdx.PackagePurposeApplication,
   683  					},
   684  					{
   685  						PackageSPDXIdentifier:   spdx.ElementID("Package-daedb173cfd43058"),
   686  						PackageDownloadLocation: "git+http://test-aggregate",
   687  						PackageName:             "ruby-typeprof",
   688  						PackageVersion:          "0.20.1",
   689  						PackageLicenseConcluded: "MIT",
   690  						PackageLicenseDeclared:  "MIT",
   691  						PackageExternalReferences: []*spdx.PackageExternalReference{
   692  							{
   693  								Category: tspdx.CategoryPackageManager,
   694  								RefType:  tspdx.RefTypePurl,
   695  								Locator:  "pkg:npm/ruby-typeprof@0.20.1",
   696  							},
   697  						},
   698  						PackageAttributionTexts: []string{
   699  							"LayerDiffID: sha256:661c3fd3cc16b34c070f3620ca6b03b6adac150f9a7e5d0e3c707a159990f88e",
   700  						},
   701  						PrimaryPackagePurpose: tspdx.PackagePurposeLibrary,
   702  						PackageSupplier:       &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion},
   703  						FilesAnalyzed:         true,
   704  						PackageVerificationCode: &spdx.PackageVerificationCode{
   705  							Value: "da39a3ee5e6b4b0d3255bfef95601890afd80709",
   706  						},
   707  					},
   708  				},
   709  				Files: []*spdx.File{
   710  					{
   711  						FileName:           "usr/local/lib/ruby/gems/3.1.0/gems/typeprof-0.21.1/vscode/package.json",
   712  						FileSPDXIdentifier: "File-a52825a3e5bc6dfe",
   713  					},
   714  				},
   715  				Relationships: []*spdx.Relationship{
   716  					{
   717  						RefA:         spdx.DocElementID{ElementRefID: "DOCUMENT"},
   718  						RefB:         spdx.DocElementID{ElementRefID: "Repository-1a78857c1a6a759e"},
   719  						Relationship: "DESCRIBES",
   720  					},
   721  					{
   722  						RefA:         spdx.DocElementID{ElementRefID: "Repository-1a78857c1a6a759e"},
   723  						RefB:         spdx.DocElementID{ElementRefID: "Application-24f8a80152e2c0fc"},
   724  						Relationship: "CONTAINS",
   725  					},
   726  					{
   727  						RefA:         spdx.DocElementID{ElementRefID: "Application-24f8a80152e2c0fc"},
   728  						RefB:         spdx.DocElementID{ElementRefID: "Package-daedb173cfd43058"},
   729  						Relationship: "CONTAINS",
   730  					},
   731  					{
   732  						RefA:         spdx.DocElementID{ElementRefID: "Package-daedb173cfd43058"},
   733  						RefB:         spdx.DocElementID{ElementRefID: "File-a52825a3e5bc6dfe"},
   734  						Relationship: "CONTAINS",
   735  					},
   736  				},
   737  			},
   738  		},
   739  		{
   740  			name: "happy path empty",
   741  			inputReport: types.Report{
   742  				SchemaVersion: report.SchemaVersion,
   743  				ArtifactName:  "empty/path",
   744  				ArtifactType:  ftypes.ArtifactFilesystem,
   745  				Results:       types.Results{},
   746  			},
   747  			wantSBOM: &spdx.Document{
   748  				SPDXVersion:       spdx.Version,
   749  				DataLicense:       spdx.DataLicense,
   750  				SPDXIdentifier:    "DOCUMENT",
   751  				DocumentName:      "empty/path",
   752  				DocumentNamespace: "http://aquasecurity.github.io/trivy/filesystem/empty/path-3ff14136-e09f-4df9-80ea-000000000001",
   753  
   754  				CreationInfo: &spdx.CreationInfo{
   755  					Creators: []common.Creator{
   756  						{
   757  							Creator:     "aquasecurity",
   758  							CreatorType: "Organization",
   759  						},
   760  						{
   761  							Creator:     "trivy-0.38.1",
   762  							CreatorType: "Tool",
   763  						},
   764  					},
   765  					Created: "2021-08-25T12:20:30Z",
   766  				},
   767  				Packages: []*spdx.Package{
   768  					{
   769  						PackageName:             "empty/path",
   770  						PackageSPDXIdentifier:   "Filesystem-70f34983067dba86",
   771  						PackageDownloadLocation: "NONE",
   772  						PackageAttributionTexts: []string{
   773  							"SchemaVersion: 2",
   774  						},
   775  						PrimaryPackagePurpose: tspdx.PackagePurposeSource,
   776  					},
   777  				},
   778  				Relationships: []*spdx.Relationship{
   779  					{
   780  						RefA:         spdx.DocElementID{ElementRefID: "DOCUMENT"},
   781  						RefB:         spdx.DocElementID{ElementRefID: "Filesystem-70f34983067dba86"},
   782  						Relationship: "DESCRIBES",
   783  					},
   784  				},
   785  			},
   786  		},
   787  		{
   788  			name: "happy path secret",
   789  			inputReport: types.Report{
   790  				SchemaVersion: report.SchemaVersion,
   791  				ArtifactName:  "secret",
   792  				ArtifactType:  ftypes.ArtifactFilesystem,
   793  				Results: types.Results{
   794  					{
   795  						Target: "key.pem",
   796  						Class:  types.ClassSecret,
   797  						Secrets: []ftypes.SecretFinding{
   798  							{
   799  								RuleID:    "private-key",
   800  								Category:  "AsymmetricPrivateKey",
   801  								Severity:  "HIGH",
   802  								Title:     "Asymmetric Private Key",
   803  								StartLine: 1,
   804  								EndLine:   1,
   805  							},
   806  						},
   807  					},
   808  				},
   809  			},
   810  			wantSBOM: &spdx.Document{
   811  				SPDXVersion:       spdx.Version,
   812  				DataLicense:       spdx.DataLicense,
   813  				SPDXIdentifier:    "DOCUMENT",
   814  				DocumentName:      "secret",
   815  				DocumentNamespace: "http://aquasecurity.github.io/trivy/filesystem/secret-3ff14136-e09f-4df9-80ea-000000000001",
   816  
   817  				CreationInfo: &spdx.CreationInfo{
   818  					Creators: []common.Creator{
   819  						{
   820  							Creator:     "aquasecurity",
   821  							CreatorType: "Organization",
   822  						},
   823  						{
   824  							Creator:     "trivy-0.38.1",
   825  							CreatorType: "Tool",
   826  						},
   827  					},
   828  					Created: "2021-08-25T12:20:30Z",
   829  				},
   830  				Packages: []*spdx.Package{
   831  					{
   832  						PackageName:             "secret",
   833  						PackageSPDXIdentifier:   "Filesystem-5c08d34162a2c5d3",
   834  						PackageDownloadLocation: "NONE",
   835  						PackageAttributionTexts: []string{
   836  							"SchemaVersion: 2",
   837  						},
   838  						PrimaryPackagePurpose: tspdx.PackagePurposeSource,
   839  					},
   840  				},
   841  				Relationships: []*spdx.Relationship{
   842  					{
   843  						RefA:         spdx.DocElementID{ElementRefID: "DOCUMENT"},
   844  						RefB:         spdx.DocElementID{ElementRefID: "Filesystem-5c08d34162a2c5d3"},
   845  						Relationship: "DESCRIBES",
   846  					},
   847  				},
   848  			},
   849  		},
   850  		{
   851  			name: "go library local",
   852  			inputReport: types.Report{
   853  				SchemaVersion: report.SchemaVersion,
   854  				ArtifactName:  "go-artifact",
   855  				ArtifactType:  ftypes.ArtifactFilesystem,
   856  				Results: types.Results{
   857  					{
   858  						Target: "artifact",
   859  						Class:  types.ClassLangPkg,
   860  						Type:   ftypes.GoBinary,
   861  						Packages: []ftypes.Package{
   862  							{
   863  								Name:    "./private_repos/cnrm.googlesource.com/cnrm/",
   864  								Version: "(devel)",
   865  							},
   866  							{
   867  								Name:    "golang.org/x/crypto",
   868  								Version: "v0.0.1",
   869  							},
   870  						},
   871  					},
   872  				},
   873  			},
   874  			wantSBOM: &spdx.Document{
   875  				SPDXVersion:       spdx.Version,
   876  				DataLicense:       spdx.DataLicense,
   877  				SPDXIdentifier:    "DOCUMENT",
   878  				DocumentName:      "go-artifact",
   879  				DocumentNamespace: "http://aquasecurity.github.io/trivy/filesystem/go-artifact-3ff14136-e09f-4df9-80ea-000000000001",
   880  				CreationInfo: &spdx.CreationInfo{
   881  					Creators: []common.Creator{
   882  						{
   883  							Creator:     "aquasecurity",
   884  							CreatorType: "Organization",
   885  						},
   886  						{
   887  							Creator:     "trivy-0.38.1",
   888  							CreatorType: "Tool",
   889  						},
   890  					},
   891  					Created: "2021-08-25T12:20:30Z",
   892  				},
   893  				Packages: []*spdx.Package{
   894  					{
   895  						PackageSPDXIdentifier:   spdx.ElementID("Package-9164ae38c5cdf815"),
   896  						PackageDownloadLocation: "NONE",
   897  						PackageName:             "./private_repos/cnrm.googlesource.com/cnrm/",
   898  						PackageVersion:          "(devel)",
   899  						PackageLicenseConcluded: "NONE",
   900  						PackageLicenseDeclared:  "NONE",
   901  						PrimaryPackagePurpose:   tspdx.PackagePurposeLibrary,
   902  						PackageSupplier:         &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion},
   903  					},
   904  					{
   905  						PackageName:             "go-artifact",
   906  						PackageSPDXIdentifier:   "Filesystem-e340f27468b382be",
   907  						PackageDownloadLocation: "NONE",
   908  						PackageAttributionTexts: []string{
   909  							"SchemaVersion: 2",
   910  						},
   911  						PrimaryPackagePurpose: tspdx.PackagePurposeSource,
   912  					},
   913  					{
   914  						PackageSPDXIdentifier:   spdx.ElementID("Application-6666b83a5d554671"),
   915  						PackageDownloadLocation: "NONE",
   916  						PackageName:             "gobinary",
   917  						PackageSourceInfo:       "artifact",
   918  						PrimaryPackagePurpose:   tspdx.PackagePurposeApplication,
   919  					},
   920  					{
   921  						PackageSPDXIdentifier:   spdx.ElementID("Package-8451f2bc8e1f45aa"),
   922  						PackageDownloadLocation: "NONE",
   923  						PackageName:             "golang.org/x/crypto",
   924  						PackageVersion:          "v0.0.1",
   925  						PackageLicenseConcluded: "NONE",
   926  						PackageLicenseDeclared:  "NONE",
   927  						PackageExternalReferences: []*spdx.PackageExternalReference{
   928  							{
   929  								Category: tspdx.CategoryPackageManager,
   930  								RefType:  tspdx.RefTypePurl,
   931  								Locator:  "pkg:golang/golang.org/x/crypto@v0.0.1",
   932  							},
   933  						},
   934  						PrimaryPackagePurpose: tspdx.PackagePurposeLibrary,
   935  						PackageSupplier:       &spdx.Supplier{Supplier: tspdx.PackageSupplierNoAssertion},
   936  					},
   937  				},
   938  				Relationships: []*spdx.Relationship{
   939  					{
   940  						RefA:         spdx.DocElementID{ElementRefID: "DOCUMENT"},
   941  						RefB:         spdx.DocElementID{ElementRefID: "Filesystem-e340f27468b382be"},
   942  						Relationship: "DESCRIBES",
   943  					},
   944  					{
   945  						RefA:         spdx.DocElementID{ElementRefID: "Filesystem-e340f27468b382be"},
   946  						RefB:         spdx.DocElementID{ElementRefID: "Application-6666b83a5d554671"},
   947  						Relationship: "CONTAINS",
   948  					},
   949  					{
   950  						RefA:         spdx.DocElementID{ElementRefID: "Application-6666b83a5d554671"},
   951  						RefB:         spdx.DocElementID{ElementRefID: "Package-9164ae38c5cdf815"},
   952  						Relationship: "CONTAINS",
   953  					},
   954  					{
   955  						RefA:         spdx.DocElementID{ElementRefID: "Application-6666b83a5d554671"},
   956  						RefB:         spdx.DocElementID{ElementRefID: "Package-8451f2bc8e1f45aa"},
   957  						Relationship: "CONTAINS",
   958  					},
   959  				},
   960  			},
   961  		},
   962  	}
   963  
   964  	for _, tc := range testCases {
   965  		t.Run(tc.name, func(t *testing.T) {
   966  			// Fake function calculating the hash value
   967  			h := fnv.New64()
   968  			hasher := func(v interface{}, format hashstructure.Format, opts *hashstructure.HashOptions) (uint64, error) {
   969  				h.Reset()
   970  
   971  				var str string
   972  				switch v.(type) {
   973  				case ftypes.Package:
   974  					str = v.(ftypes.Package).Name + v.(ftypes.Package).FilePath
   975  				case string:
   976  					str = v.(string)
   977  				case *ftypes.OS:
   978  					str = v.(*ftypes.OS).Name
   979  				default:
   980  					require.Failf(t, "unknown type", "%T", v)
   981  				}
   982  
   983  				if _, err := h.Write([]byte(str)); err != nil {
   984  					return 0, err
   985  				}
   986  
   987  				return h.Sum64(), nil
   988  			}
   989  
   990  			clock.SetFakeTime(t, time.Date(2021, 8, 25, 12, 20, 30, 5, time.UTC))
   991  			uuid.SetFakeUUID(t, "3ff14136-e09f-4df9-80ea-%012d")
   992  
   993  			marshaler := tspdx.NewMarshaler("0.38.1", tspdx.WithHasher(hasher))
   994  			spdxDoc, err := marshaler.Marshal(tc.inputReport)
   995  			require.NoError(t, err)
   996  
   997  			assert.Equal(t, tc.wantSBOM, spdxDoc)
   998  		})
   999  	}
  1000  }
  1001  
  1002  func Test_GetLicense(t *testing.T) {
  1003  	tests := []struct {
  1004  		name  string
  1005  		input ftypes.Package
  1006  		want  string
  1007  	}{
  1008  		{
  1009  			name: "happy path",
  1010  			input: ftypes.Package{
  1011  				Licenses: []string{
  1012  					"GPLv2+",
  1013  				},
  1014  			},
  1015  			want: "GPL-2.0-or-later",
  1016  		},
  1017  		{
  1018  			name: "happy path with multi license",
  1019  			input: ftypes.Package{
  1020  				Licenses: []string{
  1021  					"GPLv2+",
  1022  					"GPLv3+",
  1023  				},
  1024  			},
  1025  			want: "GPL-2.0-or-later AND GPL-3.0-or-later",
  1026  		},
  1027  		{
  1028  			name: "happy path with OR operator",
  1029  			input: ftypes.Package{
  1030  				Licenses: []string{
  1031  					"GPLv2+",
  1032  					"LGPL 2.0 or GNU LESSER",
  1033  				},
  1034  			},
  1035  			want: "GPL-2.0-or-later AND (LGPL-2.0-only OR LGPL-3.0-only)",
  1036  		},
  1037  		{
  1038  			name: "happy path with AND operator",
  1039  			input: ftypes.Package{
  1040  				Licenses: []string{
  1041  					"GPLv2+",
  1042  					"LGPL 2.0 and GNU LESSER",
  1043  				},
  1044  			},
  1045  			want: "GPL-2.0-or-later AND LGPL-2.0-only AND LGPL-3.0-only",
  1046  		},
  1047  		{
  1048  			name: "happy path with WITH operator",
  1049  			input: ftypes.Package{
  1050  				Licenses: []string{
  1051  					"AFL 2.0",
  1052  					"AFL 3.0 with distribution exception",
  1053  				},
  1054  			},
  1055  			want: "AFL-2.0 AND AFL-3.0 WITH distribution-exception",
  1056  		},
  1057  	}
  1058  	for _, tt := range tests {
  1059  		t.Run(tt.name, func(t *testing.T) {
  1060  			assert.Equalf(t, tt.want, tspdx.GetLicense(tt.input), "getLicense(%v)", tt.input)
  1061  		})
  1062  	}
  1063  }