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 }