From 8fcef352b37622c6ac1b9106a3869a8d2d1bc0ea Mon Sep 17 00:00:00 2001 From: Teppei Fukuda <knqyf263@gmail.com> Date: Tue, 12 Mar 2024 10:56:10 +0400 Subject: [PATCH] refactor(sbom): add intermediate representation for BOM (#6240) Signed-off-by: knqyf263 <knqyf263@gmail.com> Co-authored-by: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> --- go.mod | 2 +- .../testdata/conda-cyclonedx.json.golden | 18 +- .../testdata/fixtures/sbom/minikube-kbom.json | 12 +- ...fluentd-multiple-lockfiles.cdx.json.golden | 370 +- .../fluentd-multiple-lockfiles.json.golden | 3 + .../testdata/minikube-kbom.json.golden | 1 + .../testdata/pom-cyclonedx.json.golden | 12 +- magefiles/magefile.go | 14 - pkg/dependency/id.go | 32 + pkg/dependency/id_test.go | 73 + pkg/dependency/parser/c/conan/parse.go | 7 +- pkg/dependency/parser/c/conan/parse_test.go | 2 +- pkg/dependency/parser/conda/meta/parse.go | 2 +- .../parser/conda/meta/parse_test.go | 2 +- pkg/dependency/parser/dart/pub/parse.go | 12 +- pkg/dependency/parser/dart/pub/parse_test.go | 2 +- .../parser/dotnet/core_deps/parse.go | 2 +- .../parser/dotnet/core_deps/parse_test.go | 2 +- .../parser/frameworks/wordpress/parse.go | 2 +- .../parser/frameworks/wordpress/parse_test.go | 2 +- pkg/dependency/parser/golang/binary/parse.go | 2 +- .../parser/golang/binary/parse_test.go | 2 +- pkg/dependency/parser/golang/mod/parse.go | 16 +- .../parser/golang/mod/parse_test.go | 36 +- .../parser/golang/mod/parse_testcase.go | 2 +- pkg/dependency/parser/golang/sum/parse.go | 7 +- .../parser/golang/sum/parse_test.go | 2 +- .../parser/golang/sum/parse_testcase.go | 2 +- .../parser/gradle/lockfile/parse.go | 7 +- .../parser/gradle/lockfile/parse_test.go | 2 +- pkg/dependency/parser/hex/mix/parse.go | 18 +- pkg/dependency/parser/hex/mix/parse_test.go | 2 +- pkg/dependency/parser/java/jar/parse.go | 2 +- pkg/dependency/parser/java/jar/parse_test.go | 2 +- pkg/dependency/parser/java/jar/types.go | 2 +- pkg/dependency/parser/java/pom/artifact.go | 2 +- pkg/dependency/parser/java/pom/parse.go | 8 +- pkg/dependency/parser/java/pom/parse_test.go | 2 +- pkg/dependency/parser/java/pom/pom.go | 2 +- pkg/dependency/parser/java/pom/utils.go | 5 - pkg/dependency/parser/julia/manifest/parse.go | 2 +- .../parser/julia/manifest/parse_test.go | 2 +- .../parser/julia/manifest/parse_testcase.go | 2 +- pkg/dependency/parser/nodejs/npm/parse.go | 42 +- .../parser/nodejs/npm/parse_test.go | 2 +- .../parser/nodejs/npm/parse_testcase.go | 1546 ++++++++- .../parser/nodejs/packagejson/parse.go | 7 +- .../parser/nodejs/packagejson/parse_test.go | 2 +- pkg/dependency/parser/nodejs/pnpm/parse.go | 16 +- .../parser/nodejs/pnpm/parse_test.go | 2 +- .../parser/nodejs/pnpm/parse_testcase.go | 2 +- pkg/dependency/parser/nodejs/yarn/parse.go | 15 +- .../parser/nodejs/yarn/parse_test.go | 2 +- .../parser/nodejs/yarn/parse_testcase.go | 2 +- pkg/dependency/parser/nuget/config/parse.go | 2 +- .../parser/nuget/config/parse_test.go | 2 +- pkg/dependency/parser/nuget/lock/parse.go | 12 +- .../parser/nuget/lock/parse_test.go | 2 +- .../parser/nuget/lock/parse_testcase.go | 2 +- .../parser/nuget/packagesprops/parse.go | 6 +- .../parser/nuget/packagesprops/parse_test.go | 2 +- pkg/dependency/parser/php/composer/parse.go | 7 +- .../parser/php/composer/parse_test.go | 2 +- .../parser/python/packaging/parse.go | 2 +- .../parser/python/packaging/parse_test.go | 2 +- pkg/dependency/parser/python/pip/parse.go | 2 +- .../parser/python/pip/parse_test.go | 2 +- .../parser/python/pip/parse_testcase.go | 2 +- pkg/dependency/parser/python/pipenv/parse.go | 13 +- .../parser/python/pipenv/parse_test.go | 2 +- .../parser/python/pipenv/parse_testcase.go | 2 +- pkg/dependency/parser/python/poetry/parse.go | 13 +- .../parser/python/poetry/parse_test.go | 2 +- .../parser/python/poetry/parse_testcase.go | 2 +- pkg/dependency/parser/ruby/bundler/parse.go | 28 +- .../parser/ruby/bundler/parse_test.go | 2 +- pkg/dependency/parser/ruby/gemspec/parse.go | 5 +- .../parser/ruby/gemspec/parse_test.go | 2 +- pkg/dependency/parser/rust/binary/parse.go | 13 +- .../parser/rust/binary/parse_test.go | 2 +- .../parser/rust/cargo/naive_pkg_parser.go | 4 +- pkg/dependency/parser/rust/cargo/parse.go | 22 +- .../parser/rust/cargo/parse_test.go | 2 +- .../parser/swift/cocoapods/parse.go | 12 +- .../parser/swift/cocoapods/parse_test.go | 2 +- pkg/dependency/parser/swift/swift/parse.go | 7 +- .../parser/swift/swift/parse_test.go | 2 +- pkg/dependency/parser/utils/utils.go | 6 +- pkg/dependency/parser/utils/utils_test.go | 2 +- pkg/dependency/{parser => }/types/types.go | 0 pkg/fanal/analyzer/language/analyze.go | 2 +- pkg/fanal/analyzer/language/analyze_test.go | 4 +- .../analyzer/language/dart/pub/pubspec.go | 6 +- .../analyzer/language/dotnet/nuget/nuget.go | 2 +- pkg/fanal/analyzer/language/golang/mod/mod.go | 2 +- pkg/fanal/analyzer/language/nodejs/npm/npm.go | 2 +- pkg/fanal/analyzer/language/nodejs/pkg/pkg.go | 2 +- .../analyzer/language/nodejs/yarn/yarn.go | 2 +- .../language/php/composer/composer.go | 2 +- .../language/python/packaging/packaging.go | 2 +- .../analyzer/language/python/poetry/poetry.go | 2 +- .../analyzer/language/rust/cargo/cargo.go | 2 +- pkg/fanal/analyzer/sbom/sbom_test.go | 2 + pkg/fanal/artifact/image/remote_sbom_test.go | 19 +- pkg/fanal/artifact/sbom/sbom.go | 5 +- pkg/fanal/artifact/sbom/sbom_test.go | 37 +- .../handler/unpackaged/unpackaged_test.go | 1 + pkg/fanal/types/artifact.go | 3 +- pkg/fanal/types/sbom.go | 43 - pkg/k8s/report/cyclonedx.go | 15 +- pkg/k8s/report/report.go | 13 +- pkg/k8s/scanner/scanner.go | 278 +- pkg/k8s/scanner/scanner_test.go | 408 ++- pkg/k8s/writer.go | 2 +- pkg/module/module.go | 5 +- pkg/module/serialize/types.go | 10 - pkg/module/serialize/types_easyjson.go | 3024 ----------------- pkg/module/wasm/sdk.go | 23 +- pkg/purl/purl.go | 123 +- pkg/purl/purl_test.go | 432 +-- pkg/rekortest/server.go | 2 +- pkg/report/cyclonedx/cyclonedx.go | 4 +- pkg/report/github/github.go | 2 +- pkg/sbom/core/bom.go | 290 ++ pkg/sbom/cyclonedx/core/cyclonedx.go | 513 --- pkg/sbom/cyclonedx/core/cyclonedx_test.go | 380 --- pkg/sbom/cyclonedx/marshal.go | 726 ++-- pkg/sbom/cyclonedx/marshal_test.go | 256 +- .../testdata/{sad => happy}/invalid-purl.json | 0 pkg/sbom/cyclonedx/testdata/happy/kbom.json | 12 +- .../testdata/sad/invalid-serial.json | 6 + pkg/sbom/cyclonedx/unmarshal.go | 529 +-- pkg/sbom/cyclonedx/unmarshal_test.go | 179 +- pkg/sbom/io/decode.go | 336 ++ pkg/sbom/io/encode.go | 343 ++ pkg/sbom/io/encode_test.go | 339 ++ pkg/sbom/sbom.go | 29 +- pkg/sbom/spdx/marshal.go | 2 +- pkg/sbom/spdx/unmarshal.go | 8 +- pkg/sbom/spdx/unmarshal_test.go | 18 +- pkg/scanner/scan.go | 4 +- pkg/types/report.go | 5 +- pkg/types/sbom.go | 19 +- pkg/uuid/uuid.go | 12 +- pkg/uuid/uuid_tinygo.go | 13 + pkg/vex/cyclonedx.go | 8 +- pkg/vex/vex.go | 5 +- pkg/vex/vex_test.go | 7 +- 148 files changed, 4910 insertions(+), 6141 deletions(-) create mode 100644 pkg/dependency/id.go create mode 100644 pkg/dependency/id_test.go rename pkg/dependency/{parser => }/types/types.go (100%) delete mode 100644 pkg/fanal/types/sbom.go delete mode 100644 pkg/module/serialize/types_easyjson.go create mode 100644 pkg/sbom/core/bom.go delete mode 100644 pkg/sbom/cyclonedx/core/cyclonedx.go delete mode 100644 pkg/sbom/cyclonedx/core/cyclonedx_test.go rename pkg/sbom/cyclonedx/testdata/{sad => happy}/invalid-purl.json (100%) create mode 100644 pkg/sbom/cyclonedx/testdata/sad/invalid-serial.json create mode 100644 pkg/sbom/io/decode.go create mode 100644 pkg/sbom/io/encode.go create mode 100644 pkg/sbom/io/encode_test.go create mode 100644 pkg/uuid/uuid_tinygo.go diff --git a/go.mod b/go.mod index aeea13fa7d2d..ce787a0c2565 100644 --- a/go.mod +++ b/go.mod @@ -66,7 +66,7 @@ require ( github.com/kylelemons/godebug v1.1.0 github.com/liamg/jfather v0.0.7 github.com/magefile/mage v1.15.0 - github.com/mailru/easyjson v0.7.7 + github.com/mailru/easyjson v0.7.7 // indirect github.com/masahiro331/go-disk v0.0.0-20220919035250-c8da316f91ac github.com/masahiro331/go-ebs-file v0.0.0-20240112135404-d5fbb1d46323 github.com/masahiro331/go-ext4-filesystem v0.0.0-20231208112839-4339555a0cd4 diff --git a/integration/testdata/conda-cyclonedx.json.golden b/integration/testdata/conda-cyclonedx.json.golden index 30e0321b52a5..9640112cce12 100644 --- a/integration/testdata/conda-cyclonedx.json.golden +++ b/integration/testdata/conda-cyclonedx.json.golden @@ -2,7 +2,7 @@ "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.5", - "serialNumber": "urn:uuid:3ff14136-e09f-4df9-80ea-000000000001", + "serialNumber": "urn:uuid:3ff14136-e09f-4df9-80ea-000000000004", "version": 1, "metadata": { "timestamp": "2021-08-25T12:20:30+00:00", @@ -17,7 +17,7 @@ ] }, "component": { - "bom-ref": "3ff14136-e09f-4df9-80ea-000000000002", + "bom-ref": "3ff14136-e09f-4df9-80ea-000000000001", "type": "application", "name": "testdata/fixtures/repo/conda", "properties": [ @@ -30,7 +30,7 @@ }, "components": [ { - "bom-ref": "pkg:conda/openssl@1.1.1q?file_path=miniconda3%2Fenvs%2Ftestenv%2Fconda-meta%2Fopenssl-1.1.1q-h7f8727e_0.json", + "bom-ref": "pkg:conda/openssl@1.1.1q", "type": "library", "name": "openssl", "version": "1.1.1q", @@ -54,7 +54,7 @@ ] }, { - "bom-ref": "pkg:conda/pip@22.2.2?file_path=miniconda3%2Fenvs%2Ftestenv%2Fconda-meta%2Fpip-22.2.2-py38h06a4308_0.json", + "bom-ref": "pkg:conda/pip@22.2.2", "type": "library", "name": "pip", "version": "22.2.2", @@ -80,18 +80,18 @@ ], "dependencies": [ { - "ref": "3ff14136-e09f-4df9-80ea-000000000002", + "ref": "3ff14136-e09f-4df9-80ea-000000000001", "dependsOn": [ - "pkg:conda/openssl@1.1.1q?file_path=miniconda3%2Fenvs%2Ftestenv%2Fconda-meta%2Fopenssl-1.1.1q-h7f8727e_0.json", - "pkg:conda/pip@22.2.2?file_path=miniconda3%2Fenvs%2Ftestenv%2Fconda-meta%2Fpip-22.2.2-py38h06a4308_0.json" + "pkg:conda/openssl@1.1.1q", + "pkg:conda/pip@22.2.2" ] }, { - "ref": "pkg:conda/openssl@1.1.1q?file_path=miniconda3%2Fenvs%2Ftestenv%2Fconda-meta%2Fopenssl-1.1.1q-h7f8727e_0.json", + "ref": "pkg:conda/openssl@1.1.1q", "dependsOn": [] }, { - "ref": "pkg:conda/pip@22.2.2?file_path=miniconda3%2Fenvs%2Ftestenv%2Fconda-meta%2Fpip-22.2.2-py38h06a4308_0.json", + "ref": "pkg:conda/pip@22.2.2", "dependsOn": [] } ], diff --git a/integration/testdata/fixtures/sbom/minikube-kbom.json b/integration/testdata/fixtures/sbom/minikube-kbom.json index e63c5733a816..c1ee53d6c9c8 100644 --- a/integration/testdata/fixtures/sbom/minikube-kbom.json +++ b/integration/testdata/fixtures/sbom/minikube-kbom.json @@ -51,17 +51,7 @@ { "bom-ref": "a62abb1f-cb38-4fde-90f3-2bda3b87ddb2", "type": "application", - "name": "node-core-components", - "properties": [ - { - "name": "aquasecurity:trivy:Class", - "value": "lang-pkgs" - }, - { - "name": "aquasecurity:trivy:Type", - "value": "golang" - } - ] + "name": "node-core-components" }, { "bom-ref": "a6350ac3-52f6-4c5f-a3e3-184b9a634bef", diff --git a/integration/testdata/fluentd-multiple-lockfiles.cdx.json.golden b/integration/testdata/fluentd-multiple-lockfiles.cdx.json.golden index aab59a6cf47f..40fdceb532c5 100644 --- a/integration/testdata/fluentd-multiple-lockfiles.cdx.json.golden +++ b/integration/testdata/fluentd-multiple-lockfiles.cdx.json.golden @@ -2,7 +2,7 @@ "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.5", - "serialNumber": "urn:uuid:3ff14136-e09f-4df9-80ea-000000000001", + "serialNumber": "urn:uuid:3ff14136-e09f-4df9-80ea-000000000163", "version": 1, "metadata": { "timestamp": "2021-08-25T12:20:30+00:00", @@ -17,13 +17,33 @@ ] }, "component": { - "bom-ref": "3ff14136-e09f-4df9-80ea-000000000002", + "bom-ref": "3ff14136-e09f-4df9-80ea-000000000001", "type": "container", "name": "testdata/fixtures/images/fluentd-multiple-lockfiles.tar.gz", "properties": [ { "name": "aquasecurity:trivy:DiffID", - "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f,sha256:02874b2b269dea8dde0f7edb4c9906904dfe38a09de1a214f20c650cfb15c60e,sha256:3752e1f6fd759c795c13aff2c93c081529366e27635ba6621e849b0f9cfc77f0,sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9,sha256:788c00e2cfc8f2a018ae4344ccf0b2c226ebd756d7effd1ce50eea1a4252cd89,sha256:25165eb51d15842f870f97873e0a58409d5e860e6108e3dd829bd10e484c0065" + "value": "sha256:02874b2b269dea8dde0f7edb4c9906904dfe38a09de1a214f20c650cfb15c60e" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:25165eb51d15842f870f97873e0a58409d5e860e6108e3dd829bd10e484c0065" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:3752e1f6fd759c795c13aff2c93c081529366e27635ba6621e849b0f9cfc77f0" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:75e43d55939745950bc3f8fad56c5834617c4339f0f54755e69a0dd5372624e9" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:788c00e2cfc8f2a018ae4344ccf0b2c226ebd756d7effd1ce50eea1a4252cd89" + }, + { + "name": "aquasecurity:trivy:DiffID", + "value": "sha256:831c5620387fb9efec59fc82a42b948546c6be601e3ab34a87108ecf852aa15f" }, { "name": "aquasecurity:trivy:ImageID", @@ -38,7 +58,7 @@ }, "components": [ { - "bom-ref": "3ff14136-e09f-4df9-80ea-000000000003", + "bom-ref": "3ff14136-e09f-4df9-80ea-000000000002", "type": "operating-system", "name": "debian", "version": "10.2", @@ -5715,7 +5735,7 @@ ] }, { - "bom-ref": "pkg:gem/activesupport@6.0.2.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Factivesupport-6.0.2.1.gemspec", + "bom-ref": "pkg:gem/activesupport@6.0.2.1", "type": "library", "name": "activesupport", "version": "6.0.2.1", @@ -5747,7 +5767,7 @@ ] }, { - "bom-ref": "pkg:gem/addressable@2.7.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Faddressable-2.7.0.gemspec", + "bom-ref": "pkg:gem/addressable@2.7.0", "type": "library", "name": "addressable", "version": "2.7.0", @@ -5779,7 +5799,7 @@ ] }, { - "bom-ref": "pkg:gem/concurrent-ruby@1.1.6?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fconcurrent-ruby-1.1.6.gemspec", + "bom-ref": "pkg:gem/concurrent-ruby@1.1.6", "type": "library", "name": "concurrent-ruby", "version": "1.1.6", @@ -5811,7 +5831,7 @@ ] }, { - "bom-ref": "pkg:gem/cool.io@1.6.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fcool.io-1.6.0.gemspec", + "bom-ref": "pkg:gem/cool.io@1.6.0", "type": "library", "name": "cool.io", "version": "1.6.0", @@ -5836,7 +5856,7 @@ ] }, { - "bom-ref": "pkg:gem/dig_rb@1.0.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fdig_rb-1.0.1.gemspec", + "bom-ref": "pkg:gem/dig_rb@1.0.1", "type": "library", "name": "dig_rb", "version": "1.0.1", @@ -5868,7 +5888,7 @@ ] }, { - "bom-ref": "pkg:gem/domain_name@0.5.20190701?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fdomain_name-0.5.20190701.gemspec", + "bom-ref": "pkg:gem/domain_name@0.5.20190701", "type": "library", "name": "domain_name", "version": "0.5.20190701", @@ -5910,7 +5930,7 @@ ] }, { - "bom-ref": "pkg:gem/elasticsearch-api@7.5.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Felasticsearch-api-7.5.0.gemspec", + "bom-ref": "pkg:gem/elasticsearch-api@7.5.0", "type": "library", "name": "elasticsearch-api", "version": "7.5.0", @@ -5942,7 +5962,7 @@ ] }, { - "bom-ref": "pkg:gem/elasticsearch-transport@7.5.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Felasticsearch-transport-7.5.0.gemspec", + "bom-ref": "pkg:gem/elasticsearch-transport@7.5.0", "type": "library", "name": "elasticsearch-transport", "version": "7.5.0", @@ -5974,7 +5994,7 @@ ] }, { - "bom-ref": "pkg:gem/elasticsearch@7.5.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Felasticsearch-7.5.0.gemspec", + "bom-ref": "pkg:gem/elasticsearch@7.5.0", "type": "library", "name": "elasticsearch", "version": "7.5.0", @@ -6006,7 +6026,7 @@ ] }, { - "bom-ref": "pkg:gem/excon@0.72.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fexcon-0.72.0.gemspec", + "bom-ref": "pkg:gem/excon@0.72.0", "type": "library", "name": "excon", "version": "0.72.0", @@ -6038,7 +6058,7 @@ ] }, { - "bom-ref": "pkg:gem/faraday@0.17.3?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Ffaraday-0.17.3.gemspec", + "bom-ref": "pkg:gem/faraday@0.17.3", "type": "library", "name": "faraday", "version": "0.17.3", @@ -6070,7 +6090,7 @@ ] }, { - "bom-ref": "pkg:gem/ffi-compiler@1.0.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fffi-compiler-1.0.1.gemspec", + "bom-ref": "pkg:gem/ffi-compiler@1.0.1", "type": "library", "name": "ffi-compiler", "version": "1.0.1", @@ -6102,7 +6122,7 @@ ] }, { - "bom-ref": "pkg:gem/ffi@1.12.2?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fffi-1.12.2.gemspec", + "bom-ref": "pkg:gem/ffi@1.12.2", "type": "library", "name": "ffi", "version": "1.12.2", @@ -6134,7 +6154,7 @@ ] }, { - "bom-ref": "pkg:gem/fluent-plugin-concat@2.4.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Ffluent-plugin-concat-2.4.0.gemspec", + "bom-ref": "pkg:gem/fluent-plugin-concat@2.4.0", "type": "library", "name": "fluent-plugin-concat", "version": "2.4.0", @@ -6166,7 +6186,7 @@ ] }, { - "bom-ref": "pkg:gem/fluent-plugin-detect-exceptions@0.0.13?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Ffluent-plugin-detect-exceptions-0.0.13.gemspec", + "bom-ref": "pkg:gem/fluent-plugin-detect-exceptions@0.0.13", "type": "library", "name": "fluent-plugin-detect-exceptions", "version": "0.0.13", @@ -6198,7 +6218,7 @@ ] }, { - "bom-ref": "pkg:gem/fluent-plugin-elasticsearch@3.8.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Ffluent-plugin-elasticsearch-3.8.0.gemspec", + "bom-ref": "pkg:gem/fluent-plugin-elasticsearch@3.8.0", "type": "library", "name": "fluent-plugin-elasticsearch", "version": "3.8.0", @@ -6230,7 +6250,7 @@ ] }, { - "bom-ref": "pkg:gem/fluent-plugin-kubernetes_metadata_filter@2.4.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Ffluent-plugin-kubernetes_metadata_filter-2.4.1.gemspec", + "bom-ref": "pkg:gem/fluent-plugin-kubernetes_metadata_filter@2.4.1", "type": "library", "name": "fluent-plugin-kubernetes_metadata_filter", "version": "2.4.1", @@ -6262,7 +6282,7 @@ ] }, { - "bom-ref": "pkg:gem/fluent-plugin-multi-format-parser@1.0.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Ffluent-plugin-multi-format-parser-1.0.0.gemspec", + "bom-ref": "pkg:gem/fluent-plugin-multi-format-parser@1.0.0", "type": "library", "name": "fluent-plugin-multi-format-parser", "version": "1.0.0", @@ -6294,7 +6314,7 @@ ] }, { - "bom-ref": "pkg:gem/fluent-plugin-prometheus@1.7.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Ffluent-plugin-prometheus-1.7.0.gemspec", + "bom-ref": "pkg:gem/fluent-plugin-prometheus@1.7.0", "type": "library", "name": "fluent-plugin-prometheus", "version": "1.7.0", @@ -6326,7 +6346,7 @@ ] }, { - "bom-ref": "pkg:gem/fluent-plugin-systemd@1.0.2?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Ffluent-plugin-systemd-1.0.2.gemspec", + "bom-ref": "pkg:gem/fluent-plugin-systemd@1.0.2", "type": "library", "name": "fluent-plugin-systemd", "version": "1.0.2", @@ -6358,7 +6378,7 @@ ] }, { - "bom-ref": "pkg:gem/fluentd@1.8.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Ffluentd-1.8.0.gemspec", + "bom-ref": "pkg:gem/fluentd@1.8.0", "type": "library", "name": "fluentd", "version": "1.8.0", @@ -6390,7 +6410,7 @@ ] }, { - "bom-ref": "pkg:gem/http-accept@1.7.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fhttp-accept-1.7.0.gemspec", + "bom-ref": "pkg:gem/http-accept@1.7.0", "type": "library", "name": "http-accept", "version": "1.7.0", @@ -6415,7 +6435,7 @@ ] }, { - "bom-ref": "pkg:gem/http-cookie@1.0.3?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fhttp-cookie-1.0.3.gemspec", + "bom-ref": "pkg:gem/http-cookie@1.0.3", "type": "library", "name": "http-cookie", "version": "1.0.3", @@ -6447,7 +6467,7 @@ ] }, { - "bom-ref": "pkg:gem/http-form_data@2.2.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fhttp-form_data-2.2.0.gemspec", + "bom-ref": "pkg:gem/http-form_data@2.2.0", "type": "library", "name": "http-form_data", "version": "2.2.0", @@ -6479,7 +6499,7 @@ ] }, { - "bom-ref": "pkg:gem/http-parser@1.2.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fhttp-parser-1.2.1.gemspec", + "bom-ref": "pkg:gem/http-parser@1.2.1", "type": "library", "name": "http-parser", "version": "1.2.1", @@ -6511,7 +6531,7 @@ ] }, { - "bom-ref": "pkg:gem/http@4.3.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fhttp-4.3.0.gemspec", + "bom-ref": "pkg:gem/http@4.3.0", "type": "library", "name": "http", "version": "4.3.0", @@ -6543,7 +6563,7 @@ ] }, { - "bom-ref": "pkg:gem/http_parser.rb@0.6.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fhttp_parser.rb-0.6.0.gemspec", + "bom-ref": "pkg:gem/http_parser.rb@0.6.0", "type": "library", "name": "http_parser.rb", "version": "0.6.0", @@ -6575,7 +6595,7 @@ ] }, { - "bom-ref": "pkg:gem/i18n@1.8.2?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fi18n-1.8.2.gemspec", + "bom-ref": "pkg:gem/i18n@1.8.2", "type": "library", "name": "i18n", "version": "1.8.2", @@ -6607,7 +6627,7 @@ ] }, { - "bom-ref": "pkg:gem/kubeclient@4.6.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fkubeclient-4.6.0.gemspec", + "bom-ref": "pkg:gem/kubeclient@4.6.0", "type": "library", "name": "kubeclient", "version": "4.6.0", @@ -6639,7 +6659,7 @@ ] }, { - "bom-ref": "pkg:gem/lru_redux@1.1.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Flru_redux-1.1.0.gemspec", + "bom-ref": "pkg:gem/lru_redux@1.1.0", "type": "library", "name": "lru_redux", "version": "1.1.0", @@ -6671,7 +6691,7 @@ ] }, { - "bom-ref": "pkg:gem/mime-types-data@3.2019.1009?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fmime-types-data-3.2019.1009.gemspec", + "bom-ref": "pkg:gem/mime-types-data@3.2019.1009", "type": "library", "name": "mime-types-data", "version": "3.2019.1009", @@ -6703,7 +6723,7 @@ ] }, { - "bom-ref": "pkg:gem/mime-types@3.3.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fmime-types-3.3.1.gemspec", + "bom-ref": "pkg:gem/mime-types@3.3.1", "type": "library", "name": "mime-types", "version": "3.3.1", @@ -6735,7 +6755,7 @@ ] }, { - "bom-ref": "pkg:gem/minitest@5.14.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fminitest-5.14.0.gemspec", + "bom-ref": "pkg:gem/minitest@5.14.0", "type": "library", "name": "minitest", "version": "5.14.0", @@ -6767,7 +6787,7 @@ ] }, { - "bom-ref": "pkg:gem/msgpack@1.3.3?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fmsgpack-1.3.3.gemspec", + "bom-ref": "pkg:gem/msgpack@1.3.3", "type": "library", "name": "msgpack", "version": "1.3.3", @@ -6799,7 +6819,7 @@ ] }, { - "bom-ref": "pkg:gem/multi_json@1.14.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fmulti_json-1.14.1.gemspec", + "bom-ref": "pkg:gem/multi_json@1.14.1", "type": "library", "name": "multi_json", "version": "1.14.1", @@ -6831,7 +6851,7 @@ ] }, { - "bom-ref": "pkg:gem/multipart-post@2.1.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fmultipart-post-2.1.1.gemspec", + "bom-ref": "pkg:gem/multipart-post@2.1.1", "type": "library", "name": "multipart-post", "version": "2.1.1", @@ -6863,7 +6883,7 @@ ] }, { - "bom-ref": "pkg:gem/netrc@0.11.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fnetrc-0.11.0.gemspec", + "bom-ref": "pkg:gem/netrc@0.11.0", "type": "library", "name": "netrc", "version": "0.11.0", @@ -6895,7 +6915,7 @@ ] }, { - "bom-ref": "pkg:gem/oj@3.10.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Foj-3.10.0.gemspec", + "bom-ref": "pkg:gem/oj@3.10.0", "type": "library", "name": "oj", "version": "3.10.0", @@ -6927,7 +6947,7 @@ ] }, { - "bom-ref": "pkg:gem/prometheus-client@0.9.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fprometheus-client-0.9.0.gemspec", + "bom-ref": "pkg:gem/prometheus-client@0.9.0", "type": "library", "name": "prometheus-client", "version": "0.9.0", @@ -6959,7 +6979,7 @@ ] }, { - "bom-ref": "pkg:gem/public_suffix@4.0.3?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fpublic_suffix-4.0.3.gemspec", + "bom-ref": "pkg:gem/public_suffix@4.0.3", "type": "library", "name": "public_suffix", "version": "4.0.3", @@ -6991,7 +7011,7 @@ ] }, { - "bom-ref": "pkg:gem/quantile@0.2.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fquantile-0.2.1.gemspec", + "bom-ref": "pkg:gem/quantile@0.2.1", "type": "library", "name": "quantile", "version": "0.2.1", @@ -7023,7 +7043,7 @@ ] }, { - "bom-ref": "pkg:gem/rake@13.0.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Frake-13.0.1.gemspec", + "bom-ref": "pkg:gem/rake@13.0.1", "type": "library", "name": "rake", "version": "13.0.1", @@ -7055,7 +7075,7 @@ ] }, { - "bom-ref": "pkg:gem/recursive-open-struct@1.1.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Frecursive-open-struct-1.1.0.gemspec", + "bom-ref": "pkg:gem/recursive-open-struct@1.1.0", "type": "library", "name": "recursive-open-struct", "version": "1.1.0", @@ -7087,7 +7107,7 @@ ] }, { - "bom-ref": "pkg:gem/rest-client@2.1.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Frest-client-2.1.0.gemspec", + "bom-ref": "pkg:gem/rest-client@2.1.0", "type": "library", "name": "rest-client", "version": "2.1.0", @@ -7119,7 +7139,7 @@ ] }, { - "bom-ref": "pkg:gem/serverengine@2.2.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fserverengine-2.2.1.gemspec", + "bom-ref": "pkg:gem/serverengine@2.2.1", "type": "library", "name": "serverengine", "version": "2.2.1", @@ -7151,7 +7171,7 @@ ] }, { - "bom-ref": "pkg:gem/sigdump@0.2.4?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fsigdump-0.2.4.gemspec", + "bom-ref": "pkg:gem/sigdump@0.2.4", "type": "library", "name": "sigdump", "version": "0.2.4", @@ -7183,7 +7203,7 @@ ] }, { - "bom-ref": "pkg:gem/strptime@0.2.3?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fstrptime-0.2.3.gemspec", + "bom-ref": "pkg:gem/strptime@0.2.3", "type": "library", "name": "strptime", "version": "0.2.3", @@ -7215,7 +7235,7 @@ ] }, { - "bom-ref": "pkg:gem/systemd-journal@1.3.3?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fsystemd-journal-1.3.3.gemspec", + "bom-ref": "pkg:gem/systemd-journal@1.3.3", "type": "library", "name": "systemd-journal", "version": "1.3.3", @@ -7247,7 +7267,7 @@ ] }, { - "bom-ref": "pkg:gem/thread_safe@0.3.6?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fthread_safe-0.3.6.gemspec", + "bom-ref": "pkg:gem/thread_safe@0.3.6", "type": "library", "name": "thread_safe", "version": "0.3.6", @@ -7279,7 +7299,7 @@ ] }, { - "bom-ref": "pkg:gem/tzinfo-data@1.2019.3?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Ftzinfo-data-1.2019.3.gemspec", + "bom-ref": "pkg:gem/tzinfo-data@1.2019.3", "type": "library", "name": "tzinfo-data", "version": "1.2019.3", @@ -7311,7 +7331,7 @@ ] }, { - "bom-ref": "pkg:gem/tzinfo@1.2.6?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Ftzinfo-1.2.6.gemspec", + "bom-ref": "pkg:gem/tzinfo@1.2.6", "type": "library", "name": "tzinfo", "version": "1.2.6", @@ -7343,7 +7363,7 @@ ] }, { - "bom-ref": "pkg:gem/unf@0.1.4?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Funf-0.1.4.gemspec", + "bom-ref": "pkg:gem/unf@0.1.4", "type": "library", "name": "unf", "version": "0.1.4", @@ -7375,7 +7395,7 @@ ] }, { - "bom-ref": "pkg:gem/unf_ext@0.0.7.6?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Funf_ext-0.0.7.6.gemspec", + "bom-ref": "pkg:gem/unf_ext@0.0.7.6", "type": "library", "name": "unf_ext", "version": "0.0.7.6", @@ -7407,7 +7427,7 @@ ] }, { - "bom-ref": "pkg:gem/yajl-ruby@1.4.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fyajl-ruby-1.4.1.gemspec", + "bom-ref": "pkg:gem/yajl-ruby@1.4.1", "type": "library", "name": "yajl-ruby", "version": "1.4.1", @@ -7439,7 +7459,7 @@ ] }, { - "bom-ref": "pkg:gem/zeitwerk@2.3.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fzeitwerk-2.3.0.gemspec", + "bom-ref": "pkg:gem/zeitwerk@2.3.0", "type": "library", "name": "zeitwerk", "version": "2.3.0", @@ -7473,68 +7493,68 @@ ], "dependencies": [ { - "ref": "3ff14136-e09f-4df9-80ea-000000000002", + "ref": "3ff14136-e09f-4df9-80ea-000000000001", "dependsOn": [ - "3ff14136-e09f-4df9-80ea-000000000003", - "pkg:gem/activesupport@6.0.2.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Factivesupport-6.0.2.1.gemspec", - "pkg:gem/addressable@2.7.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Faddressable-2.7.0.gemspec", - "pkg:gem/concurrent-ruby@1.1.6?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fconcurrent-ruby-1.1.6.gemspec", - "pkg:gem/cool.io@1.6.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fcool.io-1.6.0.gemspec", - "pkg:gem/dig_rb@1.0.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fdig_rb-1.0.1.gemspec", - "pkg:gem/domain_name@0.5.20190701?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fdomain_name-0.5.20190701.gemspec", - "pkg:gem/elasticsearch-api@7.5.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Felasticsearch-api-7.5.0.gemspec", - "pkg:gem/elasticsearch-transport@7.5.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Felasticsearch-transport-7.5.0.gemspec", - "pkg:gem/elasticsearch@7.5.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Felasticsearch-7.5.0.gemspec", - "pkg:gem/excon@0.72.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fexcon-0.72.0.gemspec", - "pkg:gem/faraday@0.17.3?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Ffaraday-0.17.3.gemspec", - "pkg:gem/ffi-compiler@1.0.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fffi-compiler-1.0.1.gemspec", - "pkg:gem/ffi@1.12.2?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fffi-1.12.2.gemspec", - "pkg:gem/fluent-plugin-concat@2.4.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Ffluent-plugin-concat-2.4.0.gemspec", - "pkg:gem/fluent-plugin-detect-exceptions@0.0.13?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Ffluent-plugin-detect-exceptions-0.0.13.gemspec", - "pkg:gem/fluent-plugin-elasticsearch@3.8.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Ffluent-plugin-elasticsearch-3.8.0.gemspec", - "pkg:gem/fluent-plugin-kubernetes_metadata_filter@2.4.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Ffluent-plugin-kubernetes_metadata_filter-2.4.1.gemspec", - "pkg:gem/fluent-plugin-multi-format-parser@1.0.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Ffluent-plugin-multi-format-parser-1.0.0.gemspec", - "pkg:gem/fluent-plugin-prometheus@1.7.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Ffluent-plugin-prometheus-1.7.0.gemspec", - "pkg:gem/fluent-plugin-systemd@1.0.2?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Ffluent-plugin-systemd-1.0.2.gemspec", - "pkg:gem/fluentd@1.8.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Ffluentd-1.8.0.gemspec", - "pkg:gem/http-accept@1.7.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fhttp-accept-1.7.0.gemspec", - "pkg:gem/http-cookie@1.0.3?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fhttp-cookie-1.0.3.gemspec", - "pkg:gem/http-form_data@2.2.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fhttp-form_data-2.2.0.gemspec", - "pkg:gem/http-parser@1.2.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fhttp-parser-1.2.1.gemspec", - "pkg:gem/http@4.3.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fhttp-4.3.0.gemspec", - "pkg:gem/http_parser.rb@0.6.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fhttp_parser.rb-0.6.0.gemspec", - "pkg:gem/i18n@1.8.2?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fi18n-1.8.2.gemspec", - "pkg:gem/kubeclient@4.6.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fkubeclient-4.6.0.gemspec", - "pkg:gem/lru_redux@1.1.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Flru_redux-1.1.0.gemspec", - "pkg:gem/mime-types-data@3.2019.1009?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fmime-types-data-3.2019.1009.gemspec", - "pkg:gem/mime-types@3.3.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fmime-types-3.3.1.gemspec", - "pkg:gem/minitest@5.14.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fminitest-5.14.0.gemspec", - "pkg:gem/msgpack@1.3.3?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fmsgpack-1.3.3.gemspec", - "pkg:gem/multi_json@1.14.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fmulti_json-1.14.1.gemspec", - "pkg:gem/multipart-post@2.1.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fmultipart-post-2.1.1.gemspec", - "pkg:gem/netrc@0.11.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fnetrc-0.11.0.gemspec", - "pkg:gem/oj@3.10.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Foj-3.10.0.gemspec", - "pkg:gem/prometheus-client@0.9.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fprometheus-client-0.9.0.gemspec", - "pkg:gem/public_suffix@4.0.3?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fpublic_suffix-4.0.3.gemspec", - "pkg:gem/quantile@0.2.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fquantile-0.2.1.gemspec", - "pkg:gem/rake@13.0.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Frake-13.0.1.gemspec", - "pkg:gem/recursive-open-struct@1.1.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Frecursive-open-struct-1.1.0.gemspec", - "pkg:gem/rest-client@2.1.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Frest-client-2.1.0.gemspec", - "pkg:gem/serverengine@2.2.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fserverengine-2.2.1.gemspec", - "pkg:gem/sigdump@0.2.4?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fsigdump-0.2.4.gemspec", - "pkg:gem/strptime@0.2.3?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fstrptime-0.2.3.gemspec", - "pkg:gem/systemd-journal@1.3.3?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fsystemd-journal-1.3.3.gemspec", - "pkg:gem/thread_safe@0.3.6?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fthread_safe-0.3.6.gemspec", - "pkg:gem/tzinfo-data@1.2019.3?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Ftzinfo-data-1.2019.3.gemspec", - "pkg:gem/tzinfo@1.2.6?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Ftzinfo-1.2.6.gemspec", - "pkg:gem/unf@0.1.4?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Funf-0.1.4.gemspec", - "pkg:gem/unf_ext@0.0.7.6?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Funf_ext-0.0.7.6.gemspec", - "pkg:gem/yajl-ruby@1.4.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fyajl-ruby-1.4.1.gemspec", - "pkg:gem/zeitwerk@2.3.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fzeitwerk-2.3.0.gemspec" - ] - }, - { - "ref": "3ff14136-e09f-4df9-80ea-000000000003", + "3ff14136-e09f-4df9-80ea-000000000002", + "pkg:gem/activesupport@6.0.2.1", + "pkg:gem/addressable@2.7.0", + "pkg:gem/concurrent-ruby@1.1.6", + "pkg:gem/cool.io@1.6.0", + "pkg:gem/dig_rb@1.0.1", + "pkg:gem/domain_name@0.5.20190701", + "pkg:gem/elasticsearch-api@7.5.0", + "pkg:gem/elasticsearch-transport@7.5.0", + "pkg:gem/elasticsearch@7.5.0", + "pkg:gem/excon@0.72.0", + "pkg:gem/faraday@0.17.3", + "pkg:gem/ffi-compiler@1.0.1", + "pkg:gem/ffi@1.12.2", + "pkg:gem/fluent-plugin-concat@2.4.0", + "pkg:gem/fluent-plugin-detect-exceptions@0.0.13", + "pkg:gem/fluent-plugin-elasticsearch@3.8.0", + "pkg:gem/fluent-plugin-kubernetes_metadata_filter@2.4.1", + "pkg:gem/fluent-plugin-multi-format-parser@1.0.0", + "pkg:gem/fluent-plugin-prometheus@1.7.0", + "pkg:gem/fluent-plugin-systemd@1.0.2", + "pkg:gem/fluentd@1.8.0", + "pkg:gem/http-accept@1.7.0", + "pkg:gem/http-cookie@1.0.3", + "pkg:gem/http-form_data@2.2.0", + "pkg:gem/http-parser@1.2.1", + "pkg:gem/http@4.3.0", + "pkg:gem/http_parser.rb@0.6.0", + "pkg:gem/i18n@1.8.2", + "pkg:gem/kubeclient@4.6.0", + "pkg:gem/lru_redux@1.1.0", + "pkg:gem/mime-types-data@3.2019.1009", + "pkg:gem/mime-types@3.3.1", + "pkg:gem/minitest@5.14.0", + "pkg:gem/msgpack@1.3.3", + "pkg:gem/multi_json@1.14.1", + "pkg:gem/multipart-post@2.1.1", + "pkg:gem/netrc@0.11.0", + "pkg:gem/oj@3.10.0", + "pkg:gem/prometheus-client@0.9.0", + "pkg:gem/public_suffix@4.0.3", + "pkg:gem/quantile@0.2.1", + "pkg:gem/rake@13.0.1", + "pkg:gem/recursive-open-struct@1.1.0", + "pkg:gem/rest-client@2.1.0", + "pkg:gem/serverengine@2.2.1", + "pkg:gem/sigdump@0.2.4", + "pkg:gem/strptime@0.2.3", + "pkg:gem/systemd-journal@1.3.3", + "pkg:gem/thread_safe@0.3.6", + "pkg:gem/tzinfo-data@1.2019.3", + "pkg:gem/tzinfo@1.2.6", + "pkg:gem/unf@0.1.4", + "pkg:gem/unf_ext@0.0.7.6", + "pkg:gem/yajl-ruby@1.4.1", + "pkg:gem/zeitwerk@2.3.0" + ] + }, + { + "ref": "3ff14136-e09f-4df9-80ea-000000000002", "dependsOn": [ "pkg:deb/debian/adduser@3.118?arch=all&distro=debian-10.2", "pkg:deb/debian/apt@1.8.2?arch=amd64&distro=debian-10.2", @@ -8324,223 +8344,223 @@ ] }, { - "ref": "pkg:gem/activesupport@6.0.2.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Factivesupport-6.0.2.1.gemspec", + "ref": "pkg:gem/activesupport@6.0.2.1", "dependsOn": [] }, { - "ref": "pkg:gem/addressable@2.7.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Faddressable-2.7.0.gemspec", + "ref": "pkg:gem/addressable@2.7.0", "dependsOn": [] }, { - "ref": "pkg:gem/concurrent-ruby@1.1.6?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fconcurrent-ruby-1.1.6.gemspec", + "ref": "pkg:gem/concurrent-ruby@1.1.6", "dependsOn": [] }, { - "ref": "pkg:gem/cool.io@1.6.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fcool.io-1.6.0.gemspec", + "ref": "pkg:gem/cool.io@1.6.0", "dependsOn": [] }, { - "ref": "pkg:gem/dig_rb@1.0.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fdig_rb-1.0.1.gemspec", + "ref": "pkg:gem/dig_rb@1.0.1", "dependsOn": [] }, { - "ref": "pkg:gem/domain_name@0.5.20190701?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fdomain_name-0.5.20190701.gemspec", + "ref": "pkg:gem/domain_name@0.5.20190701", "dependsOn": [] }, { - "ref": "pkg:gem/elasticsearch-api@7.5.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Felasticsearch-api-7.5.0.gemspec", + "ref": "pkg:gem/elasticsearch-api@7.5.0", "dependsOn": [] }, { - "ref": "pkg:gem/elasticsearch-transport@7.5.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Felasticsearch-transport-7.5.0.gemspec", + "ref": "pkg:gem/elasticsearch-transport@7.5.0", "dependsOn": [] }, { - "ref": "pkg:gem/elasticsearch@7.5.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Felasticsearch-7.5.0.gemspec", + "ref": "pkg:gem/elasticsearch@7.5.0", "dependsOn": [] }, { - "ref": "pkg:gem/excon@0.72.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fexcon-0.72.0.gemspec", + "ref": "pkg:gem/excon@0.72.0", "dependsOn": [] }, { - "ref": "pkg:gem/faraday@0.17.3?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Ffaraday-0.17.3.gemspec", + "ref": "pkg:gem/faraday@0.17.3", "dependsOn": [] }, { - "ref": "pkg:gem/ffi-compiler@1.0.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fffi-compiler-1.0.1.gemspec", + "ref": "pkg:gem/ffi-compiler@1.0.1", "dependsOn": [] }, { - "ref": "pkg:gem/ffi@1.12.2?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fffi-1.12.2.gemspec", + "ref": "pkg:gem/ffi@1.12.2", "dependsOn": [] }, { - "ref": "pkg:gem/fluent-plugin-concat@2.4.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Ffluent-plugin-concat-2.4.0.gemspec", + "ref": "pkg:gem/fluent-plugin-concat@2.4.0", "dependsOn": [] }, { - "ref": "pkg:gem/fluent-plugin-detect-exceptions@0.0.13?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Ffluent-plugin-detect-exceptions-0.0.13.gemspec", + "ref": "pkg:gem/fluent-plugin-detect-exceptions@0.0.13", "dependsOn": [] }, { - "ref": "pkg:gem/fluent-plugin-elasticsearch@3.8.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Ffluent-plugin-elasticsearch-3.8.0.gemspec", + "ref": "pkg:gem/fluent-plugin-elasticsearch@3.8.0", "dependsOn": [] }, { - "ref": "pkg:gem/fluent-plugin-kubernetes_metadata_filter@2.4.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Ffluent-plugin-kubernetes_metadata_filter-2.4.1.gemspec", + "ref": "pkg:gem/fluent-plugin-kubernetes_metadata_filter@2.4.1", "dependsOn": [] }, { - "ref": "pkg:gem/fluent-plugin-multi-format-parser@1.0.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Ffluent-plugin-multi-format-parser-1.0.0.gemspec", + "ref": "pkg:gem/fluent-plugin-multi-format-parser@1.0.0", "dependsOn": [] }, { - "ref": "pkg:gem/fluent-plugin-prometheus@1.7.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Ffluent-plugin-prometheus-1.7.0.gemspec", + "ref": "pkg:gem/fluent-plugin-prometheus@1.7.0", "dependsOn": [] }, { - "ref": "pkg:gem/fluent-plugin-systemd@1.0.2?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Ffluent-plugin-systemd-1.0.2.gemspec", + "ref": "pkg:gem/fluent-plugin-systemd@1.0.2", "dependsOn": [] }, { - "ref": "pkg:gem/fluentd@1.8.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Ffluentd-1.8.0.gemspec", + "ref": "pkg:gem/fluentd@1.8.0", "dependsOn": [] }, { - "ref": "pkg:gem/http-accept@1.7.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fhttp-accept-1.7.0.gemspec", + "ref": "pkg:gem/http-accept@1.7.0", "dependsOn": [] }, { - "ref": "pkg:gem/http-cookie@1.0.3?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fhttp-cookie-1.0.3.gemspec", + "ref": "pkg:gem/http-cookie@1.0.3", "dependsOn": [] }, { - "ref": "pkg:gem/http-form_data@2.2.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fhttp-form_data-2.2.0.gemspec", + "ref": "pkg:gem/http-form_data@2.2.0", "dependsOn": [] }, { - "ref": "pkg:gem/http-parser@1.2.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fhttp-parser-1.2.1.gemspec", + "ref": "pkg:gem/http-parser@1.2.1", "dependsOn": [] }, { - "ref": "pkg:gem/http@4.3.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fhttp-4.3.0.gemspec", + "ref": "pkg:gem/http@4.3.0", "dependsOn": [] }, { - "ref": "pkg:gem/http_parser.rb@0.6.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fhttp_parser.rb-0.6.0.gemspec", + "ref": "pkg:gem/http_parser.rb@0.6.0", "dependsOn": [] }, { - "ref": "pkg:gem/i18n@1.8.2?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fi18n-1.8.2.gemspec", + "ref": "pkg:gem/i18n@1.8.2", "dependsOn": [] }, { - "ref": "pkg:gem/kubeclient@4.6.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fkubeclient-4.6.0.gemspec", + "ref": "pkg:gem/kubeclient@4.6.0", "dependsOn": [] }, { - "ref": "pkg:gem/lru_redux@1.1.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Flru_redux-1.1.0.gemspec", + "ref": "pkg:gem/lru_redux@1.1.0", "dependsOn": [] }, { - "ref": "pkg:gem/mime-types-data@3.2019.1009?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fmime-types-data-3.2019.1009.gemspec", + "ref": "pkg:gem/mime-types-data@3.2019.1009", "dependsOn": [] }, { - "ref": "pkg:gem/mime-types@3.3.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fmime-types-3.3.1.gemspec", + "ref": "pkg:gem/mime-types@3.3.1", "dependsOn": [] }, { - "ref": "pkg:gem/minitest@5.14.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fminitest-5.14.0.gemspec", + "ref": "pkg:gem/minitest@5.14.0", "dependsOn": [] }, { - "ref": "pkg:gem/msgpack@1.3.3?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fmsgpack-1.3.3.gemspec", + "ref": "pkg:gem/msgpack@1.3.3", "dependsOn": [] }, { - "ref": "pkg:gem/multi_json@1.14.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fmulti_json-1.14.1.gemspec", + "ref": "pkg:gem/multi_json@1.14.1", "dependsOn": [] }, { - "ref": "pkg:gem/multipart-post@2.1.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fmultipart-post-2.1.1.gemspec", + "ref": "pkg:gem/multipart-post@2.1.1", "dependsOn": [] }, { - "ref": "pkg:gem/netrc@0.11.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fnetrc-0.11.0.gemspec", + "ref": "pkg:gem/netrc@0.11.0", "dependsOn": [] }, { - "ref": "pkg:gem/oj@3.10.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Foj-3.10.0.gemspec", + "ref": "pkg:gem/oj@3.10.0", "dependsOn": [] }, { - "ref": "pkg:gem/prometheus-client@0.9.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fprometheus-client-0.9.0.gemspec", + "ref": "pkg:gem/prometheus-client@0.9.0", "dependsOn": [] }, { - "ref": "pkg:gem/public_suffix@4.0.3?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fpublic_suffix-4.0.3.gemspec", + "ref": "pkg:gem/public_suffix@4.0.3", "dependsOn": [] }, { - "ref": "pkg:gem/quantile@0.2.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fquantile-0.2.1.gemspec", + "ref": "pkg:gem/quantile@0.2.1", "dependsOn": [] }, { - "ref": "pkg:gem/rake@13.0.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Frake-13.0.1.gemspec", + "ref": "pkg:gem/rake@13.0.1", "dependsOn": [] }, { - "ref": "pkg:gem/recursive-open-struct@1.1.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Frecursive-open-struct-1.1.0.gemspec", + "ref": "pkg:gem/recursive-open-struct@1.1.0", "dependsOn": [] }, { - "ref": "pkg:gem/rest-client@2.1.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Frest-client-2.1.0.gemspec", + "ref": "pkg:gem/rest-client@2.1.0", "dependsOn": [] }, { - "ref": "pkg:gem/serverengine@2.2.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fserverengine-2.2.1.gemspec", + "ref": "pkg:gem/serverengine@2.2.1", "dependsOn": [] }, { - "ref": "pkg:gem/sigdump@0.2.4?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fsigdump-0.2.4.gemspec", + "ref": "pkg:gem/sigdump@0.2.4", "dependsOn": [] }, { - "ref": "pkg:gem/strptime@0.2.3?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fstrptime-0.2.3.gemspec", + "ref": "pkg:gem/strptime@0.2.3", "dependsOn": [] }, { - "ref": "pkg:gem/systemd-journal@1.3.3?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fsystemd-journal-1.3.3.gemspec", + "ref": "pkg:gem/systemd-journal@1.3.3", "dependsOn": [] }, { - "ref": "pkg:gem/thread_safe@0.3.6?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fthread_safe-0.3.6.gemspec", + "ref": "pkg:gem/thread_safe@0.3.6", "dependsOn": [] }, { - "ref": "pkg:gem/tzinfo-data@1.2019.3?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Ftzinfo-data-1.2019.3.gemspec", + "ref": "pkg:gem/tzinfo-data@1.2019.3", "dependsOn": [] }, { - "ref": "pkg:gem/tzinfo@1.2.6?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Ftzinfo-1.2.6.gemspec", + "ref": "pkg:gem/tzinfo@1.2.6", "dependsOn": [] }, { - "ref": "pkg:gem/unf@0.1.4?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Funf-0.1.4.gemspec", + "ref": "pkg:gem/unf@0.1.4", "dependsOn": [] }, { - "ref": "pkg:gem/unf_ext@0.0.7.6?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Funf_ext-0.0.7.6.gemspec", + "ref": "pkg:gem/unf_ext@0.0.7.6", "dependsOn": [] }, { - "ref": "pkg:gem/yajl-ruby@1.4.1?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fyajl-ruby-1.4.1.gemspec", + "ref": "pkg:gem/yajl-ruby@1.4.1", "dependsOn": [] }, { - "ref": "pkg:gem/zeitwerk@2.3.0?file_path=var%2Flib%2Fgems%2F2.5.0%2Fspecifications%2Fzeitwerk-2.3.0.gemspec", + "ref": "pkg:gem/zeitwerk@2.3.0", "dependsOn": [] } ], diff --git a/integration/testdata/fluentd-multiple-lockfiles.json.golden b/integration/testdata/fluentd-multiple-lockfiles.json.golden index 23a1ae2a8af3..701c0262753d 100644 --- a/integration/testdata/fluentd-multiple-lockfiles.json.golden +++ b/integration/testdata/fluentd-multiple-lockfiles.json.golden @@ -27,6 +27,7 @@ "Vulnerabilities": [ { "VulnerabilityID": "CVE-2019-18276", + "PkgID": "bash@5.0-4", "PkgName": "bash", "PkgIdentifier": { "PURL": "pkg:deb/debian/bash@5.0-4?distro=debian-10.2", @@ -90,6 +91,7 @@ "VendorIDs": [ "DSA-4613-1" ], + "PkgID": "libidn2-0@2.0.5-1", "PkgName": "libidn2-0", "PkgIdentifier": { "PURL": "pkg:deb/debian/libidn2-0@2.0.5-1?distro=debian-10.2", @@ -158,6 +160,7 @@ "Vulnerabilities": [ { "VulnerabilityID": "CVE-2020-8165", + "PkgID": "activesupport@6.0.2.1", "PkgName": "activesupport", "PkgPath": "var/lib/gems/2.5.0/specifications/activesupport-6.0.2.1.gemspec", "PkgIdentifier": { diff --git a/integration/testdata/minikube-kbom.json.golden b/integration/testdata/minikube-kbom.json.golden index 080e061142e8..fd5b6b24718f 100644 --- a/integration/testdata/minikube-kbom.json.golden +++ b/integration/testdata/minikube-kbom.json.golden @@ -32,6 +32,7 @@ "Vulnerabilities": [ { "VulnerabilityID": "CVE-2023-2431", + "PkgID": "k8s.io/kubelet@1.27.0", "PkgName": "k8s.io/kubelet", "PkgIdentifier": { "PURL": "pkg:k8s/k8s.io%2Fkubelet@1.27.0", diff --git a/integration/testdata/pom-cyclonedx.json.golden b/integration/testdata/pom-cyclonedx.json.golden index 5487c239e2da..27f54a71848b 100644 --- a/integration/testdata/pom-cyclonedx.json.golden +++ b/integration/testdata/pom-cyclonedx.json.golden @@ -2,7 +2,7 @@ "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json", "bomFormat": "CycloneDX", "specVersion": "1.5", - "serialNumber": "urn:uuid:3ff14136-e09f-4df9-80ea-000000000001", + "serialNumber": "urn:uuid:3ff14136-e09f-4df9-80ea-000000000005", "version": 1, "metadata": { "timestamp": "2021-08-25T12:20:30+00:00", @@ -17,7 +17,7 @@ ] }, "component": { - "bom-ref": "3ff14136-e09f-4df9-80ea-000000000002", + "bom-ref": "3ff14136-e09f-4df9-80ea-000000000001", "type": "application", "name": "testdata/fixtures/repo/pom", "properties": [ @@ -30,7 +30,7 @@ }, "components": [ { - "bom-ref": "3ff14136-e09f-4df9-80ea-000000000003", + "bom-ref": "3ff14136-e09f-4df9-80ea-000000000002", "type": "application", "name": "pom.xml", "properties": [ @@ -83,13 +83,13 @@ ], "dependencies": [ { - "ref": "3ff14136-e09f-4df9-80ea-000000000002", + "ref": "3ff14136-e09f-4df9-80ea-000000000001", "dependsOn": [ - "3ff14136-e09f-4df9-80ea-000000000003" + "3ff14136-e09f-4df9-80ea-000000000002" ] }, { - "ref": "3ff14136-e09f-4df9-80ea-000000000003", + "ref": "3ff14136-e09f-4df9-80ea-000000000002", "dependsOn": [ "pkg:maven/com.example/log4shell@1.0-SNAPSHOT", "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.9.1" diff --git a/magefiles/magefile.go b/magefiles/magefile.go index f3024c99d7c4..fbb612b9d855 100644 --- a/magefiles/magefile.go +++ b/magefiles/magefile.go @@ -77,14 +77,6 @@ func (Tool) Labeler() error { return sh.Run("go", "install", "github.com/knqyf263/labeler@latest") } -// EasyJSON installs easyjson -func (Tool) EasyJSON() error { - if exists(filepath.Join(GOBIN, "easyjson")) { - return nil - } - return sh.Run("go", "install", "github.com/mailru/easyjson/...@v0.7.7") -} - // Kind installs kind cluster func (Tool) Kind() error { return sh.RunWithV(ENV, "go", "install", "sigs.k8s.io/kind@v0.19.0") @@ -164,12 +156,6 @@ func Yacc() error { return sh.Run("go", "generate", "./pkg/licensing/expression/...") } -// Easyjson generates JSON marshaler/unmarshaler for TinyGo/WebAssembly as TinyGo doesn't support encoding/json. -func Easyjson() error { - mg.Deps(Tool{}.EasyJSON) - return sh.Run("easyjson", "./pkg/module/serialize/types.go") -} - type Test mg.Namespace // FixtureContainerImages downloads and extracts required images diff --git a/pkg/dependency/id.go b/pkg/dependency/id.go new file mode 100644 index 000000000000..d40289cedc6a --- /dev/null +++ b/pkg/dependency/id.go @@ -0,0 +1,32 @@ +package dependency + +import ( + "strings" + + "github.com/aquasecurity/trivy/pkg/fanal/types" +) + +// ID returns a unique ID for the given library. +// The package ID is used to construct the dependency graph. +// The separator is different for each language type. +func ID(ltype types.LangType, name, version string) string { + if version == "" { + return name + } + + sep := "@" + switch ltype { + case types.Conan: + sep = "/" + case types.GoModule, types.GoBinary: + // Return a module ID according the Go way. + // Format: <module_name>@v<module_version> + // e.g. github.com/aquasecurity/go-dep-parser@v0.0.0-20230130190635-5e31092b0621 + if !strings.HasPrefix(version, "v") { + version = "v" + version + } + case types.Jar, types.Pom, types.Gradle: + sep = ":" + } + return name + sep + version +} diff --git a/pkg/dependency/id_test.go b/pkg/dependency/id_test.go new file mode 100644 index 000000000000..173d71c3d726 --- /dev/null +++ b/pkg/dependency/id_test.go @@ -0,0 +1,73 @@ +package dependency_test + +import ( + "github.com/aquasecurity/trivy/pkg/dependency" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestID(t *testing.T) { + type args struct { + ltype types.LangType + name string + version string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "conan", + args: args{ + ltype: types.Conan, + name: "test", + version: "1.0.0", + }, + want: "test/1.0.0", + }, + { + name: "go module", + args: args{ + ltype: types.GoModule, + name: "test", + version: "1.0.0", + }, + want: "test@v1.0.0", + }, + { + name: "gradle", + args: args{ + ltype: types.Gradle, + name: "test", + version: "1.0.0", + }, + want: "test:1.0.0", + }, + { + name: "pip", + args: args{ + ltype: types.Pip, + name: "test", + version: "1.0.0", + }, + want: "test@1.0.0", + }, + { + name: "no version", + args: args{ + ltype: types.Pom, + name: "test", + version: "", + }, + want: "test", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := dependency.ID(tt.args.ltype, tt.args.name, tt.args.version) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/dependency/parser/c/conan/parse.go b/pkg/dependency/parser/c/conan/parse.go index 88e98e0b91dc..78e3bdc09636 100644 --- a/pkg/dependency/parser/c/conan/parse.go +++ b/pkg/dependency/parser/c/conan/parse.go @@ -1,7 +1,6 @@ package conan import ( - "fmt" "io" "strings" @@ -9,7 +8,9 @@ import ( "golang.org/x/exp/slices" "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency" + "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -109,7 +110,7 @@ func parseRef(node Node) (types.Library, error) { return types.Library{}, xerrors.Errorf("Unable to determine conan dependency: %q", node.Ref) } return types.Library{ - ID: fmt.Sprintf("%s/%s", ss[0], ss[1]), + ID: dependency.ID(ftypes.Conan, ss[0], ss[1]), Name: ss[0], Version: ss[1], Locations: []types.Location{ diff --git a/pkg/dependency/parser/c/conan/parse_test.go b/pkg/dependency/parser/c/conan/parse_test.go index 862036bec738..a22ec90afc1b 100644 --- a/pkg/dependency/parser/c/conan/parse_test.go +++ b/pkg/dependency/parser/c/conan/parse_test.go @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/dependency/parser/c/conan" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" ) func TestParse(t *testing.T) { diff --git a/pkg/dependency/parser/conda/meta/parse.go b/pkg/dependency/parser/conda/meta/parse.go index 4acdcf6c94c3..30d344bfdfa3 100644 --- a/pkg/dependency/parser/conda/meta/parse.go +++ b/pkg/dependency/parser/conda/meta/parse.go @@ -5,7 +5,7 @@ import ( "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" xio "github.com/aquasecurity/trivy/pkg/x/io" ) diff --git a/pkg/dependency/parser/conda/meta/parse_test.go b/pkg/dependency/parser/conda/meta/parse_test.go index 656eb9b70d98..8bde5e184f0e 100644 --- a/pkg/dependency/parser/conda/meta/parse_test.go +++ b/pkg/dependency/parser/conda/meta/parse_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/dependency/parser/conda/meta" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" ) func TestParse(t *testing.T) { diff --git a/pkg/dependency/parser/dart/pub/parse.go b/pkg/dependency/parser/dart/pub/parse.go index 3b6ef40280e7..efd8d9aa5d41 100644 --- a/pkg/dependency/parser/dart/pub/parse.go +++ b/pkg/dependency/parser/dart/pub/parse.go @@ -1,12 +1,12 @@ package pub import ( - "fmt" - "golang.org/x/xerrors" "gopkg.in/yaml.v3" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency" + "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -44,7 +44,7 @@ func (Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, er // It will be confusing if we exclude direct dev dependencies and include transitive dev dependencies. // We decided to keep all dev dependencies until Pub will add support for "transitive main" and "transitive dev". lib := types.Library{ - ID: pkgID(name, dep.Version), + ID: dependency.ID(ftypes.Pub, name, dep.Version), Name: name, Version: dep.Version, Indirect: dep.Dependency == transitiveDep, @@ -54,7 +54,3 @@ func (Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, er return libs, nil, nil } - -func pkgID(name, version string) string { - return fmt.Sprintf(idFormat, name, version) -} diff --git a/pkg/dependency/parser/dart/pub/parse_test.go b/pkg/dependency/parser/dart/pub/parse_test.go index 00bed49158c5..ef62ab971658 100644 --- a/pkg/dependency/parser/dart/pub/parse_test.go +++ b/pkg/dependency/parser/dart/pub/parse_test.go @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/dependency/parser/dart/pub" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" ) func TestParser_Parse(t *testing.T) { diff --git a/pkg/dependency/parser/dotnet/core_deps/parse.go b/pkg/dependency/parser/dotnet/core_deps/parse.go index a8d44b8939e3..c4bf533a87df 100644 --- a/pkg/dependency/parser/dotnet/core_deps/parse.go +++ b/pkg/dependency/parser/dotnet/core_deps/parse.go @@ -7,7 +7,7 @@ import ( "github.com/liamg/jfather" "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" "github.com/aquasecurity/trivy/pkg/log" xio "github.com/aquasecurity/trivy/pkg/x/io" ) diff --git a/pkg/dependency/parser/dotnet/core_deps/parse_test.go b/pkg/dependency/parser/dotnet/core_deps/parse_test.go index 4e6e80955b75..839cf9ed97ba 100644 --- a/pkg/dependency/parser/dotnet/core_deps/parse_test.go +++ b/pkg/dependency/parser/dotnet/core_deps/parse_test.go @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" ) func TestParse(t *testing.T) { diff --git a/pkg/dependency/parser/frameworks/wordpress/parse.go b/pkg/dependency/parser/frameworks/wordpress/parse.go index 1eb481e73481..61e00ded81cc 100644 --- a/pkg/dependency/parser/frameworks/wordpress/parse.go +++ b/pkg/dependency/parser/frameworks/wordpress/parse.go @@ -7,7 +7,7 @@ import ( "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" ) func Parse(r io.Reader) (lib types.Library, err error) { diff --git a/pkg/dependency/parser/frameworks/wordpress/parse_test.go b/pkg/dependency/parser/frameworks/wordpress/parse_test.go index aafbefe513be..623ae06b87c7 100644 --- a/pkg/dependency/parser/frameworks/wordpress/parse_test.go +++ b/pkg/dependency/parser/frameworks/wordpress/parse_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" ) func TestParseWordPress(t *testing.T) { diff --git a/pkg/dependency/parser/golang/binary/parse.go b/pkg/dependency/parser/golang/binary/parse.go index bd31cec9ad1c..8bf9bf4dab3e 100644 --- a/pkg/dependency/parser/golang/binary/parse.go +++ b/pkg/dependency/parser/golang/binary/parse.go @@ -6,7 +6,7 @@ import ( "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" xio "github.com/aquasecurity/trivy/pkg/x/io" ) diff --git a/pkg/dependency/parser/golang/binary/parse_test.go b/pkg/dependency/parser/golang/binary/parse_test.go index 1d9345fa11d3..cee58377ef25 100644 --- a/pkg/dependency/parser/golang/binary/parse_test.go +++ b/pkg/dependency/parser/golang/binary/parse_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/dependency/parser/golang/binary" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" ) func TestParse(t *testing.T) { diff --git a/pkg/dependency/parser/golang/mod/parse.go b/pkg/dependency/parser/golang/mod/parse.go index 716eed249534..dded3d6527d3 100644 --- a/pkg/dependency/parser/golang/mod/parse.go +++ b/pkg/dependency/parser/golang/mod/parse.go @@ -1,7 +1,6 @@ package mod import ( - "fmt" "io" "regexp" "strconv" @@ -11,7 +10,9 @@ import ( "golang.org/x/mod/modfile" "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency" + "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -89,7 +90,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, continue } libs[require.Mod.Path] = types.Library{ - ID: ModuleID(require.Mod.Path, require.Mod.Version[1:]), + ID: packageID(require.Mod.Path, require.Mod.Version[1:]), Name: require.Mod.Path, Version: require.Mod.Version[1:], Indirect: require.Indirect, @@ -124,7 +125,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, // Add replaced library to library register. libs[rep.New.Path] = types.Library{ - ID: ModuleID(rep.New.Path, rep.New.Version[1:]), + ID: packageID(rep.New.Path, rep.New.Version[1:]), Name: rep.New.Path, Version: rep.New.Version[1:], Indirect: old.Indirect, @@ -154,9 +155,6 @@ func lessThan117(ver string) bool { return major <= 1 && minor < 17 } -// ModuleID returns a module ID according the Go way. -// Format: <module_name>@v<module_version> -// e.g. github.com/aquasecurity/go-dep-parser@v0.0.0-20230130190635-5e31092b0621 -func ModuleID(name, version string) string { - return fmt.Sprintf("%s@v%s", name, version) +func packageID(name, version string) string { + return dependency.ID(ftypes.GoModule, name, version) } diff --git a/pkg/dependency/parser/golang/mod/parse_test.go b/pkg/dependency/parser/golang/mod/parse_test.go index d3a6ee65845b..6372785df058 100644 --- a/pkg/dependency/parser/golang/mod/parse_test.go +++ b/pkg/dependency/parser/golang/mod/parse_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" ) func TestParse(t *testing.T) { @@ -99,37 +99,3 @@ func TestParse(t *testing.T) { }) } } - -func TestModuleID(t *testing.T) { - type args struct { - name string - version string - } - tests := []struct { - name string - args args - want string - }{ - { - name: "normal", - args: args{ - name: "github.com/aquasecurity/trivy", - version: "0.38.0", - }, - want: "github.com/aquasecurity/trivy@v0.38.0", - }, - { - name: "pseudo version", - args: args{ - name: "github.com/aquasecurity/go-dep-parser", - version: "0.0.0-20230130190635-5e31092b0621", - }, - want: "github.com/aquasecurity/go-dep-parser@v0.0.0-20230130190635-5e31092b0621", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert.Equalf(t, tt.want, ModuleID(tt.args.name, tt.args.version), "ModuleID(%v, %v)", tt.args.name, tt.args.version) - }) - } -} diff --git a/pkg/dependency/parser/golang/mod/parse_testcase.go b/pkg/dependency/parser/golang/mod/parse_testcase.go index c0e683185e7a..35a396e53c1c 100644 --- a/pkg/dependency/parser/golang/mod/parse_testcase.go +++ b/pkg/dependency/parser/golang/mod/parse_testcase.go @@ -1,6 +1,6 @@ package mod -import "github.com/aquasecurity/trivy/pkg/dependency/parser/types" +import "github.com/aquasecurity/trivy/pkg/dependency/types" var ( // execute go mod tidy in normal folder diff --git a/pkg/dependency/parser/golang/sum/parse.go b/pkg/dependency/parser/golang/sum/parse.go index 7a5eee5c6f0c..e06b474c0a97 100644 --- a/pkg/dependency/parser/golang/sum/parse.go +++ b/pkg/dependency/parser/golang/sum/parse.go @@ -6,8 +6,9 @@ import ( "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/dependency/parser/golang/mod" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency" + "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -40,7 +41,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, for k, v := range uniqueLibs { libs = append(libs, types.Library{ - ID: mod.ModuleID(k, v), + ID: dependency.ID(ftypes.GoModule, k, v), Name: k, Version: v, }) diff --git a/pkg/dependency/parser/golang/sum/parse_test.go b/pkg/dependency/parser/golang/sum/parse_test.go index 39709bb5008d..3888743cdf85 100644 --- a/pkg/dependency/parser/golang/sum/parse_test.go +++ b/pkg/dependency/parser/golang/sum/parse_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" ) func TestParse(t *testing.T) { diff --git a/pkg/dependency/parser/golang/sum/parse_testcase.go b/pkg/dependency/parser/golang/sum/parse_testcase.go index ea72984159f0..70d4972c6f76 100644 --- a/pkg/dependency/parser/golang/sum/parse_testcase.go +++ b/pkg/dependency/parser/golang/sum/parse_testcase.go @@ -1,6 +1,6 @@ package sum -import "github.com/aquasecurity/trivy/pkg/dependency/parser/types" +import "github.com/aquasecurity/trivy/pkg/dependency/types" var ( // docker run --name gomod --rm -it golang:1.15 bash diff --git a/pkg/dependency/parser/gradle/lockfile/parse.go b/pkg/dependency/parser/gradle/lockfile/parse.go index d88aab6bf461..3a60f3f58872 100644 --- a/pkg/dependency/parser/gradle/lockfile/parse.go +++ b/pkg/dependency/parser/gradle/lockfile/parse.go @@ -2,11 +2,12 @@ package lockfile import ( "bufio" - "fmt" "strings" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency" "github.com/aquasecurity/trivy/pkg/dependency/parser/utils" + "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -36,7 +37,7 @@ func (Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, er name := strings.Join(dep[:2], ":") version := strings.Split(dep[2], "=")[0] // remove classPaths libs = append(libs, types.Library{ - ID: fmt.Sprintf("%s:%s", name, version), + ID: dependency.ID(ftypes.Gradle, name, version), Name: name, Version: version, Locations: []types.Location{ diff --git a/pkg/dependency/parser/gradle/lockfile/parse_test.go b/pkg/dependency/parser/gradle/lockfile/parse_test.go index be198774081a..e9f76883e4e5 100644 --- a/pkg/dependency/parser/gradle/lockfile/parse_test.go +++ b/pkg/dependency/parser/gradle/lockfile/parse_test.go @@ -6,7 +6,7 @@ import ( "strings" "testing" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" "github.com/stretchr/testify/assert" ) diff --git a/pkg/dependency/parser/hex/mix/parse.go b/pkg/dependency/parser/hex/mix/parse.go index 6430d406e0af..edc43fd284c6 100644 --- a/pkg/dependency/parser/hex/mix/parse.go +++ b/pkg/dependency/parser/hex/mix/parse.go @@ -2,12 +2,13 @@ package mix import ( "bufio" - "fmt" "strings" "unicode" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency" "github.com/aquasecurity/trivy/pkg/dependency/parser/utils" + "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -50,10 +51,15 @@ func (Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, er } version := strings.Trim(ss[2], `"`) libs = append(libs, types.Library{ - ID: fmt.Sprintf("%s@%s", name, version), - Name: name, - Version: version, - Locations: []types.Location{{StartLine: lineNumber, EndLine: lineNumber}}, + ID: dependency.ID(ftypes.Hex, name, version), + Name: name, + Version: version, + Locations: []types.Location{ + { + StartLine: lineNumber, + EndLine: lineNumber, + }, + }, }) } diff --git a/pkg/dependency/parser/hex/mix/parse_test.go b/pkg/dependency/parser/hex/mix/parse_test.go index 3392b39468c5..ab3d929dd96f 100644 --- a/pkg/dependency/parser/hex/mix/parse_test.go +++ b/pkg/dependency/parser/hex/mix/parse_test.go @@ -6,7 +6,7 @@ import ( "strings" "testing" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" "github.com/stretchr/testify/assert" ) diff --git a/pkg/dependency/parser/java/jar/parse.go b/pkg/dependency/parser/java/jar/parse.go index 800fc648c14e..d5f1f6df0a4d 100644 --- a/pkg/dependency/parser/java/jar/parse.go +++ b/pkg/dependency/parser/java/jar/parse.go @@ -18,7 +18,7 @@ import ( "go.uber.org/zap" "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" "github.com/aquasecurity/trivy/pkg/log" xio "github.com/aquasecurity/trivy/pkg/x/io" ) diff --git a/pkg/dependency/parser/java/jar/parse_test.go b/pkg/dependency/parser/java/jar/parse_test.go index cf706063335f..88125a5a34b1 100644 --- a/pkg/dependency/parser/java/jar/parse_test.go +++ b/pkg/dependency/parser/java/jar/parse_test.go @@ -14,7 +14,7 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/dependency/parser/java/jar" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" ) var ( diff --git a/pkg/dependency/parser/java/jar/types.go b/pkg/dependency/parser/java/jar/types.go index c01ba6ce4866..ddd378b778b7 100644 --- a/pkg/dependency/parser/java/jar/types.go +++ b/pkg/dependency/parser/java/jar/types.go @@ -5,7 +5,7 @@ import ( "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" ) var ArtifactNotFoundErr = xerrors.New("no artifact found") diff --git a/pkg/dependency/parser/java/pom/artifact.go b/pkg/dependency/parser/java/pom/artifact.go index 86448170f910..7cbab3b5b651 100644 --- a/pkg/dependency/parser/java/pom/artifact.go +++ b/pkg/dependency/parser/java/pom/artifact.go @@ -9,7 +9,7 @@ import ( "github.com/samber/lo" "golang.org/x/exp/slices" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" "github.com/aquasecurity/trivy/pkg/log" ) diff --git a/pkg/dependency/parser/java/pom/parse.go b/pkg/dependency/parser/java/pom/parse.go index fa70995ea5aa..8abecc5df36c 100644 --- a/pkg/dependency/parser/java/pom/parse.go +++ b/pkg/dependency/parser/java/pom/parse.go @@ -18,8 +18,10 @@ import ( "golang.org/x/net/html/charset" "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency" "github.com/aquasecurity/trivy/pkg/dependency/parser/utils" + "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -688,3 +690,7 @@ func parsePom(r io.Reader) (*pomXML, error) { } return parsed, nil } + +func packageID(name, version string) string { + return dependency.ID(ftypes.Pom, name, version) +} diff --git a/pkg/dependency/parser/java/pom/parse_test.go b/pkg/dependency/parser/java/pom/parse_test.go index f3c8c7a41c89..4123d1dde960 100644 --- a/pkg/dependency/parser/java/pom/parse_test.go +++ b/pkg/dependency/parser/java/pom/parse_test.go @@ -11,7 +11,7 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/dependency/parser/java/pom" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" ) func TestPom_Parse(t *testing.T) { diff --git a/pkg/dependency/parser/java/pom/pom.go b/pkg/dependency/parser/java/pom/pom.go index eb3e9ff7cc86..8b610cc5925b 100644 --- a/pkg/dependency/parser/java/pom/pom.go +++ b/pkg/dependency/parser/java/pom/pom.go @@ -12,8 +12,8 @@ import ( "github.com/samber/lo" "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" "github.com/aquasecurity/trivy/pkg/dependency/parser/utils" + "github.com/aquasecurity/trivy/pkg/dependency/types" "github.com/aquasecurity/trivy/pkg/log" ) diff --git a/pkg/dependency/parser/java/pom/utils.go b/pkg/dependency/parser/java/pom/utils.go index 73be21fcc12c..fb42673515f2 100644 --- a/pkg/dependency/parser/java/pom/utils.go +++ b/pkg/dependency/parser/java/pom/utils.go @@ -1,7 +1,6 @@ package pom import ( - "fmt" "os" "strings" ) @@ -20,7 +19,3 @@ func isProperty(version string) bool { } return false } - -func packageID(name, version string) string { - return fmt.Sprintf("%s:%s", name, version) -} diff --git a/pkg/dependency/parser/julia/manifest/parse.go b/pkg/dependency/parser/julia/manifest/parse.go index 3fc99d5ab0b3..685a54074629 100644 --- a/pkg/dependency/parser/julia/manifest/parse.go +++ b/pkg/dependency/parser/julia/manifest/parse.go @@ -8,7 +8,7 @@ import ( "golang.org/x/exp/maps" "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" xio "github.com/aquasecurity/trivy/pkg/x/io" ) diff --git a/pkg/dependency/parser/julia/manifest/parse_test.go b/pkg/dependency/parser/julia/manifest/parse_test.go index 6a3081e99164..6eacf5a76133 100644 --- a/pkg/dependency/parser/julia/manifest/parse_test.go +++ b/pkg/dependency/parser/julia/manifest/parse_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" ) func TestParse(t *testing.T) { diff --git a/pkg/dependency/parser/julia/manifest/parse_testcase.go b/pkg/dependency/parser/julia/manifest/parse_testcase.go index c5b7a00c720c..3055cc8d15a8 100644 --- a/pkg/dependency/parser/julia/manifest/parse_testcase.go +++ b/pkg/dependency/parser/julia/manifest/parse_testcase.go @@ -1,6 +1,6 @@ package julia -import "github.com/aquasecurity/trivy/pkg/dependency/parser/types" +import "github.com/aquasecurity/trivy/pkg/dependency/types" var ( juliaV1_6Libs = []types.Library{ diff --git a/pkg/dependency/parser/nodejs/npm/parse.go b/pkg/dependency/parser/nodejs/npm/parse.go index 0dbc1b1b2bac..e289720b89a2 100644 --- a/pkg/dependency/parser/nodejs/npm/parse.go +++ b/pkg/dependency/parser/nodejs/npm/parse.go @@ -12,8 +12,10 @@ import ( "golang.org/x/exp/maps" "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency" "github.com/aquasecurity/trivy/pkg/dependency/parser/utils" + "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -107,7 +109,7 @@ func (p *Parser) parseV2(packages map[string]Package) ([]types.Library, []types. pkgName = pkgNameFromPath(pkgPath) } - pkgID := utils.PackageID(pkgName, pkg.Version) + pkgID := packageID(pkgName, pkg.Version) location := types.Location{ StartLine: pkg.StartLine, EndLine: pkg.EndLine, @@ -237,7 +239,7 @@ func findDependsOn(pkgPath, depName string, packages map[string]Package) (string modulePath = joinPaths(modulePath, depName) if dep, ok := packages[modulePath]; ok { - return utils.PackageID(depName, dep.Version), nil + return packageID(depName, dep.Version), nil } } @@ -254,40 +256,40 @@ func (p *Parser) parseV1(dependencies map[string]Dependency, versions map[string var libs []types.Library var deps []types.Dependency - for pkgName, dependency := range dependencies { + for pkgName, dep := range dependencies { lib := types.Library{ - ID: utils.PackageID(pkgName, dependency.Version), + ID: packageID(pkgName, dep.Version), Name: pkgName, - Version: dependency.Version, - Dev: dependency.Dev, + Version: dep.Version, + Dev: dep.Dev, Indirect: true, // lockfile v1 schema doesn't have information about Direct dependencies ExternalReferences: []types.ExternalRef{ { Type: types.RefOther, - URL: dependency.Resolved, + URL: dep.Resolved, }, }, Locations: []types.Location{ { - StartLine: dependency.StartLine, - EndLine: dependency.EndLine, + StartLine: dep.StartLine, + EndLine: dep.EndLine, }, }, } libs = append(libs, lib) - dependsOn := make([]string, 0, len(dependency.Requires)) - for libName, requiredVer := range dependency.Requires { + dependsOn := make([]string, 0, len(dep.Requires)) + for libName, requiredVer := range dep.Requires { // Try to resolve the version with nested dependencies first - if resolvedDep, ok := dependency.Dependencies[libName]; ok { - libID := utils.PackageID(libName, resolvedDep.Version) + if resolvedDep, ok := dep.Dependencies[libName]; ok { + libID := packageID(libName, resolvedDep.Version) dependsOn = append(dependsOn, libID) continue } // Try to resolve the version with the higher level dependencies if ver, ok := versions[libName]; ok { - dependsOn = append(dependsOn, utils.PackageID(libName, ver)) + dependsOn = append(dependsOn, packageID(libName, ver)) continue } @@ -297,14 +299,14 @@ func (p *Parser) parseV1(dependencies map[string]Dependency, versions map[string if len(dependsOn) > 0 { deps = append(deps, types.Dependency{ - ID: utils.PackageID(lib.Name, lib.Version), + ID: packageID(lib.Name, lib.Version), DependsOn: dependsOn, }) } - if dependency.Dependencies != nil { + if dep.Dependencies != nil { // Recursion - childLibs, childDeps := p.parseV1(dependency.Dependencies, maps.Clone(versions)) + childLibs, childDeps := p.parseV1(dep.Dependencies, maps.Clone(versions)) libs = append(libs, childLibs...) deps = append(deps, childDeps...) } @@ -379,3 +381,7 @@ func (t *Package) UnmarshalJSONWithMetadata(node jfather.Node) error { t.EndLine = node.Range().End.Line return nil } + +func packageID(name, version string) string { + return dependency.ID(ftypes.Npm, name, version) +} diff --git a/pkg/dependency/parser/nodejs/npm/parse_test.go b/pkg/dependency/parser/nodejs/npm/parse_test.go index 9ccb37f165d0..c67055a71628 100644 --- a/pkg/dependency/parser/nodejs/npm/parse_test.go +++ b/pkg/dependency/parser/nodejs/npm/parse_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" ) func TestParse(t *testing.T) { diff --git a/pkg/dependency/parser/nodejs/npm/parse_testcase.go b/pkg/dependency/parser/nodejs/npm/parse_testcase.go index 6a7237e2bdca..e68addd15219 100644 --- a/pkg/dependency/parser/nodejs/npm/parse_testcase.go +++ b/pkg/dependency/parser/nodejs/npm/parse_testcase.go @@ -1,6 +1,6 @@ package npm -import "github.com/aquasecurity/trivy/pkg/dependency/parser/types" +import "github.com/aquasecurity/trivy/pkg/dependency/types" var ( // docker run --name node --rm -it node@sha256:51dd437f31812df71108b81385e2945071ec813d5815fa3403855669c8f3432b sh @@ -13,52 +13,683 @@ var ( // libraries are filled manually npmV1Libs = []types.Library{ - {ID: "@babel/helper-string-parser@7.19.4", Name: "@babel/helper-string-parser", Version: "7.19.4", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz"}}, Locations: []types.Location{{StartLine: 7, EndLine: 11}}}, - {ID: "asap@2.0.6", Name: "asap", Version: "2.0.6", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz"}}, Locations: []types.Location{{StartLine: 12, EndLine: 17}}}, - {ID: "body-parser@1.18.3", Name: "body-parser", Version: "1.18.3", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz"}}, Locations: []types.Location{{StartLine: 18, EndLine: 49}}}, - {ID: "bytes@3.0.0", Name: "bytes", Version: "3.0.0", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz"}}, Locations: []types.Location{{StartLine: 50, EndLine: 54}}}, - {ID: "content-type@1.0.5", Name: "content-type", Version: "1.0.5", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz"}}, Locations: []types.Location{{StartLine: 55, EndLine: 59}}}, - {ID: "debug@2.5.2", Name: "debug", Version: "2.5.2", Dev: true, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/debug/-/debug-2.5.2.tgz"}}, Locations: []types.Location{{StartLine: 60, EndLine: 76}}}, - {ID: "debug@2.6.9", Name: "debug", Version: "2.6.9", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz"}}, Locations: []types.Location{{StartLine: 35, EndLine: 42}, {StartLine: 111, EndLine: 118}}}, - {ID: "depd@1.1.2", Name: "depd", Version: "1.1.2", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz"}}, Locations: []types.Location{{StartLine: 77, EndLine: 81}}}, - {ID: "ee-first@1.1.1", Name: "ee-first", Version: "1.1.1", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz"}}, Locations: []types.Location{{StartLine: 82, EndLine: 86}}}, - {ID: "encodeurl@1.0.2", Name: "encodeurl", Version: "1.0.2", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz"}}, Locations: []types.Location{{StartLine: 87, EndLine: 91}}}, - {ID: "escape-html@1.0.3", Name: "escape-html", Version: "1.0.3", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz"}}, Locations: []types.Location{{StartLine: 92, EndLine: 96}}}, - {ID: "finalhandler@1.1.1", Name: "finalhandler", Version: "1.1.1", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz"}}, Locations: []types.Location{{StartLine: 97, EndLine: 125}}}, - {ID: "http-errors@1.6.3", Name: "http-errors", Version: "1.6.3", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz"}}, Locations: []types.Location{{StartLine: 126, EndLine: 136}}}, - {ID: "iconv-lite@0.4.23", Name: "iconv-lite", Version: "0.4.23", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz"}}, Locations: []types.Location{{StartLine: 137, EndLine: 144}}}, - {ID: "inherits@2.0.3", Name: "inherits", Version: "2.0.3", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz"}}, Locations: []types.Location{{StartLine: 145, EndLine: 149}}}, - {ID: "media-typer@0.3.0", Name: "media-typer", Version: "0.3.0", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz"}}, Locations: []types.Location{{StartLine: 150, EndLine: 154}}}, - {ID: "mime-db@1.52.0", Name: "mime-db", Version: "1.52.0", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz"}}, Locations: []types.Location{{StartLine: 155, EndLine: 159}}}, - {ID: "mime-types@2.1.35", Name: "mime-types", Version: "2.1.35", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz"}}, Locations: []types.Location{{StartLine: 160, EndLine: 167}}}, - {ID: "ms@0.7.2", Name: "ms", Version: "0.7.2", Dev: true, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz"}}, Locations: []types.Location{{StartLine: 69, EndLine: 74}}}, - {ID: "ms@1.0.0", Name: "ms", Version: "1.0.0", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/ms/-/ms-1.0.0.tgz"}}, Locations: []types.Location{{StartLine: 168, EndLine: 172}}}, - {ID: "ms@2.0.0", Name: "ms", Version: "2.0.0", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz"}}, Locations: []types.Location{{StartLine: 43, EndLine: 47}, {StartLine: 119, EndLine: 123}}}, - {ID: "on-finished@2.3.0", Name: "on-finished", Version: "2.3.0", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz"}}, Locations: []types.Location{{StartLine: 173, EndLine: 180}}}, - {ID: "parseurl@1.3.3", Name: "parseurl", Version: "1.3.3", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz"}}, Locations: []types.Location{{StartLine: 181, EndLine: 185}}}, - {ID: "promise@8.3.0", Name: "promise", Version: "8.3.0", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz"}}, Locations: []types.Location{{StartLine: 186, EndLine: 194}}}, - {ID: "qs@6.5.2", Name: "qs", Version: "6.5.2", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz"}}, Locations: []types.Location{{StartLine: 195, EndLine: 199}}}, - {ID: "raw-body@2.3.3", Name: "raw-body", Version: "2.3.3", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz"}}, Locations: []types.Location{{StartLine: 200, EndLine: 210}}}, - {ID: "safer-buffer@2.1.2", Name: "safer-buffer", Version: "2.1.2", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz"}}, Locations: []types.Location{{StartLine: 211, EndLine: 215}}}, - {ID: "setprototypeof@1.1.0", Name: "setprototypeof", Version: "1.1.0", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz"}}, Locations: []types.Location{{StartLine: 216, EndLine: 220}}}, - {ID: "statuses@1.4.0", Name: "statuses", Version: "1.4.0", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz"}}, Locations: []types.Location{{StartLine: 221, EndLine: 225}}}, - {ID: "type-is@1.6.18", Name: "type-is", Version: "1.6.18", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz"}}, Locations: []types.Location{{StartLine: 226, EndLine: 234}}}, - {ID: "unpipe@1.0.0", Name: "unpipe", Version: "1.0.0", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz"}}, Locations: []types.Location{{StartLine: 235, EndLine: 239}}}, + { + ID: "@babel/helper-string-parser@7.19.4", + Name: "@babel/helper-string-parser", + Version: "7.19.4", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 7, + EndLine: 11, + }, + }, + }, + { + ID: "asap@2.0.6", + Name: "asap", + Version: "2.0.6", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 12, + EndLine: 17, + }, + }, + }, + { + ID: "body-parser@1.18.3", + Name: "body-parser", + Version: "1.18.3", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 18, + EndLine: 49, + }, + }, + }, + { + ID: "bytes@3.0.0", + Name: "bytes", + Version: "3.0.0", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 50, + EndLine: 54, + }, + }, + }, + { + ID: "content-type@1.0.5", + Name: "content-type", + Version: "1.0.5", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 55, + EndLine: 59, + }, + }, + }, + { + ID: "debug@2.5.2", + Name: "debug", + Version: "2.5.2", + Dev: true, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/debug/-/debug-2.5.2.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 60, + EndLine: 76, + }, + }, + }, + { + ID: "debug@2.6.9", + Name: "debug", + Version: "2.6.9", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 35, + EndLine: 42, + }, + { + StartLine: 111, + EndLine: 118, + }, + }, + }, + { + ID: "depd@1.1.2", + Name: "depd", + Version: "1.1.2", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 77, + EndLine: 81, + }, + }, + }, + { + ID: "ee-first@1.1.1", + Name: "ee-first", + Version: "1.1.1", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 82, + EndLine: 86, + }, + }, + }, + { + ID: "encodeurl@1.0.2", + Name: "encodeurl", + Version: "1.0.2", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 87, + EndLine: 91, + }, + }, + }, + { + ID: "escape-html@1.0.3", + Name: "escape-html", + Version: "1.0.3", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 92, + EndLine: 96, + }, + }, + }, + { + ID: "finalhandler@1.1.1", + Name: "finalhandler", + Version: "1.1.1", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 97, + EndLine: 125, + }, + }, + }, + { + ID: "http-errors@1.6.3", + Name: "http-errors", + Version: "1.6.3", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 126, + EndLine: 136, + }, + }, + }, + { + ID: "iconv-lite@0.4.23", + Name: "iconv-lite", + Version: "0.4.23", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 137, + EndLine: 144, + }, + }, + }, + { + ID: "inherits@2.0.3", + Name: "inherits", + Version: "2.0.3", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 145, + EndLine: 149, + }, + }, + }, + { + ID: "media-typer@0.3.0", + Name: "media-typer", + Version: "0.3.0", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 150, + EndLine: 154, + }, + }, + }, + { + ID: "mime-db@1.52.0", + Name: "mime-db", + Version: "1.52.0", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 155, + EndLine: 159, + }, + }, + }, + { + ID: "mime-types@2.1.35", + Name: "mime-types", + Version: "2.1.35", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 160, + EndLine: 167, + }, + }, + }, + { + ID: "ms@0.7.2", + Name: "ms", + Version: "0.7.2", + Dev: true, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 69, + EndLine: 74, + }, + }, + }, + { + ID: "ms@1.0.0", + Name: "ms", + Version: "1.0.0", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/ms/-/ms-1.0.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 168, + EndLine: 172, + }, + }, + }, + { + ID: "ms@2.0.0", + Name: "ms", + Version: "2.0.0", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 43, + EndLine: 47, + }, + { + StartLine: 119, + EndLine: 123, + }, + }, + }, + { + ID: "on-finished@2.3.0", + Name: "on-finished", + Version: "2.3.0", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 173, + EndLine: 180, + }, + }, + }, + { + ID: "parseurl@1.3.3", + Name: "parseurl", + Version: "1.3.3", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 181, + EndLine: 185, + }, + }, + }, + { + ID: "promise@8.3.0", + Name: "promise", + Version: "8.3.0", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 186, + EndLine: 194, + }, + }, + }, + { + ID: "qs@6.5.2", + Name: "qs", + Version: "6.5.2", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 195, + EndLine: 199, + }, + }, + }, + { + ID: "raw-body@2.3.3", + Name: "raw-body", + Version: "2.3.3", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 200, + EndLine: 210, + }, + }, + }, + { + ID: "safer-buffer@2.1.2", + Name: "safer-buffer", + Version: "2.1.2", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 211, + EndLine: 215, + }, + }, + }, + { + ID: "setprototypeof@1.1.0", + Name: "setprototypeof", + Version: "1.1.0", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 216, + EndLine: 220, + }, + }, + }, + { + ID: "statuses@1.4.0", + Name: "statuses", + Version: "1.4.0", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 221, + EndLine: 225, + }, + }, + }, + { + ID: "type-is@1.6.18", + Name: "type-is", + Version: "1.6.18", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 226, + EndLine: 234, + }, + }, + }, + { + ID: "unpipe@1.0.0", + Name: "unpipe", + Version: "1.0.0", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 235, + EndLine: 239, + }, + }, + }, } // dependencies are filled manually npmDeps = []types.Dependency{ - {ID: "body-parser@1.18.3", DependsOn: []string{"bytes@3.0.0", "content-type@1.0.5", "debug@2.6.9", "depd@1.1.2", "http-errors@1.6.3", "iconv-lite@0.4.23", "on-finished@2.3.0", "qs@6.5.2", "raw-body@2.3.3", "type-is@1.6.18"}}, - {ID: "debug@2.5.2", DependsOn: []string{"ms@0.7.2"}}, - {ID: "debug@2.6.9", DependsOn: []string{"ms@2.0.0"}}, - {ID: "finalhandler@1.1.1", DependsOn: []string{"debug@2.6.9", "encodeurl@1.0.2", "escape-html@1.0.3", "on-finished@2.3.0", "parseurl@1.3.3", "statuses@1.4.0", "unpipe@1.0.0"}}, - {ID: "http-errors@1.6.3", DependsOn: []string{"depd@1.1.2", "inherits@2.0.3", "setprototypeof@1.1.0", "statuses@1.4.0"}}, - {ID: "iconv-lite@0.4.23", DependsOn: []string{"safer-buffer@2.1.2"}}, - {ID: "mime-types@2.1.35", DependsOn: []string{"mime-db@1.52.0"}}, - {ID: "on-finished@2.3.0", DependsOn: []string{"ee-first@1.1.1"}}, - {ID: "promise@8.3.0", DependsOn: []string{"asap@2.0.6"}}, - {ID: "raw-body@2.3.3", DependsOn: []string{"bytes@3.0.0", "http-errors@1.6.3", "iconv-lite@0.4.23", "unpipe@1.0.0"}}, - {ID: "type-is@1.6.18", DependsOn: []string{"media-typer@0.3.0", "mime-types@2.1.35"}}, + { + ID: "body-parser@1.18.3", + DependsOn: []string{ + "bytes@3.0.0", + "content-type@1.0.5", + "debug@2.6.9", + "depd@1.1.2", + "http-errors@1.6.3", + "iconv-lite@0.4.23", + "on-finished@2.3.0", + "qs@6.5.2", + "raw-body@2.3.3", + "type-is@1.6.18", + }, + }, + { + ID: "debug@2.5.2", + DependsOn: []string{"ms@0.7.2"}, + }, + { + ID: "debug@2.6.9", + DependsOn: []string{"ms@2.0.0"}, + }, + { + ID: "finalhandler@1.1.1", + DependsOn: []string{ + "debug@2.6.9", + "encodeurl@1.0.2", + "escape-html@1.0.3", + "on-finished@2.3.0", + "parseurl@1.3.3", + "statuses@1.4.0", + "unpipe@1.0.0", + }, + }, + { + ID: "http-errors@1.6.3", + DependsOn: []string{ + "depd@1.1.2", + "inherits@2.0.3", + "setprototypeof@1.1.0", + "statuses@1.4.0", + }, + }, + { + ID: "iconv-lite@0.4.23", + DependsOn: []string{"safer-buffer@2.1.2"}, + }, + { + ID: "mime-types@2.1.35", + DependsOn: []string{"mime-db@1.52.0"}, + }, + { + ID: "on-finished@2.3.0", + DependsOn: []string{"ee-first@1.1.1"}, + }, + { + ID: "promise@8.3.0", + DependsOn: []string{"asap@2.0.6"}, + }, + { + ID: "raw-body@2.3.3", + DependsOn: []string{ + "bytes@3.0.0", + "http-errors@1.6.3", + "iconv-lite@0.4.23", + "unpipe@1.0.0", + }, + }, + { + ID: "type-is@1.6.18", + DependsOn: []string{ + "media-typer@0.3.0", + "mime-types@2.1.35", + }, + }, } // ... and @@ -69,37 +700,603 @@ var ( // npm i --lockfile-version 3 // same as npmV2Libs. npmV2Libs = []types.Library{ - {ID: "@babel/helper-string-parser@7.19.4", Name: "@babel/helper-string-parser", Version: "7.19.4", Dev: false, Indirect: false, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz"}}, Locations: []types.Location{{StartLine: 24, EndLine: 31}}}, - {ID: "asap@2.0.6", Name: "asap", Version: "2.0.6", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz"}}, Locations: []types.Location{{StartLine: 32, EndLine: 37}}}, - {ID: "body-parser@1.18.3", Name: "body-parser", Version: "1.18.3", Dev: false, Indirect: false, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz"}}, Locations: []types.Location{{StartLine: 38, EndLine: 57}}}, - {ID: "bytes@3.0.0", Name: "bytes", Version: "3.0.0", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz"}}, Locations: []types.Location{{StartLine: 71, EndLine: 78}}}, - {ID: "content-type@1.0.5", Name: "content-type", Version: "1.0.5", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz"}}, Locations: []types.Location{{StartLine: 79, EndLine: 86}}}, - {ID: "debug@2.5.2", Name: "debug", Version: "2.5.2", Dev: true, Indirect: false, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/debug/-/debug-2.5.2.tgz"}}, Locations: []types.Location{{StartLine: 87, EndLine: 95}}}, - {ID: "debug@2.6.9", Name: "debug", Version: "2.6.9", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz"}}, Locations: []types.Location{{StartLine: 58, EndLine: 65}, {StartLine: 145, EndLine: 152}}}, - {ID: "depd@1.1.2", Name: "depd", Version: "1.1.2", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz"}}, Locations: []types.Location{{StartLine: 102, EndLine: 109}}}, - {ID: "ee-first@1.1.1", Name: "ee-first", Version: "1.1.1", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz"}}, Locations: []types.Location{{StartLine: 110, EndLine: 114}}}, - {ID: "encodeurl@1.0.2", Name: "encodeurl", Version: "1.0.2", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz"}}, Locations: []types.Location{{StartLine: 115, EndLine: 122}}}, - {ID: "escape-html@1.0.3", Name: "escape-html", Version: "1.0.3", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz"}}, Locations: []types.Location{{StartLine: 123, EndLine: 127}}}, - {ID: "finalhandler@1.1.1", Name: "finalhandler", Version: "1.1.1", Dev: false, Indirect: false, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz"}}, Locations: []types.Location{{StartLine: 128, EndLine: 144}}}, - {ID: "http-errors@1.6.3", Name: "http-errors", Version: "1.6.3", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz"}}, Locations: []types.Location{{StartLine: 158, EndLine: 171}}}, - {ID: "iconv-lite@0.4.23", Name: "iconv-lite", Version: "0.4.23", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz"}}, Locations: []types.Location{{StartLine: 172, EndLine: 182}}}, - {ID: "inherits@2.0.3", Name: "inherits", Version: "2.0.3", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz"}}, Locations: []types.Location{{StartLine: 183, EndLine: 187}}}, - {ID: "media-typer@0.3.0", Name: "media-typer", Version: "0.3.0", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz"}}, Locations: []types.Location{{StartLine: 188, EndLine: 195}}}, - {ID: "mime-db@1.52.0", Name: "mime-db", Version: "1.52.0", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz"}}, Locations: []types.Location{{StartLine: 196, EndLine: 203}}}, - {ID: "mime-types@2.1.35", Name: "mime-types", Version: "2.1.35", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz"}}, Locations: []types.Location{{StartLine: 204, EndLine: 214}}}, - {ID: "ms@0.7.2", Name: "ms", Version: "0.7.2", Dev: true, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz"}}, Locations: []types.Location{{StartLine: 96, EndLine: 101}}}, - {ID: "ms@1.0.0", Name: "ms", Version: "1.0.0", Dev: false, Indirect: false, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/ms/-/ms-1.0.0.tgz"}}, Locations: []types.Location{{StartLine: 215, EndLine: 219}}}, - {ID: "ms@2.0.0", Name: "ms", Version: "2.0.0", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz"}}, Locations: []types.Location{{StartLine: 66, EndLine: 70}, {StartLine: 153, EndLine: 157}}}, - {ID: "on-finished@2.3.0", Name: "on-finished", Version: "2.3.0", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz"}}, Locations: []types.Location{{StartLine: 220, EndLine: 230}}}, - {ID: "parseurl@1.3.3", Name: "parseurl", Version: "1.3.3", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz"}}, Locations: []types.Location{{StartLine: 231, EndLine: 238}}}, - {ID: "promise@8.3.0", Name: "promise", Version: "8.3.0", Dev: false, Indirect: false, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz"}}, Locations: []types.Location{{StartLine: 239, EndLine: 247}}}, - {ID: "qs@6.5.2", Name: "qs", Version: "6.5.2", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz"}}, Locations: []types.Location{{StartLine: 248, EndLine: 255}}}, - {ID: "raw-body@2.3.3", Name: "raw-body", Version: "2.3.3", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz"}}, Locations: []types.Location{{StartLine: 256, EndLine: 269}}}, - {ID: "safer-buffer@2.1.2", Name: "safer-buffer", Version: "2.1.2", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz"}}, Locations: []types.Location{{StartLine: 270, EndLine: 274}}}, - {ID: "setprototypeof@1.1.0", Name: "setprototypeof", Version: "1.1.0", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz"}}, Locations: []types.Location{{StartLine: 275, EndLine: 279}}}, - {ID: "statuses@1.4.0", Name: "statuses", Version: "1.4.0", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz"}}, Locations: []types.Location{{StartLine: 280, EndLine: 287}}}, - {ID: "type-is@1.6.18", Name: "type-is", Version: "1.6.18", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz"}}, Locations: []types.Location{{StartLine: 288, EndLine: 299}}}, - {ID: "unpipe@1.0.0", Name: "unpipe", Version: "1.0.0", Dev: false, Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz"}}, Locations: []types.Location{{StartLine: 300, EndLine: 307}}}, + { + ID: "@babel/helper-string-parser@7.19.4", + Name: "@babel/helper-string-parser", + Version: "7.19.4", + Dev: false, + Indirect: false, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 24, + EndLine: 31, + }, + }, + }, + { + ID: "asap@2.0.6", + Name: "asap", + Version: "2.0.6", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 32, + EndLine: 37, + }, + }, + }, + { + ID: "body-parser@1.18.3", + Name: "body-parser", + Version: "1.18.3", + Dev: false, + Indirect: false, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 38, + EndLine: 57, + }, + }, + }, + { + ID: "bytes@3.0.0", + Name: "bytes", + Version: "3.0.0", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 71, + EndLine: 78, + }, + }, + }, + { + ID: "content-type@1.0.5", + Name: "content-type", + Version: "1.0.5", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 79, + EndLine: 86, + }, + }, + }, + { + ID: "debug@2.5.2", + Name: "debug", + Version: "2.5.2", + Dev: true, + Indirect: false, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/debug/-/debug-2.5.2.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 87, + EndLine: 95, + }, + }, + }, + { + ID: "debug@2.6.9", + Name: "debug", + Version: "2.6.9", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 58, + EndLine: 65, + }, + { + StartLine: 145, + EndLine: 152, + }, + }, + }, + { + ID: "depd@1.1.2", + Name: "depd", + Version: "1.1.2", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 102, + EndLine: 109, + }, + }, + }, + { + ID: "ee-first@1.1.1", + Name: "ee-first", + Version: "1.1.1", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 110, + EndLine: 114, + }, + }, + }, + { + ID: "encodeurl@1.0.2", + Name: "encodeurl", + Version: "1.0.2", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 115, + EndLine: 122, + }, + }, + }, + { + ID: "escape-html@1.0.3", + Name: "escape-html", + Version: "1.0.3", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 123, + EndLine: 127, + }, + }, + }, + { + ID: "finalhandler@1.1.1", + Name: "finalhandler", + Version: "1.1.1", + Dev: false, + Indirect: false, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 128, + EndLine: 144, + }, + }, + }, + { + ID: "http-errors@1.6.3", + Name: "http-errors", + Version: "1.6.3", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 158, + EndLine: 171, + }, + }, + }, + { + ID: "iconv-lite@0.4.23", + Name: "iconv-lite", + Version: "0.4.23", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 172, + EndLine: 182, + }, + }, + }, + { + ID: "inherits@2.0.3", + Name: "inherits", + Version: "2.0.3", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 183, + EndLine: 187, + }, + }, + }, + { + ID: "media-typer@0.3.0", + Name: "media-typer", + Version: "0.3.0", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 188, + EndLine: 195, + }, + }, + }, + { + ID: "mime-db@1.52.0", + Name: "mime-db", + Version: "1.52.0", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 196, + EndLine: 203, + }, + }, + }, + { + ID: "mime-types@2.1.35", + Name: "mime-types", + Version: "2.1.35", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 204, + EndLine: 214, + }, + }, + }, + { + ID: "ms@0.7.2", + Name: "ms", + Version: "0.7.2", + Dev: true, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 96, + EndLine: 101, + }, + }, + }, + { + ID: "ms@1.0.0", + Name: "ms", + Version: "1.0.0", + Dev: false, + Indirect: false, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/ms/-/ms-1.0.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 215, + EndLine: 219, + }, + }, + }, + { + ID: "ms@2.0.0", + Name: "ms", + Version: "2.0.0", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 66, + EndLine: 70, + }, + { + StartLine: 153, + EndLine: 157, + }, + }, + }, + { + ID: "on-finished@2.3.0", + Name: "on-finished", + Version: "2.3.0", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 220, + EndLine: 230, + }, + }, + }, + { + ID: "parseurl@1.3.3", + Name: "parseurl", + Version: "1.3.3", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 231, + EndLine: 238, + }, + }, + }, + { + ID: "promise@8.3.0", + Name: "promise", + Version: "8.3.0", + Dev: false, + Indirect: false, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 239, + EndLine: 247, + }, + }, + }, + { + ID: "qs@6.5.2", + Name: "qs", + Version: "6.5.2", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 248, + EndLine: 255, + }, + }, + }, + { + ID: "raw-body@2.3.3", + Name: "raw-body", + Version: "2.3.3", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 256, + EndLine: 269, + }, + }, + }, + { + ID: "safer-buffer@2.1.2", + Name: "safer-buffer", + Version: "2.1.2", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 270, + EndLine: 274, + }, + }, + }, + { + ID: "setprototypeof@1.1.0", + Name: "setprototypeof", + Version: "1.1.0", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 275, + EndLine: 279, + }, + }, + }, + { + ID: "statuses@1.4.0", + Name: "statuses", + Version: "1.4.0", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 280, + EndLine: 287, + }, + }, + }, + { + ID: "type-is@1.6.18", + Name: "type-is", + Version: "1.6.18", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 288, + EndLine: 299, + }, + }, + }, + { + ID: "unpipe@1.0.0", + Name: "unpipe", + Version: "1.0.0", + Dev: false, + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 300, + EndLine: 307, + }, + }, + }, } // docker run --name node --rm -it node@sha256:51dd437f31812df71108b81385e2945071ec813d5815fa3403855669c8f3432b sh @@ -117,19 +1314,133 @@ var ( // npm update // libraries are filled manually npmV3WithWorkspaceLibs = []types.Library{ - {ID: "debug@2.5.2", Name: "debug", Version: "2.5.2", Indirect: false, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/debug/-/debug-2.5.2.tgz"}}, Locations: []types.Location{{StartLine: 39, EndLine: 46}}}, - {ID: "debug@2.6.9", Name: "debug", Version: "2.6.9", Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz"}}, Locations: []types.Location{{StartLine: 31, EndLine: 38}}}, - {ID: "function1@", Name: "function1", Version: "", Indirect: false, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "functions/func1"}}, Locations: []types.Location{{StartLine: 18, EndLine: 23}}}, - {ID: "ms@0.7.2", Name: "ms", Version: "0.7.2", Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz"}}, Locations: []types.Location{{StartLine: 47, EndLine: 51}}}, - {ID: "ms@2.0.0", Name: "ms", Version: "2.0.0", Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz"}}, Locations: []types.Location{{StartLine: 56, EndLine: 60}}}, - {ID: "nested_func@1.0.0", Name: "nested_func", Version: "1.0.0", Indirect: false, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "functions/nested_func"}}, Locations: []types.Location{{StartLine: 24, EndLine: 30}}}, + { + ID: "debug@2.5.2", + Name: "debug", + Version: "2.5.2", + Indirect: false, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/debug/-/debug-2.5.2.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 39, + EndLine: 46, + }, + }, + }, + { + ID: "debug@2.6.9", + Name: "debug", + Version: "2.6.9", + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 31, + EndLine: 38, + }, + }, + }, + { + ID: "function1", + Name: "function1", + Version: "", + Indirect: false, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "functions/func1", + }, + }, + Locations: []types.Location{ + { + StartLine: 18, + EndLine: 23, + }, + }, + }, + { + ID: "ms@0.7.2", + Name: "ms", + Version: "0.7.2", + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 47, + EndLine: 51, + }, + }, + }, + { + ID: "ms@2.0.0", + Name: "ms", + Version: "2.0.0", + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 56, + EndLine: 60, + }, + }, + }, + { + ID: "nested_func@1.0.0", + Name: "nested_func", + Version: "1.0.0", + Indirect: false, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "functions/nested_func", + }, + }, + Locations: []types.Location{ + { + StartLine: 24, + EndLine: 30, + }, + }, + }, } npmV3WithWorkspaceDeps = []types.Dependency{ - {ID: "debug@2.5.2", DependsOn: []string{"ms@0.7.2"}}, - {ID: "debug@2.6.9", DependsOn: []string{"ms@2.0.0"}}, - {ID: "function1@", DependsOn: []string{"nested_func@1.0.0"}}, - {ID: "nested_func@1.0.0", DependsOn: []string{"debug@2.6.9"}}, + { + ID: "debug@2.5.2", + DependsOn: []string{"ms@0.7.2"}, + }, + { + ID: "debug@2.6.9", + DependsOn: []string{"ms@2.0.0"}, + }, + { + ID: "function1", + DependsOn: []string{"nested_func@1.0.0"}, + }, + { + ID: "nested_func@1.0.0", + DependsOn: []string{"debug@2.6.9"}, + }, } // docker run --name node --rm -it node@sha256:51dd437f31812df71108b81385e2945071ec813d5815fa3403855669c8f3432b sh @@ -139,13 +1450,70 @@ var ( // npm install --save debug@2.6.9 -w func1 // libraries are filled manually npmV3WithoutRootDepsField = []types.Library{ - {ID: "debug@2.6.9", Name: "debug", Version: "2.6.9", Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz"}}, Locations: []types.Location{{StartLine: 22, EndLine: 29}}}, - {ID: "func1@1.0.0", Name: "func1", Version: "1.0.0", Indirect: false, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "functions/func1"}}, Locations: []types.Location{{StartLine: 15, EndLine: 21}}}, - {ID: "ms@2.0.0", Name: "ms", Version: "2.0.0", Indirect: true, ExternalReferences: []types.ExternalRef{{Type: types.RefOther, URL: "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz"}}, Locations: []types.Location{{StartLine: 34, EndLine: 38}}}, + { + ID: "debug@2.6.9", + Name: "debug", + Version: "2.6.9", + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 22, + EndLine: 29, + }, + }, + }, + { + ID: "func1@1.0.0", + Name: "func1", + Version: "1.0.0", + Indirect: false, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "functions/func1", + }, + }, + Locations: []types.Location{ + { + StartLine: 15, + EndLine: 21, + }, + }, + }, + { + ID: "ms@2.0.0", + Name: "ms", + Version: "2.0.0", + Indirect: true, + ExternalReferences: []types.ExternalRef{ + { + Type: types.RefOther, + URL: "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + }, + }, + Locations: []types.Location{ + { + StartLine: 34, + EndLine: 38, + }, + }, + }, } npmV3WithoutRootDepsFieldDeps = []types.Dependency{ - {ID: "debug@2.6.9", DependsOn: []string{"ms@2.0.0"}}, - {ID: "func1@1.0.0", DependsOn: []string{"debug@2.6.9"}}, + { + ID: "debug@2.6.9", + DependsOn: []string{"ms@2.0.0"}, + }, + { + ID: "func1@1.0.0", + DependsOn: []string{"debug@2.6.9"}, + }, } ) diff --git a/pkg/dependency/parser/nodejs/packagejson/parse.go b/pkg/dependency/parser/nodejs/packagejson/parse.go index f2558750c323..f4bf258f7aae 100644 --- a/pkg/dependency/parser/nodejs/packagejson/parse.go +++ b/pkg/dependency/parser/nodejs/packagejson/parse.go @@ -7,8 +7,9 @@ import ( "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" - "github.com/aquasecurity/trivy/pkg/dependency/parser/utils" + "github.com/aquasecurity/trivy/pkg/dependency" + "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) var nameRegexp = regexp.MustCompile(`^(@[A-Za-z0-9-._]+/)?[A-Za-z0-9-._]+$`) @@ -51,7 +52,7 @@ func (p *Parser) Parse(r io.Reader) (Package, error) { // Name and version fields are optional // https://docs.npmjs.com/cli/v9/configuring-npm/package-json#name if pkgJSON.Name != "" && pkgJSON.Version != "" { - id = utils.PackageID(pkgJSON.Name, pkgJSON.Version) + id = dependency.ID(ftypes.NodePkg, pkgJSON.Name, pkgJSON.Version) } return Package{ diff --git a/pkg/dependency/parser/nodejs/packagejson/parse_test.go b/pkg/dependency/parser/nodejs/packagejson/parse_test.go index 9b925a525be3..4f04cebcc1ee 100644 --- a/pkg/dependency/parser/nodejs/packagejson/parse_test.go +++ b/pkg/dependency/parser/nodejs/packagejson/parse_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/dependency/parser/nodejs/packagejson" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" ) func TestParse(t *testing.T) { diff --git a/pkg/dependency/parser/nodejs/pnpm/parse.go b/pkg/dependency/parser/nodejs/pnpm/parse.go index e36166f84782..9e93be6a89c1 100644 --- a/pkg/dependency/parser/nodejs/pnpm/parse.go +++ b/pkg/dependency/parser/nodejs/pnpm/parse.go @@ -9,7 +9,9 @@ import ( "gopkg.in/yaml.v3" "github.com/aquasecurity/go-version/pkg/semver" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency" + "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -40,10 +42,6 @@ func NewParser() types.Parser { return &Parser{} } -func (p *Parser) ID(name, version string) string { - return fmt.Sprintf("%s@%s", name, version) -} - func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, error) { var lockFile LockFile if err := yaml.NewDecoder(r).Decode(&lockFile); err != nil { @@ -80,11 +78,11 @@ func (p *Parser) parse(lockVer float64, lockFile LockFile) ([]types.Library, []t if name == "" { name, version = parsePackage(depPath, lockVer) } - pkgID := p.ID(name, version) + pkgID := packageID(name, version) dependencies := make([]string, 0, len(info.Dependencies)) for depName, depVer := range info.Dependencies { - dependencies = append(dependencies, p.ID(depName, depVer)) + dependencies = append(dependencies, packageID(depName, depVer)) } libs = append(libs, types.Library{ @@ -178,3 +176,7 @@ func parseDepPath(depPath, versionSep string) (string, string) { } return name, version } + +func packageID(name, version string) string { + return dependency.ID(ftypes.Pnpm, name, version) +} diff --git a/pkg/dependency/parser/nodejs/pnpm/parse_test.go b/pkg/dependency/parser/nodejs/pnpm/parse_test.go index 183c015a12d9..19851a2c21c0 100644 --- a/pkg/dependency/parser/nodejs/pnpm/parse_test.go +++ b/pkg/dependency/parser/nodejs/pnpm/parse_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" ) func TestParse(t *testing.T) { diff --git a/pkg/dependency/parser/nodejs/pnpm/parse_testcase.go b/pkg/dependency/parser/nodejs/pnpm/parse_testcase.go index 83ea044db638..2b776cd0a9c8 100644 --- a/pkg/dependency/parser/nodejs/pnpm/parse_testcase.go +++ b/pkg/dependency/parser/nodejs/pnpm/parse_testcase.go @@ -1,6 +1,6 @@ package pnpm -import "github.com/aquasecurity/trivy/pkg/dependency/parser/types" +import "github.com/aquasecurity/trivy/pkg/dependency/types" var ( // docker run --name node --rm -it node:16-alpine sh diff --git a/pkg/dependency/parser/nodejs/yarn/parse.go b/pkg/dependency/parser/nodejs/yarn/parse.go index 0264afc7cefa..9b8394eb57c8 100644 --- a/pkg/dependency/parser/nodejs/yarn/parse.go +++ b/pkg/dependency/parser/nodejs/yarn/parse.go @@ -10,8 +10,9 @@ import ( "github.com/samber/lo" "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" - "github.com/aquasecurity/trivy/pkg/dependency/parser/utils" + "github.com/aquasecurity/trivy/pkg/dependency" + "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -86,7 +87,7 @@ func parsePackagePatterns(target string) (packagename, protocol string, patterns } patterns = lo.Map(patternsSplit, func(pattern string, _ int) string { _, _, version, _ := parsePattern(pattern) - return utils.PackageID(packagename, version) + return packageID(packagename, version) }) return } @@ -261,7 +262,7 @@ func parseDependency(line string) (string, error) { if name, version, err := getDependency(line); err != nil { return "", err } else { - return utils.PackageID(name, version), nil + return packageID(name, version), nil } } @@ -286,7 +287,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, continue } - libID := utils.PackageID(lib.Name, lib.Version) + libID := packageID(lib.Name, lib.Version) libs = append(libs, types.Library{ ID: libID, Name: lib.Name, @@ -314,3 +315,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, deps := parseResults(patternIDs, dependsOn) return libs, deps, nil } + +func packageID(name, version string) string { + return dependency.ID(ftypes.Yarn, name, version) +} diff --git a/pkg/dependency/parser/nodejs/yarn/parse_test.go b/pkg/dependency/parser/nodejs/yarn/parse_test.go index 07a85a718fc0..90ce497ed9d8 100644 --- a/pkg/dependency/parser/nodejs/yarn/parse_test.go +++ b/pkg/dependency/parser/nodejs/yarn/parse_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" ) func TestParsePattern(t *testing.T) { diff --git a/pkg/dependency/parser/nodejs/yarn/parse_testcase.go b/pkg/dependency/parser/nodejs/yarn/parse_testcase.go index 4325b0e53ef6..e9d48c246f29 100644 --- a/pkg/dependency/parser/nodejs/yarn/parse_testcase.go +++ b/pkg/dependency/parser/nodejs/yarn/parse_testcase.go @@ -1,6 +1,6 @@ package yarn -import "github.com/aquasecurity/trivy/pkg/dependency/parser/types" +import "github.com/aquasecurity/trivy/pkg/dependency/types" var ( yarnHappy = []types.Library{ diff --git a/pkg/dependency/parser/nuget/config/parse.go b/pkg/dependency/parser/nuget/config/parse.go index ff6bafd31a03..6a43d30ddf94 100644 --- a/pkg/dependency/parser/nuget/config/parse.go +++ b/pkg/dependency/parser/nuget/config/parse.go @@ -5,8 +5,8 @@ import ( "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" "github.com/aquasecurity/trivy/pkg/dependency/parser/utils" + "github.com/aquasecurity/trivy/pkg/dependency/types" xio "github.com/aquasecurity/trivy/pkg/x/io" ) diff --git a/pkg/dependency/parser/nuget/config/parse_test.go b/pkg/dependency/parser/nuget/config/parse_test.go index 229e09f50217..864246bc9c44 100644 --- a/pkg/dependency/parser/nuget/config/parse_test.go +++ b/pkg/dependency/parser/nuget/config/parse_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/dependency/parser/nuget/config" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" ) func TestParse(t *testing.T) { diff --git a/pkg/dependency/parser/nuget/lock/parse.go b/pkg/dependency/parser/nuget/lock/parse.go index 8b3a238178d0..30205362db8a 100644 --- a/pkg/dependency/parser/nuget/lock/parse.go +++ b/pkg/dependency/parser/nuget/lock/parse.go @@ -6,8 +6,10 @@ import ( "github.com/liamg/jfather" "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency" "github.com/aquasecurity/trivy/pkg/dependency/parser/utils" + "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -51,7 +53,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, continue } - depId := utils.PackageID(packageName, packageContent.Resolved) + depId := packageID(packageName, packageContent.Resolved) lib := types.Library{ ID: depId, @@ -70,7 +72,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, var dependsOn []string for depName := range packageContent.Dependencies { - dependsOn = append(dependsOn, utils.PackageID(depName, targetContent[depName].Resolved)) + dependsOn = append(dependsOn, packageID(depName, targetContent[depName].Resolved)) } if savedDependsOn, ok := depsMap[depId]; ok { @@ -105,3 +107,7 @@ func (t *Dependency) UnmarshalJSONWithMetadata(node jfather.Node) error { t.EndLine = node.Range().End.Line return nil } + +func packageID(name, version string) string { + return dependency.ID(ftypes.NuGet, name, version) +} diff --git a/pkg/dependency/parser/nuget/lock/parse_test.go b/pkg/dependency/parser/nuget/lock/parse_test.go index 18be1118c0a0..04ddb22244df 100644 --- a/pkg/dependency/parser/nuget/lock/parse_test.go +++ b/pkg/dependency/parser/nuget/lock/parse_test.go @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" ) func TestParse(t *testing.T) { diff --git a/pkg/dependency/parser/nuget/lock/parse_testcase.go b/pkg/dependency/parser/nuget/lock/parse_testcase.go index d6676e0bf6c5..699db9f8c037 100644 --- a/pkg/dependency/parser/nuget/lock/parse_testcase.go +++ b/pkg/dependency/parser/nuget/lock/parse_testcase.go @@ -1,6 +1,6 @@ package lock -import "github.com/aquasecurity/trivy/pkg/dependency/parser/types" +import "github.com/aquasecurity/trivy/pkg/dependency/types" var ( // docker run --rm -i -t mcr.microsoft.com/dotnet/sdk:latest diff --git a/pkg/dependency/parser/nuget/packagesprops/parse.go b/pkg/dependency/parser/nuget/packagesprops/parse.go index 2471d6faf7db..5e4c6831d1a1 100644 --- a/pkg/dependency/parser/nuget/packagesprops/parse.go +++ b/pkg/dependency/parser/nuget/packagesprops/parse.go @@ -6,8 +6,10 @@ import ( "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency" "github.com/aquasecurity/trivy/pkg/dependency/parser/utils" + "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -44,7 +46,7 @@ func (p pkg) library() types.Library { name = strings.TrimSpace(name) version := strings.TrimSpace(p.Version) return types.Library{ - ID: utils.PackageID(name, version), + ID: dependency.ID(ftypes.NuGet, name, version), Name: name, Version: version, } diff --git a/pkg/dependency/parser/nuget/packagesprops/parse_test.go b/pkg/dependency/parser/nuget/packagesprops/parse_test.go index 8fcd6b7ae6ea..96a50716d7ef 100644 --- a/pkg/dependency/parser/nuget/packagesprops/parse_test.go +++ b/pkg/dependency/parser/nuget/packagesprops/parse_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/require" config "github.com/aquasecurity/trivy/pkg/dependency/parser/nuget/packagesprops" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" ) func TestParse(t *testing.T) { diff --git a/pkg/dependency/parser/php/composer/parse.go b/pkg/dependency/parser/php/composer/parse.go index 849a67941d34..1fbf4316db9a 100644 --- a/pkg/dependency/parser/php/composer/parse.go +++ b/pkg/dependency/parser/php/composer/parse.go @@ -9,8 +9,9 @@ import ( "golang.org/x/exp/maps" "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" - "github.com/aquasecurity/trivy/pkg/dependency/parser/utils" + "github.com/aquasecurity/trivy/pkg/dependency" + "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -47,7 +48,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, foundDeps := make(map[string][]string) for _, pkg := range lockFile.Packages { lib := types.Library{ - ID: utils.PackageID(pkg.Name, pkg.Version), + ID: dependency.ID(ftypes.Composer, pkg.Name, pkg.Version), Name: pkg.Name, Version: pkg.Version, Indirect: false, // composer.lock file doesn't have info about Direct/Indirect deps. Will think that all dependencies are Direct diff --git a/pkg/dependency/parser/php/composer/parse_test.go b/pkg/dependency/parser/php/composer/parse_test.go index c56c4d3aa8b6..8c80899bc4ba 100644 --- a/pkg/dependency/parser/php/composer/parse_test.go +++ b/pkg/dependency/parser/php/composer/parse_test.go @@ -1,7 +1,7 @@ package composer import ( - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "os" diff --git a/pkg/dependency/parser/python/packaging/parse.go b/pkg/dependency/parser/python/packaging/parse.go index 50ef1d3ba5cd..41514872fbb7 100644 --- a/pkg/dependency/parser/python/packaging/parse.go +++ b/pkg/dependency/parser/python/packaging/parse.go @@ -9,7 +9,7 @@ import ( "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" "github.com/aquasecurity/trivy/pkg/log" xio "github.com/aquasecurity/trivy/pkg/x/io" ) diff --git a/pkg/dependency/parser/python/packaging/parse_test.go b/pkg/dependency/parser/python/packaging/parse_test.go index 7bbc890cf70b..ee08c5bdca82 100644 --- a/pkg/dependency/parser/python/packaging/parse_test.go +++ b/pkg/dependency/parser/python/packaging/parse_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/dependency/parser/python/packaging" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" ) func TestParse(t *testing.T) { diff --git a/pkg/dependency/parser/python/pip/parse.go b/pkg/dependency/parser/python/pip/parse.go index ca7b412f3817..4d4f893d63c0 100644 --- a/pkg/dependency/parser/python/pip/parse.go +++ b/pkg/dependency/parser/python/pip/parse.go @@ -10,7 +10,7 @@ import ( "golang.org/x/text/transform" "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" xio "github.com/aquasecurity/trivy/pkg/x/io" ) diff --git a/pkg/dependency/parser/python/pip/parse_test.go b/pkg/dependency/parser/python/pip/parse_test.go index aea0c7d6e7a6..a3a183f94a8e 100644 --- a/pkg/dependency/parser/python/pip/parse_test.go +++ b/pkg/dependency/parser/python/pip/parse_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" ) func TestParse(t *testing.T) { diff --git a/pkg/dependency/parser/python/pip/parse_testcase.go b/pkg/dependency/parser/python/pip/parse_testcase.go index b33f8f54e398..45642d47f2fa 100644 --- a/pkg/dependency/parser/python/pip/parse_testcase.go +++ b/pkg/dependency/parser/python/pip/parse_testcase.go @@ -1,6 +1,6 @@ package pip -import "github.com/aquasecurity/trivy/pkg/dependency/parser/types" +import "github.com/aquasecurity/trivy/pkg/dependency/types" var ( requirementsFlask = []types.Library{ diff --git a/pkg/dependency/parser/python/pipenv/parse.go b/pkg/dependency/parser/python/pipenv/parse.go index 7b773fb1dbf6..70332764195e 100644 --- a/pkg/dependency/parser/python/pipenv/parse.go +++ b/pkg/dependency/parser/python/pipenv/parse.go @@ -7,7 +7,7 @@ import ( "github.com/liamg/jfather" "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -39,9 +39,14 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, var libs []types.Library for pkgName, dependency := range lockFile.Default { libs = append(libs, types.Library{ - Name: pkgName, - Version: strings.TrimLeft(dependency.Version, "="), - Locations: []types.Location{{StartLine: dependency.StartLine, EndLine: dependency.EndLine}}, + Name: pkgName, + Version: strings.TrimLeft(dependency.Version, "="), + Locations: []types.Location{ + { + StartLine: dependency.StartLine, + EndLine: dependency.EndLine, + }, + }, }) } return libs, nil, nil diff --git a/pkg/dependency/parser/python/pipenv/parse_test.go b/pkg/dependency/parser/python/pipenv/parse_test.go index 9390680f65d7..03fbe573ee7b 100644 --- a/pkg/dependency/parser/python/pipenv/parse_test.go +++ b/pkg/dependency/parser/python/pipenv/parse_test.go @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" ) func TestParse(t *testing.T) { diff --git a/pkg/dependency/parser/python/pipenv/parse_testcase.go b/pkg/dependency/parser/python/pipenv/parse_testcase.go index 718301bfc21e..6a611944d3eb 100644 --- a/pkg/dependency/parser/python/pipenv/parse_testcase.go +++ b/pkg/dependency/parser/python/pipenv/parse_testcase.go @@ -1,6 +1,6 @@ package pipenv -import "github.com/aquasecurity/trivy/pkg/dependency/parser/types" +import "github.com/aquasecurity/trivy/pkg/dependency/types" var ( // docker run --name pipenv --rm -it python:3.9-alpine sh diff --git a/pkg/dependency/parser/python/poetry/parse.go b/pkg/dependency/parser/python/poetry/parse.go index 38dcac2e1870..e476b8c18d93 100644 --- a/pkg/dependency/parser/python/poetry/parse.go +++ b/pkg/dependency/parser/python/poetry/parse.go @@ -8,8 +8,9 @@ import ( "golang.org/x/xerrors" version "github.com/aquasecurity/go-pep440-version" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" - "github.com/aquasecurity/trivy/pkg/dependency/parser/utils" + "github.com/aquasecurity/trivy/pkg/dependency" + "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -50,7 +51,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, continue } - pkgID := utils.PackageID(pkg.Name, pkg.Version) + pkgID := packageID(pkg.Name, pkg.Version) libs = append(libs, types.Library{ ID: pkgID, Name: pkg.Name, @@ -124,7 +125,7 @@ func parseDependency(name string, versRange any, libVersions map[string][]string if matched, err := matchVersion(ver, vRange); err != nil { return "", xerrors.Errorf("failed to match version for %s: %w", name, err) } else if matched { - return utils.PackageID(name, ver), nil + return packageID(name, ver), nil } } return "", xerrors.Errorf("no matched version found for %q", name) @@ -153,3 +154,7 @@ func normalizePkgName(name string) string { name = strings.ReplaceAll(name, ".", "-") // e.g. https://github.com/python-poetry/poetry/blob/c8945eb110aeda611cc6721565d7ad0c657d453a/poetry.lock#L816 return name } + +func packageID(name, ver string) string { + return dependency.ID(ftypes.Poetry, name, ver) +} diff --git a/pkg/dependency/parser/python/poetry/parse_test.go b/pkg/dependency/parser/python/poetry/parse_test.go index 91518be8aa1f..c02999a8eff8 100644 --- a/pkg/dependency/parser/python/poetry/parse_test.go +++ b/pkg/dependency/parser/python/poetry/parse_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" ) func TestParser_Parse(t *testing.T) { diff --git a/pkg/dependency/parser/python/poetry/parse_testcase.go b/pkg/dependency/parser/python/poetry/parse_testcase.go index 440131b6fd3f..c6511c0bd089 100644 --- a/pkg/dependency/parser/python/poetry/parse_testcase.go +++ b/pkg/dependency/parser/python/poetry/parse_testcase.go @@ -1,6 +1,6 @@ package poetry -import "github.com/aquasecurity/trivy/pkg/dependency/parser/types" +import "github.com/aquasecurity/trivy/pkg/dependency/types" var ( // docker run --name pipenv --rm -it python@sha256:e1141f10176d74d1a0e87a7c0a0a5a98dd98ec5ac12ce867768f40c6feae2fd9 sh diff --git a/pkg/dependency/parser/ruby/bundler/parse.go b/pkg/dependency/parser/ruby/bundler/parse.go index 24b05d4aca38..e8cd538e0da6 100644 --- a/pkg/dependency/parser/ruby/bundler/parse.go +++ b/pkg/dependency/parser/ruby/bundler/parse.go @@ -8,8 +8,9 @@ import ( "golang.org/x/exp/maps" "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" - "github.com/aquasecurity/trivy/pkg/dependency/parser/utils" + "github.com/aquasecurity/trivy/pkg/dependency" + "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -47,13 +48,18 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, version := strings.Trim(s[1], "()") // drop parentheses version = strings.SplitN(version, "-", 2)[0] // drop platform (e.g. 1.13.6-x86_64-linux => 1.13.6) name := s[0] - pkgID = utils.PackageID(name, version) + pkgID = packageID(name, version) libs[name] = types.Library{ - ID: pkgID, - Name: name, - Version: version, - Indirect: true, - Locations: []types.Location{{StartLine: lineNum, EndLine: lineNum}}, + ID: pkgID, + Name: name, + Version: version, + Indirect: true, + Locations: []types.Location{ + { + StartLine: lineNum, + EndLine: lineNum, + }, + }, } } // Parse dependency graph @@ -89,7 +95,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, dependsOn = make([]string, 0) for _, pkgName := range dep.DependsOn { if lib, ok := libs[pkgName]; ok { - dependsOn = append(dependsOn, utils.PackageID(pkgName, lib.Version)) + dependsOn = append(dependsOn, packageID(pkgName, lib.Version)) } } deps[i].DependsOn = dependsOn @@ -134,3 +140,7 @@ func parseDirectDeps(scanner *bufio.Scanner) []string { } return deps } + +func packageID(name, version string) string { + return dependency.ID(ftypes.Bundler, name, version) +} diff --git a/pkg/dependency/parser/ruby/bundler/parse_test.go b/pkg/dependency/parser/ruby/bundler/parse_test.go index 3732b97f0f35..6b2b27dd5e3a 100644 --- a/pkg/dependency/parser/ruby/bundler/parse_test.go +++ b/pkg/dependency/parser/ruby/bundler/parse_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/dependency/parser/ruby/bundler" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" ) var ( diff --git a/pkg/dependency/parser/ruby/gemspec/parse.go b/pkg/dependency/parser/ruby/gemspec/parse.go index 65c94c6d2cef..e458f6bacd0e 100644 --- a/pkg/dependency/parser/ruby/gemspec/parse.go +++ b/pkg/dependency/parser/ruby/gemspec/parse.go @@ -8,7 +8,7 @@ import ( "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -99,7 +99,8 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) (libs []types.Library, deps []types.D Name: name, Version: version, License: license, - }}, nil, nil + }, + }, nil, nil } func findSubString(re *regexp.Regexp, line, name string) string { diff --git a/pkg/dependency/parser/ruby/gemspec/parse_test.go b/pkg/dependency/parser/ruby/gemspec/parse_test.go index 41610a68f45d..586c0ee6b941 100644 --- a/pkg/dependency/parser/ruby/gemspec/parse_test.go +++ b/pkg/dependency/parser/ruby/gemspec/parse_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/dependency/parser/ruby/gemspec" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" ) func TestParse(t *testing.T) { diff --git a/pkg/dependency/parser/rust/binary/parse.go b/pkg/dependency/parser/rust/binary/parse.go index 71acbb2d5dce..5ddb2cf20be6 100644 --- a/pkg/dependency/parser/rust/binary/parse.go +++ b/pkg/dependency/parser/rust/binary/parse.go @@ -5,8 +5,9 @@ import ( rustaudit "github.com/microsoft/go-rustaudit" "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" - "github.com/aquasecurity/trivy/pkg/dependency/parser/utils" + "github.com/aquasecurity/trivy/pkg/dependency" + "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -48,7 +49,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, if pkg.Kind != rustaudit.Runtime { continue } - pkgID := utils.PackageID(pkg.Name, pkg.Version) + pkgID := packageID(pkg.Name, pkg.Version) libs = append(libs, types.Library{ ID: pkgID, Name: pkg.Name, @@ -60,7 +61,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, for _, dep_idx := range pkg.Dependencies { dep := info.Packages[dep_idx] if dep.Kind == rustaudit.Runtime { - childDeps = append(childDeps, utils.PackageID(dep.Name, dep.Version)) + childDeps = append(childDeps, packageID(dep.Name, dep.Version)) } } if len(childDeps) > 0 { @@ -73,3 +74,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, return libs, deps, nil } + +func packageID(name, version string) string { + return dependency.ID(ftypes.RustBinary, name, version) +} diff --git a/pkg/dependency/parser/rust/binary/parse_test.go b/pkg/dependency/parser/rust/binary/parse_test.go index a3e2b381cc2d..63b0a3a705d1 100644 --- a/pkg/dependency/parser/rust/binary/parse_test.go +++ b/pkg/dependency/parser/rust/binary/parse_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/dependency/parser/rust/binary" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" ) // Test binaries generated from cargo-auditable test fixture diff --git a/pkg/dependency/parser/rust/cargo/naive_pkg_parser.go b/pkg/dependency/parser/rust/cargo/naive_pkg_parser.go index f12134a7891f..775dae13477f 100644 --- a/pkg/dependency/parser/rust/cargo/naive_pkg_parser.go +++ b/pkg/dependency/parser/rust/cargo/naive_pkg_parser.go @@ -5,8 +5,6 @@ import ( "fmt" "io" "strings" - - "github.com/aquasecurity/trivy/pkg/dependency/parser/utils" ) type pkgPosition struct { @@ -40,7 +38,7 @@ func (parser *naivePkgParser) parse() map[string]pkgPosition { switch { case strings.HasPrefix(strings.TrimSpace(line), "["): if currentPkg.name != "" { - pkgId := utils.PackageID(currentPkg.name, currentPkg.version) + pkgId := packageID(currentPkg.name, currentPkg.version) currentPkg.setEndPositionIfEmpty(lineNum - 1) idx[pkgId] = currentPkg.position } diff --git a/pkg/dependency/parser/rust/cargo/parse.go b/pkg/dependency/parser/rust/cargo/parse.go index e9426f5069d2..282e25152d04 100644 --- a/pkg/dependency/parser/rust/cargo/parse.go +++ b/pkg/dependency/parser/rust/cargo/parse.go @@ -9,8 +9,9 @@ import ( "github.com/samber/lo" "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" - "github.com/aquasecurity/trivy/pkg/dependency/parser/utils" + "github.com/aquasecurity/trivy/pkg/dependency" + "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -54,14 +55,19 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, var libs []types.Library var deps []types.Dependency for _, pkg := range lockfile.Packages { - pkgID := utils.PackageID(pkg.Name, pkg.Version) + pkgID := packageID(pkg.Name, pkg.Version) lib := types.Library{ ID: pkgID, Name: pkg.Name, Version: pkg.Version, } if pos, ok := lineNumIdx[pkgID]; ok { - lib.Locations = []types.Location{{StartLine: pos.start, EndLine: pos.end}} + lib.Locations = []types.Location{ + { + StartLine: pos.start, + EndLine: pos.end, + }, + } } libs = append(libs, lib) @@ -96,11 +102,11 @@ func parseDependencies(pkgId string, pkg cargoPkg, pkgs map[string]cargoPkg) *ty log.Logger.Debugf("can't find version for %s", name) continue } - dependOn = append(dependOn, utils.PackageID(name, version.Version)) + dependOn = append(dependOn, packageID(name, version.Version)) // 2: non-unique dependency in new lock file // 3: old lock file case 2, 3: - dependOn = append(dependOn, utils.PackageID(fields[0], fields[1])) + dependOn = append(dependOn, packageID(fields[0], fields[1])) default: log.Logger.Debugf("wrong dependency format for %s", pkgDep) continue @@ -116,3 +122,7 @@ func parseDependencies(pkgId string, pkg cargoPkg, pkgs map[string]cargoPkg) *ty return nil } } + +func packageID(name, version string) string { + return dependency.ID(ftypes.Cargo, name, version) +} diff --git a/pkg/dependency/parser/rust/cargo/parse_test.go b/pkg/dependency/parser/rust/cargo/parse_test.go index 0bf494702696..903cf0190a7d 100644 --- a/pkg/dependency/parser/rust/cargo/parse_test.go +++ b/pkg/dependency/parser/rust/cargo/parse_test.go @@ -11,7 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" ) var ( diff --git a/pkg/dependency/parser/swift/cocoapods/parse.go b/pkg/dependency/parser/swift/cocoapods/parse.go index 3caeeec04366..7b2a580fd74c 100644 --- a/pkg/dependency/parser/swift/cocoapods/parse.go +++ b/pkg/dependency/parser/swift/cocoapods/parse.go @@ -8,8 +8,10 @@ import ( "golang.org/x/xerrors" "gopkg.in/yaml.v3" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency" "github.com/aquasecurity/trivy/pkg/dependency/parser/utils" + "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -72,7 +74,7 @@ func (Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, er var dependsOn []string // find versions for child dependencies for _, childDep := range childDeps { - dependsOn = append(dependsOn, utils.PackageID(childDep, parsedDeps[childDep].Version)) + dependsOn = append(dependsOn, packageID(childDep, parsedDeps[childDep].Version)) } deps = append(deps, types.Dependency{ ID: parsedDeps[dep].ID, @@ -99,10 +101,14 @@ func parseDep(dep string) (types.Library, error) { name := ss[0] version := strings.Trim(strings.TrimSpace(ss[1]), "()") lib := types.Library{ - ID: utils.PackageID(name, version), + ID: packageID(name, version), Name: name, Version: version, } return lib, nil } + +func packageID(name, version string) string { + return dependency.ID(ftypes.Cocoapods, name, version) +} diff --git a/pkg/dependency/parser/swift/cocoapods/parse_test.go b/pkg/dependency/parser/swift/cocoapods/parse_test.go index c1628e566af2..3a0823338713 100644 --- a/pkg/dependency/parser/swift/cocoapods/parse_test.go +++ b/pkg/dependency/parser/swift/cocoapods/parse_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/aquasecurity/trivy/pkg/dependency/parser/swift/cocoapods" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/pkg/dependency/parser/swift/swift/parse.go b/pkg/dependency/parser/swift/swift/parse.go index 19f5943b6b78..daeb8d3ef243 100644 --- a/pkg/dependency/parser/swift/swift/parse.go +++ b/pkg/dependency/parser/swift/swift/parse.go @@ -9,8 +9,9 @@ import ( "github.com/samber/lo" "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" - "github.com/aquasecurity/trivy/pkg/dependency/parser/utils" + "github.com/aquasecurity/trivy/pkg/dependency" + "github.com/aquasecurity/trivy/pkg/dependency/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" xio "github.com/aquasecurity/trivy/pkg/x/io" ) @@ -51,7 +52,7 @@ func (Parser) Parse(r xio.ReadSeekerAt) ([]types.Library, []types.Dependency, er version := lo.Ternary(pin.State.Version != "", pin.State.Version, pin.State.Branch) libs = append(libs, types.Library{ - ID: utils.PackageID(name, version), + ID: dependency.ID(ftypes.Swift, name, version), Name: name, Version: version, Locations: []types.Location{ diff --git a/pkg/dependency/parser/swift/swift/parse_test.go b/pkg/dependency/parser/swift/swift/parse_test.go index 530186c419d0..b1d3d127a85a 100644 --- a/pkg/dependency/parser/swift/swift/parse_test.go +++ b/pkg/dependency/parser/swift/swift/parse_test.go @@ -1,7 +1,7 @@ package swift import ( - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" "github.com/stretchr/testify/assert" "os" "testing" diff --git a/pkg/dependency/parser/utils/utils.go b/pkg/dependency/parser/utils/utils.go index 8c128e0cfb4b..e89bc4ca3b65 100644 --- a/pkg/dependency/parser/utils/utils.go +++ b/pkg/dependency/parser/utils/utils.go @@ -6,7 +6,7 @@ import ( "golang.org/x/exp/maps" - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" ) func UniqueStrings(ss []string) []string { @@ -65,7 +65,3 @@ func MergeMaps(parent, child map[string]string) map[string]string { } return newParent } - -func PackageID(name, version string) string { - return fmt.Sprintf("%s@%s", name, version) -} diff --git a/pkg/dependency/parser/utils/utils_test.go b/pkg/dependency/parser/utils/utils_test.go index 6292aad9f07c..ca8c8ab67568 100644 --- a/pkg/dependency/parser/utils/utils_test.go +++ b/pkg/dependency/parser/utils/utils_test.go @@ -1,7 +1,7 @@ package utils import ( - "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + "github.com/aquasecurity/trivy/pkg/dependency/types" "github.com/stretchr/testify/require" "testing" ) diff --git a/pkg/dependency/parser/types/types.go b/pkg/dependency/types/types.go similarity index 100% rename from pkg/dependency/parser/types/types.go rename to pkg/dependency/types/types.go diff --git a/pkg/fanal/analyzer/language/analyze.go b/pkg/fanal/analyzer/language/analyze.go index b60eebafc5d0..d31cbb2dfb0f 100644 --- a/pkg/fanal/analyzer/language/analyze.go +++ b/pkg/fanal/analyzer/language/analyze.go @@ -6,7 +6,7 @@ import ( "golang.org/x/xerrors" - godeptypes "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types" "github.com/aquasecurity/trivy/pkg/digest" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/types" diff --git a/pkg/fanal/analyzer/language/analyze_test.go b/pkg/fanal/analyzer/language/analyze_test.go index b1dc6654db86..260c86b59ae8 100644 --- a/pkg/fanal/analyzer/language/analyze_test.go +++ b/pkg/fanal/analyzer/language/analyze_test.go @@ -9,11 +9,11 @@ import ( "github.com/stretchr/testify/require" "golang.org/x/xerrors" - xio "github.com/aquasecurity/trivy/pkg/x/io" - godeptypes "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" "github.com/aquasecurity/trivy/pkg/fanal/types" + xio "github.com/aquasecurity/trivy/pkg/x/io" ) type mockParser struct { diff --git a/pkg/fanal/analyzer/language/dart/pub/pubspec.go b/pkg/fanal/analyzer/language/dart/pub/pubspec.go index 285dbc74f963..ab924cafd191 100644 --- a/pkg/fanal/analyzer/language/dart/pub/pubspec.go +++ b/pkg/fanal/analyzer/language/dart/pub/pubspec.go @@ -14,9 +14,9 @@ import ( "golang.org/x/xerrors" "gopkg.in/yaml.v3" + "github.com/aquasecurity/trivy/pkg/dependency" "github.com/aquasecurity/trivy/pkg/dependency/parser/dart/pub" - godeptypes "github.com/aquasecurity/trivy/pkg/dependency/parser/types" - "github.com/aquasecurity/trivy/pkg/dependency/parser/utils" + godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" "github.com/aquasecurity/trivy/pkg/fanal/types" @@ -166,7 +166,7 @@ func parsePubSpecYaml(r io.Reader) (string, []string, error) { // save only dependencies names dependsOn := maps.Keys(spec.Dependencies) - return utils.PackageID(spec.Name, spec.Version), dependsOn, nil + return dependency.ID(types.Pub, spec.Name, spec.Version), dependsOn, nil } func (a pubSpecLockAnalyzer) Required(filePath string, _ os.FileInfo) bool { diff --git a/pkg/fanal/analyzer/language/dotnet/nuget/nuget.go b/pkg/fanal/analyzer/language/dotnet/nuget/nuget.go index 5374d8789d90..6411f4d1bc8d 100644 --- a/pkg/fanal/analyzer/language/dotnet/nuget/nuget.go +++ b/pkg/fanal/analyzer/language/dotnet/nuget/nuget.go @@ -14,7 +14,7 @@ import ( "github.com/aquasecurity/trivy/pkg/dependency/parser/nuget/config" "github.com/aquasecurity/trivy/pkg/dependency/parser/nuget/lock" - godeptypes "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" "github.com/aquasecurity/trivy/pkg/fanal/types" diff --git a/pkg/fanal/analyzer/language/golang/mod/mod.go b/pkg/fanal/analyzer/language/golang/mod/mod.go index beb2f9f1213e..5aa0ae2293fe 100644 --- a/pkg/fanal/analyzer/language/golang/mod/mod.go +++ b/pkg/fanal/analyzer/language/golang/mod/mod.go @@ -19,7 +19,7 @@ import ( "github.com/aquasecurity/trivy/pkg/dependency/parser/golang/mod" "github.com/aquasecurity/trivy/pkg/dependency/parser/golang/sum" - godeptypes "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" "github.com/aquasecurity/trivy/pkg/fanal/types" diff --git a/pkg/fanal/analyzer/language/nodejs/npm/npm.go b/pkg/fanal/analyzer/language/nodejs/npm/npm.go index ecca16aa1996..c5dd5d26eed0 100644 --- a/pkg/fanal/analyzer/language/nodejs/npm/npm.go +++ b/pkg/fanal/analyzer/language/nodejs/npm/npm.go @@ -13,7 +13,7 @@ import ( "github.com/aquasecurity/trivy/pkg/dependency/parser/nodejs/npm" "github.com/aquasecurity/trivy/pkg/dependency/parser/nodejs/packagejson" - godeptypes "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" "github.com/aquasecurity/trivy/pkg/fanal/types" diff --git a/pkg/fanal/analyzer/language/nodejs/pkg/pkg.go b/pkg/fanal/analyzer/language/nodejs/pkg/pkg.go index d8c87b9b60ad..aabc4da6b6a2 100644 --- a/pkg/fanal/analyzer/language/nodejs/pkg/pkg.go +++ b/pkg/fanal/analyzer/language/nodejs/pkg/pkg.go @@ -6,7 +6,7 @@ import ( "path/filepath" "github.com/aquasecurity/trivy/pkg/dependency/parser/nodejs/packagejson" - godeptypes "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" "github.com/aquasecurity/trivy/pkg/fanal/types" diff --git a/pkg/fanal/analyzer/language/nodejs/yarn/yarn.go b/pkg/fanal/analyzer/language/nodejs/yarn/yarn.go index 86e185d95360..1cbd6b4896aa 100644 --- a/pkg/fanal/analyzer/language/nodejs/yarn/yarn.go +++ b/pkg/fanal/analyzer/language/nodejs/yarn/yarn.go @@ -20,7 +20,7 @@ import ( "github.com/aquasecurity/trivy/pkg/dependency/parser/nodejs/packagejson" "github.com/aquasecurity/trivy/pkg/dependency/parser/nodejs/yarn" - godeptypes "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types" "github.com/aquasecurity/trivy/pkg/detector/library/compare/npm" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" diff --git a/pkg/fanal/analyzer/language/php/composer/composer.go b/pkg/fanal/analyzer/language/php/composer/composer.go index 8e795468b88f..d0244514a0e7 100644 --- a/pkg/fanal/analyzer/language/php/composer/composer.go +++ b/pkg/fanal/analyzer/language/php/composer/composer.go @@ -15,7 +15,7 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/dependency/parser/php/composer" - godeptypes "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" "github.com/aquasecurity/trivy/pkg/fanal/types" diff --git a/pkg/fanal/analyzer/language/python/packaging/packaging.go b/pkg/fanal/analyzer/language/python/packaging/packaging.go index b5e505140fd0..c45a4f5aef0f 100644 --- a/pkg/fanal/analyzer/language/python/packaging/packaging.go +++ b/pkg/fanal/analyzer/language/python/packaging/packaging.go @@ -16,7 +16,7 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/dependency/parser/python/packaging" - godeptypes "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" "github.com/aquasecurity/trivy/pkg/fanal/types" diff --git a/pkg/fanal/analyzer/language/python/poetry/poetry.go b/pkg/fanal/analyzer/language/python/poetry/poetry.go index 0dc95b428ca8..90897e8f12ba 100644 --- a/pkg/fanal/analyzer/language/python/poetry/poetry.go +++ b/pkg/fanal/analyzer/language/python/poetry/poetry.go @@ -12,7 +12,7 @@ import ( "github.com/aquasecurity/trivy/pkg/dependency/parser/python/poetry" "github.com/aquasecurity/trivy/pkg/dependency/parser/python/pyproject" - godeptypes "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" "github.com/aquasecurity/trivy/pkg/fanal/types" diff --git a/pkg/fanal/analyzer/language/rust/cargo/cargo.go b/pkg/fanal/analyzer/language/rust/cargo/cargo.go index c9baf7f4f12b..f487ba0c46e8 100644 --- a/pkg/fanal/analyzer/language/rust/cargo/cargo.go +++ b/pkg/fanal/analyzer/language/rust/cargo/cargo.go @@ -20,7 +20,7 @@ import ( "github.com/aquasecurity/go-version/pkg/semver" goversion "github.com/aquasecurity/go-version/pkg/version" "github.com/aquasecurity/trivy/pkg/dependency/parser/rust/cargo" - godeptypes "github.com/aquasecurity/trivy/pkg/dependency/parser/types" + godeptypes "github.com/aquasecurity/trivy/pkg/dependency/types" "github.com/aquasecurity/trivy/pkg/detector/library/compare" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" diff --git a/pkg/fanal/analyzer/sbom/sbom_test.go b/pkg/fanal/analyzer/sbom/sbom_test.go index 096d0ececec5..c6f5b4b33701 100644 --- a/pkg/fanal/analyzer/sbom/sbom_test.go +++ b/pkg/fanal/analyzer/sbom/sbom_test.go @@ -124,6 +124,7 @@ func Test_sbomAnalyzer_Analyze(t *testing.T) { Libraries: types.Packages{ { FilePath: "opt/bitnami/elasticsearch/modules/apm/elastic-apm-agent-1.36.0.jar", + ID: "co.elastic.apm:apm-agent:1.36.0", Name: "co.elastic.apm:apm-agent", Version: "1.36.0", Identifier: types.PkgIdentifier{ @@ -138,6 +139,7 @@ func Test_sbomAnalyzer_Analyze(t *testing.T) { }, { FilePath: "opt/bitnami/elasticsearch/modules/apm/elastic-apm-agent-1.36.0.jar", + ID: "co.elastic.apm:apm-agent-cached-lookup-key:1.36.0", Name: "co.elastic.apm:apm-agent-cached-lookup-key", Version: "1.36.0", Identifier: types.PkgIdentifier{ diff --git a/pkg/fanal/artifact/image/remote_sbom_test.go b/pkg/fanal/artifact/image/remote_sbom_test.go index 3e445fc0b9ee..c9255c2057bd 100644 --- a/pkg/fanal/artifact/image/remote_sbom_test.go +++ b/pkg/fanal/artifact/image/remote_sbom_test.go @@ -70,7 +70,7 @@ func TestArtifact_InspectRekorAttestation(t *testing.T) { putBlobExpectations: []cache.ArtifactCachePutBlobExpectation{ { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:5c9fad635c53ddafd1b5248fcd989b6c0f311c91a2fe2a206c7d67a715335fa1", + BlobID: "sha256:066b9998617ffb7dfe0a3219ac5c3efc1008a6223606fcf474e7d5c965e4e8da", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, OS: types.OS{ @@ -81,6 +81,7 @@ func TestArtifact_InspectRekorAttestation(t *testing.T) { { Packages: types.Packages{ { + ID: "musl@1.2.3-r0", Name: "musl", Version: "1.2.3-r0", Identifier: types.PkgIdentifier{ @@ -119,9 +120,9 @@ func TestArtifact_InspectRekorAttestation(t *testing.T) { want: types.ArtifactReference{ Name: "test/image:10", Type: types.ArtifactCycloneDX, - ID: "sha256:5c9fad635c53ddafd1b5248fcd989b6c0f311c91a2fe2a206c7d67a715335fa1", + ID: "sha256:066b9998617ffb7dfe0a3219ac5c3efc1008a6223606fcf474e7d5c965e4e8da", BlobIDs: []string{ - "sha256:5c9fad635c53ddafd1b5248fcd989b6c0f311c91a2fe2a206c7d67a715335fa1", + "sha256:066b9998617ffb7dfe0a3219ac5c3efc1008a6223606fcf474e7d5c965e4e8da", }, }, }, @@ -169,7 +170,7 @@ func TestArtifact_InspectRekorAttestation(t *testing.T) { return } require.NoError(t, err, tt.name) - got.CycloneDX = nil + got.BOM = nil assert.Equal(t, tt.want, got) }) } @@ -222,7 +223,7 @@ func TestArtifact_inspectOCIReferrerSBOM(t *testing.T) { putBlobExpectations: []cache.ArtifactCachePutBlobExpectation{ { Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:fb9379cfc2aeff911515f04ca04300a8c0609c8a2a19b4a3b05a984802fa44eb", + BlobID: "sha256:7e0b5476a5ff5a10594ad1ed7566220fcc43ecff29b831236cb2e98e574a1d05", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Applications: []types.Application{ @@ -230,6 +231,7 @@ func TestArtifact_inspectOCIReferrerSBOM(t *testing.T) { Type: types.GoBinary, Libraries: types.Packages{ { + ID: "github.com/opencontainers/go-digest@v1.0.0", Name: "github.com/opencontainers/go-digest", Version: "v1.0.0", Identifier: types.PkgIdentifier{ @@ -243,6 +245,7 @@ func TestArtifact_inspectOCIReferrerSBOM(t *testing.T) { }, }, { + ID: "golang.org/x/sync@v0.1.0", Name: "golang.org/x/sync", Version: "v0.1.0", Identifier: types.PkgIdentifier{ @@ -265,9 +268,9 @@ func TestArtifact_inspectOCIReferrerSBOM(t *testing.T) { want: types.ArtifactReference{ Name: registry + "/test/image:10", Type: types.ArtifactCycloneDX, - ID: "sha256:fb9379cfc2aeff911515f04ca04300a8c0609c8a2a19b4a3b05a984802fa44eb", + ID: "sha256:7e0b5476a5ff5a10594ad1ed7566220fcc43ecff29b831236cb2e98e574a1d05", BlobIDs: []string{ - "sha256:fb9379cfc2aeff911515f04ca04300a8c0609c8a2a19b4a3b05a984802fa44eb", + "sha256:7e0b5476a5ff5a10594ad1ed7566220fcc43ecff29b831236cb2e98e574a1d05", }, }, }, @@ -309,7 +312,7 @@ func TestArtifact_inspectOCIReferrerSBOM(t *testing.T) { } require.NoError(t, err, tt.name) - got.CycloneDX = nil + got.BOM = nil assert.Equal(t, tt.want, got) }) } diff --git a/pkg/fanal/artifact/sbom/sbom.go b/pkg/fanal/artifact/sbom/sbom.go index e788e14b48db..2be0ce31e6a4 100644 --- a/pkg/fanal/artifact/sbom/sbom.go +++ b/pkg/fanal/artifact/sbom/sbom.go @@ -8,6 +8,7 @@ import ( "path/filepath" "github.com/opencontainers/go-digest" + "github.com/samber/lo" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" @@ -57,7 +58,7 @@ func (a Artifact) Inspect(_ context.Context) (types.ArtifactReference, error) { blobInfo := types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, - OS: bom.OS, + OS: lo.FromPtr(bom.Metadata.OS), PackageInfos: bom.Packages, Applications: bom.Applications, } @@ -87,7 +88,7 @@ func (a Artifact) Inspect(_ context.Context) (types.ArtifactReference, error) { BlobIDs: []string{cacheKey}, // Keep an original report - CycloneDX: bom.CycloneDX, + BOM: bom.BOM, }, nil } diff --git a/pkg/fanal/artifact/sbom/sbom_test.go b/pkg/fanal/artifact/sbom/sbom_test.go index f0286d4c3198..3b26194cfa55 100644 --- a/pkg/fanal/artifact/sbom/sbom_test.go +++ b/pkg/fanal/artifact/sbom/sbom_test.go @@ -30,7 +30,7 @@ func TestArtifact_Inspect(t *testing.T) { filePath: filepath.Join("testdata", "bom.json"), putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:5d7b14f463a56006dd4060d04295afbda468350f2e5a786ae48d18fd02fb9a89", + BlobID: "sha256:f6d4bf4edf2818010ef009b6cd0f837c94dac3464d99e665470c8d05648478e3", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, OS: types.OS{ @@ -41,6 +41,7 @@ func TestArtifact_Inspect(t *testing.T) { { Packages: types.Packages{ { + ID: "musl@1.2.3-r0", Name: "musl", Version: "1.2.3-r0", SrcName: "musl", @@ -74,6 +75,7 @@ func TestArtifact_Inspect(t *testing.T) { FilePath: "app/composer/composer.lock", Libraries: types.Packages{ { + ID: "pear/log@1.13.1", Name: "pear/log", Version: "1.13.1", Layer: types.Layer{ @@ -90,7 +92,7 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - + ID: "pear/pear_exception@v1.0.0", Name: "pear/pear_exception", Version: "v1.0.0", Layer: types.Layer{ @@ -113,6 +115,7 @@ func TestArtifact_Inspect(t *testing.T) { FilePath: "app/gobinary/gobinary", Libraries: types.Packages{ { + ID: "github.com/package-url/packageurl-go@v0.1.1-0.20220203205134-d70459300c8a", Name: "github.com/package-url/packageurl-go", Version: "v0.1.1-0.20220203205134-d70459300c8a", Layer: types.Layer{ @@ -135,6 +138,7 @@ func TestArtifact_Inspect(t *testing.T) { FilePath: "", Libraries: types.Packages{ { + ID: "org.codehaus.mojo:child-project:1.0", Name: "org.codehaus.mojo:child-project", Version: "1.0", Layer: types.Layer{ @@ -148,6 +152,7 @@ func TestArtifact_Inspect(t *testing.T) { Name: "child-project", Version: "1.0", }, + // Keep the original value BOMRef: "pkg:maven/org.codehaus.mojo/child-project@1.0?file_path=app%2Fmaven%2Ftarget%2Fchild-project-1.0.jar", }, }, @@ -158,6 +163,7 @@ func TestArtifact_Inspect(t *testing.T) { FilePath: "", Libraries: types.Packages{ { + ID: "bootstrap@5.0.2", Name: "bootstrap", Version: "5.0.2", Licenses: []string{"MIT"}, @@ -171,6 +177,7 @@ func TestArtifact_Inspect(t *testing.T) { Name: "bootstrap", Version: "5.0.2", }, + // Keep the original value BOMRef: "pkg:npm/bootstrap@5.0.2?file_path=app%2Fapp%2Fpackage.json", }, }, @@ -184,9 +191,9 @@ func TestArtifact_Inspect(t *testing.T) { want: types.ArtifactReference{ Name: filepath.Join("testdata", "bom.json"), Type: types.ArtifactCycloneDX, - ID: "sha256:5d7b14f463a56006dd4060d04295afbda468350f2e5a786ae48d18fd02fb9a89", + ID: "sha256:f6d4bf4edf2818010ef009b6cd0f837c94dac3464d99e665470c8d05648478e3", BlobIDs: []string{ - "sha256:5d7b14f463a56006dd4060d04295afbda468350f2e5a786ae48d18fd02fb9a89", + "sha256:f6d4bf4edf2818010ef009b6cd0f837c94dac3464d99e665470c8d05648478e3", }, }, }, @@ -195,7 +202,7 @@ func TestArtifact_Inspect(t *testing.T) { filePath: filepath.Join("testdata", "sbom.cdx.intoto.jsonl"), putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:5d7b14f463a56006dd4060d04295afbda468350f2e5a786ae48d18fd02fb9a89", + BlobID: "sha256:f6d4bf4edf2818010ef009b6cd0f837c94dac3464d99e665470c8d05648478e3", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, OS: types.OS{ @@ -206,6 +213,7 @@ func TestArtifact_Inspect(t *testing.T) { { Packages: types.Packages{ { + ID: "musl@1.2.3-r0", Name: "musl", Version: "1.2.3-r0", SrcName: "musl", @@ -239,6 +247,7 @@ func TestArtifact_Inspect(t *testing.T) { FilePath: "app/composer/composer.lock", Libraries: types.Packages{ { + ID: "pear/log@1.13.1", Name: "pear/log", Version: "1.13.1", Identifier: types.PkgIdentifier{ @@ -255,7 +264,7 @@ func TestArtifact_Inspect(t *testing.T) { }, }, { - + ID: "pear/pear_exception@v1.0.0", Name: "pear/pear_exception", Version: "v1.0.0", Identifier: types.PkgIdentifier{ @@ -278,6 +287,7 @@ func TestArtifact_Inspect(t *testing.T) { FilePath: "app/gobinary/gobinary", Libraries: types.Packages{ { + ID: "github.com/package-url/packageurl-go@v0.1.1-0.20220203205134-d70459300c8a", Name: "github.com/package-url/packageurl-go", Version: "v0.1.1-0.20220203205134-d70459300c8a", Identifier: types.PkgIdentifier{ @@ -300,6 +310,7 @@ func TestArtifact_Inspect(t *testing.T) { FilePath: "", Libraries: types.Packages{ { + ID: "org.codehaus.mojo:child-project:1.0", Name: "org.codehaus.mojo:child-project", Version: "1.0", Identifier: types.PkgIdentifier{ @@ -309,6 +320,7 @@ func TestArtifact_Inspect(t *testing.T) { Name: "child-project", Version: "1.0", }, + // Keep the original value BOMRef: "pkg:maven/org.codehaus.mojo/child-project@1.0?file_path=app%2Fmaven%2Ftarget%2Fchild-project-1.0.jar", }, Layer: types.Layer{ @@ -323,6 +335,7 @@ func TestArtifact_Inspect(t *testing.T) { FilePath: "", Libraries: types.Packages{ { + ID: "bootstrap@5.0.2", Name: "bootstrap", Version: "5.0.2", Identifier: types.PkgIdentifier{ @@ -331,6 +344,7 @@ func TestArtifact_Inspect(t *testing.T) { Name: "bootstrap", Version: "5.0.2", }, + // Keep the original value BOMRef: "pkg:npm/bootstrap@5.0.2?file_path=app%2Fapp%2Fpackage.json", }, Licenses: []string{"MIT"}, @@ -349,9 +363,9 @@ func TestArtifact_Inspect(t *testing.T) { want: types.ArtifactReference{ Name: filepath.Join("testdata", "sbom.cdx.intoto.jsonl"), Type: types.ArtifactCycloneDX, - ID: "sha256:5d7b14f463a56006dd4060d04295afbda468350f2e5a786ae48d18fd02fb9a89", + ID: "sha256:f6d4bf4edf2818010ef009b6cd0f837c94dac3464d99e665470c8d05648478e3", BlobIDs: []string{ - "sha256:5d7b14f463a56006dd4060d04295afbda468350f2e5a786ae48d18fd02fb9a89", + "sha256:f6d4bf4edf2818010ef009b6cd0f837c94dac3464d99e665470c8d05648478e3", }, }, }, @@ -368,16 +382,13 @@ func TestArtifact_Inspect(t *testing.T) { filePath: filepath.Join("testdata", "os-only-bom.json"), putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:033dc76e6daf7d8ba439d678dc7e33400687098f3e9f563f6975adf4eb440eee", + BlobID: "sha256:911a6c875617315c51971dddf19fa2d47d6132cd14e9c6a87deb074afaf07818", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, OS: types.OS{ Family: "alpine", Name: "3.16.0", }, - PackageInfos: []types.PackageInfo{ - {}, - }, }, }, Returns: cache.ArtifactCachePutBlobReturns{ @@ -410,7 +421,7 @@ func TestArtifact_Inspect(t *testing.T) { } // Not compare the original CycloneDX report - got.CycloneDX = nil + got.BOM = nil require.NoError(t, err) assert.Equal(t, tt.want, got) diff --git a/pkg/fanal/handler/unpackaged/unpackaged_test.go b/pkg/fanal/handler/unpackaged/unpackaged_test.go index a37748101144..b466aa83282a 100644 --- a/pkg/fanal/handler/unpackaged/unpackaged_test.go +++ b/pkg/fanal/handler/unpackaged/unpackaged_test.go @@ -43,6 +43,7 @@ func Test_unpackagedHook_Handle(t *testing.T) { FilePath: "go.mod", Libraries: types.Packages{ { + ID: "github.com/spf13/cobra@v1.5.0", Name: "github.com/spf13/cobra", Version: "1.5.0", Identifier: types.PkgIdentifier{ diff --git a/pkg/fanal/types/artifact.go b/pkg/fanal/types/artifact.go index cd005c6582b1..86870d52ee40 100644 --- a/pkg/fanal/types/artifact.go +++ b/pkg/fanal/types/artifact.go @@ -10,6 +10,7 @@ import ( "github.com/samber/lo" "github.com/aquasecurity/trivy/pkg/digest" + "github.com/aquasecurity/trivy/pkg/sbom/core" ) type OS struct { @@ -277,7 +278,7 @@ type ArtifactReference struct { ImageMetadata ImageMetadata // SBOM - CycloneDX *CycloneDX + BOM *core.BOM } type ImageMetadata struct { diff --git a/pkg/fanal/types/sbom.go b/pkg/fanal/types/sbom.go deleted file mode 100644 index 6a7298bc2605..000000000000 --- a/pkg/fanal/types/sbom.go +++ /dev/null @@ -1,43 +0,0 @@ -package types - -// CycloneDX re-defines only necessary fields from cyclondx/cyclonedx-go -// cf. https://github.com/CycloneDX/cyclonedx-go/blob/de6bc07025d148badc8f6699ccb556744a5f4070/cyclonedx.go#L58-L77 -// -// The encoding/xml package that cyclondx-go depends on cannot be imported due to some limitations in TinyGo. -// cf. https://tinygo.org/docs/reference/lang-support/stdlib/ -type CycloneDX struct { - // JSON specific fields - BOMFormat string `json:"bomFormat" xml:"-"` - SpecVersion SpecVersion `json:"specVersion" xml:"-"` - - SerialNumber string `json:"serialNumber,omitempty" xml:"serialNumber,attr,omitempty"` - Version int `json:"version" xml:"version,attr"` - Metadata Metadata `json:"metadata,omitempty" xml:"metadata,omitempty"` - Components []Component `json:"components,omitempty" xml:"components>component,omitempty"` -} - -type Metadata struct { - Timestamp string `json:"timestamp,omitempty" xml:"timestamp,omitempty"` - Component Component `json:"component,omitempty" xml:"component,omitempty"` -} - -type Component struct { - BOMRef string `json:"bom-ref,omitempty" xml:"bom-ref,attr,omitempty"` - MIMEType string `json:"mime-type,omitempty" xml:"mime-type,attr,omitempty"` - Type ComponentType `json:"type" xml:"type,attr"` - Name string `json:"name" xml:"name"` - Group string `json:"group" xml:"group"` - Version string `json:"version,omitempty" xml:"version,omitempty"` - PackageURL string `json:"purl,omitempty" xml:"purl,omitempty"` - Properties []Property `json:"properties,omitempty" xml:"properties>property,omitempty"` -} - -type Property struct { - Name string `json:"name" xml:"name,attr"` - Value string `json:"value" xml:",chardata"` -} - -type ( - ComponentType string - SpecVersion int -) diff --git a/pkg/k8s/report/cyclonedx.go b/pkg/k8s/report/cyclonedx.go index 6e10d34e7e89..1d87769c29b2 100644 --- a/pkg/k8s/report/cyclonedx.go +++ b/pkg/k8s/report/cyclonedx.go @@ -5,14 +5,16 @@ import ( "io" cdx "github.com/CycloneDX/cyclonedx-go" + "golang.org/x/xerrors" - "github.com/aquasecurity/trivy/pkg/sbom/cyclonedx/core" + "github.com/aquasecurity/trivy/pkg/sbom/core" + "github.com/aquasecurity/trivy/pkg/sbom/cyclonedx" ) // CycloneDXWriter implements types.Writer type CycloneDXWriter struct { encoder cdx.BOMEncoder - marshaler *core.CycloneDX + marshaler cyclonedx.Marshaler } // NewCycloneDXWriter constract new CycloneDXWriter @@ -22,11 +24,14 @@ func NewCycloneDXWriter(output io.Writer, format cdx.BOMFileFormat, appVersion s encoder.SetEscapeHTML(false) return CycloneDXWriter{ encoder: encoder, - marshaler: core.NewCycloneDX(appVersion), + marshaler: cyclonedx.NewMarshaler(appVersion), } } -func (w CycloneDXWriter) Write(ctx context.Context, component *core.Component) error { - bom := w.marshaler.Marshal(ctx, component) +func (w CycloneDXWriter) Write(ctx context.Context, component *core.BOM) error { + bom, err := w.marshaler.Marshal(ctx, component) + if err != nil { + return xerrors.Errorf("CycloneDX marshal error: %w", err) + } return w.encoder.Encode(bom) } diff --git a/pkg/k8s/report/report.go b/pkg/k8s/report/report.go index dc0b44c7ab73..5de332a703bc 100644 --- a/pkg/k8s/report/report.go +++ b/pkg/k8s/report/report.go @@ -12,7 +12,7 @@ import ( "github.com/aquasecurity/trivy-kubernetes/pkg/artifacts" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" - "github.com/aquasecurity/trivy/pkg/sbom/cyclonedx/core" + "github.com/aquasecurity/trivy/pkg/sbom/core" "github.com/aquasecurity/trivy/pkg/types" ) @@ -40,8 +40,8 @@ type Option struct { type Report struct { SchemaVersion int `json:",omitempty"` ClusterName string - Resources []Resource `json:",omitempty"` - RootComponent *core.Component `json:"-"` + Resources []Resource `json:",omitempty"` + BOM *core.BOM `json:"-"` name string } @@ -201,7 +201,12 @@ func SeparateMisconfigReports(k8sReport Report, scanners types.Scanners, compone } func rbacResource(misConfig Resource) bool { - return slices.Contains([]string{"Role", "RoleBinding", "ClusterRole", "ClusterRoleBinding"}, misConfig.Kind) + return slices.Contains([]string{ + "Role", + "RoleBinding", + "ClusterRole", + "ClusterRoleBinding", + }, misConfig.Kind) } func infraResource(misConfig Resource) bool { diff --git a/pkg/k8s/scanner/scanner.go b/pkg/k8s/scanner/scanner.go index ff5018406b66..16ac301c9fa4 100644 --- a/pkg/k8s/scanner/scanner.go +++ b/pkg/k8s/scanner/scanner.go @@ -7,7 +7,6 @@ import ( "sort" "strings" - cdx "github.com/CycloneDX/cyclonedx-go" ms "github.com/mitchellh/mapstructure" "github.com/package-url/packageurl-go" "github.com/samber/lo" @@ -25,14 +24,14 @@ import ( "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/parallel" "github.com/aquasecurity/trivy/pkg/purl" - cyc "github.com/aquasecurity/trivy/pkg/sbom/cyclonedx" - "github.com/aquasecurity/trivy/pkg/sbom/cyclonedx/core" + "github.com/aquasecurity/trivy/pkg/sbom/core" + "github.com/aquasecurity/trivy/pkg/sbom/cyclonedx" "github.com/aquasecurity/trivy/pkg/scanner/local" "github.com/aquasecurity/trivy/pkg/types" ) const ( - k8sCoreComponentNamespace = core.Namespace + "resource:" + k8sCoreComponentNamespace = cyclonedx.Namespace + "resource:" k8sComponentType = "Type" k8sComponentName = "Name" k8sComponentNode = "node" @@ -71,13 +70,13 @@ func (s *Scanner) Scan(ctx context.Context, artifactsData []*artifacts.Artifact) }() if s.opts.Format == types.FormatCycloneDX { - rootComponent, err := clusterInfoToReportResources(artifactsData) + kbom, err := s.clusterInfoToReportResources(artifactsData) if err != nil { return report.Report{}, err } return report.Report{ SchemaVersion: 0, - RootComponent: rootComponent, + BOM: kbom, }, nil } var resourceArtifacts []*artifacts.Artifact @@ -213,7 +212,6 @@ func (s *Scanner) filter(ctx context.Context, r types.Report, artifact *artifact } const ( - golang = "golang" oci = "oci" kubelet = "k8s.io/kubelet" controlPlaneComponents = "ControlPlaneComponents" @@ -225,7 +223,7 @@ const ( func (s *Scanner) scanK8sVulns(ctx context.Context, artifactsData []*artifacts.Artifact) ([]report.Resource, error) { var resources []report.Resource var nodeName string - if nodeName = findNodeName(artifactsData); nodeName == "" { + if nodeName = s.findNodeName(artifactsData); nodeName == "" { return resources, nil } @@ -357,7 +355,7 @@ func (s *Scanner) scanK8sVulns(ctx context.Context, artifactsData []*artifacts.A return resources, nil } -func findNodeName(allArtifact []*artifacts.Artifact) string { +func (*Scanner) findNodeName(allArtifact []*artifacts.Artifact) string { for _, artifact := range allArtifact { if artifact.Kind != nodeComponents { continue @@ -367,25 +365,36 @@ func findNodeName(allArtifact []*artifacts.Artifact) string { return "" } -func clusterInfoToReportResources(allArtifact []*artifacts.Artifact) (*core.Component, error) { +func (s *Scanner) clusterInfoToReportResources(allArtifact []*artifacts.Artifact) (*core.BOM, error) { + var rootComponent *core.Component var coreComponents []*core.Component - var cInfo *core.Component // Find the first node name to identify AKS cluster var nodeName string - if nodeName = findNodeName(allArtifact); nodeName == "" { + if nodeName = s.findNodeName(allArtifact); nodeName == "" { return nil, fmt.Errorf("failed to find node name") } + kbom := core.NewBOM() for _, artifact := range allArtifact { switch artifact.Kind { case controlPlaneComponents: var comp bom.Component - err := ms.Decode(artifact.RawResource, &comp) - if err != nil { + if err := ms.Decode(artifact.RawResource, &comp); err != nil { return nil, err } - var imageComponents []*core.Component + + controlPlane := &core.Component{ + Name: comp.Name, + Version: comp.Version, + Type: core.TypeApplication, + Properties: toProperties(comp.Properties, k8sCoreComponentNamespace), + PkgID: core.PkgID{ + PURL: generatePURL(comp.Name, comp.Version, nodeName), + }, + } + coreComponents = append(coreComponents, controlPlane) + for _, c := range comp.Containers { name := fmt.Sprintf("%s/%s", c.Registry, c.Repository) cDigest := c.Digest @@ -399,67 +408,149 @@ func clusterInfoToReportResources(allArtifact []*artifacts.Artifact) (*core.Comp fmt.Sprintf("%s@%s", name, cDigest), }, }, ftypes.Package{}) - if err != nil { return nil, xerrors.Errorf("failed to create PURL: %w", err) } - imageComponents = append(imageComponents, &core.Component{ - PackageURL: imagePURL, - Type: cdx.ComponentTypeContainer, - Name: name, - Version: cDigest, + + imageComponent := &core.Component{ + Type: core.TypeContainer, + Name: name, + Version: cDigest, + PkgID: core.PkgID{ + PURL: imagePURL.Unwrap(), + }, Properties: []core.Property{ { - Name: cyc.PropertyPkgID, + Name: core.PropertyPkgID, Value: fmt.Sprintf("%s:%s", name, ver), }, { - Name: cyc.PropertyPkgType, + Name: core.PropertyPkgType, Value: oci, }, }, - }) - } - rootComponent := &core.Component{ - Name: comp.Name, - Version: comp.Version, - Type: cdx.ComponentTypeApplication, - Properties: toProperties(comp.Properties, k8sCoreComponentNamespace), - Components: imageComponents, - PackageURL: generatePURL(comp.Name, comp.Version, nodeName), + } + kbom.AddRelationship(controlPlane, imageComponent, core.RelationshipDependsOn) } - coreComponents = append(coreComponents, rootComponent) case nodeComponents: var nf bom.NodeInfo err := ms.Decode(artifact.RawResource, &nf) if err != nil { return nil, err } - coreComponents = append(coreComponents, nodeComponent(nf)) + coreComponents = append(coreComponents, s.nodeComponent(kbom, nf)) case clusterInfo: var cf bom.ClusterInfo - err := ms.Decode(artifact.RawResource, &cf) - if err != nil { + if err := ms.Decode(artifact.RawResource, &cf); err != nil { return nil, err } - cInfo = &core.Component{ + rootComponent = &core.Component{ + Type: core.TypePlatform, Name: cf.Name, Version: cf.Version, Properties: toProperties(cf.Properties, k8sCoreComponentNamespace), + PkgID: core.PkgID{ + PURL: generatePURL(cf.Name, cf.Version, nodeName), + }, + Root: true, } + kbom.AddComponent(rootComponent) default: return nil, fmt.Errorf("resource kind %s is not supported", artifact.Kind) } } - rootComponent := &core.Component{ - Name: cInfo.Name, - Version: cInfo.Version, - Type: cdx.ComponentTypePlatform, - Properties: cInfo.Properties, - Components: coreComponents, - PackageURL: generatePURL(cInfo.Name, cInfo.Version, nodeName), + + for _, c := range coreComponents { + kbom.AddRelationship(rootComponent, c, core.RelationshipContains) + } + + return kbom, nil +} + +func (s *Scanner) nodeComponent(b *core.BOM, nf bom.NodeInfo) *core.Component { + osName, osVersion := osNameVersion(nf.OsImage) + runtimeName, runtimeVersion := runtimeNameVersion(nf.ContainerRuntimeVersion) + kubeletVersion := sanitizedVersion(nf.KubeletVersion) + properties := toProperties(nf.Properties, "") + properties = append(properties, toProperties(map[string]string{ + k8sComponentType: k8sComponentNode, + k8sComponentName: nf.NodeName, + }, k8sCoreComponentNamespace)...) + + nodeComponent := &core.Component{ + Type: core.TypePlatform, + Name: nf.NodeName, + Properties: properties, + } + + osComponent := &core.Component{ + Type: core.TypeOS, + Name: osName, + Version: osVersion, + Properties: []core.Property{ + { + Name: "Class", + Value: string(types.ClassOSPkg), + }, + { + Name: "Type", + Value: osName, + }, + }, + } + b.AddRelationship(nodeComponent, osComponent, core.RelationshipContains) + + appComponent := &core.Component{ + Type: core.TypeApplication, + Name: nodeCoreComponents, + } + b.AddRelationship(nodeComponent, appComponent, core.RelationshipContains) + + kubeletComponent := &core.Component{ + Type: core.TypeApplication, + Name: kubelet, + Version: kubeletVersion, + Properties: []core.Property{ + { + Name: k8sComponentType, + Value: k8sComponentNode, + Namespace: k8sCoreComponentNamespace, + }, + { + Name: k8sComponentName, + Value: kubelet, + Namespace: k8sCoreComponentNamespace, + }, + }, + PkgID: core.PkgID{ + PURL: generatePURL(kubelet, kubeletVersion, nf.NodeName), + }, + } + b.AddRelationship(appComponent, kubeletComponent, core.RelationshipContains) + + runtimeComponent := &core.Component{ + Type: core.TypeApplication, + Name: runtimeName, + Version: runtimeVersion, + Properties: []core.Property{ + { + Name: k8sComponentType, + Value: k8sComponentNode, + Namespace: k8sCoreComponentNamespace, + }, + { + Name: k8sComponentName, + Value: runtimeName, + Namespace: k8sCoreComponentNamespace, + }, + }, + PkgID: core.PkgID{ + PURL: packageurl.NewPackageURL(packageurl.TypeGolang, "", runtimeName, runtimeVersion, packageurl.Qualifiers{}, ""), + }, } - return rootComponent, nil + b.AddRelationship(appComponent, runtimeComponent, core.RelationshipContains) + + return nodeComponent } func sanitizedVersion(ver string) string { @@ -500,93 +591,6 @@ func runtimeNameVersion(name string) (string, string) { return name, ver } -func nodeComponent(nf bom.NodeInfo) *core.Component { - osName, osVersion := osNameVersion(nf.OsImage) - runtimeName, runtimeVersion := runtimeNameVersion(nf.ContainerRuntimeVersion) - kubeletVersion := sanitizedVersion(nf.KubeletVersion) - properties := toProperties(nf.Properties, "") - properties = append(properties, toProperties(map[string]string{ - k8sComponentType: k8sComponentNode, - k8sComponentName: nf.NodeName, - }, k8sCoreComponentNamespace)...) - return &core.Component{ - Type: cdx.ComponentTypePlatform, - Name: nf.NodeName, - Properties: properties, - Components: []*core.Component{ - { - Type: cdx.ComponentTypeOS, - Name: osName, - Version: osVersion, - Properties: []core.Property{ - { - Name: "Class", - Value: string(types.ClassOSPkg), - }, - { - Name: "Type", - Value: osName, - }, - }, - }, - { - Type: cdx.ComponentTypeApplication, - Name: nodeCoreComponents, - Properties: []core.Property{ - { - Name: "Class", - Value: string(types.ClassLangPkg), - }, - { - Name: "Type", - Value: golang, - }, - }, - Components: []*core.Component{ - { - Type: cdx.ComponentTypeApplication, - Name: kubelet, - Version: kubeletVersion, - Properties: []core.Property{ - { - Name: k8sComponentType, - Value: k8sComponentNode, - Namespace: k8sCoreComponentNamespace, - }, - { - Name: k8sComponentName, - Value: kubelet, - Namespace: k8sCoreComponentNamespace, - }, - }, - PackageURL: generatePURL(kubelet, kubeletVersion, nf.NodeName), - }, - { - Type: cdx.ComponentTypeApplication, - Name: runtimeName, - Version: runtimeVersion, - Properties: []core.Property{ - { - Name: k8sComponentType, - Value: k8sComponentNode, - Namespace: k8sCoreComponentNamespace, - }, - { - Name: k8sComponentName, - Value: runtimeName, - Namespace: k8sCoreComponentNamespace, - }, - }, - PackageURL: &purl.PackageURL{ - PackageURL: *packageurl.NewPackageURL(golang, "", runtimeName, runtimeVersion, packageurl.Qualifiers{}, ""), - }, - }, - }, - }, - }, - } -} - func toProperties(props map[string]string, namespace string) []core.Property { properties := lo.MapToSlice(props, func(k, v string) core.Property { return core.Property{ @@ -595,14 +599,16 @@ func toProperties(props map[string]string, namespace string) []core.Property { Namespace: namespace, } }) + if len(properties) == 0 { + return nil + } sort.Slice(properties, func(i, j int) bool { return properties[i].Name < properties[j].Name }) return properties } -func generatePURL(name, ver, nodeName string) *purl.PackageURL { - +func generatePURL(name, ver, nodeName string) *packageurl.PackageURL { var namespace string // Identify k8s distribution. An empty namespace means upstream. if namespace = k8sNamespace(ver, nodeName); namespace == "" { @@ -611,9 +617,7 @@ func generatePURL(name, ver, nodeName string) *purl.PackageURL { namespace = "" } - return &purl.PackageURL{ - PackageURL: *packageurl.NewPackageURL(purl.TypeK8s, namespace, name, ver, nil, ""), - } + return packageurl.NewPackageURL(purl.TypeK8s, namespace, name, ver, nil, "") } func k8sNamespace(ver, nodeName string) string { diff --git a/pkg/k8s/scanner/scanner_test.go b/pkg/k8s/scanner/scanner_test.go index ff879d2ac14c..8c9850c12b76 100644 --- a/pkg/k8s/scanner/scanner_test.go +++ b/pkg/k8s/scanner/scanner_test.go @@ -2,10 +2,13 @@ package scanner import ( "context" + "github.com/aquasecurity/trivy/pkg/sbom/core" + "github.com/aquasecurity/trivy/pkg/uuid" + "github.com/stretchr/testify/require" + "golang.org/x/exp/maps" "sort" "testing" - cdx "github.com/CycloneDX/cyclonedx-go" "github.com/package-url/packageurl-go" "github.com/stretchr/testify/assert" @@ -13,17 +16,15 @@ import ( cmd "github.com/aquasecurity/trivy/pkg/commands/artifact" "github.com/aquasecurity/trivy/pkg/flag" "github.com/aquasecurity/trivy/pkg/purl" - cyc "github.com/aquasecurity/trivy/pkg/sbom/cyclonedx" - "github.com/aquasecurity/trivy/pkg/sbom/cyclonedx/core" ) func TestScanner_Scan(t *testing.T) { flagOpts := flag.Options{ReportOptions: flag.ReportOptions{Format: "cyclonedx"}} tests := []struct { - name string - clusterName string - artifacts []*artifacts.Artifact - want *core.Component + name string + clusterName string + artifacts []*artifacts.Artifact + wantComponents []*core.Component }{ { name: "test cluster info with resources", @@ -81,194 +82,190 @@ func TestScanner_Scan(t *testing.T) { }, }, }, - want: &core.Component{ - Type: cdx.ComponentTypePlatform, - Name: "k8s.io/kubernetes", - Version: "1.21.1", - Properties: []core.Property{ - { - Name: "Name", - Value: "kind-kind", - Namespace: k8sCoreComponentNamespace, + wantComponents: []*core.Component{ + { + Type: core.TypeApplication, + Name: "github.com/containerd/containerd", + Version: "1.5.2", + Properties: []core.Property{ + { + Name: k8sComponentName, + Value: "github.com/containerd/containerd", + Namespace: k8sCoreComponentNamespace, + }, + { + Name: k8sComponentType, + Value: "node", + Namespace: k8sCoreComponentNamespace, + }, }, - { - Name: "Type", - Value: "cluster", - Namespace: k8sCoreComponentNamespace, + PkgID: core.PkgID{ + PURL: &packageurl.PackageURL{ + Type: "golang", + Name: "github.com/containerd/containerd", + Version: "1.5.2", + Qualifiers: packageurl.Qualifiers{}, + }, + BOMRef: "pkg:golang/github.com%2Fcontainerd%2Fcontainerd@1.5.2", }, }, - PackageURL: &purl.PackageURL{ - PackageURL: packageurl.PackageURL{ - Type: purl.TypeK8s, - Name: "k8s.io/kubernetes", - Version: "1.21.1", + { + Type: core.TypeApplication, + Name: "k8s.io/apiserver", + Version: "1.21.1", + PkgID: core.PkgID{ + PURL: &packageurl.PackageURL{ + Type: purl.TypeK8s, + Name: "k8s.io/apiserver", + Version: "1.21.1", + }, + BOMRef: "pkg:k8s/k8s.io%2Fapiserver@1.21.1", }, }, - Components: []*core.Component{ - { - Type: cdx.ComponentTypeApplication, - Name: "k8s.io/apiserver", - Version: "1.21.1", - PackageURL: &purl.PackageURL{ - PackageURL: packageurl.PackageURL{ - Type: purl.TypeK8s, - Name: "k8s.io/apiserver", - Version: "1.21.1", - }, + { + Type: core.TypeApplication, + Name: "k8s.io/kubelet", + Version: "1.21.1", + Properties: []core.Property{ + { + Name: k8sComponentName, + Value: "k8s.io/kubelet", + Namespace: k8sCoreComponentNamespace, }, - Properties: []core.Property{}, - Components: []*core.Component{ - { - Type: cdx.ComponentTypeContainer, - Name: "k8s.gcr.io/kube-apiserver", - Version: "sha256:18e61c783b41758dd391ab901366ec3546b26fae00eef7e223d1f94da808e02f", - PackageURL: &purl.PackageURL{ - PackageURL: packageurl.PackageURL{ - Type: "oci", - Name: "kube-apiserver", - Version: "sha256:18e61c783b41758dd391ab901366ec3546b26fae00eef7e223d1f94da808e02f", - Qualifiers: packageurl.Qualifiers{ - { - Key: "repository_url", - Value: "k8s.gcr.io/kube-apiserver", - }, - }, - }, - }, - Properties: []core.Property{ - { - Name: cyc.PropertyPkgID, - Value: "k8s.gcr.io/kube-apiserver:1.21.1", - }, - { - Name: cyc.PropertyPkgType, - Value: "oci", - }, - }, - }, + { + Name: k8sComponentType, + Value: "node", + Namespace: k8sCoreComponentNamespace, }, }, - { - Type: cdx.ComponentTypePlatform, - Name: "kind-control-plane", - Properties: []core.Property{ - { - Name: "Architecture", - Value: "arm64", - }, - { - Name: "HostName", - Value: "kind-control-plane", - }, - { - Name: "KernelVersion", - Value: "6.2.15-300.fc38.aarch64", - }, - { - Name: "NodeRole", - Value: "master", - }, - { - Name: "OperatingSystem", - Value: "linux", - }, - { - Name: k8sComponentName, - Value: "kind-control-plane", - Namespace: k8sCoreComponentNamespace, - }, - { - Name: k8sComponentType, - Value: "node", - Namespace: k8sCoreComponentNamespace, - }, + PkgID: core.PkgID{ + PURL: &packageurl.PackageURL{ + Type: "k8s", + Name: "k8s.io/kubelet", + Version: "1.21.1", }, - Components: []*core.Component{ - { - Type: cdx.ComponentTypeOS, - Name: "ubuntu", - Version: "21.04", - Properties: []core.Property{ - { - Name: "Class", - Value: "os-pkgs", - Namespace: "", - }, - { - Name: "Type", - Value: "ubuntu", - Namespace: "", - }, - }, - }, - { - Type: cdx.ComponentTypeApplication, - Name: "node-core-components", - Properties: []core.Property{ - { - Name: "Class", - Value: "lang-pkgs", - Namespace: "", - }, - { - Name: "Type", - Value: "golang", - Namespace: "", - }, - }, - Components: []*core.Component{ - { - Type: cdx.ComponentTypeApplication, - Name: "k8s.io/kubelet", - Version: "1.21.1", - Properties: []core.Property{ - { - Name: k8sComponentType, - Value: "node", - Namespace: k8sCoreComponentNamespace, - }, - { - Name: k8sComponentName, - Value: "k8s.io/kubelet", - Namespace: k8sCoreComponentNamespace, - }, - }, - PackageURL: &purl.PackageURL{ - PackageURL: packageurl.PackageURL{ - Type: "k8s", - Name: "k8s.io/kubelet", - Version: "1.21.1", - }, - }, - }, - { - Type: cdx.ComponentTypeApplication, - Name: "github.com/containerd/containerd", - Version: "1.5.2", - Properties: []core.Property{ - { - Name: k8sComponentType, - Value: "node", - Namespace: k8sCoreComponentNamespace, - }, - { - Name: k8sComponentName, - Value: "github.com/containerd/containerd", - Namespace: k8sCoreComponentNamespace, - }, - }, - PackageURL: &purl.PackageURL{ - PackageURL: packageurl.PackageURL{ - Type: "golang", - Name: "github.com/containerd/containerd", - Version: "1.5.2", - Qualifiers: packageurl.Qualifiers{}, - }, - }, - }, + BOMRef: "pkg:k8s/k8s.io%2Fkubelet@1.21.1", + }, + }, + { + Type: core.TypeApplication, + Name: "node-core-components", + PkgID: core.PkgID{ + BOMRef: "3ff14136-e09f-4df9-80ea-000000000006", + }, + }, + { + Type: core.TypeContainer, + Name: "k8s.gcr.io/kube-apiserver", + Version: "sha256:18e61c783b41758dd391ab901366ec3546b26fae00eef7e223d1f94da808e02f", + PkgID: core.PkgID{ + PURL: &packageurl.PackageURL{ + Type: "oci", + Name: "kube-apiserver", + Version: "sha256:18e61c783b41758dd391ab901366ec3546b26fae00eef7e223d1f94da808e02f", + Qualifiers: packageurl.Qualifiers{ + { + Key: "repository_url", + Value: "k8s.gcr.io/kube-apiserver", }, }, }, + BOMRef: "pkg:oci/kube-apiserver@sha256%3A18e61c783b41758dd391ab901366ec3546b26fae00eef7e223d1f94da808e02f?repository_url=k8s.gcr.io%2Fkube-apiserver", + }, + Properties: []core.Property{ + { + Name: core.PropertyPkgID, + Value: "k8s.gcr.io/kube-apiserver:1.21.1", + }, + { + Name: core.PropertyPkgType, + Value: "oci", + }, + }, + }, + { + Type: core.TypeOS, + Name: "ubuntu", + Version: "21.04", + Properties: []core.Property{ + { + Name: "Class", + Value: "os-pkgs", + Namespace: "", + }, + { + Name: "Type", + Value: "ubuntu", + Namespace: "", + }, + }, + PkgID: core.PkgID{ + BOMRef: "3ff14136-e09f-4df9-80ea-000000000005", + }, + }, + { + Type: core.TypePlatform, + Root: true, + Name: "k8s.io/kubernetes", + Version: "1.21.1", + Properties: []core.Property{ + { + Name: "Name", + Value: "kind-kind", + Namespace: k8sCoreComponentNamespace, + }, + { + Name: "Type", + Value: "cluster", + Namespace: k8sCoreComponentNamespace, + }, + }, + PkgID: core.PkgID{ + PURL: &packageurl.PackageURL{ + Type: purl.TypeK8s, + Name: "k8s.io/kubernetes", + Version: "1.21.1", + }, + BOMRef: "pkg:k8s/k8s.io%2Fkubernetes@1.21.1", + }, + }, + { + Type: core.TypePlatform, + Name: "kind-control-plane", + Properties: []core.Property{ + { + Name: "Architecture", + Value: "arm64", + }, + { + Name: "HostName", + Value: "kind-control-plane", + }, + { + Name: "KernelVersion", + Value: "6.2.15-300.fc38.aarch64", + }, + { + Name: k8sComponentName, + Value: "kind-control-plane", + Namespace: k8sCoreComponentNamespace, + }, + { + Name: "NodeRole", + Value: "master", + }, + { + Name: "OperatingSystem", + Value: "linux", + }, + { + Name: k8sComponentType, + Value: "node", + Namespace: k8sCoreComponentNamespace, + }, + }, + PkgID: core.PkgID{ + BOMRef: "3ff14136-e09f-4df9-80ea-000000000004", }, }, }, @@ -276,34 +273,34 @@ func TestScanner_Scan(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - ctx := context.TODO() + ctx := context.Background() + uuid.SetFakeUUID(t, "3ff14136-e09f-4df9-80ea-%012d") + runner, err := cmd.NewRunner(ctx, flagOpts) assert.NoError(t, err) + scanner := NewScanner(tt.clusterName, runner, flagOpts) got, err := scanner.Scan(ctx, tt.artifacts) - sortNodeComponents(got.RootComponent) - sortNodeComponents(tt.want) - assert.Equal(t, tt.want, got.RootComponent) - }) - } -} + require.NoError(t, err) -func sortNodeComponents(component *core.Component) { - nodeComp := findComponentByName(component, "node-core-components") - sort.Slice(nodeComp.Components, func(i, j int) bool { - return nodeComp.Components[i].Name < nodeComp.Components[j].Name - }) -} + gotComponents := maps.Values(got.BOM.Components()) + require.Equal(t, len(tt.wantComponents), len(gotComponents)) -func findComponentByName(component *core.Component, compName string) *core.Component { - if component.Name == compName { - return component - } - var fComp *core.Component - for _, comp := range component.Components { - fComp = findComponentByName(comp, compName) + sort.Slice(gotComponents, func(i, j int) bool { + switch { + case gotComponents[i].Type != gotComponents[j].Type: + return gotComponents[i].Type < gotComponents[j].Type + case gotComponents[i].Name != gotComponents[j].Name: + return gotComponents[i].Name < gotComponents[j].Name + default: + return gotComponents[i].Version < gotComponents[j].Version + } + }) + for i, want := range tt.wantComponents { + assert.EqualExportedValues(t, *want, *gotComponents[i], want.Name) + } + }) } - return fComp } func TestTestOsNameVersion(t *testing.T) { @@ -546,7 +543,8 @@ func TestFindNodeName(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := findNodeName(tt.artifacts) + s := Scanner{} + got := s.findNodeName(tt.artifacts) assert.Equal(t, tt.want, got) }) } diff --git a/pkg/k8s/writer.go b/pkg/k8s/writer.go index 0e218b38f30f..13204501fb59 100644 --- a/pkg/k8s/writer.go +++ b/pkg/k8s/writer.go @@ -46,7 +46,7 @@ func Write(ctx context.Context, k8sreport report.Report, option report.Option) e return nil case types.FormatCycloneDX: w := report.NewCycloneDXWriter(option.Output, cdx.BOMFileFormatJSON, option.APIVersion) - return w.Write(ctx, k8sreport.RootComponent) + return w.Write(ctx, k8sreport.BOM) } return nil } diff --git a/pkg/module/module.go b/pkg/module/module.go index 4395feeddeb3..3d670999e1b9 100644 --- a/pkg/module/module.go +++ b/pkg/module/module.go @@ -9,7 +9,6 @@ import ( "regexp" "sync" - "github.com/mailru/easyjson" "github.com/samber/lo" "github.com/tetratelabs/wazero" "github.com/tetratelabs/wazero/api" @@ -235,8 +234,8 @@ func unmarshal(mem api.Memory, ptrSize uint64, v any) error { return nil } -func marshal(ctx context.Context, m api.Module, malloc api.Function, v easyjson.Marshaler) (uint64, uint64, error) { - b, err := easyjson.Marshal(v) +func marshal(ctx context.Context, m api.Module, malloc api.Function, v any) (uint64, uint64, error) { + b, err := json.Marshal(v) if err != nil { return 0, 0, xerrors.Errorf("marshal error: %w", err) } diff --git a/pkg/module/serialize/types.go b/pkg/module/serialize/types.go index 21733f736082..df72a953eee3 100644 --- a/pkg/module/serialize/types.go +++ b/pkg/module/serialize/types.go @@ -4,15 +4,8 @@ import ( "github.com/aquasecurity/trivy/pkg/types" ) -// TinyGo doesn't support encoding/json, but github.com/mailru/easyjson for now. -// We need to generate JSON-related methods like MarshalEasyJSON implementing easyjson.Marshaler. -// -// $ make easyjson - -//easyjson:json type StringSlice []string -//easyjson:json type AnalysisResult struct { // TODO: support other fields as well // OS *types.OS @@ -34,7 +27,6 @@ type CustomResource struct { type PostScanAction string -//easyjson:json type PostScanSpec struct { // What action the module will do in post scanning. // value: INSERT, UPDATE and DELETE @@ -45,8 +37,6 @@ type PostScanSpec struct { IDs []string } -//easyjson:json type Results []Result -//easyjson:json type Result types.Result diff --git a/pkg/module/serialize/types_easyjson.go b/pkg/module/serialize/types_easyjson.go deleted file mode 100644 index 988347841c28..000000000000 --- a/pkg/module/serialize/types_easyjson.go +++ /dev/null @@ -1,3024 +0,0 @@ -// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT. - -package serialize - -import ( - json "encoding/json" - types2 "github.com/aquasecurity/trivy-db/pkg/types" - digest "github.com/aquasecurity/trivy/pkg/digest" - types1 "github.com/aquasecurity/trivy/pkg/fanal/types" - types "github.com/aquasecurity/trivy/pkg/types" - easyjson "github.com/mailru/easyjson" - jlexer "github.com/mailru/easyjson/jlexer" - jwriter "github.com/mailru/easyjson/jwriter" - time "time" -) - -// suppress unused package warning -var ( - _ *json.RawMessage - _ *jlexer.Lexer - _ *jwriter.Writer - _ easyjson.Marshaler -) - -func easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgModuleSerialize(in *jlexer.Lexer, out *StringSlice) { - isTopLevel := in.IsStart() - if in.IsNull() { - in.Skip() - *out = nil - } else { - in.Delim('[') - if *out == nil { - if !in.IsDelim(']') { - *out = make(StringSlice, 0, 4) - } else { - *out = StringSlice{} - } - } else { - *out = (*out)[:0] - } - for !in.IsDelim(']') { - var v1 string - v1 = string(in.String()) - *out = append(*out, v1) - in.WantComma() - } - in.Delim(']') - } - if isTopLevel { - in.Consumed() - } -} -func easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgModuleSerialize(out *jwriter.Writer, in StringSlice) { - if in == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { - out.RawString("null") - } else { - out.RawByte('[') - for v2, v3 := range in { - if v2 > 0 { - out.RawByte(',') - } - out.String(string(v3)) - } - out.RawByte(']') - } -} - -// MarshalJSON supports json.Marshaler interface -func (v StringSlice) MarshalJSON() ([]byte, error) { - w := jwriter.Writer{} - easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgModuleSerialize(&w, v) - return w.Buffer.BuildBytes(), w.Error -} - -// MarshalEasyJSON supports easyjson.Marshaler interface -func (v StringSlice) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgModuleSerialize(w, v) -} - -// UnmarshalJSON supports json.Unmarshaler interface -func (v *StringSlice) UnmarshalJSON(data []byte) error { - r := jlexer.Lexer{Data: data} - easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgModuleSerialize(&r, v) - return r.Error() -} - -// UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *StringSlice) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgModuleSerialize(l, v) -} -func easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgModuleSerialize1(in *jlexer.Lexer, out *Results) { - isTopLevel := in.IsStart() - if in.IsNull() { - in.Skip() - *out = nil - } else { - in.Delim('[') - if *out == nil { - if !in.IsDelim(']') { - *out = make(Results, 0, 0) - } else { - *out = Results{} - } - } else { - *out = (*out)[:0] - } - for !in.IsDelim(']') { - var v4 Result - (v4).UnmarshalEasyJSON(in) - *out = append(*out, v4) - in.WantComma() - } - in.Delim(']') - } - if isTopLevel { - in.Consumed() - } -} -func easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgModuleSerialize1(out *jwriter.Writer, in Results) { - if in == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { - out.RawString("null") - } else { - out.RawByte('[') - for v5, v6 := range in { - if v5 > 0 { - out.RawByte(',') - } - (v6).MarshalEasyJSON(out) - } - out.RawByte(']') - } -} - -// MarshalJSON supports json.Marshaler interface -func (v Results) MarshalJSON() ([]byte, error) { - w := jwriter.Writer{} - easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgModuleSerialize1(&w, v) - return w.Buffer.BuildBytes(), w.Error -} - -// MarshalEasyJSON supports easyjson.Marshaler interface -func (v Results) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgModuleSerialize1(w, v) -} - -// UnmarshalJSON supports json.Unmarshaler interface -func (v *Results) UnmarshalJSON(data []byte) error { - r := jlexer.Lexer{Data: data} - easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgModuleSerialize1(&r, v) - return r.Error() -} - -// UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *Results) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgModuleSerialize1(l, v) -} -func easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgModuleSerialize2(in *jlexer.Lexer, out *Result) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } - switch key { - case "Target": - out.Target = string(in.String()) - case "Class": - out.Class = types.ResultClass(in.String()) - case "Type": - out.Type = types1.TargetType(in.String()) - case "Packages": - if in.IsNull() { - in.Skip() - out.Packages = nil - } else { - in.Delim('[') - if out.Packages == nil { - if !in.IsDelim(']') { - out.Packages = make([]types1.Package, 0, 0) - } else { - out.Packages = []types1.Package{} - } - } else { - out.Packages = (out.Packages)[:0] - } - for !in.IsDelim(']') { - var v7 types1.Package - easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgFanalTypes(in, &v7) - out.Packages = append(out.Packages, v7) - in.WantComma() - } - in.Delim(']') - } - case "Vulnerabilities": - if in.IsNull() { - in.Skip() - out.Vulnerabilities = nil - } else { - in.Delim('[') - if out.Vulnerabilities == nil { - if !in.IsDelim(']') { - out.Vulnerabilities = make([]types.DetectedVulnerability, 0, 0) - } else { - out.Vulnerabilities = []types.DetectedVulnerability{} - } - } else { - out.Vulnerabilities = (out.Vulnerabilities)[:0] - } - for !in.IsDelim(']') { - var v8 types.DetectedVulnerability - easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgTypes(in, &v8) - out.Vulnerabilities = append(out.Vulnerabilities, v8) - in.WantComma() - } - in.Delim(']') - } - case "MisconfSummary": - if in.IsNull() { - in.Skip() - out.MisconfSummary = nil - } else { - if out.MisconfSummary == nil { - out.MisconfSummary = new(types.MisconfSummary) - } - easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgTypes1(in, out.MisconfSummary) - } - case "Misconfigurations": - if in.IsNull() { - in.Skip() - out.Misconfigurations = nil - } else { - in.Delim('[') - if out.Misconfigurations == nil { - if !in.IsDelim(']') { - out.Misconfigurations = make([]types.DetectedMisconfiguration, 0, 0) - } else { - out.Misconfigurations = []types.DetectedMisconfiguration{} - } - } else { - out.Misconfigurations = (out.Misconfigurations)[:0] - } - for !in.IsDelim(']') { - var v9 types.DetectedMisconfiguration - easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgTypes2(in, &v9) - out.Misconfigurations = append(out.Misconfigurations, v9) - in.WantComma() - } - in.Delim(']') - } - case "Secrets": - if in.IsNull() { - in.Skip() - out.Secrets = nil - } else { - in.Delim('[') - if out.Secrets == nil { - if !in.IsDelim(']') { - out.Secrets = make([]types.DetectedSecret, 0, 0) - } else { - out.Secrets = []types.DetectedSecret{} - } - } else { - out.Secrets = (out.Secrets)[:0] - } - for !in.IsDelim(']') { - var v10 types.DetectedSecret - easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgTypes3(in, &v10) - out.Secrets = append(out.Secrets, v10) - in.WantComma() - } - in.Delim(']') - } - case "Licenses": - if in.IsNull() { - in.Skip() - out.Licenses = nil - } else { - in.Delim('[') - if out.Licenses == nil { - if !in.IsDelim(']') { - out.Licenses = make([]types.DetectedLicense, 0, 0) - } else { - out.Licenses = []types.DetectedLicense{} - } - } else { - out.Licenses = (out.Licenses)[:0] - } - for !in.IsDelim(']') { - var v11 types.DetectedLicense - easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgTypes4(in, &v11) - out.Licenses = append(out.Licenses, v11) - in.WantComma() - } - in.Delim(']') - } - case "CustomResources": - if in.IsNull() { - in.Skip() - out.CustomResources = nil - } else { - in.Delim('[') - if out.CustomResources == nil { - if !in.IsDelim(']') { - out.CustomResources = make([]types1.CustomResource, 0, 0) - } else { - out.CustomResources = []types1.CustomResource{} - } - } else { - out.CustomResources = (out.CustomResources)[:0] - } - for !in.IsDelim(']') { - var v12 types1.CustomResource - easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgFanalTypes1(in, &v12) - out.CustomResources = append(out.CustomResources, v12) - in.WantComma() - } - in.Delim(']') - } - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgModuleSerialize2(out *jwriter.Writer, in Result) { - out.RawByte('{') - first := true - _ = first - { - const prefix string = ",\"Target\":" - out.RawString(prefix[1:]) - out.String(string(in.Target)) - } - if in.Class != "" { - const prefix string = ",\"Class\":" - out.RawString(prefix) - out.String(string(in.Class)) - } - if in.Type != "" { - const prefix string = ",\"Type\":" - out.RawString(prefix) - out.String(string(in.Type)) - } - if len(in.Packages) != 0 { - const prefix string = ",\"Packages\":" - out.RawString(prefix) - { - out.RawByte('[') - for v13, v14 := range in.Packages { - if v13 > 0 { - out.RawByte(',') - } - easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgFanalTypes(out, v14) - } - out.RawByte(']') - } - } - if len(in.Vulnerabilities) != 0 { - const prefix string = ",\"Vulnerabilities\":" - out.RawString(prefix) - { - out.RawByte('[') - for v15, v16 := range in.Vulnerabilities { - if v15 > 0 { - out.RawByte(',') - } - easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgTypes(out, v16) - } - out.RawByte(']') - } - } - if in.MisconfSummary != nil { - const prefix string = ",\"MisconfSummary\":" - out.RawString(prefix) - easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgTypes1(out, *in.MisconfSummary) - } - if len(in.Misconfigurations) != 0 { - const prefix string = ",\"Misconfigurations\":" - out.RawString(prefix) - { - out.RawByte('[') - for v17, v18 := range in.Misconfigurations { - if v17 > 0 { - out.RawByte(',') - } - easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgTypes2(out, v18) - } - out.RawByte(']') - } - } - if len(in.Secrets) != 0 { - const prefix string = ",\"Secrets\":" - out.RawString(prefix) - { - out.RawByte('[') - for v19, v20 := range in.Secrets { - if v19 > 0 { - out.RawByte(',') - } - easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgTypes3(out, v20) - } - out.RawByte(']') - } - } - if len(in.Licenses) != 0 { - const prefix string = ",\"Licenses\":" - out.RawString(prefix) - { - out.RawByte('[') - for v21, v22 := range in.Licenses { - if v21 > 0 { - out.RawByte(',') - } - easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgTypes4(out, v22) - } - out.RawByte(']') - } - } - if len(in.CustomResources) != 0 { - const prefix string = ",\"CustomResources\":" - out.RawString(prefix) - { - out.RawByte('[') - for v23, v24 := range in.CustomResources { - if v23 > 0 { - out.RawByte(',') - } - easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgFanalTypes1(out, v24) - } - out.RawByte(']') - } - } - out.RawByte('}') -} - -// MarshalJSON supports json.Marshaler interface -func (v Result) MarshalJSON() ([]byte, error) { - w := jwriter.Writer{} - easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgModuleSerialize2(&w, v) - return w.Buffer.BuildBytes(), w.Error -} - -// MarshalEasyJSON supports easyjson.Marshaler interface -func (v Result) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgModuleSerialize2(w, v) -} - -// UnmarshalJSON supports json.Unmarshaler interface -func (v *Result) UnmarshalJSON(data []byte) error { - r := jlexer.Lexer{Data: data} - easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgModuleSerialize2(&r, v) - return r.Error() -} - -// UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *Result) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgModuleSerialize2(l, v) -} -func easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgFanalTypes1(in *jlexer.Lexer, out *types1.CustomResource) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } - switch key { - case "Type": - out.Type = string(in.String()) - case "FilePath": - out.FilePath = string(in.String()) - case "Layer": - easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgFanalTypes2(in, &out.Layer) - case "Data": - if m, ok := out.Data.(easyjson.Unmarshaler); ok { - m.UnmarshalEasyJSON(in) - } else if m, ok := out.Data.(json.Unmarshaler); ok { - _ = m.UnmarshalJSON(in.Raw()) - } else { - out.Data = in.Interface() - } - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgFanalTypes1(out *jwriter.Writer, in types1.CustomResource) { - out.RawByte('{') - first := true - _ = first - { - const prefix string = ",\"Type\":" - out.RawString(prefix[1:]) - out.String(string(in.Type)) - } - { - const prefix string = ",\"FilePath\":" - out.RawString(prefix) - out.String(string(in.FilePath)) - } - { - const prefix string = ",\"Layer\":" - out.RawString(prefix) - easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgFanalTypes2(out, in.Layer) - } - { - const prefix string = ",\"Data\":" - out.RawString(prefix) - if m, ok := in.Data.(easyjson.Marshaler); ok { - m.MarshalEasyJSON(out) - } else if m, ok := in.Data.(json.Marshaler); ok { - out.Raw(m.MarshalJSON()) - } else { - out.Raw(json.Marshal(in.Data)) - } - } - out.RawByte('}') -} -func easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgFanalTypes2(in *jlexer.Lexer, out *types1.Layer) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } - switch key { - case "Digest": - out.Digest = string(in.String()) - case "DiffID": - out.DiffID = string(in.String()) - case "CreatedBy": - out.CreatedBy = string(in.String()) - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgFanalTypes2(out *jwriter.Writer, in types1.Layer) { - out.RawByte('{') - first := true - _ = first - if in.Digest != "" { - const prefix string = ",\"Digest\":" - first = false - out.RawString(prefix[1:]) - out.String(string(in.Digest)) - } - if in.DiffID != "" { - const prefix string = ",\"DiffID\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.DiffID)) - } - if in.CreatedBy != "" { - const prefix string = ",\"CreatedBy\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.CreatedBy)) - } - out.RawByte('}') -} -func easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgTypes4(in *jlexer.Lexer, out *types.DetectedLicense) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } - switch key { - case "Severity": - out.Severity = string(in.String()) - case "Category": - out.Category = types1.LicenseCategory(in.String()) - case "PkgName": - out.PkgName = string(in.String()) - case "FilePath": - out.FilePath = string(in.String()) - case "Name": - out.Name = string(in.String()) - case "Confidence": - out.Confidence = float64(in.Float64()) - case "Link": - out.Link = string(in.String()) - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgTypes4(out *jwriter.Writer, in types.DetectedLicense) { - out.RawByte('{') - first := true - _ = first - { - const prefix string = ",\"Severity\":" - out.RawString(prefix[1:]) - out.String(string(in.Severity)) - } - { - const prefix string = ",\"Category\":" - out.RawString(prefix) - out.String(string(in.Category)) - } - { - const prefix string = ",\"PkgName\":" - out.RawString(prefix) - out.String(string(in.PkgName)) - } - { - const prefix string = ",\"FilePath\":" - out.RawString(prefix) - out.String(string(in.FilePath)) - } - { - const prefix string = ",\"Name\":" - out.RawString(prefix) - out.String(string(in.Name)) - } - { - const prefix string = ",\"Confidence\":" - out.RawString(prefix) - out.Float64(float64(in.Confidence)) - } - { - const prefix string = ",\"Link\":" - out.RawString(prefix) - out.String(string(in.Link)) - } - out.RawByte('}') -} -func easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgTypes3(in *jlexer.Lexer, out *types.DetectedSecret) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } - switch key { - case "RuleID": - out.RuleID = string(in.String()) - case "Category": - out.Category = types1.SecretRuleCategory(in.String()) - case "Severity": - out.Severity = string(in.String()) - case "Title": - out.Title = string(in.String()) - case "StartLine": - out.StartLine = int(in.Int()) - case "EndLine": - out.EndLine = int(in.Int()) - case "Code": - easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgFanalTypes3(in, &out.Code) - case "Match": - out.Match = string(in.String()) - case "Layer": - easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgFanalTypes2(in, &out.Layer) - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgTypes3(out *jwriter.Writer, in types.DetectedSecret) { - out.RawByte('{') - first := true - _ = first - { - const prefix string = ",\"RuleID\":" - out.RawString(prefix[1:]) - out.String(string(in.RuleID)) - } - { - const prefix string = ",\"Category\":" - out.RawString(prefix) - out.String(string(in.Category)) - } - { - const prefix string = ",\"Severity\":" - out.RawString(prefix) - out.String(string(in.Severity)) - } - { - const prefix string = ",\"Title\":" - out.RawString(prefix) - out.String(string(in.Title)) - } - { - const prefix string = ",\"StartLine\":" - out.RawString(prefix) - out.Int(int(in.StartLine)) - } - { - const prefix string = ",\"EndLine\":" - out.RawString(prefix) - out.Int(int(in.EndLine)) - } - { - const prefix string = ",\"Code\":" - out.RawString(prefix) - easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgFanalTypes3(out, in.Code) - } - { - const prefix string = ",\"Match\":" - out.RawString(prefix) - out.String(string(in.Match)) - } - if true { - const prefix string = ",\"Layer\":" - out.RawString(prefix) - easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgFanalTypes2(out, in.Layer) - } - out.RawByte('}') -} -func easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgFanalTypes3(in *jlexer.Lexer, out *types1.Code) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } - switch key { - case "Lines": - if in.IsNull() { - in.Skip() - out.Lines = nil - } else { - in.Delim('[') - if out.Lines == nil { - if !in.IsDelim(']') { - out.Lines = make([]types1.Line, 0, 0) - } else { - out.Lines = []types1.Line{} - } - } else { - out.Lines = (out.Lines)[:0] - } - for !in.IsDelim(']') { - var v25 types1.Line - easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgFanalTypes4(in, &v25) - out.Lines = append(out.Lines, v25) - in.WantComma() - } - in.Delim(']') - } - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgFanalTypes3(out *jwriter.Writer, in types1.Code) { - out.RawByte('{') - first := true - _ = first - { - const prefix string = ",\"Lines\":" - out.RawString(prefix[1:]) - if in.Lines == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { - out.RawString("null") - } else { - out.RawByte('[') - for v26, v27 := range in.Lines { - if v26 > 0 { - out.RawByte(',') - } - easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgFanalTypes4(out, v27) - } - out.RawByte(']') - } - } - out.RawByte('}') -} -func easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgFanalTypes4(in *jlexer.Lexer, out *types1.Line) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } - switch key { - case "Number": - out.Number = int(in.Int()) - case "Content": - out.Content = string(in.String()) - case "IsCause": - out.IsCause = bool(in.Bool()) - case "Annotation": - out.Annotation = string(in.String()) - case "Truncated": - out.Truncated = bool(in.Bool()) - case "Highlighted": - out.Highlighted = string(in.String()) - case "FirstCause": - out.FirstCause = bool(in.Bool()) - case "LastCause": - out.LastCause = bool(in.Bool()) - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgFanalTypes4(out *jwriter.Writer, in types1.Line) { - out.RawByte('{') - first := true - _ = first - { - const prefix string = ",\"Number\":" - out.RawString(prefix[1:]) - out.Int(int(in.Number)) - } - { - const prefix string = ",\"Content\":" - out.RawString(prefix) - out.String(string(in.Content)) - } - { - const prefix string = ",\"IsCause\":" - out.RawString(prefix) - out.Bool(bool(in.IsCause)) - } - { - const prefix string = ",\"Annotation\":" - out.RawString(prefix) - out.String(string(in.Annotation)) - } - { - const prefix string = ",\"Truncated\":" - out.RawString(prefix) - out.Bool(bool(in.Truncated)) - } - if in.Highlighted != "" { - const prefix string = ",\"Highlighted\":" - out.RawString(prefix) - out.String(string(in.Highlighted)) - } - { - const prefix string = ",\"FirstCause\":" - out.RawString(prefix) - out.Bool(bool(in.FirstCause)) - } - { - const prefix string = ",\"LastCause\":" - out.RawString(prefix) - out.Bool(bool(in.LastCause)) - } - out.RawByte('}') -} -func easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgTypes2(in *jlexer.Lexer, out *types.DetectedMisconfiguration) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } - switch key { - case "Type": - out.Type = string(in.String()) - case "ID": - out.ID = string(in.String()) - case "AVDID": - out.AVDID = string(in.String()) - case "Title": - out.Title = string(in.String()) - case "Description": - out.Description = string(in.String()) - case "Message": - out.Message = string(in.String()) - case "Namespace": - out.Namespace = string(in.String()) - case "Query": - out.Query = string(in.String()) - case "Resolution": - out.Resolution = string(in.String()) - case "Severity": - out.Severity = string(in.String()) - case "PrimaryURL": - out.PrimaryURL = string(in.String()) - case "References": - if in.IsNull() { - in.Skip() - out.References = nil - } else { - in.Delim('[') - if out.References == nil { - if !in.IsDelim(']') { - out.References = make([]string, 0, 4) - } else { - out.References = []string{} - } - } else { - out.References = (out.References)[:0] - } - for !in.IsDelim(']') { - var v28 string - v28 = string(in.String()) - out.References = append(out.References, v28) - in.WantComma() - } - in.Delim(']') - } - case "Status": - out.Status = types.MisconfStatus(in.String()) - case "Layer": - easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgFanalTypes2(in, &out.Layer) - case "CauseMetadata": - easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgFanalTypes5(in, &out.CauseMetadata) - case "Traces": - if in.IsNull() { - in.Skip() - out.Traces = nil - } else { - in.Delim('[') - if out.Traces == nil { - if !in.IsDelim(']') { - out.Traces = make([]string, 0, 4) - } else { - out.Traces = []string{} - } - } else { - out.Traces = (out.Traces)[:0] - } - for !in.IsDelim(']') { - var v29 string - v29 = string(in.String()) - out.Traces = append(out.Traces, v29) - in.WantComma() - } - in.Delim(']') - } - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgTypes2(out *jwriter.Writer, in types.DetectedMisconfiguration) { - out.RawByte('{') - first := true - _ = first - if in.Type != "" { - const prefix string = ",\"Type\":" - first = false - out.RawString(prefix[1:]) - out.String(string(in.Type)) - } - if in.ID != "" { - const prefix string = ",\"ID\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.ID)) - } - if in.AVDID != "" { - const prefix string = ",\"AVDID\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.AVDID)) - } - if in.Title != "" { - const prefix string = ",\"Title\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.Title)) - } - if in.Description != "" { - const prefix string = ",\"Description\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.Description)) - } - if in.Message != "" { - const prefix string = ",\"Message\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.Message)) - } - if in.Namespace != "" { - const prefix string = ",\"Namespace\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.Namespace)) - } - if in.Query != "" { - const prefix string = ",\"Query\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.Query)) - } - if in.Resolution != "" { - const prefix string = ",\"Resolution\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.Resolution)) - } - if in.Severity != "" { - const prefix string = ",\"Severity\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.Severity)) - } - if in.PrimaryURL != "" { - const prefix string = ",\"PrimaryURL\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.PrimaryURL)) - } - if len(in.References) != 0 { - const prefix string = ",\"References\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - { - out.RawByte('[') - for v30, v31 := range in.References { - if v30 > 0 { - out.RawByte(',') - } - out.String(string(v31)) - } - out.RawByte(']') - } - } - if in.Status != "" { - const prefix string = ",\"Status\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.Status)) - } - if true { - const prefix string = ",\"Layer\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgFanalTypes2(out, in.Layer) - } - if true { - const prefix string = ",\"CauseMetadata\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgFanalTypes5(out, in.CauseMetadata) - } - if len(in.Traces) != 0 { - const prefix string = ",\"Traces\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - { - out.RawByte('[') - for v32, v33 := range in.Traces { - if v32 > 0 { - out.RawByte(',') - } - out.String(string(v33)) - } - out.RawByte(']') - } - } - out.RawByte('}') -} -func easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgFanalTypes5(in *jlexer.Lexer, out *types1.CauseMetadata) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } - switch key { - case "Resource": - out.Resource = string(in.String()) - case "Provider": - out.Provider = string(in.String()) - case "Service": - out.Service = string(in.String()) - case "StartLine": - out.StartLine = int(in.Int()) - case "EndLine": - out.EndLine = int(in.Int()) - case "Code": - easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgFanalTypes3(in, &out.Code) - case "Occurrences": - if in.IsNull() { - in.Skip() - out.Occurrences = nil - } else { - in.Delim('[') - if out.Occurrences == nil { - if !in.IsDelim(']') { - out.Occurrences = make([]types1.Occurrence, 0, 1) - } else { - out.Occurrences = []types1.Occurrence{} - } - } else { - out.Occurrences = (out.Occurrences)[:0] - } - for !in.IsDelim(']') { - var v34 types1.Occurrence - easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgFanalTypes6(in, &v34) - out.Occurrences = append(out.Occurrences, v34) - in.WantComma() - } - in.Delim(']') - } - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgFanalTypes5(out *jwriter.Writer, in types1.CauseMetadata) { - out.RawByte('{') - first := true - _ = first - if in.Resource != "" { - const prefix string = ",\"Resource\":" - first = false - out.RawString(prefix[1:]) - out.String(string(in.Resource)) - } - if in.Provider != "" { - const prefix string = ",\"Provider\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.Provider)) - } - if in.Service != "" { - const prefix string = ",\"Service\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.Service)) - } - if in.StartLine != 0 { - const prefix string = ",\"StartLine\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.Int(int(in.StartLine)) - } - if in.EndLine != 0 { - const prefix string = ",\"EndLine\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.Int(int(in.EndLine)) - } - if true { - const prefix string = ",\"Code\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgFanalTypes3(out, in.Code) - } - if len(in.Occurrences) != 0 { - const prefix string = ",\"Occurrences\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - { - out.RawByte('[') - for v35, v36 := range in.Occurrences { - if v35 > 0 { - out.RawByte(',') - } - easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgFanalTypes6(out, v36) - } - out.RawByte(']') - } - } - out.RawByte('}') -} -func easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgFanalTypes6(in *jlexer.Lexer, out *types1.Occurrence) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } - switch key { - case "Resource": - out.Resource = string(in.String()) - case "Filename": - out.Filename = string(in.String()) - case "Location": - easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgFanalTypes7(in, &out.Location) - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgFanalTypes6(out *jwriter.Writer, in types1.Occurrence) { - out.RawByte('{') - first := true - _ = first - if in.Resource != "" { - const prefix string = ",\"Resource\":" - first = false - out.RawString(prefix[1:]) - out.String(string(in.Resource)) - } - if in.Filename != "" { - const prefix string = ",\"Filename\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.Filename)) - } - { - const prefix string = ",\"Location\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgFanalTypes7(out, in.Location) - } - out.RawByte('}') -} -func easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgFanalTypes7(in *jlexer.Lexer, out *types1.Location) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } - switch key { - case "StartLine": - out.StartLine = int(in.Int()) - case "EndLine": - out.EndLine = int(in.Int()) - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgFanalTypes7(out *jwriter.Writer, in types1.Location) { - out.RawByte('{') - first := true - _ = first - if in.StartLine != 0 { - const prefix string = ",\"StartLine\":" - first = false - out.RawString(prefix[1:]) - out.Int(int(in.StartLine)) - } - if in.EndLine != 0 { - const prefix string = ",\"EndLine\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.Int(int(in.EndLine)) - } - out.RawByte('}') -} -func easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgTypes1(in *jlexer.Lexer, out *types.MisconfSummary) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } - switch key { - case "Successes": - out.Successes = int(in.Int()) - case "Failures": - out.Failures = int(in.Int()) - case "Exceptions": - out.Exceptions = int(in.Int()) - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgTypes1(out *jwriter.Writer, in types.MisconfSummary) { - out.RawByte('{') - first := true - _ = first - { - const prefix string = ",\"Successes\":" - out.RawString(prefix[1:]) - out.Int(int(in.Successes)) - } - { - const prefix string = ",\"Failures\":" - out.RawString(prefix) - out.Int(int(in.Failures)) - } - { - const prefix string = ",\"Exceptions\":" - out.RawString(prefix) - out.Int(int(in.Exceptions)) - } - out.RawByte('}') -} -func easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgTypes(in *jlexer.Lexer, out *types.DetectedVulnerability) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } - switch key { - case "VulnerabilityID": - out.VulnerabilityID = string(in.String()) - case "VendorIDs": - if in.IsNull() { - in.Skip() - out.VendorIDs = nil - } else { - in.Delim('[') - if out.VendorIDs == nil { - if !in.IsDelim(']') { - out.VendorIDs = make([]string, 0, 4) - } else { - out.VendorIDs = []string{} - } - } else { - out.VendorIDs = (out.VendorIDs)[:0] - } - for !in.IsDelim(']') { - var v37 string - v37 = string(in.String()) - out.VendorIDs = append(out.VendorIDs, v37) - in.WantComma() - } - in.Delim(']') - } - case "PkgID": - out.PkgID = string(in.String()) - case "PkgName": - out.PkgName = string(in.String()) - case "PkgPath": - out.PkgPath = string(in.String()) - case "PkgIdentifier": - if data := in.Raw(); in.Ok() { - in.AddError((out.PkgIdentifier).UnmarshalJSON(data)) - } - case "InstalledVersion": - out.InstalledVersion = string(in.String()) - case "FixedVersion": - out.FixedVersion = string(in.String()) - case "Status": - if data := in.Raw(); in.Ok() { - in.AddError((out.Status).UnmarshalJSON(data)) - } - case "Layer": - easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgFanalTypes2(in, &out.Layer) - case "SeveritySource": - out.SeveritySource = types2.SourceID(in.String()) - case "PrimaryURL": - out.PrimaryURL = string(in.String()) - case "DataSource": - if in.IsNull() { - in.Skip() - out.DataSource = nil - } else { - if out.DataSource == nil { - out.DataSource = new(types2.DataSource) - } - easyjson6601e8cdDecodeGithubComAquasecurityTrivyDbPkgTypes(in, out.DataSource) - } - case "Custom": - if m, ok := out.Custom.(easyjson.Unmarshaler); ok { - m.UnmarshalEasyJSON(in) - } else if m, ok := out.Custom.(json.Unmarshaler); ok { - _ = m.UnmarshalJSON(in.Raw()) - } else { - out.Custom = in.Interface() - } - case "Title": - out.Title = string(in.String()) - case "Description": - out.Description = string(in.String()) - case "Severity": - out.Severity = string(in.String()) - case "CweIDs": - if in.IsNull() { - in.Skip() - out.CweIDs = nil - } else { - in.Delim('[') - if out.CweIDs == nil { - if !in.IsDelim(']') { - out.CweIDs = make([]string, 0, 4) - } else { - out.CweIDs = []string{} - } - } else { - out.CweIDs = (out.CweIDs)[:0] - } - for !in.IsDelim(']') { - var v38 string - v38 = string(in.String()) - out.CweIDs = append(out.CweIDs, v38) - in.WantComma() - } - in.Delim(']') - } - case "VendorSeverity": - if in.IsNull() { - in.Skip() - } else { - in.Delim('{') - if !in.IsDelim('}') { - out.VendorSeverity = make(types2.VendorSeverity) - } else { - out.VendorSeverity = nil - } - for !in.IsDelim('}') { - key := types2.SourceID(in.String()) - in.WantColon() - var v39 types2.Severity - v39 = types2.Severity(in.Int()) - (out.VendorSeverity)[key] = v39 - in.WantComma() - } - in.Delim('}') - } - case "CVSS": - if in.IsNull() { - in.Skip() - } else { - in.Delim('{') - if !in.IsDelim('}') { - out.CVSS = make(types2.VendorCVSS) - } else { - out.CVSS = nil - } - for !in.IsDelim('}') { - key := types2.SourceID(in.String()) - in.WantColon() - var v40 types2.CVSS - easyjson6601e8cdDecodeGithubComAquasecurityTrivyDbPkgTypes1(in, &v40) - (out.CVSS)[key] = v40 - in.WantComma() - } - in.Delim('}') - } - case "References": - if in.IsNull() { - in.Skip() - out.References = nil - } else { - in.Delim('[') - if out.References == nil { - if !in.IsDelim(']') { - out.References = make([]string, 0, 4) - } else { - out.References = []string{} - } - } else { - out.References = (out.References)[:0] - } - for !in.IsDelim(']') { - var v41 string - v41 = string(in.String()) - out.References = append(out.References, v41) - in.WantComma() - } - in.Delim(']') - } - case "PublishedDate": - if in.IsNull() { - in.Skip() - out.PublishedDate = nil - } else { - if out.PublishedDate == nil { - out.PublishedDate = new(time.Time) - } - if data := in.Raw(); in.Ok() { - in.AddError((*out.PublishedDate).UnmarshalJSON(data)) - } - } - case "LastModifiedDate": - if in.IsNull() { - in.Skip() - out.LastModifiedDate = nil - } else { - if out.LastModifiedDate == nil { - out.LastModifiedDate = new(time.Time) - } - if data := in.Raw(); in.Ok() { - in.AddError((*out.LastModifiedDate).UnmarshalJSON(data)) - } - } - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgTypes(out *jwriter.Writer, in types.DetectedVulnerability) { - out.RawByte('{') - first := true - _ = first - if in.VulnerabilityID != "" { - const prefix string = ",\"VulnerabilityID\":" - first = false - out.RawString(prefix[1:]) - out.String(string(in.VulnerabilityID)) - } - if len(in.VendorIDs) != 0 { - const prefix string = ",\"VendorIDs\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - { - out.RawByte('[') - for v42, v43 := range in.VendorIDs { - if v42 > 0 { - out.RawByte(',') - } - out.String(string(v43)) - } - out.RawByte(']') - } - } - if in.PkgID != "" { - const prefix string = ",\"PkgID\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.PkgID)) - } - if in.PkgName != "" { - const prefix string = ",\"PkgName\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.PkgName)) - } - if in.PkgPath != "" { - const prefix string = ",\"PkgPath\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.PkgPath)) - } - if true { - const prefix string = ",\"PkgIdentifier\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.Raw((in.PkgIdentifier).MarshalJSON()) - } - if in.InstalledVersion != "" { - const prefix string = ",\"InstalledVersion\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.InstalledVersion)) - } - if in.FixedVersion != "" { - const prefix string = ",\"FixedVersion\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.FixedVersion)) - } - if in.Status != 0 { - const prefix string = ",\"Status\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.Raw((in.Status).MarshalJSON()) - } - if true { - const prefix string = ",\"Layer\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgFanalTypes2(out, in.Layer) - } - if in.SeveritySource != "" { - const prefix string = ",\"SeveritySource\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.SeveritySource)) - } - if in.PrimaryURL != "" { - const prefix string = ",\"PrimaryURL\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.PrimaryURL)) - } - if in.DataSource != nil { - const prefix string = ",\"DataSource\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - easyjson6601e8cdEncodeGithubComAquasecurityTrivyDbPkgTypes(out, *in.DataSource) - } - if in.Custom != nil { - const prefix string = ",\"Custom\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - if m, ok := in.Custom.(easyjson.Marshaler); ok { - m.MarshalEasyJSON(out) - } else if m, ok := in.Custom.(json.Marshaler); ok { - out.Raw(m.MarshalJSON()) - } else { - out.Raw(json.Marshal(in.Custom)) - } - } - if in.Title != "" { - const prefix string = ",\"Title\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.Title)) - } - if in.Description != "" { - const prefix string = ",\"Description\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.Description)) - } - if in.Severity != "" { - const prefix string = ",\"Severity\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.Severity)) - } - if len(in.CweIDs) != 0 { - const prefix string = ",\"CweIDs\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - { - out.RawByte('[') - for v44, v45 := range in.CweIDs { - if v44 > 0 { - out.RawByte(',') - } - out.String(string(v45)) - } - out.RawByte(']') - } - } - if len(in.VendorSeverity) != 0 { - const prefix string = ",\"VendorSeverity\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - { - out.RawByte('{') - v46First := true - for v46Name, v46Value := range in.VendorSeverity { - if v46First { - v46First = false - } else { - out.RawByte(',') - } - out.String(string(v46Name)) - out.RawByte(':') - out.Int(int(v46Value)) - } - out.RawByte('}') - } - } - if len(in.CVSS) != 0 { - const prefix string = ",\"CVSS\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - { - out.RawByte('{') - v47First := true - for v47Name, v47Value := range in.CVSS { - if v47First { - v47First = false - } else { - out.RawByte(',') - } - out.String(string(v47Name)) - out.RawByte(':') - easyjson6601e8cdEncodeGithubComAquasecurityTrivyDbPkgTypes1(out, v47Value) - } - out.RawByte('}') - } - } - if len(in.References) != 0 { - const prefix string = ",\"References\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - { - out.RawByte('[') - for v48, v49 := range in.References { - if v48 > 0 { - out.RawByte(',') - } - out.String(string(v49)) - } - out.RawByte(']') - } - } - if in.PublishedDate != nil { - const prefix string = ",\"PublishedDate\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.Raw((*in.PublishedDate).MarshalJSON()) - } - if in.LastModifiedDate != nil { - const prefix string = ",\"LastModifiedDate\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.Raw((*in.LastModifiedDate).MarshalJSON()) - } - out.RawByte('}') -} -func easyjson6601e8cdDecodeGithubComAquasecurityTrivyDbPkgTypes1(in *jlexer.Lexer, out *types2.CVSS) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } - switch key { - case "V2Vector": - out.V2Vector = string(in.String()) - case "V3Vector": - out.V3Vector = string(in.String()) - case "V2Score": - out.V2Score = float64(in.Float64()) - case "V3Score": - out.V3Score = float64(in.Float64()) - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func easyjson6601e8cdEncodeGithubComAquasecurityTrivyDbPkgTypes1(out *jwriter.Writer, in types2.CVSS) { - out.RawByte('{') - first := true - _ = first - if in.V2Vector != "" { - const prefix string = ",\"V2Vector\":" - first = false - out.RawString(prefix[1:]) - out.String(string(in.V2Vector)) - } - if in.V3Vector != "" { - const prefix string = ",\"V3Vector\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.V3Vector)) - } - if in.V2Score != 0 { - const prefix string = ",\"V2Score\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.Float64(float64(in.V2Score)) - } - if in.V3Score != 0 { - const prefix string = ",\"V3Score\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.Float64(float64(in.V3Score)) - } - out.RawByte('}') -} -func easyjson6601e8cdDecodeGithubComAquasecurityTrivyDbPkgTypes(in *jlexer.Lexer, out *types2.DataSource) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } - switch key { - case "ID": - out.ID = types2.SourceID(in.String()) - case "Name": - out.Name = string(in.String()) - case "URL": - out.URL = string(in.String()) - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func easyjson6601e8cdEncodeGithubComAquasecurityTrivyDbPkgTypes(out *jwriter.Writer, in types2.DataSource) { - out.RawByte('{') - first := true - _ = first - if in.ID != "" { - const prefix string = ",\"ID\":" - first = false - out.RawString(prefix[1:]) - out.String(string(in.ID)) - } - if in.Name != "" { - const prefix string = ",\"Name\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.Name)) - } - if in.URL != "" { - const prefix string = ",\"URL\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.URL)) - } - out.RawByte('}') -} -func easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgFanalTypes(in *jlexer.Lexer, out *types1.Package) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } - switch key { - case "ID": - out.ID = string(in.String()) - case "Name": - out.Name = string(in.String()) - case "Identifier": - if data := in.Raw(); in.Ok() { - in.AddError((out.Identifier).UnmarshalJSON(data)) - } - case "Version": - out.Version = string(in.String()) - case "Release": - out.Release = string(in.String()) - case "Epoch": - out.Epoch = int(in.Int()) - case "Arch": - out.Arch = string(in.String()) - case "Dev": - out.Dev = bool(in.Bool()) - case "SrcName": - out.SrcName = string(in.String()) - case "SrcVersion": - out.SrcVersion = string(in.String()) - case "SrcRelease": - out.SrcRelease = string(in.String()) - case "SrcEpoch": - out.SrcEpoch = int(in.Int()) - case "Licenses": - if in.IsNull() { - in.Skip() - out.Licenses = nil - } else { - in.Delim('[') - if out.Licenses == nil { - if !in.IsDelim(']') { - out.Licenses = make([]string, 0, 4) - } else { - out.Licenses = []string{} - } - } else { - out.Licenses = (out.Licenses)[:0] - } - for !in.IsDelim(']') { - var v50 string - v50 = string(in.String()) - out.Licenses = append(out.Licenses, v50) - in.WantComma() - } - in.Delim(']') - } - case "Maintainer": - out.Maintainer = string(in.String()) - case "Modularitylabel": - out.Modularitylabel = string(in.String()) - case "BuildInfo": - if in.IsNull() { - in.Skip() - out.BuildInfo = nil - } else { - if out.BuildInfo == nil { - out.BuildInfo = new(types1.BuildInfo) - } - easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgFanalTypes8(in, out.BuildInfo) - } - case "Indirect": - out.Indirect = bool(in.Bool()) - case "DependsOn": - if in.IsNull() { - in.Skip() - out.DependsOn = nil - } else { - in.Delim('[') - if out.DependsOn == nil { - if !in.IsDelim(']') { - out.DependsOn = make([]string, 0, 4) - } else { - out.DependsOn = []string{} - } - } else { - out.DependsOn = (out.DependsOn)[:0] - } - for !in.IsDelim(']') { - var v51 string - v51 = string(in.String()) - out.DependsOn = append(out.DependsOn, v51) - in.WantComma() - } - in.Delim(']') - } - case "Layer": - easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgFanalTypes2(in, &out.Layer) - case "FilePath": - out.FilePath = string(in.String()) - case "Digest": - out.Digest = digest.Digest(in.String()) - case "Locations": - if in.IsNull() { - in.Skip() - out.Locations = nil - } else { - in.Delim('[') - if out.Locations == nil { - if !in.IsDelim(']') { - out.Locations = make([]types1.Location, 0, 4) - } else { - out.Locations = []types1.Location{} - } - } else { - out.Locations = (out.Locations)[:0] - } - for !in.IsDelim(']') { - var v52 types1.Location - easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgFanalTypes7(in, &v52) - out.Locations = append(out.Locations, v52) - in.WantComma() - } - in.Delim(']') - } - case "InstalledFiles": - if in.IsNull() { - in.Skip() - out.InstalledFiles = nil - } else { - in.Delim('[') - if out.InstalledFiles == nil { - if !in.IsDelim(']') { - out.InstalledFiles = make([]string, 0, 4) - } else { - out.InstalledFiles = []string{} - } - } else { - out.InstalledFiles = (out.InstalledFiles)[:0] - } - for !in.IsDelim(']') { - var v53 string - v53 = string(in.String()) - out.InstalledFiles = append(out.InstalledFiles, v53) - in.WantComma() - } - in.Delim(']') - } - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgFanalTypes(out *jwriter.Writer, in types1.Package) { - out.RawByte('{') - first := true - _ = first - if in.ID != "" { - const prefix string = ",\"ID\":" - first = false - out.RawString(prefix[1:]) - out.String(string(in.ID)) - } - if in.Name != "" { - const prefix string = ",\"Name\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.Name)) - } - if true { - const prefix string = ",\"Identifier\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.Raw((in.Identifier).MarshalJSON()) - } - if in.Version != "" { - const prefix string = ",\"Version\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.Version)) - } - if in.Release != "" { - const prefix string = ",\"Release\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.Release)) - } - if in.Epoch != 0 { - const prefix string = ",\"Epoch\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.Int(int(in.Epoch)) - } - if in.Arch != "" { - const prefix string = ",\"Arch\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.Arch)) - } - if in.Dev { - const prefix string = ",\"Dev\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.Bool(bool(in.Dev)) - } - if in.SrcName != "" { - const prefix string = ",\"SrcName\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.SrcName)) - } - if in.SrcVersion != "" { - const prefix string = ",\"SrcVersion\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.SrcVersion)) - } - if in.SrcRelease != "" { - const prefix string = ",\"SrcRelease\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.SrcRelease)) - } - if in.SrcEpoch != 0 { - const prefix string = ",\"SrcEpoch\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.Int(int(in.SrcEpoch)) - } - if len(in.Licenses) != 0 { - const prefix string = ",\"Licenses\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - { - out.RawByte('[') - for v54, v55 := range in.Licenses { - if v54 > 0 { - out.RawByte(',') - } - out.String(string(v55)) - } - out.RawByte(']') - } - } - if in.Maintainer != "" { - const prefix string = ",\"Maintainer\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.Maintainer)) - } - if in.Modularitylabel != "" { - const prefix string = ",\"Modularitylabel\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.Modularitylabel)) - } - if in.BuildInfo != nil { - const prefix string = ",\"BuildInfo\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgFanalTypes8(out, *in.BuildInfo) - } - if in.Indirect { - const prefix string = ",\"Indirect\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.Bool(bool(in.Indirect)) - } - if len(in.DependsOn) != 0 { - const prefix string = ",\"DependsOn\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - { - out.RawByte('[') - for v56, v57 := range in.DependsOn { - if v56 > 0 { - out.RawByte(',') - } - out.String(string(v57)) - } - out.RawByte(']') - } - } - if true { - const prefix string = ",\"Layer\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgFanalTypes2(out, in.Layer) - } - if in.FilePath != "" { - const prefix string = ",\"FilePath\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.FilePath)) - } - if in.Digest != "" { - const prefix string = ",\"Digest\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.Digest)) - } - if len(in.Locations) != 0 { - const prefix string = ",\"Locations\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - { - out.RawByte('[') - for v58, v59 := range in.Locations { - if v58 > 0 { - out.RawByte(',') - } - easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgFanalTypes7(out, v59) - } - out.RawByte(']') - } - } - if len(in.InstalledFiles) != 0 { - const prefix string = ",\"InstalledFiles\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - { - out.RawByte('[') - for v60, v61 := range in.InstalledFiles { - if v60 > 0 { - out.RawByte(',') - } - out.String(string(v61)) - } - out.RawByte(']') - } - } - out.RawByte('}') -} -func easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgFanalTypes8(in *jlexer.Lexer, out *types1.BuildInfo) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } - switch key { - case "ContentSets": - if in.IsNull() { - in.Skip() - out.ContentSets = nil - } else { - in.Delim('[') - if out.ContentSets == nil { - if !in.IsDelim(']') { - out.ContentSets = make([]string, 0, 4) - } else { - out.ContentSets = []string{} - } - } else { - out.ContentSets = (out.ContentSets)[:0] - } - for !in.IsDelim(']') { - var v62 string - v62 = string(in.String()) - out.ContentSets = append(out.ContentSets, v62) - in.WantComma() - } - in.Delim(']') - } - case "Nvr": - out.Nvr = string(in.String()) - case "Arch": - out.Arch = string(in.String()) - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgFanalTypes8(out *jwriter.Writer, in types1.BuildInfo) { - out.RawByte('{') - first := true - _ = first - if len(in.ContentSets) != 0 { - const prefix string = ",\"ContentSets\":" - first = false - out.RawString(prefix[1:]) - { - out.RawByte('[') - for v63, v64 := range in.ContentSets { - if v63 > 0 { - out.RawByte(',') - } - out.String(string(v64)) - } - out.RawByte(']') - } - } - if in.Nvr != "" { - const prefix string = ",\"Nvr\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.Nvr)) - } - if in.Arch != "" { - const prefix string = ",\"Arch\":" - if first { - first = false - out.RawString(prefix[1:]) - } else { - out.RawString(prefix) - } - out.String(string(in.Arch)) - } - out.RawByte('}') -} -func easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgModuleSerialize3(in *jlexer.Lexer, out *PostScanSpec) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } - switch key { - case "Action": - out.Action = PostScanAction(in.String()) - case "IDs": - if in.IsNull() { - in.Skip() - out.IDs = nil - } else { - in.Delim('[') - if out.IDs == nil { - if !in.IsDelim(']') { - out.IDs = make([]string, 0, 4) - } else { - out.IDs = []string{} - } - } else { - out.IDs = (out.IDs)[:0] - } - for !in.IsDelim(']') { - var v65 string - v65 = string(in.String()) - out.IDs = append(out.IDs, v65) - in.WantComma() - } - in.Delim(']') - } - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgModuleSerialize3(out *jwriter.Writer, in PostScanSpec) { - out.RawByte('{') - first := true - _ = first - { - const prefix string = ",\"Action\":" - out.RawString(prefix[1:]) - out.String(string(in.Action)) - } - { - const prefix string = ",\"IDs\":" - out.RawString(prefix) - if in.IDs == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { - out.RawString("null") - } else { - out.RawByte('[') - for v66, v67 := range in.IDs { - if v66 > 0 { - out.RawByte(',') - } - out.String(string(v67)) - } - out.RawByte(']') - } - } - out.RawByte('}') -} - -// MarshalJSON supports json.Marshaler interface -func (v PostScanSpec) MarshalJSON() ([]byte, error) { - w := jwriter.Writer{} - easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgModuleSerialize3(&w, v) - return w.Buffer.BuildBytes(), w.Error -} - -// MarshalEasyJSON supports easyjson.Marshaler interface -func (v PostScanSpec) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgModuleSerialize3(w, v) -} - -// UnmarshalJSON supports json.Unmarshaler interface -func (v *PostScanSpec) UnmarshalJSON(data []byte) error { - r := jlexer.Lexer{Data: data} - easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgModuleSerialize3(&r, v) - return r.Error() -} - -// UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *PostScanSpec) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgModuleSerialize3(l, v) -} -func easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgModuleSerialize4(in *jlexer.Lexer, out *AnalysisResult) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } - switch key { - case "CustomResources": - if in.IsNull() { - in.Skip() - out.CustomResources = nil - } else { - in.Delim('[') - if out.CustomResources == nil { - if !in.IsDelim(']') { - out.CustomResources = make([]CustomResource, 0, 1) - } else { - out.CustomResources = []CustomResource{} - } - } else { - out.CustomResources = (out.CustomResources)[:0] - } - for !in.IsDelim(']') { - var v68 CustomResource - easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgModuleSerialize5(in, &v68) - out.CustomResources = append(out.CustomResources, v68) - in.WantComma() - } - in.Delim(']') - } - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgModuleSerialize4(out *jwriter.Writer, in AnalysisResult) { - out.RawByte('{') - first := true - _ = first - { - const prefix string = ",\"CustomResources\":" - out.RawString(prefix[1:]) - if in.CustomResources == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 { - out.RawString("null") - } else { - out.RawByte('[') - for v69, v70 := range in.CustomResources { - if v69 > 0 { - out.RawByte(',') - } - easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgModuleSerialize5(out, v70) - } - out.RawByte(']') - } - } - out.RawByte('}') -} - -// MarshalJSON supports json.Marshaler interface -func (v AnalysisResult) MarshalJSON() ([]byte, error) { - w := jwriter.Writer{} - easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgModuleSerialize4(&w, v) - return w.Buffer.BuildBytes(), w.Error -} - -// MarshalEasyJSON supports easyjson.Marshaler interface -func (v AnalysisResult) MarshalEasyJSON(w *jwriter.Writer) { - easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgModuleSerialize4(w, v) -} - -// UnmarshalJSON supports json.Unmarshaler interface -func (v *AnalysisResult) UnmarshalJSON(data []byte) error { - r := jlexer.Lexer{Data: data} - easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgModuleSerialize4(&r, v) - return r.Error() -} - -// UnmarshalEasyJSON supports easyjson.Unmarshaler interface -func (v *AnalysisResult) UnmarshalEasyJSON(l *jlexer.Lexer) { - easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgModuleSerialize4(l, v) -} -func easyjson6601e8cdDecodeGithubComAquasecurityTrivyPkgModuleSerialize5(in *jlexer.Lexer, out *CustomResource) { - isTopLevel := in.IsStart() - if in.IsNull() { - if isTopLevel { - in.Consumed() - } - in.Skip() - return - } - in.Delim('{') - for !in.IsDelim('}') { - key := in.UnsafeFieldName(false) - in.WantColon() - if in.IsNull() { - in.Skip() - in.WantComma() - continue - } - switch key { - case "Type": - out.Type = string(in.String()) - case "FilePath": - out.FilePath = string(in.String()) - case "Data": - if m, ok := out.Data.(easyjson.Unmarshaler); ok { - m.UnmarshalEasyJSON(in) - } else if m, ok := out.Data.(json.Unmarshaler); ok { - _ = m.UnmarshalJSON(in.Raw()) - } else { - out.Data = in.Interface() - } - default: - in.SkipRecursive() - } - in.WantComma() - } - in.Delim('}') - if isTopLevel { - in.Consumed() - } -} -func easyjson6601e8cdEncodeGithubComAquasecurityTrivyPkgModuleSerialize5(out *jwriter.Writer, in CustomResource) { - out.RawByte('{') - first := true - _ = first - { - const prefix string = ",\"Type\":" - out.RawString(prefix[1:]) - out.String(string(in.Type)) - } - { - const prefix string = ",\"FilePath\":" - out.RawString(prefix) - out.String(string(in.FilePath)) - } - { - const prefix string = ",\"Data\":" - out.RawString(prefix) - if m, ok := in.Data.(easyjson.Marshaler); ok { - m.MarshalEasyJSON(out) - } else if m, ok := in.Data.(json.Marshaler); ok { - out.Raw(m.MarshalJSON()) - } else { - out.Raw(json.Marshal(in.Data)) - } - } - out.RawByte('}') -} diff --git a/pkg/module/wasm/sdk.go b/pkg/module/wasm/sdk.go index dab28a88497c..25b9f1e777c3 100644 --- a/pkg/module/wasm/sdk.go +++ b/pkg/module/wasm/sdk.go @@ -6,12 +6,11 @@ package wasm // TinyGo can build this package, but Go cannot. import ( + "encoding/json" "fmt" "reflect" "unsafe" - "github.com/mailru/easyjson" - "github.com/aquasecurity/trivy/pkg/module/api" "github.com/aquasecurity/trivy/pkg/module/serialize" ) @@ -40,20 +39,16 @@ func Error(message string) { _error(ptr, size) } -//go:wasm-module env -//export debug +//go:wasmimport env debug func _debug(ptr uint32, size uint32) -//go:wasm-module env -//export info +//go:wasmimport env info func _info(ptr uint32, size uint32) -//go:wasm-module env -//export warn +//go:wasmimport env warn func _warn(ptr uint32, size uint32) -//go:wasm-module env -//export error +//go:wasmimport env error func _error(ptr uint32, size uint32) var module api.Module @@ -134,8 +129,8 @@ func _post_scan(ptr, size uint32) uint64 { return marshal(results) } -func marshal(v easyjson.Marshaler) uint64 { - b, err := easyjson.Marshal(v) +func marshal(v any) uint64 { + b, err := json.Marshal(v) if err != nil { Error(fmt.Sprintf("marshal error: %s", err)) return 0 @@ -145,14 +140,14 @@ func marshal(v easyjson.Marshaler) uint64 { return (uint64(p) << uint64(32)) | uint64(len(b)) } -func unmarshal(ptr, size uint32, v easyjson.Unmarshaler) error { +func unmarshal(ptr, size uint32, v any) error { var b []byte s := (*reflect.SliceHeader)(unsafe.Pointer(&b)) s.Len = uintptr(size) s.Cap = uintptr(size) s.Data = uintptr(ptr) - if err := easyjson.Unmarshal(b, v); err != nil { + if err := json.Unmarshal(b, v); err != nil { return fmt.Errorf("unmarshal error: %s", err) } diff --git a/pkg/purl/purl.go b/pkg/purl/purl.go index de02b309667a..59a4b99df30c 100644 --- a/pkg/purl/purl.go +++ b/pkg/purl/purl.go @@ -8,8 +8,10 @@ import ( cn "github.com/google/go-containerregistry/pkg/name" version "github.com/knqyf263/go-rpm-version" packageurl "github.com/package-url/packageurl-go" + "github.com/samber/lo" "golang.org/x/xerrors" + "github.com/aquasecurity/trivy/pkg/dependency" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/scanner/utils" "github.com/aquasecurity/trivy/pkg/types" @@ -42,10 +44,7 @@ const ( TypeUnknown = "unknown" ) -type PackageURL struct { - packageurl.PackageURL - FilePath string -} +type PackageURL packageurl.PackageURL func FromString(s string) (*PackageURL, error) { p, err := packageurl.FromString(s) @@ -53,25 +52,11 @@ func FromString(s string) (*PackageURL, error) { return nil, xerrors.Errorf("failed to parse purl(%s): %w", s, err) } - // Take out and delete the file path from qualifiers - var filePath string - for i, q := range p.Qualifiers { - if q.Key != "file_path" { - continue - } - filePath = q.Value - p.Qualifiers = append(p.Qualifiers[:i], p.Qualifiers[i+1:]...) - break - } - if len(p.Qualifiers) == 0 { p.Qualifiers = nil } - return &PackageURL{ - PackageURL: p, - FilePath: filePath, - }, nil + return lo.ToPtr(PackageURL(p)), nil } // nolint: gocyclo @@ -120,52 +105,24 @@ func New(t ftypes.TargetType, metadata types.Metadata, pkg ftypes.Package) (*Pac purl, err := parseOCI(metadata) if err != nil { return nil, err - } else if purl.Type == "" { + } else if purl == nil { return nil, nil } - return &PackageURL{PackageURL: purl}, nil + return (*PackageURL)(purl), nil } - return &PackageURL{ - PackageURL: *packageurl.NewPackageURL(ptype, namespace, name, ver, qualifiers, subpath), - FilePath: pkg.FilePath, - }, nil -} - -// WithPath wraps packageurl.PackageURL with the given file path -func WithPath(purl *packageurl.PackageURL, filePath string) *PackageURL { - if purl == nil { - return nil - } - return &PackageURL{ - PackageURL: *purl, - FilePath: filePath, - } -} - -func (p *PackageURL) BOMRef() string { - // 'bom-ref' must be unique within BOM, but PURLs may conflict - // when the same packages are installed in an artifact. - // In that case, we prefer to make PURLs unique by adding file paths, - // rather than using UUIDs, even if it is not PURL technically. - // ref. https://cyclonedx.org/use-cases/#dependency-graph - purl := p.PackageURL // so that it will not override the qualifiers below - if p.FilePath != "" { - purl.Qualifiers = append(purl.Qualifiers, - packageurl.Qualifier{ - Key: "file_path", - Value: p.FilePath, - }, - ) - } - return purl.String() + return (*PackageURL)(packageurl.NewPackageURL(ptype, namespace, name, ver, qualifiers, subpath)), nil } func (p *PackageURL) Unwrap() *packageurl.PackageURL { if p == nil { return nil } - return &p.PackageURL + purl := (*packageurl.PackageURL)(p) + if len(purl.Qualifiers) == 0 { + purl.Qualifiers = nil + } + return purl } // LangType returns an application type in Trivy @@ -236,8 +193,28 @@ func (p *PackageURL) Class() types.ResultClass { } func (p *PackageURL) Package() *ftypes.Package { + pkgName := p.Name + if p.Namespace != "" && p.Class() != types.ClassOSPkg { + if p.Type == packageurl.TypeMaven || p.Type == packageurl.TypeGradle { + // Maven and Gradle packages separate ":" + // e.g. org.springframework:spring-core + pkgName = p.Namespace + ":" + p.Name + } else { + pkgName = p.Namespace + "/" + p.Name + } + } + + // CocoaPods purl has no namespace, but has subpath + // https://github.com/package-url/purl-spec/blob/a748c36ad415c8aeffe2b8a4a5d8a50d16d6d85f/PURL-TYPES.rst#cocoapods + if p.Subpath != "" && p.Type == packageurl.TypeCocoapods { + // CocoaPods uses <moduleName>/<submoduleName> format for package name + // e.g. `pkg:cocoapods/GoogleUtilities@7.5.2#NSData+zlib` => `GoogleUtilities/NSData+zlib` + pkgName = p.Name + "/" + p.Subpath + } + pkg := &ftypes.Package{ - Name: p.Name, + ID: dependency.ID(p.LangType(), pkgName, p.Version), + Name: pkgName, Version: p.Version, Identifier: ftypes.PkgIdentifier{ PURL: p.Unwrap(), @@ -257,34 +234,12 @@ func (p *PackageURL) Package() *ftypes.Package { } } - // CocoaPods purl has no namespace, but has subpath - // https://github.com/package-url/purl-spec/blob/a748c36ad415c8aeffe2b8a4a5d8a50d16d6d85f/PURL-TYPES.rst#cocoapods - if p.Type == packageurl.TypeCocoapods && p.Subpath != "" { - // CocoaPods uses <moduleName>/<submoduleName> format for package name - // e.g. `pkg:cocoapods/GoogleUtilities@7.5.2#NSData+zlib` => `GoogleUtilities/NSData+zlib` - pkg.Name = p.Name + "/" + p.Subpath - } - if p.Type == packageurl.TypeRPM { rpmVer := version.NewVersion(p.Version) pkg.Release = rpmVer.Release() pkg.Version = rpmVer.Version() } - // Return packages without namespace. - // OS packages are not supposed to have namespace. - if p.Namespace == "" || p.Class() == types.ClassOSPkg { - return pkg - } - - if p.Type == packageurl.TypeMaven || p.Type == packageurl.TypeGradle { - // Maven and Gradle packages separate ":" - // e.g. org.springframework:spring-core - pkg.Name = p.Namespace + ":" + p.Name - } else { - pkg.Name = p.Namespace + "/" + p.Name - } - return pkg } @@ -318,15 +273,19 @@ func (p *PackageURL) Match(target *packageurl.PackageURL) bool { return true } +func (p *PackageURL) String() string { + return p.Unwrap().String() +} + // ref. https://github.com/package-url/purl-spec/blob/a748c36ad415c8aeffe2b8a4a5d8a50d16d6d85f/PURL-TYPES.rst#oci -func parseOCI(metadata types.Metadata) (packageurl.PackageURL, error) { +func parseOCI(metadata types.Metadata) (*packageurl.PackageURL, error) { if len(metadata.RepoDigests) == 0 { - return *packageurl.NewPackageURL("", "", "", "", nil, ""), nil + return nil, nil } digest, err := cn.NewDigest(metadata.RepoDigests[0]) if err != nil { - return packageurl.PackageURL{}, xerrors.Errorf("failed to parse digest: %w", err) + return nil, xerrors.Errorf("failed to parse digest: %w", err) } name := strings.ToLower(digest.RepositoryStr()) @@ -349,7 +308,7 @@ func parseOCI(metadata types.Metadata) (packageurl.PackageURL, error) { }) } - return *packageurl.NewPackageURL(packageurl.TypeOCI, "", name, digest.DigestStr(), qualifiers, ""), nil + return packageurl.NewPackageURL(packageurl.TypeOCI, "", name, digest.DigestStr(), qualifiers, ""), nil } // ref. https://github.com/package-url/purl-spec/blob/master/PURL-TYPES.rst#apk diff --git a/pkg/purl/purl_test.go b/pkg/purl/purl_test.go index 876911ca521a..79b2647cff4d 100644 --- a/pkg/purl/purl_test.go +++ b/pkg/purl/purl_test.go @@ -31,12 +31,10 @@ func TestNewPackageURL(t *testing.T) { Version: "5.3.14", }, want: &purl.PackageURL{ - PackageURL: packageurl.PackageURL{ - Type: packageurl.TypeMaven, - Namespace: "org.springframework", - Name: "spring-core", - Version: "5.3.14", - }, + Type: packageurl.TypeMaven, + Namespace: "org.springframework", + Name: "spring-core", + Version: "5.3.14", }, }, { @@ -47,12 +45,10 @@ func TestNewPackageURL(t *testing.T) { Version: "5.3.14", }, want: &purl.PackageURL{ - PackageURL: packageurl.PackageURL{ - Type: packageurl.TypeMaven, - Namespace: "org.springframework", - Name: "spring-core", - Version: "5.3.14", - }, + Type: packageurl.TypeMaven, + Namespace: "org.springframework", + Name: "spring-core", + Version: "5.3.14", }, }, { @@ -63,12 +59,10 @@ func TestNewPackageURL(t *testing.T) { Version: "1.2.0", }, want: &purl.PackageURL{ - PackageURL: packageurl.PackageURL{ - Type: packageurl.TypeNPM, - Namespace: "@xtuc", - Name: "ieee754", - Version: "1.2.0", - }, + Type: packageurl.TypeNPM, + Namespace: "@xtuc", + Name: "ieee754", + Version: "1.2.0", }, }, { @@ -79,11 +73,9 @@ func TestNewPackageURL(t *testing.T) { Version: "4.17.21", }, want: &purl.PackageURL{ - PackageURL: packageurl.PackageURL{ - Type: packageurl.TypeNPM, - Name: "lodash", - Version: "4.17.21", - }, + Type: packageurl.TypeNPM, + Name: "lodash", + Version: "4.17.21", }, }, { @@ -94,12 +86,10 @@ func TestNewPackageURL(t *testing.T) { Version: "1.2.0", }, want: &purl.PackageURL{ - PackageURL: packageurl.PackageURL{ - Type: packageurl.TypeNPM, - Namespace: "@xtuc", - Name: "ieee754", - Version: "1.2.0", - }, + Type: packageurl.TypeNPM, + Namespace: "@xtuc", + Name: "ieee754", + Version: "1.2.0", }, }, { @@ -110,11 +100,9 @@ func TestNewPackageURL(t *testing.T) { Version: "4.17.21", }, want: &purl.PackageURL{ - PackageURL: packageurl.PackageURL{ - Type: packageurl.TypeNPM, - Name: "lodash", - Version: "4.17.21", - }, + Type: packageurl.TypeNPM, + Name: "lodash", + Version: "4.17.21", }, }, { @@ -125,11 +113,9 @@ func TestNewPackageURL(t *testing.T) { Version: "1.2.0", }, want: &purl.PackageURL{ - PackageURL: packageurl.PackageURL{ - Type: packageurl.TypePyPi, - Name: "django-test", - Version: "1.2.0", - }, + Type: packageurl.TypePyPi, + Name: "django-test", + Version: "1.2.0", }, }, { @@ -140,11 +126,9 @@ func TestNewPackageURL(t *testing.T) { Version: "0.4.1", }, want: &purl.PackageURL{ - PackageURL: packageurl.PackageURL{ - Type: packageurl.TypeConda, - Name: "absl-py", - Version: "0.4.1", - }, + Type: packageurl.TypeConda, + Name: "absl-py", + Version: "0.4.1", }, }, { @@ -155,12 +139,10 @@ func TestNewPackageURL(t *testing.T) { Version: "v1.0.2", }, want: &purl.PackageURL{ - PackageURL: packageurl.PackageURL{ - Type: packageurl.TypeComposer, - Namespace: "symfony", - Name: "contracts", - Version: "v1.0.2", - }, + Type: packageurl.TypeComposer, + Namespace: "symfony", + Name: "contracts", + Version: "v1.0.2", }, }, { @@ -171,12 +153,10 @@ func TestNewPackageURL(t *testing.T) { Version: "v1.5.0", }, want: &purl.PackageURL{ - PackageURL: packageurl.PackageURL{ - Type: packageurl.TypeGolang, - Namespace: "github.com/go-sql-driver", - Name: "mysql", - Version: "v1.5.0", - }, + Type: packageurl.TypeGolang, + Namespace: "github.com/go-sql-driver", + Name: "mysql", + Version: "v1.5.0", }, }, { @@ -203,11 +183,9 @@ func TestNewPackageURL(t *testing.T) { }, }, want: &purl.PackageURL{ - PackageURL: packageurl.PackageURL{ - Type: packageurl.TypeHex, - Name: "bunt", - Version: "0.2.0", - }, + Type: packageurl.TypeHex, + Name: "bunt", + Version: "0.2.0", }, }, { @@ -218,11 +196,9 @@ func TestNewPackageURL(t *testing.T) { Version: "0.13.2", }, want: &purl.PackageURL{ - PackageURL: packageurl.PackageURL{ - Type: packageurl.TypePub, - Name: "http", - Version: "0.13.2", - }, + Type: packageurl.TypePub, + Name: "http", + Version: "0.13.2", }, }, { @@ -234,12 +210,10 @@ func TestNewPackageURL(t *testing.T) { Version: "1.1.0", }, want: &purl.PackageURL{ - PackageURL: packageurl.PackageURL{ - Type: packageurl.TypeSwift, - Namespace: "github.com/apple", - Name: "swift-atomics", - Version: "1.1.0", - }, + Type: packageurl.TypeSwift, + Namespace: "github.com/apple", + Name: "swift-atomics", + Version: "1.1.0", }, }, { @@ -251,12 +225,10 @@ func TestNewPackageURL(t *testing.T) { Version: "7.5.2", }, want: &purl.PackageURL{ - PackageURL: packageurl.PackageURL{ - Type: packageurl.TypeCocoapods, - Name: "GoogleUtilities", - Version: "7.5.2", - Subpath: "NSData+zlib", - }, + Type: packageurl.TypeCocoapods, + Name: "GoogleUtilities", + Version: "7.5.2", + Subpath: "NSData+zlib", }, }, { @@ -268,11 +240,9 @@ func TestNewPackageURL(t *testing.T) { Version: "0.7.3", }, want: &purl.PackageURL{ - PackageURL: packageurl.PackageURL{ - Type: packageurl.TypeCargo, - Name: "abomination", - Version: "0.7.3", - }, + Type: packageurl.TypeCargo, + Name: "abomination", + Version: "0.7.3", }, }, { @@ -284,11 +254,9 @@ func TestNewPackageURL(t *testing.T) { Version: "9.0.1", }, want: &purl.PackageURL{ - PackageURL: packageurl.PackageURL{ - Type: packageurl.TypeNuget, - Name: "Newtonsoft.Json", - Version: "9.0.1", - }, + Type: packageurl.TypeNuget, + Name: "Newtonsoft.Json", + Version: "9.0.1", }, }, { @@ -314,24 +282,22 @@ func TestNewPackageURL(t *testing.T) { }, }, want: &purl.PackageURL{ - PackageURL: packageurl.PackageURL{ - Type: packageurl.TypeRPM, - Namespace: "redhat", - Name: "acl", - Version: "2.2.53-1.el8", - Qualifiers: packageurl.Qualifiers{ - { - Key: "arch", - Value: "aarch64", - }, - { - Key: "epoch", - Value: "1", - }, - { - Key: "distro", - Value: "redhat-8", - }, + Type: packageurl.TypeRPM, + Namespace: "redhat", + Name: "acl", + Version: "2.2.53-1.el8", + Qualifiers: packageurl.Qualifiers{ + { + Key: "arch", + Value: "aarch64", + }, + { + Key: "epoch", + Value: "1", + }, + { + Key: "distro", + Value: "redhat-8", }, }, }, @@ -352,20 +318,18 @@ func TestNewPackageURL(t *testing.T) { }, }, want: &purl.PackageURL{ - PackageURL: packageurl.PackageURL{ - Type: packageurl.TypeOCI, - Namespace: "", - Name: "core", - Version: "sha256:8fe1727132b2506c17ba0e1f6a6ed8a016bb1f5735e43b2738cd3fd1979b6260", - Qualifiers: packageurl.Qualifiers{ - { - Key: "repository_url", - Value: "cblmariner2preview.azurecr.io/base/core", - }, - { - Key: "arch", - Value: "amd64", - }, + Type: packageurl.TypeOCI, + Namespace: "", + Name: "core", + Version: "sha256:8fe1727132b2506c17ba0e1f6a6ed8a016bb1f5735e43b2738cd3fd1979b6260", + Qualifiers: packageurl.Qualifiers{ + { + Key: "repository_url", + Value: "cblmariner2preview.azurecr.io/base/core", + }, + { + Key: "arch", + Value: "amd64", }, }, }, @@ -400,20 +364,18 @@ func TestNewPackageURL(t *testing.T) { }, }, want: &purl.PackageURL{ - PackageURL: packageurl.PackageURL{ - Type: packageurl.TypeOCI, - Namespace: "", - Name: "alpine", - Version: "sha256:8fe1727132b2506c17ba0e1f6a6ed8a016bb1f5735e43b2738cd3fd1979b6260", - Qualifiers: packageurl.Qualifiers{ - { - Key: "repository_url", - Value: "index.docker.io/library/alpine", - }, - { - Key: "arch", - Value: "amd64", - }, + Type: packageurl.TypeOCI, + Namespace: "", + Name: "alpine", + Version: "sha256:8fe1727132b2506c17ba0e1f6a6ed8a016bb1f5735e43b2738cd3fd1979b6260", + Qualifiers: packageurl.Qualifiers{ + { + Key: "repository_url", + Value: "index.docker.io/library/alpine", + }, + { + Key: "arch", + Value: "amd64", }, }, }, @@ -458,79 +420,65 @@ func TestFromString(t *testing.T) { name: "happy path for maven", purl: "pkg:maven/org.springframework/spring-core@5.0.4.RELEASE", want: purl.PackageURL{ - PackageURL: packageurl.PackageURL{ - Type: packageurl.TypeMaven, - Namespace: "org.springframework", - Version: "5.0.4.RELEASE", - Name: "spring-core", - }, - FilePath: "", + Type: packageurl.TypeMaven, + Namespace: "org.springframework", + Version: "5.0.4.RELEASE", + Name: "spring-core", }, }, { name: "happy path for npm", - purl: "pkg:npm/bootstrap@5.0.2?file_path=app%2Fapp%2Fpackage.json", + purl: "pkg:npm/bootstrap@5.0.2", want: purl.PackageURL{ - PackageURL: packageurl.PackageURL{ - Type: packageurl.TypeNPM, - Name: "bootstrap", - Version: "5.0.2", - }, - FilePath: "app/app/package.json", + Type: packageurl.TypeNPM, + Name: "bootstrap", + Version: "5.0.2", }, }, { name: "happy path for coocapods", purl: "pkg:cocoapods/GoogleUtilities@7.5.2#NSData+zlib", want: purl.PackageURL{ - PackageURL: packageurl.PackageURL{ - Type: packageurl.TypeCocoapods, - Name: "GoogleUtilities", - Version: "7.5.2", - Subpath: "NSData+zlib", - }, + Type: packageurl.TypeCocoapods, + Name: "GoogleUtilities", + Version: "7.5.2", + Subpath: "NSData+zlib", }, }, { name: "happy path for hex", purl: "pkg:hex/plug@1.14.0", want: purl.PackageURL{ - PackageURL: packageurl.PackageURL{ - Type: packageurl.TypeHex, - Name: "plug", - Version: "1.14.0", - }, + Type: packageurl.TypeHex, + Name: "plug", + Version: "1.14.0", }, }, { name: "happy path for dart", purl: "pkg:pub/http@0.13.2", want: purl.PackageURL{ - PackageURL: packageurl.PackageURL{ - Type: packageurl.TypePub, - Name: "http", - Version: "0.13.2", - }, + Type: packageurl.TypePub, + Name: "http", + Version: "0.13.2", }, }, { name: "happy path for apk", purl: "pkg:apk/alpine/alpine-baselayout@3.2.0-r16?distro=3.14.2&epoch=1", want: purl.PackageURL{ - PackageURL: packageurl.PackageURL{ - Type: string(analyzer.TypeApk), - Namespace: "alpine", - Name: "alpine-baselayout", - Version: "3.2.0-r16", - Qualifiers: packageurl.Qualifiers{ - { - Key: "distro", - Value: "3.14.2", - }, - { - Key: "epoch", - Value: "1", - }, + Type: string(analyzer.TypeApk), + Namespace: "alpine", + Name: "alpine-baselayout", + Version: "3.2.0-r16", + Qualifiers: packageurl.Qualifiers{ + { + Key: "distro", + Value: "3.14.2", + }, + { + Key: "epoch", + Value: "1", }, }, }, @@ -539,35 +487,29 @@ func TestFromString(t *testing.T) { name: "happy path for rpm", purl: "pkg:rpm/redhat/containers-common@0.1.14", want: purl.PackageURL{ - PackageURL: packageurl.PackageURL{ - Type: packageurl.TypeRPM, - Namespace: "redhat", - Name: "containers-common", - Version: "0.1.14", - }, + Type: packageurl.TypeRPM, + Namespace: "redhat", + Name: "containers-common", + Version: "0.1.14", }, }, { name: "happy path for conda", purl: "pkg:conda/absl-py@0.4.1", want: purl.PackageURL{ - PackageURL: packageurl.PackageURL{ - Type: packageurl.TypeConda, - Name: "absl-py", - Version: "0.4.1", - }, + Type: packageurl.TypeConda, + Name: "absl-py", + Version: "0.4.1", }, }, { name: "bad rpm", purl: "pkg:rpm/redhat/a--@1.0.0", want: purl.PackageURL{ - PackageURL: packageurl.PackageURL{ - Type: packageurl.TypeRPM, - Namespace: "redhat", - Name: "a--", - Version: "1.0.0", - }, + Type: packageurl.TypeRPM, + Namespace: "redhat", + Name: "a--", + Version: "1.0.0", }, }, } @@ -594,32 +536,31 @@ func TestPackageURL_Package(t *testing.T) { { name: "rpm + Qualifiers", pkgURL: &purl.PackageURL{ - PackageURL: packageurl.PackageURL{ - Type: packageurl.TypeRPM, - Namespace: "redhat", - Name: "nodejs-full-i18n", - Version: "10.21.0-3.module_el8.2.0+391+8da3adc6", - Qualifiers: packageurl.Qualifiers{ - { - Key: "arch", - Value: "x86_64", - }, - { - Key: "epoch", - Value: "1", - }, - { - Key: "modularitylabel", - Value: "nodejs:10:8020020200707141642:6a468ee4", - }, - { - Key: "distro", - Value: "redhat-8", - }, + Type: packageurl.TypeRPM, + Namespace: "redhat", + Name: "nodejs-full-i18n", + Version: "10.21.0-3.module_el8.2.0+391+8da3adc6", + Qualifiers: packageurl.Qualifiers{ + { + Key: "arch", + Value: "x86_64", + }, + { + Key: "epoch", + Value: "1", + }, + { + Key: "modularitylabel", + Value: "nodejs:10:8020020200707141642:6a468ee4", + }, + { + Key: "distro", + Value: "redhat-8", }, }, }, wantPkg: &ftypes.Package{ + ID: "nodejs-full-i18n@10.21.0-3.module_el8.2.0+391+8da3adc6", Name: "nodejs-full-i18n", Version: "10.21.0", Release: "3.module_el8.2.0+391+8da3adc6", @@ -657,14 +598,14 @@ func TestPackageURL_Package(t *testing.T) { { name: "composer with namespace", pkgURL: &purl.PackageURL{ - PackageURL: packageurl.PackageURL{ - Type: packageurl.TypeComposer, - Namespace: "symfony", - Name: "contracts", - Version: "1.0.2", - }, + Type: packageurl.TypeComposer, + Namespace: "symfony", + Name: "contracts", + Version: "1.0.2", }, + wantPkg: &ftypes.Package{ + ID: "symfony/contracts@1.0.2", Name: "symfony/contracts", Version: "1.0.2", Identifier: ftypes.PkgIdentifier{ @@ -680,14 +621,14 @@ func TestPackageURL_Package(t *testing.T) { { name: "maven with namespace", pkgURL: &purl.PackageURL{ - PackageURL: packageurl.PackageURL{ - Type: packageurl.TypeMaven, - Namespace: "org.springframework", - Name: "spring-core", - Version: "5.0.4.RELEASE", - }, + Type: packageurl.TypeMaven, + Namespace: "org.springframework", + Name: "spring-core", + Version: "5.0.4.RELEASE", }, + wantPkg: &ftypes.Package{ + ID: "org.springframework:spring-core:5.0.4.RELEASE", Name: "org.springframework:spring-core", Version: "5.0.4.RELEASE", Identifier: ftypes.PkgIdentifier{ @@ -703,14 +644,14 @@ func TestPackageURL_Package(t *testing.T) { { name: "cocoapods with subpath", pkgURL: &purl.PackageURL{ - PackageURL: packageurl.PackageURL{ - Type: packageurl.TypeCocoapods, - Version: "4.2.0", - Name: "AppCenter", - Subpath: "Analytics", - }, + Type: packageurl.TypeCocoapods, + Version: "4.2.0", + Name: "AppCenter", + Subpath: "Analytics", }, + wantPkg: &ftypes.Package{ + ID: "AppCenter/Analytics@4.2.0", Name: "AppCenter/Analytics", Version: "4.2.0", Identifier: ftypes.PkgIdentifier{ @@ -726,20 +667,19 @@ func TestPackageURL_Package(t *testing.T) { { name: "wrong epoch", pkgURL: &purl.PackageURL{ - PackageURL: packageurl.PackageURL{ - Type: packageurl.TypeRPM, - Namespace: "redhat", - Name: "acl", - Version: "2.2.53-1.el8", - Qualifiers: packageurl.Qualifiers{ - { - Key: "epoch", - Value: "wrong", - }, + Type: packageurl.TypeRPM, + Namespace: "redhat", + Name: "acl", + Version: "2.2.53-1.el8", + Qualifiers: packageurl.Qualifiers{ + { + Key: "epoch", + Value: "wrong", }, }, }, wantPkg: &ftypes.Package{ + ID: "acl@2.2.53-1.el8", Name: "acl", Version: "2.2.53", Release: "1.el8", @@ -807,7 +747,7 @@ func TestPackageURL_LangType(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - p := &purl.PackageURL{PackageURL: tt.purl} + p := (purl.PackageURL)(tt.purl) assert.Equalf(t, tt.want, p.LangType(), "LangType()") }) } diff --git a/pkg/rekortest/server.go b/pkg/rekortest/server.go index e5eb7dbd7858..0e9207507492 100644 --- a/pkg/rekortest/server.go +++ b/pkg/rekortest/server.go @@ -141,7 +141,7 @@ var ( }, Dependencies: &[]cyclonedx.Dependency{ { - Ref: "pkg:oci/alpine@sha256:bc41182d7ef5ffc53a40b044e725193bc10142a1243f395ee852a8d9730fc2ad?repository_url=index.docker.io%2Flibrary%2Falpine&6arch=amd64", + Ref: "pkg:oci/alpine@sha256:bc41182d7ef5ffc53a40b044e725193bc10142a1243f395ee852a8d9730fc2ad?repository_url=index.docker.io%2Flibrary%2Falpine&arch=amd64", Dependencies: &[]string{ "fad4eb97-3d2a-4499-ace7-2c94444148a7", }, diff --git a/pkg/report/cyclonedx/cyclonedx.go b/pkg/report/cyclonedx/cyclonedx.go index f624c02e83b2..76cd91d67752 100644 --- a/pkg/report/cyclonedx/cyclonedx.go +++ b/pkg/report/cyclonedx/cyclonedx.go @@ -15,7 +15,7 @@ import ( type Writer struct { output io.Writer format cdx.BOMFileFormat - marshaler *cyclonedx.Marshaler + marshaler cyclonedx.Marshaler } func NewWriter(output io.Writer, appVersion string) Writer { @@ -28,7 +28,7 @@ func NewWriter(output io.Writer, appVersion string) Writer { // Write writes the results in CycloneDX format func (w Writer) Write(ctx context.Context, report types.Report) error { - bom, err := w.marshaler.Marshal(ctx, report) + bom, err := w.marshaler.MarshalReport(ctx, report) if err != nil { return xerrors.Errorf("CycloneDX marshal error: %w", err) } diff --git a/pkg/report/github/github.go b/pkg/report/github/github.go index e92d9921a2e6..5de466ad6beb 100644 --- a/pkg/report/github/github.go +++ b/pkg/report/github/github.go @@ -190,5 +190,5 @@ func buildPurl(t ftypes.TargetType, metadata types.Metadata, pkg ftypes.Package) if packageUrl == nil { return "", nil } - return packageUrl.ToString(), nil + return packageUrl.String(), nil } diff --git a/pkg/sbom/core/bom.go b/pkg/sbom/core/bom.go new file mode 100644 index 000000000000..5f55f673306b --- /dev/null +++ b/pkg/sbom/core/bom.go @@ -0,0 +1,290 @@ +package core + +import ( + "sort" + + "github.com/package-url/packageurl-go" + + dtypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/digest" + "github.com/aquasecurity/trivy/pkg/uuid" +) + +const ( + TypeApplication ComponentType = "application" + TypeContainer ComponentType = "container" + TypeLibrary ComponentType = "library" + TypeOS ComponentType = "os" + TypePlatform ComponentType = "platform" + + // Metadata properties + PropertySchemaVersion = "SchemaVersion" + PropertyType = "Type" + PropertyClass = "Class" + + // Image properties + PropertySize = "Size" + PropertyImageID = "ImageID" + PropertyRepoDigest = "RepoDigest" + PropertyDiffID = "DiffID" + PropertyRepoTag = "RepoTag" + + // Package properties + PropertyPkgID = "PkgID" + PropertyPkgType = "PkgType" + PropertySrcName = "SrcName" + PropertySrcVersion = "SrcVersion" + PropertySrcRelease = "SrcRelease" + PropertySrcEpoch = "SrcEpoch" + PropertyModularitylabel = "Modularitylabel" + PropertyFilePath = "FilePath" + PropertyLayerDigest = "LayerDigest" + PropertyLayerDiffID = "LayerDiffID" + + // Relationships + RelationshipDescribes RelationshipType = "describes" + RelationshipContains RelationshipType = "contains" + RelationshipDependsOn RelationshipType = "depends_on" +) + +type ComponentType string +type RelationshipType string + +// BOM represents an intermediate representation of a component for SBOM. +type BOM struct { + SerialNumber string + Version int + + rootID uuid.UUID + components map[uuid.UUID]*Component + relationships map[uuid.UUID][]Relationship + + // Vulnerabilities is a list of vulnerabilities that affect the component + // CycloneDX: vulnerabilities + // SPDX: N/A + vulnerabilities map[uuid.UUID][]Vulnerability + + // purls is a map of package URLs to UUIDs + // This is used to ensure that each package URL is only represented once in the BOM. + purls map[string][]uuid.UUID +} + +type Component struct { + // id is the unique identifier of the component for internal use. + // It's transparently generated by UUIDv4 + id uuid.UUID + + // Type is the type of the component + // CycloneDX: component.type + Type ComponentType + + // Root represents the root of the BOM + // Only one root is allowed in a BOM. + // CycloneDX: metadata.component + Root bool + + // Name is the name of the component + // CycloneDX: component.name + // SPDX: package.name + Name string + + // Group is the group of the component + // CycloneDX: component.group + // SPDX: N/A + Group string + + // Version is the version of the component + // CycloneDX: component.version + // SPDX: package.versionInfo + Version string + + // Licenses is a list of licenses that apply to the component + // CycloneDX: component.licenses + // SPDX: package.licenseConcluded, package.licenseDeclared + Licenses []string + + // PkgID has PURL and BOMRef for the component + // PURL: + // CycloneDX: component.purl + // SPDX: package.externalRefs.referenceLocator + // BOMRef: + // CycloneDX: component.bom-ref + // SPDX: N/A + PkgID PkgID + + // Supplier is the name of the supplier of the component + // CycloneDX: component.supplier + // SPDX: package.supplier + Supplier string + + // Files is a list of files that are part of the component. + // CycloneDX: component.properties + // SPDX: files + Files []File + + // Properties is a list of key-value pairs that provide additional information about the component + // CycloneDX: component.properties + // SPDX: package.attributionTexts + Properties Properties `hash:"set"` +} + +func (c *Component) ID() uuid.UUID { + return c.id +} + +type File struct { + // Path is a path of the file. + // CycloneDX: N/A + // SPDX: package.files[].fileName + Path string + + // Hash is a hash that uniquely identify the component. + // CycloneDX: component.hashes + // SPDX: package.files[].checksum + Hash digest.Digest +} + +type Property struct { + Name string + Value string + Namespace string +} + +type Properties []Property + +func (p Properties) Len() int { return len(p) } +func (p Properties) Less(i, j int) bool { + if p[i].Name != p[j].Name { + return p[i].Name < p[j].Name + } + return p[i].Value < p[j].Value +} +func (p Properties) Swap(i, j int) { p[i], p[j] = p[j], p[i] } + +type Relationship struct { + Dependency uuid.UUID + Type RelationshipType +} + +type PkgID struct { + PURL *packageurl.PackageURL + BOMRef string +} + +type Vulnerability struct { + dtypes.Vulnerability + ID string + PkgID string + PkgName string + InstalledVersion string + FixedVersion string + PrimaryURL string + DataSource *dtypes.DataSource +} + +func NewBOM() *BOM { + return &BOM{ + components: make(map[uuid.UUID]*Component), + relationships: make(map[uuid.UUID][]Relationship), + vulnerabilities: make(map[uuid.UUID][]Vulnerability), + purls: make(map[string][]uuid.UUID), + } +} + +func (b *BOM) setupComponent(c *Component) { + if c.id == uuid.Nil { + c.id = uuid.New() + } + if c.PkgID.PURL != nil { + p := c.PkgID.PURL.String() + b.purls[p] = append(b.purls[p], c.id) + } + sort.Sort(c.Properties) +} + +func (b *BOM) AddComponent(c *Component) { + b.setupComponent(c) + if c.Root { + b.rootID = c.id + } + b.components[c.id] = c +} + +func (b *BOM) AddRelationship(parent, child *Component, relationshipType RelationshipType) { + if parent.id == uuid.Nil { + b.AddComponent(parent) + } + + if child == nil { + b.relationships[parent.id] = nil // Meaning no dependencies + return + } + + if child.id == uuid.Nil { + b.AddComponent(child) + } + + b.relationships[parent.id] = append(b.relationships[parent.id], Relationship{ + Type: relationshipType, + Dependency: child.id, + }) +} + +func (b *BOM) AddVulnerabilities(c *Component, vulns []Vulnerability) { + if c.id == uuid.Nil { + b.AddComponent(c) + } + if _, ok := b.vulnerabilities[c.id]; ok { + return + } + b.vulnerabilities[c.id] = vulns +} + +func (b *BOM) Root() *Component { + root, ok := b.components[b.rootID] + if !ok { + return nil + } + root.PkgID.BOMRef = b.bomRef(root) + return root +} + +func (b *BOM) Components() map[uuid.UUID]*Component { + // Fill in BOMRefs for components + for id, c := range b.components { + b.components[id].PkgID.BOMRef = b.bomRef(c) + } + return b.components +} + +func (b *BOM) Relationships() map[uuid.UUID][]Relationship { + return b.relationships +} + +func (b *BOM) Vulnerabilities() map[uuid.UUID][]Vulnerability { + return b.vulnerabilities +} + +func (b *BOM) NumComponents() int { + return len(b.components) + 1 // +1 for the root component +} + +// bomRef returns BOMRef for CycloneDX +// When multiple lock files have the same dependency with the same name and version, PURL in the BOM can conflict. +// In that case, PURL cannot be used as a unique identifier, and UUIDv4 be used for BOMRef. +func (b *BOM) bomRef(c *Component) string { + if c.PkgID.BOMRef != "" { + return c.PkgID.BOMRef + } + // Return the UUID of the component if the PURL is not present. + if c.PkgID.PURL == nil { + return c.id.String() + } + p := c.PkgID.PURL.String() + + // Return the UUID of the component if the PURL is not unique in the BOM. + if len(b.purls[p]) > 1 { + return c.id.String() + } + return p +} diff --git a/pkg/sbom/cyclonedx/core/cyclonedx.go b/pkg/sbom/cyclonedx/core/cyclonedx.go deleted file mode 100644 index fc326145d5c6..000000000000 --- a/pkg/sbom/cyclonedx/core/cyclonedx.go +++ /dev/null @@ -1,513 +0,0 @@ -package core - -import ( - "context" - "fmt" - "sort" - "strconv" - "strings" - - cdx "github.com/CycloneDX/cyclonedx-go" - "github.com/samber/lo" - "golang.org/x/exp/slices" - - dtypes "github.com/aquasecurity/trivy-db/pkg/types" - "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" - "github.com/aquasecurity/trivy/pkg/clock" - "github.com/aquasecurity/trivy/pkg/digest" - "github.com/aquasecurity/trivy/pkg/log" - "github.com/aquasecurity/trivy/pkg/purl" - "github.com/aquasecurity/trivy/pkg/types" - "github.com/aquasecurity/trivy/pkg/uuid" -) - -const ( - ToolVendor = "aquasecurity" - ToolName = "trivy" - Namespace = ToolVendor + ":" + ToolName + ":" - - // https://json-schema.org/understanding-json-schema/reference/string.html#dates-and-times - timeLayout = "2006-01-02T15:04:05+00:00" -) - -type CycloneDX struct { - appVersion string -} - -type Component struct { - Type cdx.ComponentType - Name string - Group string - Version string - PackageURL *purl.PackageURL - Licenses []string - Hashes []digest.Digest - Supplier string - Properties []Property - - Components []*Component - Vulnerabilities []types.DetectedVulnerability -} - -type Property struct { - Name string - Value string - Namespace string -} - -func NewCycloneDX(version string) *CycloneDX { - return &CycloneDX{ - appVersion: version, - } -} - -func (c *CycloneDX) Marshal(ctx context.Context, root *Component) *cdx.BOM { - bom := cdx.NewBOM() - bom.SerialNumber = uuid.New().URN() - bom.Metadata = c.Metadata(ctx) - - components := make(map[string]*cdx.Component) - dependencies := make(map[string]*[]string) - vulnerabilities := make(map[string]*cdx.Vulnerability) - bom.Metadata.Component = c.MarshalComponent(root, components, dependencies, vulnerabilities) - - // Remove metadata component - delete(components, bom.Metadata.Component.BOMRef) - - bom.Components = c.Components(components) - bom.Dependencies = c.Dependencies(dependencies) - bom.Vulnerabilities = c.Vulnerabilities(vulnerabilities) - - return bom -} - -func (c *CycloneDX) MarshalComponent(component *Component, components map[string]*cdx.Component, - deps map[string]*[]string, vulns map[string]*cdx.Vulnerability) *cdx.Component { - bomRef := c.BOMRef(component) - - // When multiple lock files have the same dependency with the same name and version, - // "BOM-Ref" (PURL technically) of "Library" components may conflict. - // In that case, only one "Library" component will be added and - // some "Application" components will refer to the same component. - // e.g. - // Application component (/app1/package-lock.json) - // | - // | Application component (/app2/package-lock.json) - // | | - // â””----â”´----> Library component (npm package, express-4.17.3) - // - if v, ok := components[bomRef]; ok { - return v - } - - cdxComponent := &cdx.Component{ - BOMRef: bomRef, - Type: component.Type, - Name: component.Name, - Group: component.Group, - Version: component.Version, - PackageURL: c.PackageURL(component.PackageURL), - Supplier: c.Supplier(component.Supplier), - Hashes: c.Hashes(component.Hashes), - Licenses: c.Licenses(component.Licenses), - Properties: lo.ToPtr(c.Properties(component.Properties)), - } - components[cdxComponent.BOMRef] = cdxComponent - - for _, v := range component.Vulnerabilities { - // If the same vulnerability affects multiple packages, those packages will be aggregated into one vulnerability. - // Vulnerability component (CVE-2020-26247) - // -> Library component (nokogiri /srv/app1/vendor/bundle/ruby/3.0.0/specifications/nokogiri-1.10.0.gemspec) - // -> Library component (nokogiri /srv/app2/vendor/bundle/ruby/3.0.0/specifications/nokogiri-1.10.0.gemspec) - if vuln, ok := vulns[v.VulnerabilityID]; ok { - *vuln.Affects = append(*vuln.Affects, cdxAffects(bomRef, v.InstalledVersion)) - if v.FixedVersion != "" { - // new recommendation - rec := fmt.Sprintf("Upgrade %s to version %s", v.PkgName, v.FixedVersion) - // previous recommendations - recs := strings.Split(vuln.Recommendation, "; ") - if !slices.Contains(recs, rec) { - recs = append(recs, rec) - slices.Sort(recs) - vuln.Recommendation = strings.Join(recs, "; ") - } - } - } else { - vulns[v.VulnerabilityID] = c.marshalVulnerability(cdxComponent.BOMRef, v) - } - } - - dependencies := make([]string, 0) // nolint:gocritic // Components that do not have their own dependencies must be declared as empty elements - for _, child := range component.Components { - childComponent := c.MarshalComponent(child, components, deps, vulns) - dependencies = append(dependencies, childComponent.BOMRef) - } - sort.Strings(dependencies) - - deps[cdxComponent.BOMRef] = &dependencies - - return cdxComponent -} - -func (c *CycloneDX) marshalVulnerability(bomRef string, vuln types.DetectedVulnerability) *cdx.Vulnerability { - v := &cdx.Vulnerability{ - ID: vuln.VulnerabilityID, - Source: cdxSource(vuln.DataSource), - Ratings: cdxRatings(vuln), - CWEs: cwes(vuln.CweIDs), - Description: vuln.Description, - Advisories: cdxAdvisories(append([]string{vuln.PrimaryURL}, vuln.References...)), - } - if vuln.FixedVersion != "" { - v.Recommendation = fmt.Sprintf("Upgrade %s to version %s", vuln.PkgName, vuln.FixedVersion) - } - if vuln.PublishedDate != nil { - v.Published = vuln.PublishedDate.Format(timeLayout) - } - if vuln.LastModifiedDate != nil { - v.Updated = vuln.LastModifiedDate.Format(timeLayout) - } - - v.Affects = &[]cdx.Affects{cdxAffects(bomRef, vuln.InstalledVersion)} - - return v -} - -func (c *CycloneDX) BOMRef(component *Component) string { - // PURL takes precedence over UUID - if component.PackageURL == nil { - return uuid.New().String() - } - return component.PackageURL.BOMRef() -} - -func (c *CycloneDX) Metadata(ctx context.Context) *cdx.Metadata { - return &cdx.Metadata{ - Timestamp: clock.Now(ctx).UTC().Format(timeLayout), - Tools: &cdx.ToolsChoice{ - Components: &[]cdx.Component{ - { - Type: cdx.ComponentTypeApplication, - Group: ToolVendor, - Name: ToolName, - Version: c.appVersion, - }, - }, - }, - } -} - -func (c *CycloneDX) Components(uniq map[string]*cdx.Component) *[]cdx.Component { - // Convert components from map to slice and sort by BOM-Ref - components := lo.MapToSlice(uniq, func(_ string, value *cdx.Component) cdx.Component { - return *value - }) - sort.Slice(components, func(i, j int) bool { - return components[i].BOMRef < components[j].BOMRef - }) - return &components -} - -func (c *CycloneDX) Dependencies(uniq map[string]*[]string) *[]cdx.Dependency { - // Convert dependencies from map to slice and sort by BOM-Ref - dependencies := lo.MapToSlice(uniq, func(bomRef string, value *[]string) cdx.Dependency { - return cdx.Dependency{ - Ref: bomRef, - Dependencies: value, - } - }) - sort.Slice(dependencies, func(i, j int) bool { - return dependencies[i].Ref < dependencies[j].Ref - }) - return &dependencies -} - -func (c *CycloneDX) Vulnerabilities(uniq map[string]*cdx.Vulnerability) *[]cdx.Vulnerability { - vulns := lo.MapToSlice(uniq, func(bomRef string, value *cdx.Vulnerability) cdx.Vulnerability { - sort.Slice(*value.Affects, func(i, j int) bool { - return (*value.Affects)[i].Ref < (*value.Affects)[j].Ref - }) - return *value - }) - sort.Slice(vulns, func(i, j int) bool { - return vulns[i].ID < vulns[j].ID - }) - return &vulns -} - -func (c *CycloneDX) PackageURL(p *purl.PackageURL) string { - if p == nil { - return "" - } - return p.String() -} - -func (c *CycloneDX) Supplier(supplier string) *cdx.OrganizationalEntity { - if supplier == "" { - return nil - } - return &cdx.OrganizationalEntity{ - Name: supplier, - } -} - -func (c *CycloneDX) Hashes(hashes []digest.Digest) *[]cdx.Hash { - if len(hashes) == 0 { - return nil - } - var cdxHashes []cdx.Hash - for _, hash := range hashes { - var alg cdx.HashAlgorithm - switch hash.Algorithm() { - case digest.SHA1: - alg = cdx.HashAlgoSHA1 - case digest.SHA256: - alg = cdx.HashAlgoSHA256 - case digest.MD5: - alg = cdx.HashAlgoMD5 - default: - log.Logger.Debugf("Unable to convert %q algorithm to CycloneDX format", hash.Algorithm()) - continue - } - - cdxHashes = append(cdxHashes, cdx.Hash{ - Algorithm: alg, - Value: hash.Encoded(), - }) - } - return &cdxHashes -} - -func (c *CycloneDX) Licenses(licenses []string) *cdx.Licenses { - if len(licenses) == 0 { - return nil - } - choices := lo.Map(licenses, func(license string, i int) cdx.LicenseChoice { - return cdx.LicenseChoice{ - License: &cdx.License{ - Name: license, - }, - } - }) - return lo.ToPtr(cdx.Licenses(choices)) -} - -func (c *CycloneDX) Properties(properties []Property) []cdx.Property { - cdxProps := make([]cdx.Property, 0, len(properties)) - for _, property := range properties { - namespace := Namespace - if len(property.Namespace) > 0 { - namespace = property.Namespace - } - cdxProps = append(cdxProps, - cdx.Property{ - Name: namespace + property.Name, - Value: property.Value, - }) - } - sort.Slice(cdxProps, func(i, j int) bool { - return cdxProps[i].Name < cdxProps[j].Name - }) - return cdxProps -} - -func IsTrivySBOM(c *cdx.BOM) bool { - if c == nil || c.Metadata == nil || c.Metadata.Tools == nil { - return false - } - - for _, component := range lo.FromPtr(c.Metadata.Tools.Components) { - if component.Group == ToolVendor && component.Name == ToolName { - return true - } - } - - // Metadata.Tools array is deprecated (as of CycloneDX v1.5). We check this field for backward compatibility. - // cf. https://github.com/CycloneDX/cyclonedx-go/blob/b9654ae9b4705645152d20eb9872b5f3d73eac49/cyclonedx.go#L988 - for _, tool := range lo.FromPtr(c.Metadata.Tools.Tools) { - if tool.Vendor == ToolVendor && tool.Name == ToolName { - return true - } - } - - return false -} - -func LookupProperty(properties *[]cdx.Property, key string) string { - for _, p := range lo.FromPtr(properties) { - if p.Name == Namespace+key { - return p.Value - } - } - return "" -} - -func UnmarshalProperties(properties *[]cdx.Property) map[string]string { - props := make(map[string]string) - for _, prop := range lo.FromPtr(properties) { - if !strings.HasPrefix(prop.Name, Namespace) { - continue - } - props[strings.TrimPrefix(prop.Name, Namespace)] = prop.Value - } - return props -} - -func cdxAdvisories(refs []string) *[]cdx.Advisory { - refs = lo.Uniq(refs) - advs := lo.FilterMap(refs, func(ref string, _ int) (cdx.Advisory, bool) { - return cdx.Advisory{URL: ref}, ref != "" - }) - - // cyclonedx converts link to empty `[]cdx.Advisory` to `null` - // `bom-1.5.schema.json` doesn't support this - `Invalid type. Expected: array, given: null` - // we need to explicitly set `nil` for empty `refs` slice - if len(advs) == 0 { - return nil - } - - return &advs -} - -func cwes(cweIDs []string) *[]int { - // to skip cdx.Vulnerability.CWEs when generating json - // we should return 'clear' nil without 'type' interface part - if cweIDs == nil { - return nil - } - var ret []int - for _, cweID := range cweIDs { - number, err := strconv.Atoi(strings.TrimPrefix(strings.ToLower(cweID), "cwe-")) - if err != nil { - log.Logger.Debugf("cwe id parse error: %s", err) - continue - } - ret = append(ret, number) - } - return &ret -} - -func cdxRatings(vuln types.DetectedVulnerability) *[]cdx.VulnerabilityRating { - rates := make([]cdx.VulnerabilityRating, 0) // nolint:gocritic // To export an empty array in JSON - for sourceID, severity := range vuln.VendorSeverity { - // When the vendor also provides CVSS score/vector - if cvss, ok := vuln.CVSS[sourceID]; ok { - if cvss.V2Score != 0 || cvss.V2Vector != "" { - rates = append(rates, cdxRatingV2(sourceID, severity, cvss)) - } - if cvss.V3Score != 0 || cvss.V3Vector != "" { - rates = append(rates, cdxRatingV3(sourceID, severity, cvss)) - } - } else { // When the vendor provides only severity - rate := cdx.VulnerabilityRating{ - Source: &cdx.Source{ - Name: string(sourceID), - }, - Severity: toCDXSeverity(severity), - } - rates = append(rates, rate) - } - } - - // For consistency - sort.Slice(rates, func(i, j int) bool { - if rates[i].Source.Name != rates[j].Source.Name { - return rates[i].Source.Name < rates[j].Source.Name - } - if rates[i].Method != rates[j].Method { - return rates[i].Method < rates[j].Method - } - if rates[i].Score != nil && rates[j].Score != nil { - return *rates[i].Score < *rates[j].Score - } - return rates[i].Vector < rates[j].Vector - }) - return &rates -} - -func cdxRatingV2(sourceID dtypes.SourceID, severity dtypes.Severity, cvss dtypes.CVSS) cdx.VulnerabilityRating { - cdxSeverity := toCDXSeverity(severity) - - // Trivy keeps only CVSSv3 severity for NVD. - // The CVSSv2 severity must be calculated according to CVSSv2 score. - if sourceID == vulnerability.NVD { - cdxSeverity = nvdSeverityV2(cvss.V2Score) - } - return cdx.VulnerabilityRating{ - Source: &cdx.Source{ - Name: string(sourceID), - }, - Score: &cvss.V2Score, - Method: cdx.ScoringMethodCVSSv2, - Severity: cdxSeverity, - Vector: cvss.V2Vector, - } -} - -func cdxRatingV3(sourceID dtypes.SourceID, severity dtypes.Severity, cvss dtypes.CVSS) cdx.VulnerabilityRating { - rate := cdx.VulnerabilityRating{ - Source: &cdx.Source{ - Name: string(sourceID), - }, - Score: &cvss.V3Score, - Method: cdx.ScoringMethodCVSSv3, - Severity: toCDXSeverity(severity), - Vector: cvss.V3Vector, - } - if strings.HasPrefix(cvss.V3Vector, "CVSS:3.1") { - rate.Method = cdx.ScoringMethodCVSSv31 - } - return rate -} - -func nvdSeverityV2(score float64) cdx.Severity { - // cf. https://nvd.nist.gov/vuln-metrics/cvss - switch { - case score < 4.0: - return cdx.SeverityInfo - case 4.0 <= score && score < 7.0: - return cdx.SeverityMedium - case 7.0 <= score: - return cdx.SeverityHigh - } - return cdx.SeverityUnknown -} - -func toCDXSeverity(s dtypes.Severity) cdx.Severity { - switch s { - case dtypes.SeverityLow: - return cdx.SeverityLow - case dtypes.SeverityMedium: - return cdx.SeverityMedium - case dtypes.SeverityHigh: - return cdx.SeverityHigh - case dtypes.SeverityCritical: - return cdx.SeverityCritical - default: - return cdx.SeverityUnknown - } -} - -func cdxSource(source *dtypes.DataSource) *cdx.Source { - if source == nil { - return nil - } - - return &cdx.Source{ - Name: string(source.ID), - URL: source.URL, - } -} - -func cdxAffects(ref, version string) cdx.Affects { - return cdx.Affects{ - Ref: ref, - Range: &[]cdx.AffectedVersions{ - { - Version: version, - Status: cdx.VulnerabilityStatusAffected, - // "AffectedVersions.Range" is not included, because it does not exist in DetectedVulnerability. - }, - }, - } -} diff --git a/pkg/sbom/cyclonedx/core/cyclonedx_test.go b/pkg/sbom/cyclonedx/core/cyclonedx_test.go deleted file mode 100644 index 87d5a589f9bf..000000000000 --- a/pkg/sbom/cyclonedx/core/cyclonedx_test.go +++ /dev/null @@ -1,380 +0,0 @@ -package core_test - -import ( - "context" - "github.com/aquasecurity/trivy/pkg/purl" - "testing" - "time" - - cdx "github.com/CycloneDX/cyclonedx-go" - "github.com/package-url/packageurl-go" - "github.com/stretchr/testify/assert" - - "github.com/aquasecurity/trivy/pkg/clock" - "github.com/aquasecurity/trivy/pkg/digest" - "github.com/aquasecurity/trivy/pkg/sbom/cyclonedx/core" - "github.com/aquasecurity/trivy/pkg/uuid" -) - -func TestMarshaler_CoreComponent(t *testing.T) { - noDepRefs := []string{} - tests := []struct { - name string - rootComponent *core.Component - want *cdx.BOM - }{ - { - name: "marshal CoreComponent", - rootComponent: &core.Component{ - Type: cdx.ComponentTypeContainer, - Name: "test-cluster", - Components: []*core.Component{ - { - Type: cdx.ComponentTypeApplication, - Name: "kube-apiserver-kind-control-plane", - Properties: []core.Property{ - { - Name: "control_plane_components", - Value: "kube-apiserver", - }, - }, - Components: []*core.Component{ - { - Type: cdx.ComponentTypeContainer, - Name: "k8s.gcr.io/kube-apiserver", - Version: "sha256:18e61c783b41758dd391ab901366ec3546b26fae00eef7e223d1f94da808e02f", - PackageURL: &purl.PackageURL{ - PackageURL: packageurl.PackageURL{ - Type: "oci", - Name: "kube-apiserver", - Version: "sha256:18e61c783b41758dd391ab901366ec3546b26fae00eef7e223d1f94da808e02f", - Qualifiers: packageurl.Qualifiers{ - { - Key: "repository_url", - Value: "k8s.gcr.io/kube-apiserver", - }, - { - Key: "arch", - }, - }, - }, - }, - Hashes: []digest.Digest{"sha256:18e61c783b41758dd391ab901366ec3546b26fae00eef7e223d1f94da808e02f"}, - Properties: []core.Property{ - { - Name: "PkgID", - Value: "k8s.gcr.io/kube-apiserver:1.21.1", - }, - { - Name: "PkgType", - Value: "oci", - }, - }, - }, - }, - }, - { - Type: cdx.ComponentTypeContainer, - Name: "kind-control-plane", - Properties: []core.Property{ - { - Name: "architecture", - Value: "arm64", - }, - { - Name: "host_name", - Value: "kind-control-plane", - }, - { - Name: "kernel_version", - Value: "6.2.13-300.fc38.aarch64", - }, - { - Name: "node_role", - Value: "master", - }, - { - Name: "operating_system", - Value: "linux", - }, - }, - Components: []*core.Component{ - { - Type: cdx.ComponentTypeOS, - Name: "ubuntu", - Version: "21.04", - Properties: []core.Property{ - { - Name: "Class", - Value: "os-pkgs", - }, - { - Name: "Type", - Value: "ubuntu", - }, - }, - }, - { - Type: cdx.ComponentTypeApplication, - Name: "node-core-components", - Properties: []core.Property{ - { - Name: "Class", - Value: "lang-pkgs", - }, - { - Name: "Type", - Value: "golang", - }, - }, - Components: []*core.Component{ - { - Type: cdx.ComponentTypeLibrary, - Name: "kubelet", - Version: "1.21.1", - Properties: []core.Property{ - { - Name: "PkgType", - Value: "golang", - }, - }, - PackageURL: &purl.PackageURL{ - PackageURL: packageurl.PackageURL{ - Type: "golang", - Name: "kubelet", - Version: "1.21.1", - Qualifiers: packageurl.Qualifiers{}, - }, - }, - }, - { - Type: cdx.ComponentTypeLibrary, - Name: "containerd", - Version: "1.5.2", - Properties: []core.Property{ - { - Name: "PkgType", - Value: "golang", - }, - }, - PackageURL: &purl.PackageURL{ - PackageURL: packageurl.PackageURL{ - Type: "golang", - Name: "containerd", - Version: "1.5.2", - Qualifiers: packageurl.Qualifiers{}, - }, - }, - }, - }, - }, - }, - }, - }, - }, - - want: &cdx.BOM{ - XMLNS: "http://cyclonedx.org/schema/bom/1.5", - BOMFormat: "CycloneDX", - SerialNumber: "urn:uuid:3ff14136-e09f-4df9-80ea-000000000001", - JSONSchema: "http://cyclonedx.org/schema/bom-1.5.schema.json", - SpecVersion: cdx.SpecVersion1_5, - Version: 1, - Metadata: &cdx.Metadata{ - Timestamp: "2021-08-25T12:20:30+00:00", - Tools: &cdx.ToolsChoice{ - Components: &[]cdx.Component{ - { - Type: cdx.ComponentTypeApplication, - Name: "trivy", - Group: "aquasecurity", - Version: "dev", - }, - }, - }, - Component: &cdx.Component{ - BOMRef: "3ff14136-e09f-4df9-80ea-000000000002", - Name: "test-cluster", - Properties: &[]cdx.Property{}, - Type: cdx.ComponentTypeContainer, - }, - }, - Vulnerabilities: &[]cdx.Vulnerability{}, - Components: &[]cdx.Component{ - { - BOMRef: "3ff14136-e09f-4df9-80ea-000000000003", - Type: "application", - Name: "kube-apiserver-kind-control-plane", - Properties: &[]cdx.Property{ - { - Name: "aquasecurity:trivy:control_plane_components", - Value: "kube-apiserver", - }, - }, - }, - { - BOMRef: "3ff14136-e09f-4df9-80ea-000000000004", - Type: "container", - Name: "kind-control-plane", - Properties: &[]cdx.Property{ - { - Name: "aquasecurity:trivy:architecture", - Value: "arm64", - }, - { - Name: "aquasecurity:trivy:host_name", - Value: "kind-control-plane", - }, - { - Name: "aquasecurity:trivy:kernel_version", - Value: "6.2.13-300.fc38.aarch64", - }, - { - Name: "aquasecurity:trivy:node_role", - Value: "master", - }, - { - Name: "aquasecurity:trivy:operating_system", - Value: "linux", - }, - }, - }, - { - BOMRef: "3ff14136-e09f-4df9-80ea-000000000005", - Type: "operating-system", - Name: "ubuntu", - Version: "21.04", - Properties: &[]cdx.Property{ - { - Name: "aquasecurity:trivy:Class", - Value: "os-pkgs", - }, - { - Name: "aquasecurity:trivy:Type", - Value: "ubuntu", - }, - }, - }, - { - BOMRef: "3ff14136-e09f-4df9-80ea-000000000006", - Type: "application", - Name: "node-core-components", - Properties: &[]cdx.Property{ - { - Name: "aquasecurity:trivy:Class", - Value: "lang-pkgs", - }, - { - Name: "aquasecurity:trivy:Type", - Value: "golang", - }, - }, - }, - { - BOMRef: "pkg:golang/containerd@1.5.2", - Type: "library", - Name: "containerd", - Version: "1.5.2", - PackageURL: "pkg:golang/containerd@1.5.2", - Properties: &[]cdx.Property{ - { - Name: "aquasecurity:trivy:PkgType", - Value: "golang", - }, - }, - }, - { - BOMRef: "pkg:golang/kubelet@1.21.1", - Type: "library", - Name: "kubelet", - Version: "1.21.1", - PackageURL: "pkg:golang/kubelet@1.21.1", - Properties: &[]cdx.Property{ - { - Name: "aquasecurity:trivy:PkgType", - Value: "golang", - }, - }, - }, - { - BOMRef: "pkg:oci/kube-apiserver@sha256%3A18e61c783b41758dd391ab901366ec3546b26fae00eef7e223d1f94da808e02f?arch=&repository_url=k8s.gcr.io%2Fkube-apiserver", - Hashes: &[]cdx.Hash{ - { - Algorithm: "SHA-256", - Value: "18e61c783b41758dd391ab901366ec3546b26fae00eef7e223d1f94da808e02f", - }, - }, - Type: "container", - Name: "k8s.gcr.io/kube-apiserver", - Version: "sha256:18e61c783b41758dd391ab901366ec3546b26fae00eef7e223d1f94da808e02f", - PackageURL: "pkg:oci/kube-apiserver@sha256%3A18e61c783b41758dd391ab901366ec3546b26fae00eef7e223d1f94da808e02f?arch=&repository_url=k8s.gcr.io%2Fkube-apiserver", - Properties: &[]cdx.Property{ - { - Name: "aquasecurity:trivy:PkgID", - Value: "k8s.gcr.io/kube-apiserver:1.21.1", - }, - { - Name: "aquasecurity:trivy:PkgType", - Value: "oci", - }, - }, - }, - }, - Dependencies: &[]cdx.Dependency{ - { - Ref: "3ff14136-e09f-4df9-80ea-000000000002", - Dependencies: &[]string{ - "3ff14136-e09f-4df9-80ea-000000000003", - "3ff14136-e09f-4df9-80ea-000000000004", - }, - }, - { - Ref: "3ff14136-e09f-4df9-80ea-000000000003", - Dependencies: &[]string{"pkg:oci/kube-apiserver@sha256%3A18e61c783b41758dd391ab901366ec3546b26fae00eef7e223d1f94da808e02f?arch=&repository_url=k8s.gcr.io%2Fkube-apiserver"}, - }, - { - Ref: "3ff14136-e09f-4df9-80ea-000000000004", - Dependencies: &[]string{ - "3ff14136-e09f-4df9-80ea-000000000005", - "3ff14136-e09f-4df9-80ea-000000000006", - }, - }, - { - Ref: "3ff14136-e09f-4df9-80ea-000000000005", - Dependencies: &noDepRefs, - }, - { - Ref: "3ff14136-e09f-4df9-80ea-000000000006", - Dependencies: &[]string{ - "pkg:golang/containerd@1.5.2", - "pkg:golang/kubelet@1.21.1", - }, - }, - { - Ref: "pkg:golang/containerd@1.5.2", - Dependencies: &noDepRefs, - }, - { - Ref: "pkg:golang/kubelet@1.21.1", - Dependencies: &noDepRefs, - }, - { - Ref: "pkg:oci/kube-apiserver@sha256%3A18e61c783b41758dd391ab901366ec3546b26fae00eef7e223d1f94da808e02f?arch=&repository_url=k8s.gcr.io%2Fkube-apiserver", - Dependencies: &noDepRefs, - }, - }, - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(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 := core.NewCycloneDX("dev") - got := marshaler.Marshal(ctx, tt.rootComponent) - assert.Equal(t, tt.want, got) - }) - } -} diff --git a/pkg/sbom/cyclonedx/marshal.go b/pkg/sbom/cyclonedx/marshal.go index 97bdb6092b42..be9fc23372b7 100644 --- a/pkg/sbom/cyclonedx/marshal.go +++ b/pkg/sbom/cyclonedx/marshal.go @@ -3,6 +3,8 @@ package cyclonedx import ( "context" "fmt" + "slices" + "sort" "strconv" "strings" @@ -11,424 +13,488 @@ import ( "github.com/samber/lo" "golang.org/x/xerrors" + dtypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability" + "github.com/aquasecurity/trivy/pkg/clock" "github.com/aquasecurity/trivy/pkg/digest" - ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" - "github.com/aquasecurity/trivy/pkg/purl" - "github.com/aquasecurity/trivy/pkg/sbom/cyclonedx/core" - "github.com/aquasecurity/trivy/pkg/scanner/utils" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/sbom/core" + sbomio "github.com/aquasecurity/trivy/pkg/sbom/io" "github.com/aquasecurity/trivy/pkg/types" + "github.com/aquasecurity/trivy/pkg/uuid" ) const ( - PropertySchemaVersion = "SchemaVersion" - PropertyType = "Type" - PropertyClass = "Class" - - // Image properties - PropertySize = "Size" - PropertyImageID = "ImageID" - PropertyRepoDigest = "RepoDigest" - PropertyDiffID = "DiffID" - PropertyRepoTag = "RepoTag" - - // Package properties - PropertyPkgID = "PkgID" - PropertyPkgType = "PkgType" - PropertySrcName = "SrcName" - PropertySrcVersion = "SrcVersion" - PropertySrcRelease = "SrcRelease" - PropertySrcEpoch = "SrcEpoch" - PropertyModularitylabel = "Modularitylabel" - PropertyFilePath = "FilePath" - PropertyLayerDigest = "LayerDigest" - PropertyLayerDiffID = "LayerDiffID" -) + ToolVendor = "aquasecurity" + ToolName = "trivy" + Namespace = ToolVendor + ":" + ToolName + ":" -var ( - ErrInvalidBOMLink = xerrors.New("invalid bomLink format error") + // https://json-schema.org/understanding-json-schema/reference/string.html#dates-and-times + timeLayout = "2006-01-02T15:04:05+00:00" ) type Marshaler struct { - core *core.CycloneDX + appVersion string // Trivy version + bom *core.BOM + componentIDs map[uuid.UUID]string } -func NewMarshaler(version string) *Marshaler { - return &Marshaler{ - core: core.NewCycloneDX(version), +func NewMarshaler(version string) Marshaler { + return Marshaler{ + appVersion: version, } } -// Marshal converts the Trivy report to the CycloneDX format -func (e *Marshaler) Marshal(ctx context.Context, report types.Report) (*cdx.BOM, error) { - // Convert - root, err := e.MarshalReport(report) +// MarshalReport converts the Trivy report to the CycloneDX format +func (m *Marshaler) MarshalReport(ctx context.Context, report types.Report) (*cdx.BOM, error) { + // Convert into an intermediate representation + bom, err := sbomio.NewEncoder().Encode(report) if err != nil { return nil, xerrors.Errorf("failed to marshal report: %w", err) } - return e.core.Marshal(ctx, root), nil + return m.Marshal(ctx, bom) } -func (e *Marshaler) MarshalReport(r types.Report) (*core.Component, error) { - // Metadata component - root, err := e.rootComponent(r) - if err != nil { - return nil, err +// Marshal converts the Trivy component to the CycloneDX format +func (m *Marshaler) Marshal(ctx context.Context, bom *core.BOM) (*cdx.BOM, error) { + m.bom = bom + m.componentIDs = make(map[uuid.UUID]string, m.bom.NumComponents()) + + cdxBOM := cdx.NewBOM() + cdxBOM.SerialNumber = uuid.New().URN() + cdxBOM.Metadata = m.Metadata(ctx) + + var err error + if cdxBOM.Metadata.Component, err = m.MarshalRoot(); err != nil { + return nil, xerrors.Errorf("failed to marshal component: %w", err) } - for _, result := range r.Results { - components, err := e.marshalResult(r.Metadata, result) - if err != nil { - return nil, err - } - root.Components = append(root.Components, components...) + if cdxBOM.Components, err = m.marshalComponents(); err != nil { + return nil, xerrors.Errorf("failed to marshal components: %w", err) } - return root, nil -} -func (e *Marshaler) marshalResult(metadata types.Metadata, result types.Result) ([]*core.Component, error) { - if result.Type == ftypes.NodePkg || result.Type == ftypes.PythonPkg || - result.Type == ftypes.GemSpec || result.Type == ftypes.Jar || result.Type == ftypes.CondaPkg { - // If a package is language-specific package that isn't associated with a lock file, - // it will be a dependency of a component under "metadata". - // e.g. - // Container component (alpine:3.15) ----------------------- #1 - // -> Library component (npm package, express-4.17.3) ---- #2 - // -> Library component (python package, django-4.0.2) --- #2 - // -> etc. - // ref. https://cyclonedx.org/use-cases/#inventory - - // Dependency graph from #1 to #2 - components, err := e.marshalPackages(metadata, result) - if err != nil { - return nil, err - } - return components, nil - } else if result.Class == types.ClassOSPkg || result.Class == types.ClassLangPkg { - // If a package is OS package, it will be a dependency of "Operating System" component. - // e.g. - // Container component (alpine:3.15) --------------------- #1 - // -> Operating System Component (Alpine Linux 3.15) --- #2 - // -> Library component (bash-4.12) ------------------ #3 - // -> Library component (vim-8.2) ------------------ #3 - // -> etc. - // - // Else if a package is language-specific package associated with a lock file, - // it will be a dependency of "Application" component. - // e.g. - // Container component (alpine:3.15) ------------------------ #1 - // -> Application component (/app/package-lock.json) ------ #2 - // -> Library component (npm package, express-4.17.3) --- #3 - // -> Library component (npm package, lodash-4.17.21) --- #3 - // -> etc. - - // #2 - appComponent := e.resultComponent(result, metadata.OS) - - // #3 - components, err := e.marshalPackages(metadata, result) - if err != nil { - return nil, err - } + cdxBOM.Dependencies = m.marshalDependencies() + cdxBOM.Vulnerabilities = m.marshalVulnerabilities() - // Dependency graph from #2 to #3 - appComponent.Components = components + return cdxBOM, nil +} - // Dependency graph from #1 to #2 - return []*core.Component{appComponent}, nil +func (m *Marshaler) Metadata(ctx context.Context) *cdx.Metadata { + return &cdx.Metadata{ + Timestamp: clock.Now(ctx).UTC().Format(timeLayout), + Tools: &cdx.ToolsChoice{ + Components: &[]cdx.Component{ + { + Type: cdx.ComponentTypeApplication, + Group: ToolVendor, + Name: ToolName, + Version: m.appVersion, + }, + }, + }, } - return nil, nil } -func (e *Marshaler) marshalPackages(metadata types.Metadata, result types.Result) ([]*core.Component, error) { - // Get dependency parents first - parents := ftypes.Packages(result.Packages).ParentDeps() +func (m *Marshaler) MarshalRoot() (*cdx.Component, error) { + return m.MarshalComponent(m.bom.Root()) +} + +func (m *Marshaler) MarshalComponent(component *core.Component) (*cdx.Component, error) { + componentType, err := m.componentType(component.Type) + if err != nil { + return nil, xerrors.Errorf("failed to get cdx component type: %w", err) + } - // Group vulnerabilities by package ID - vulns := lo.GroupBy(result.Vulnerabilities, func(v types.DetectedVulnerability) string { - return lo.Ternary(v.PkgID == "", fmt.Sprintf("%s@%s", v.PkgName, v.InstalledVersion), v.PkgID) - }) + cdxComponent := &cdx.Component{ + BOMRef: component.PkgID.BOMRef, + Type: componentType, + Name: component.Name, + Group: component.Group, + Version: component.Version, + PackageURL: m.PackageURL(component.PkgID.PURL), + Supplier: m.Supplier(component.Supplier), + Hashes: m.Hashes(component.Files), + Licenses: m.Licenses(component.Licenses), + Properties: m.Properties(component.Properties), + } + m.componentIDs[component.ID()] = cdxComponent.BOMRef + + return cdxComponent, nil +} - // Create package map - pkgs := lo.SliceToMap(result.Packages, func(pkg ftypes.Package) (string, Package) { - pkgID := lo.Ternary(pkg.ID == "", fmt.Sprintf("%s@%s", pkg.Name, utils.FormatVersion(pkg)), pkg.ID) - return pkgID, Package{ - Type: result.Type, - Metadata: metadata, - Package: pkg, - Vulnerabilities: vulns[pkgID], +func (m *Marshaler) marshalComponents() (*[]cdx.Component, error) { + var cdxComponents []cdx.Component + for _, component := range m.bom.Components() { + if component.Root { + continue } + c, err := m.MarshalComponent(component) + if err != nil { + return nil, xerrors.Errorf("failed to marshal component: %w", err) + } + cdxComponents = append(cdxComponents, *c) + } + + // CycloneDX requires an empty slice rather than a nil slice + if len(cdxComponents) == 0 { + return &[]cdx.Component{}, nil + } + + // Sort components by BOM-Ref + sort.Slice(cdxComponents, func(i, j int) bool { + return cdxComponents[i].BOMRef < cdxComponents[j].BOMRef }) + return &cdxComponents, nil +} - var directComponents []*core.Component - for _, pkg := range pkgs { - // Skip indirect dependencies - if pkg.Indirect && len(parents[pkg.ID]) != 0 { +func (m *Marshaler) marshalDependencies() *[]cdx.Dependency { + var dependencies []cdx.Dependency + for key, rels := range m.bom.Relationships() { + ref, ok := m.componentIDs[key] + if !ok { continue } - // Recursive packages from direct dependencies - if component, err := e.marshalPackage(pkg, pkgs, make(map[string]*core.Component)); err != nil { - return nil, nil - } else if component != nil { - directComponents = append(directComponents, component) + deps := lo.FilterMap(rels, func(rel core.Relationship, _ int) (string, bool) { + d, ok := m.componentIDs[rel.Dependency] + return d, ok + }) + sort.Strings(deps) + + dependencies = append(dependencies, cdx.Dependency{ + Ref: ref, + Dependencies: &deps, + }) + } + + // Sort dependencies by BOM-Ref + sort.Slice(dependencies, func(i, j int) bool { + return dependencies[i].Ref < dependencies[j].Ref + }) + return &dependencies +} + +func (m *Marshaler) marshalVulnerabilities() *[]cdx.Vulnerability { + vulns := make(map[string]*cdx.Vulnerability) + for id, vv := range m.bom.Vulnerabilities() { + bomRef := m.componentIDs[id] + for _, v := range vv { + // If the same vulnerability affects multiple packages, those packages will be aggregated into one vulnerability. + // Vulnerability component (CVE-2020-26247) + // -> Library component (nokogiri /srv/app1/vendor/bundle/ruby/3.0.0/specifications/nokogiri-1.10.0.gemspec) + // -> Library component (nokogiri /srv/app2/vendor/bundle/ruby/3.0.0/specifications/nokogiri-1.10.0.gemspec) + if vuln, ok := vulns[v.ID]; ok { + *vuln.Affects = append(*vuln.Affects, m.affects(bomRef, v.InstalledVersion)) + if v.FixedVersion != "" { + // new recommendation + rec := fmt.Sprintf("Upgrade %s to version %s", v.PkgName, v.FixedVersion) + // previous recommendations + recs := strings.Split(vuln.Recommendation, "; ") + if !slices.Contains(recs, rec) { + recs = append(recs, rec) + slices.Sort(recs) + vuln.Recommendation = strings.Join(recs, "; ") + } + } + } else { + vulns[v.ID] = m.marshalVulnerability(bomRef, v) + } } } - return directComponents, nil + vulnList := lo.MapToSlice(vulns, func(_ string, value *cdx.Vulnerability) cdx.Vulnerability { + sort.Slice(*value.Affects, func(i, j int) bool { + return (*value.Affects)[i].Ref < (*value.Affects)[j].Ref + }) + return *value + }) + sort.Slice(vulnList, func(i, j int) bool { + return vulnList[i].ID < vulnList[j].ID + }) + return &vulnList } -type Package struct { - ftypes.Package - Type ftypes.TargetType - Metadata types.Metadata - Vulnerabilities []types.DetectedVulnerability +// componentType converts the Trivy component type to the CycloneDX component type +func (*Marshaler) componentType(t core.ComponentType) (cdx.ComponentType, error) { + switch t { + case core.TypeContainer: + return cdx.ComponentTypeContainer, nil + case core.TypeApplication: + return cdx.ComponentTypeApplication, nil + case core.TypeLibrary: + return cdx.ComponentTypeLibrary, nil + case core.TypeOS: + return cdx.ComponentTypeOS, nil + case core.TypePlatform: + return cdx.ComponentTypePlatform, nil + } + return "", xerrors.Errorf("unknown component type: %s", t) } -func (e *Marshaler) marshalPackage(pkg Package, pkgs map[string]Package, components map[string]*core.Component, -) (*core.Component, error) { - if c, ok := components[pkg.ID]; ok { - return c, nil +func (*Marshaler) PackageURL(p *packageurl.PackageURL) string { + if p == nil { + return "" } + return p.String() +} - component, err := pkgComponent(pkg) - if err != nil { - return nil, xerrors.Errorf("failed to parse pkg: %w", err) +func (*Marshaler) Supplier(supplier string) *cdx.OrganizationalEntity { + if supplier == "" { + return nil } + return &cdx.OrganizationalEntity{ + Name: supplier, + } +} - // Skip component that can't be converted from `Package` - if component == nil { - return nil, nil +func (*Marshaler) Hashes(files []core.File) *[]cdx.Hash { + hashes := lo.FilterMap(files, func(f core.File, index int) (digest.Digest, bool) { + return f.Hash, f.Hash != "" + }) + if len(hashes) == 0 { + return nil } - components[pkg.ID] = component - // Iterate dependencies - for _, dep := range pkg.DependsOn { - childPkg, ok := pkgs[dep] - if !ok { + var cdxHashes []cdx.Hash + for _, h := range hashes { + var alg cdx.HashAlgorithm + switch h.Algorithm() { + case digest.SHA1: + alg = cdx.HashAlgoSHA1 + case digest.SHA256: + alg = cdx.HashAlgoSHA256 + case digest.MD5: + alg = cdx.HashAlgoMD5 + default: + log.Logger.Debugf("Unable to convert %q algorithm to CycloneDX format", h.Algorithm()) continue } - child, err := e.marshalPackage(childPkg, pkgs, components) - if err != nil { - return nil, xerrors.Errorf("failed to parse pkg: %w", err) - } - component.Components = append(component.Components, child) + cdxHashes = append(cdxHashes, cdx.Hash{ + Algorithm: alg, + Value: h.Encoded(), + }) } - return component, nil + return &cdxHashes } -func (e *Marshaler) rootComponent(r types.Report) (*core.Component, error) { - root := &core.Component{ - Name: r.ArtifactName, +func (*Marshaler) Licenses(licenses []string) *cdx.Licenses { + if len(licenses) == 0 { + return nil } + choices := lo.Map(licenses, func(license string, i int) cdx.LicenseChoice { + return cdx.LicenseChoice{ + License: &cdx.License{ + Name: license, + }, + } + }) + return lo.ToPtr(cdx.Licenses(choices)) +} - props := []core.Property{ - { - Name: PropertySchemaVersion, - Value: strconv.Itoa(r.SchemaVersion), - }, - } +func (*Marshaler) Properties(properties []core.Property) *[]cdx.Property { + cdxProps := make([]cdx.Property, 0, len(properties)) + for _, property := range properties { + namespace := Namespace + if len(property.Namespace) > 0 { + namespace = property.Namespace + } - switch r.ArtifactType { - case ftypes.ArtifactContainerImage: - root.Type = cdx.ComponentTypeContainer - props = append(props, core.Property{ - Name: PropertyImageID, - Value: r.Metadata.ImageID, + cdxProps = append(cdxProps, cdx.Property{ + Name: namespace + property.Name, + Value: property.Value, }) - - p, err := purl.New(purl.TypeOCI, r.Metadata, ftypes.Package{}) - if err != nil { - return nil, xerrors.Errorf("failed to new package url for oci: %w", err) - } - if p != nil { - root.PackageURL = p + } + sort.Slice(cdxProps, func(i, j int) bool { + if cdxProps[i].Name != cdxProps[j].Name { + return cdxProps[i].Name < cdxProps[j].Name } + return cdxProps[i].Value < cdxProps[j].Value + }) + return &cdxProps +} - case ftypes.ArtifactVM: - root.Type = cdx.ComponentTypeContainer - case ftypes.ArtifactFilesystem, ftypes.ArtifactRepository: - root.Type = cdx.ComponentTypeApplication - case ftypes.ArtifactCycloneDX: - return toCoreComponent(r.CycloneDX.Metadata.Component) +func (*Marshaler) affects(ref, version string) cdx.Affects { + return cdx.Affects{ + Ref: ref, + Range: &[]cdx.AffectedVersions{ + { + Version: version, + Status: cdx.VulnerabilityStatusAffected, + // "AffectedVersions.Range" is not included, because it does not exist in DetectedVulnerability. + }, + }, } +} - if r.Metadata.Size != 0 { - props = append(props, core.Property{ - Name: PropertySize, - Value: strconv.FormatInt(r.Metadata.Size, 10), - }) +func (*Marshaler) advisories(refs []string) *[]cdx.Advisory { + refs = lo.Uniq(refs) + advs := lo.FilterMap(refs, func(ref string, _ int) (cdx.Advisory, bool) { + return cdx.Advisory{URL: ref}, ref != "" + }) + + // cyclonedx converts link to empty `[]cdx.Advisory` to `null` + // `bom-1.5.schema.json` doesn't support this - `Invalid type. Expected: array, given: null` + // we need to explicitly set `nil` for empty `refs` slice + if len(advs) == 0 { + return nil } - if len(r.Metadata.RepoDigests) > 0 { - props = append(props, core.Property{ - Name: PropertyRepoDigest, - Value: strings.Join(r.Metadata.RepoDigests, ","), - }) + return &advs +} + +func (m *Marshaler) marshalVulnerability(bomRef string, vuln core.Vulnerability) *cdx.Vulnerability { + v := &cdx.Vulnerability{ + ID: vuln.ID, + Source: m.source(vuln.DataSource), + Ratings: m.ratings(vuln), + CWEs: m.cwes(vuln.CweIDs), + Description: vuln.Description, + Advisories: m.advisories(append([]string{vuln.PrimaryURL}, vuln.References...)), } - if len(r.Metadata.DiffIDs) > 0 { - props = append(props, core.Property{ - Name: PropertyDiffID, - Value: strings.Join(r.Metadata.DiffIDs, ","), - }) + if vuln.FixedVersion != "" { + v.Recommendation = fmt.Sprintf("Upgrade %s to version %s", vuln.PkgName, vuln.FixedVersion) } - if len(r.Metadata.RepoTags) > 0 { - props = append(props, core.Property{ - Name: PropertyRepoTag, - Value: strings.Join(r.Metadata.RepoTags, ","), - }) + if vuln.PublishedDate != nil { + v.Published = vuln.PublishedDate.Format(timeLayout) + } + if vuln.LastModifiedDate != nil { + v.Updated = vuln.LastModifiedDate.Format(timeLayout) } - root.Properties = filterProperties(props) + v.Affects = &[]cdx.Affects{m.affects(bomRef, vuln.InstalledVersion)} - return root, nil + return v } -func (e *Marshaler) resultComponent(r types.Result, osFound *ftypes.OS) *core.Component { - component := &core.Component{ - Name: r.Target, - Properties: []core.Property{ - { - Name: PropertyType, - Value: string(r.Type), - }, - { - Name: PropertyClass, - Value: string(r.Class), - }, - }, +func (*Marshaler) source(source *dtypes.DataSource) *cdx.Source { + if source == nil { + return nil } - switch r.Class { - case types.ClassOSPkg: - // UUID needs to be generated since Operating System Component cannot generate PURL. - // https://cyclonedx.org/use-cases/#known-vulnerabilities - if osFound != nil { - component.Name = string(osFound.Family) - component.Version = osFound.Name - } - component.Type = cdx.ComponentTypeOS - case types.ClassLangPkg: - // UUID needs to be generated since Application Component cannot generate PURL. - // https://cyclonedx.org/use-cases/#known-vulnerabilities - component.Type = cdx.ComponentTypeApplication + return &cdx.Source{ + Name: string(source.ID), + URL: source.URL, } - - return component } -func pkgComponent(pkg Package) (*core.Component, error) { - name := pkg.Name - version := pkg.Version - var group string - // there are cases when we can't build purl - // e.g. local Go packages - if pu := pkg.Identifier.PURL; pu != nil { - version = pu.Version - // Use `group` field for GroupID and `name` for ArtifactID for java files - // https://github.com/aquasecurity/trivy/issues/4675 - // Use `group` field for npm scopes - // https://github.com/aquasecurity/trivy/issues/5908 - if pu.Type == packageurl.TypeMaven || pu.Type == packageurl.TypeNPM { - name = pu.Name - group = pu.Namespace +func (*Marshaler) cwes(cweIDs []string) *[]int { + // to skip cdx.Vulnerability.CWEs when generating json + // we should return 'clear' nil without 'type' interface part + if cweIDs == nil { + return nil + } + var ret []int + for _, cweID := range cweIDs { + number, err := strconv.Atoi(strings.TrimPrefix(strings.ToLower(cweID), "cwe-")) + if err != nil { + log.Logger.Debugf("cwe id parse error: %s", err) + continue } + ret = append(ret, number) } + return &ret +} - properties := []core.Property{ - { - Name: PropertyPkgID, - Value: pkg.ID, - }, - { - Name: PropertyPkgType, - Value: string(pkg.Type), - }, - { - Name: PropertyFilePath, - Value: pkg.FilePath, - }, - { - Name: PropertySrcName, - Value: pkg.SrcName, - }, - { - Name: PropertySrcVersion, - Value: pkg.SrcVersion, - }, - { - Name: PropertySrcRelease, - Value: pkg.SrcRelease, - }, - { - Name: PropertySrcEpoch, - Value: strconv.Itoa(pkg.SrcEpoch), - }, - { - Name: PropertyModularitylabel, - Value: pkg.Modularitylabel, - }, - { - Name: PropertyLayerDigest, - Value: pkg.Layer.Digest, - }, - { - Name: PropertyLayerDiffID, - Value: pkg.Layer.DiffID, - }, +func (m *Marshaler) ratings(vuln core.Vulnerability) *[]cdx.VulnerabilityRating { + rates := make([]cdx.VulnerabilityRating, 0) // nolint:gocritic // To export an empty array in JSON + for sourceID, severity := range vuln.VendorSeverity { + // When the vendor also provides CVSS score/vector + if cvss, ok := vuln.CVSS[sourceID]; ok { + if cvss.V2Score != 0 || cvss.V2Vector != "" { + rates = append(rates, m.ratingV2(sourceID, severity, cvss)) + } + if cvss.V3Score != 0 || cvss.V3Vector != "" { + rates = append(rates, m.ratingV3(sourceID, severity, cvss)) + } + } else { // When the vendor provides only severity + rate := cdx.VulnerabilityRating{ + Source: &cdx.Source{ + Name: string(sourceID), + }, + Severity: m.severity(severity), + } + rates = append(rates, rate) + } } - return &core.Component{ - Type: cdx.ComponentTypeLibrary, - Name: name, - Group: group, - Version: version, - PackageURL: purl.WithPath(pkg.Identifier.PURL, pkg.FilePath), - Supplier: pkg.Maintainer, - Licenses: pkg.Licenses, - Hashes: lo.Ternary(pkg.Digest == "", nil, []digest.Digest{pkg.Digest}), - Properties: filterProperties(properties), - Vulnerabilities: pkg.Vulnerabilities, - }, nil + // For consistency + sort.Slice(rates, func(i, j int) bool { + if rates[i].Source.Name != rates[j].Source.Name { + return rates[i].Source.Name < rates[j].Source.Name + } + if rates[i].Method != rates[j].Method { + return rates[i].Method < rates[j].Method + } + if rates[i].Score != nil && rates[j].Score != nil { + return *rates[i].Score < *rates[j].Score + } + return rates[i].Vector < rates[j].Vector + }) + return &rates } -func filterProperties(props []core.Property) []core.Property { - return lo.Filter(props, func(property core.Property, index int) bool { - return !(property.Value == "" || (property.Name == PropertySrcEpoch && property.Value == "0")) - }) +func (m *Marshaler) ratingV2(sourceID dtypes.SourceID, severity dtypes.Severity, cvss dtypes.CVSS) cdx.VulnerabilityRating { + cdxSeverity := m.severity(severity) + + // Trivy keeps only CVSSv3 severity for NVD. + // The CVSSv2 severity must be calculated according to CVSSv2 score. + if sourceID == vulnerability.NVD { + cdxSeverity = m.nvdSeverityV2(cvss.V2Score) + } + return cdx.VulnerabilityRating{ + Source: &cdx.Source{ + Name: string(sourceID), + }, + Score: &cvss.V2Score, + Method: cdx.ScoringMethodCVSSv2, + Severity: cdxSeverity, + Vector: cvss.V2Vector, + } } -func toCoreComponent(c ftypes.Component) (*core.Component, error) { - var props []core.Property - for _, prop := range c.Properties { - var namespace string - name := prop.Name - // Separate the Trivy namespace to avoid double spelling of namespaces. - if strings.HasPrefix(name, core.Namespace) { - name = strings.TrimPrefix(name, core.Namespace) - namespace = core.Namespace - } - props = append(props, core.Property{ - Namespace: namespace, - Name: name, - Value: prop.Value, - }) +func (m *Marshaler) ratingV3(sourceID dtypes.SourceID, severity dtypes.Severity, cvss dtypes.CVSS) cdx.VulnerabilityRating { + rate := cdx.VulnerabilityRating{ + Source: &cdx.Source{ + Name: string(sourceID), + }, + Score: &cvss.V3Score, + Method: cdx.ScoringMethodCVSSv3, + Severity: m.severity(severity), + Vector: cvss.V3Vector, } - var p *purl.PackageURL - if c.PackageURL != "" { - var err error - p, err = purl.FromString(c.PackageURL) - if err != nil { - return nil, xerrors.Errorf("failed to parse purl: %w", err) - } + if strings.HasPrefix(cvss.V3Vector, "CVSS:3.1") { + rate.Method = cdx.ScoringMethodCVSSv31 } + return rate +} - return &core.Component{ - Name: c.Name, - Group: c.Group, - PackageURL: p, - Type: cdx.ComponentType(c.Type), - Properties: props, - }, nil +// severity converts the Trivy severity to the CycloneDX severity +func (*Marshaler) severity(s dtypes.Severity) cdx.Severity { + switch s { + case dtypes.SeverityLow: + return cdx.SeverityLow + case dtypes.SeverityMedium: + return cdx.SeverityMedium + case dtypes.SeverityHigh: + return cdx.SeverityHigh + case dtypes.SeverityCritical: + return cdx.SeverityCritical + default: + return cdx.SeverityUnknown + } +} + +func (*Marshaler) nvdSeverityV2(score float64) cdx.Severity { + // cf. https://nvd.nist.gov/vuln-metrics/cvss + switch { + case score < 4.0: + return cdx.SeverityInfo + case 4.0 <= score && score < 7.0: + return cdx.SeverityMedium + case 7.0 <= score: + return cdx.SeverityHigh + } + return cdx.SeverityUnknown } diff --git a/pkg/sbom/cyclonedx/marshal_test.go b/pkg/sbom/cyclonedx/marshal_test.go index 1819daa3a92b..7999ea2eae70 100644 --- a/pkg/sbom/cyclonedx/marshal_test.go +++ b/pkg/sbom/cyclonedx/marshal_test.go @@ -2,6 +2,7 @@ package cyclonedx_test import ( "context" + "github.com/aquasecurity/trivy/pkg/sbom/core" "github.com/package-url/packageurl-go" "testing" "time" @@ -22,7 +23,23 @@ import ( "github.com/aquasecurity/trivy/pkg/uuid" ) -func TestMarshaler_Marshal(t *testing.T) { +func TestMarshaler_MarshalReport(t *testing.T) { + testSBOM := core.NewBOM() + testSBOM.AddComponent(&core.Component{ + Root: true, + Type: core.TypeApplication, + Name: "jackson-databind-2.13.4.1.jar", + PkgID: core.PkgID{ + BOMRef: "aff65b54-6009-4c32-968d-748949ef46e8", + }, + Properties: []core.Property{ + { + Name: "SchemaVersion", + Value: "2", + }, + }, + }) + tests := []struct { name string inputReport types.Report @@ -139,6 +156,7 @@ func TestMarshaler_Marshal(t *testing.T) { Type: ftypes.Bundler, Packages: []ftypes.Package{ { + // This package conflicts ID: "actionpack@7.0.0", Name: "actionpack", Version: "7.0.0", @@ -175,6 +193,7 @@ func TestMarshaler_Marshal(t *testing.T) { Type: ftypes.Bundler, Packages: []ftypes.Package{ { + // This package conflicts ID: "actionpack@7.0.0", Name: "actionpack", Version: "7.0.0", @@ -238,7 +257,7 @@ func TestMarshaler_Marshal(t *testing.T) { BOMFormat: "CycloneDX", SpecVersion: cdx.SpecVersion1_5, JSONSchema: "http://cyclonedx.org/schema/bom-1.5.schema.json", - SerialNumber: "urn:uuid:3ff14136-e09f-4df9-80ea-000000000001", + SerialNumber: "urn:uuid:3ff14136-e09f-4df9-80ea-000000000014", Version: 1, Metadata: &cdx.Metadata{ Timestamp: "2021-08-25T12:20:30+00:00", @@ -303,7 +322,7 @@ func TestMarshaler_Marshal(t *testing.T) { }, }, { - BOMRef: "3ff14136-e09f-4df9-80ea-000000000003", + BOMRef: "3ff14136-e09f-4df9-80ea-000000000004", Type: cdx.ComponentTypeApplication, Name: "app/subproject/Gemfile.lock", Version: "", @@ -319,7 +338,24 @@ func TestMarshaler_Marshal(t *testing.T) { }, }, { - BOMRef: "3ff14136-e09f-4df9-80ea-000000000004", + BOMRef: "3ff14136-e09f-4df9-80ea-000000000005", + Type: cdx.ComponentTypeLibrary, + Name: "actionpack", + Version: "7.0.0", + PackageURL: "pkg:gem/actionpack@7.0.0", + Properties: &[]cdx.Property{ + { + Name: "aquasecurity:trivy:PkgID", + Value: "actionpack@7.0.0", + }, + { + Name: "aquasecurity:trivy:PkgType", + Value: "bundler", + }, + }, + }, + { + BOMRef: "3ff14136-e09f-4df9-80ea-000000000007", Type: cdx.ComponentTypeApplication, Name: "app/Gemfile.lock", Version: "", @@ -335,7 +371,24 @@ func TestMarshaler_Marshal(t *testing.T) { }, }, { - BOMRef: "3ff14136-e09f-4df9-80ea-000000000005", + BOMRef: "3ff14136-e09f-4df9-80ea-000000000008", + Type: cdx.ComponentTypeLibrary, + Name: "actionpack", + Version: "7.0.0", + PackageURL: "pkg:gem/actionpack@7.0.0", + Properties: &[]cdx.Property{ + { + Name: "aquasecurity:trivy:PkgID", + Value: "actionpack@7.0.0", + }, + { + Name: "aquasecurity:trivy:PkgType", + Value: "bundler", + }, + }, + }, + { + BOMRef: "3ff14136-e09f-4df9-80ea-000000000009", Type: cdx.ComponentTypeApplication, Name: "app/datacollector.deps.json", Version: "", @@ -351,10 +404,9 @@ func TestMarshaler_Marshal(t *testing.T) { }, }, { - BOMRef: "3ff14136-e09f-4df9-80ea-000000000006", - Type: cdx.ComponentTypeApplication, - Name: "usr/local/bin/tfsec", - Version: "", + BOMRef: "3ff14136-e09f-4df9-80ea-000000000011", + Type: cdx.ComponentTypeApplication, + Name: "usr/local/bin/tfsec", Properties: &[]cdx.Property{ { Name: "aquasecurity:trivy:Class", @@ -368,7 +420,7 @@ func TestMarshaler_Marshal(t *testing.T) { }, { // Use UUID for local Go packages - BOMRef: "3ff14136-e09f-4df9-80ea-000000000007", + BOMRef: "3ff14136-e09f-4df9-80ea-000000000013", Type: cdx.ComponentTypeLibrary, Name: "./api", Version: "(devel)", @@ -396,23 +448,6 @@ func TestMarshaler_Marshal(t *testing.T) { }, }, }, - { - BOMRef: "pkg:gem/actionpack@7.0.0", - Type: cdx.ComponentTypeLibrary, - Name: "actionpack", - Version: "7.0.0", - PackageURL: "pkg:gem/actionpack@7.0.0", - Properties: &[]cdx.Property{ - { - Name: "aquasecurity:trivy:PkgID", - Value: "actionpack@7.0.0", - }, - { - Name: "aquasecurity:trivy:PkgType", - Value: "bundler", - }, - }, - }, { BOMRef: "pkg:golang/golang.org/x/crypto@v0.0.0-20210421170649-83a5a9bb288b", Type: cdx.ComponentTypeLibrary, @@ -497,45 +532,49 @@ func TestMarshaler_Marshal(t *testing.T) { }, }, { - Ref: "3ff14136-e09f-4df9-80ea-000000000003", + Ref: "3ff14136-e09f-4df9-80ea-000000000004", Dependencies: &[]string{ + "3ff14136-e09f-4df9-80ea-000000000005", "pkg:gem/actioncontroller@7.0.0", - "pkg:gem/actionpack@7.0.0", }, }, { - Ref: "3ff14136-e09f-4df9-80ea-000000000004", + Ref: "3ff14136-e09f-4df9-80ea-000000000005", + Dependencies: &[]string{}, + }, + { + Ref: "3ff14136-e09f-4df9-80ea-000000000007", Dependencies: &[]string{ - "pkg:gem/actionpack@7.0.0", + "3ff14136-e09f-4df9-80ea-000000000008", }, }, { - Ref: "3ff14136-e09f-4df9-80ea-000000000005", + Ref: "3ff14136-e09f-4df9-80ea-000000000008", + Dependencies: &[]string{}, + }, + { + Ref: "3ff14136-e09f-4df9-80ea-000000000009", Dependencies: &[]string{ "pkg:nuget/Newtonsoft.Json@9.0.1", }, }, { - Ref: "3ff14136-e09f-4df9-80ea-000000000006", + Ref: "3ff14136-e09f-4df9-80ea-000000000011", Dependencies: &[]string{ - "3ff14136-e09f-4df9-80ea-000000000007", + "3ff14136-e09f-4df9-80ea-000000000013", "pkg:golang/golang.org/x/crypto@v0.0.0-20210421170649-83a5a9bb288b", }, }, { - Ref: "3ff14136-e09f-4df9-80ea-000000000007", + Ref: "3ff14136-e09f-4df9-80ea-000000000013", Dependencies: lo.ToPtr([]string{}), }, { Ref: "pkg:gem/actioncontroller@7.0.0", Dependencies: &[]string{ - "pkg:gem/actionpack@7.0.0", + "3ff14136-e09f-4df9-80ea-000000000005", }, }, - { - Ref: "pkg:gem/actionpack@7.0.0", - Dependencies: lo.ToPtr([]string{}), - }, { Ref: "pkg:golang/golang.org/x/crypto@v0.0.0-20210421170649-83a5a9bb288b", Dependencies: lo.ToPtr([]string{}), @@ -548,10 +587,10 @@ func TestMarshaler_Marshal(t *testing.T) { Ref: "pkg:oci/rails@sha256%3Aa27fd8080b517143cbbbab9dfb7c8571c40d67d534bbdee55bd6c473f432b177?arch=arm64&repository_url=index.docker.io%2Flibrary%2Frails", Dependencies: &[]string{ "3ff14136-e09f-4df9-80ea-000000000002", - "3ff14136-e09f-4df9-80ea-000000000003", "3ff14136-e09f-4df9-80ea-000000000004", - "3ff14136-e09f-4df9-80ea-000000000005", - "3ff14136-e09f-4df9-80ea-000000000006", + "3ff14136-e09f-4df9-80ea-000000000007", + "3ff14136-e09f-4df9-80ea-000000000009", + "3ff14136-e09f-4df9-80ea-000000000011", }, }, { @@ -873,7 +912,7 @@ func TestMarshaler_Marshal(t *testing.T) { BOMFormat: "CycloneDX", SpecVersion: cdx.SpecVersion1_5, JSONSchema: "http://cyclonedx.org/schema/bom-1.5.schema.json", - SerialNumber: "urn:uuid:3ff14136-e09f-4df9-80ea-000000000001", + SerialNumber: "urn:uuid:3ff14136-e09f-4df9-80ea-000000000007", Version: 1, Metadata: &cdx.Metadata{ Timestamp: "2021-08-25T12:20:30+00:00", @@ -889,7 +928,7 @@ func TestMarshaler_Marshal(t *testing.T) { }, Component: &cdx.Component{ Type: cdx.ComponentTypeContainer, - BOMRef: "3ff14136-e09f-4df9-80ea-000000000002", + BOMRef: "3ff14136-e09f-4df9-80ea-000000000001", PackageURL: "", Name: "centos:latest", Properties: &[]cdx.Property{ @@ -914,7 +953,7 @@ func TestMarshaler_Marshal(t *testing.T) { }, Components: &[]cdx.Component{ { - BOMRef: "3ff14136-e09f-4df9-80ea-000000000003", + BOMRef: "3ff14136-e09f-4df9-80ea-000000000002", Type: cdx.ComponentTypeOS, Name: string(ftypes.CentOS), Version: "8.3.2011", @@ -930,7 +969,7 @@ func TestMarshaler_Marshal(t *testing.T) { }, }, { - BOMRef: "pkg:gem/actionpack@7.0.0?file_path=tools%2Fproject-john%2Fspecifications%2Factionpack.gemspec", + BOMRef: "pkg:gem/actionpack@7.0.0", Type: cdx.ComponentTypeLibrary, Name: "actionpack", Version: "7.0.0", @@ -955,7 +994,7 @@ func TestMarshaler_Marshal(t *testing.T) { }, }, { - BOMRef: "pkg:gem/actionpack@7.0.1?file_path=tools%2Fproject-doe%2Fspecifications%2Factionpack.gemspec", + BOMRef: "pkg:gem/actionpack@7.0.1", Type: cdx.ComponentTypeLibrary, Name: "actionpack", Version: "7.0.1", @@ -1070,15 +1109,15 @@ func TestMarshaler_Marshal(t *testing.T) { }, Dependencies: &[]cdx.Dependency{ { - Ref: "3ff14136-e09f-4df9-80ea-000000000002", + Ref: "3ff14136-e09f-4df9-80ea-000000000001", Dependencies: &[]string{ - "3ff14136-e09f-4df9-80ea-000000000003", - "pkg:gem/actionpack@7.0.0?file_path=tools%2Fproject-john%2Fspecifications%2Factionpack.gemspec", - "pkg:gem/actionpack@7.0.1?file_path=tools%2Fproject-doe%2Fspecifications%2Factionpack.gemspec", + "3ff14136-e09f-4df9-80ea-000000000002", + "pkg:gem/actionpack@7.0.0", + "pkg:gem/actionpack@7.0.1", }, }, { - Ref: "3ff14136-e09f-4df9-80ea-000000000003", + Ref: "3ff14136-e09f-4df9-80ea-000000000002", Dependencies: &[]string{ "pkg:rpm/centos/acl@2.2.53-1.el8?arch=aarch64&distro=centos-8.3.2011&epoch=1", // Trivy is unable to identify the direct OS packages as of today. @@ -1086,11 +1125,11 @@ func TestMarshaler_Marshal(t *testing.T) { }, }, { - Ref: "pkg:gem/actionpack@7.0.0?file_path=tools%2Fproject-john%2Fspecifications%2Factionpack.gemspec", + Ref: "pkg:gem/actionpack@7.0.0", Dependencies: lo.ToPtr([]string{}), }, { - Ref: "pkg:gem/actionpack@7.0.1?file_path=tools%2Fproject-doe%2Fspecifications%2Factionpack.gemspec", + Ref: "pkg:gem/actionpack@7.0.1", Dependencies: lo.ToPtr([]string{}), }, { @@ -1163,7 +1202,7 @@ func TestMarshaler_Marshal(t *testing.T) { Updated: "2022-02-22T21:47:00+00:00", Affects: &[]cdx.Affects{ { - Ref: "pkg:gem/actionpack@7.0.0?file_path=tools%2Fproject-john%2Fspecifications%2Factionpack.gemspec", + Ref: "pkg:gem/actionpack@7.0.0", Range: &[]cdx.AffectedVersions{ { Version: "7.0.0", @@ -1172,7 +1211,7 @@ func TestMarshaler_Marshal(t *testing.T) { }, }, { - Ref: "pkg:gem/actionpack@7.0.1?file_path=tools%2Fproject-doe%2Fspecifications%2Factionpack.gemspec", + Ref: "pkg:gem/actionpack@7.0.1", Range: &[]cdx.AffectedVersions{ { Version: "7.0.1", @@ -1257,7 +1296,7 @@ func TestMarshaler_Marshal(t *testing.T) { BOMFormat: "CycloneDX", SpecVersion: cdx.SpecVersion1_5, JSONSchema: "http://cyclonedx.org/schema/bom-1.5.schema.json", - SerialNumber: "urn:uuid:3ff14136-e09f-4df9-80ea-000000000001", + SerialNumber: "urn:uuid:3ff14136-e09f-4df9-80ea-000000000007", Version: 1, Metadata: &cdx.Metadata{ Timestamp: "2021-08-25T12:20:30+00:00", @@ -1272,7 +1311,7 @@ func TestMarshaler_Marshal(t *testing.T) { }, }, Component: &cdx.Component{ - BOMRef: "3ff14136-e09f-4df9-80ea-000000000002", + BOMRef: "3ff14136-e09f-4df9-80ea-000000000001", Type: cdx.ComponentTypeApplication, Name: "masahiro331/CVE-2021-41098", Properties: &[]cdx.Property{ @@ -1285,7 +1324,7 @@ func TestMarshaler_Marshal(t *testing.T) { }, Components: &[]cdx.Component{ { - BOMRef: "3ff14136-e09f-4df9-80ea-000000000003", + BOMRef: "3ff14136-e09f-4df9-80ea-000000000002", Type: cdx.ComponentTypeApplication, Name: "Gemfile.lock", Properties: &[]cdx.Property{ @@ -1300,7 +1339,7 @@ func TestMarshaler_Marshal(t *testing.T) { }, }, { - BOMRef: "3ff14136-e09f-4df9-80ea-000000000004", + BOMRef: "3ff14136-e09f-4df9-80ea-000000000005", Type: cdx.ComponentTypeApplication, Name: "yarn.lock", Properties: &[]cdx.Property{ @@ -1328,7 +1367,7 @@ func TestMarshaler_Marshal(t *testing.T) { }, }, { - BOMRef: "pkg:maven/org.springframework/spring-web@5.3.22?file_path=spring-web-5.3.22.jar", + BOMRef: "pkg:maven/org.springframework/spring-web@5.3.22", Type: "library", Name: "spring-web", Group: "org.springframework", @@ -1367,21 +1406,21 @@ func TestMarshaler_Marshal(t *testing.T) { Vulnerabilities: &[]cdx.Vulnerability{}, Dependencies: &[]cdx.Dependency{ { - Ref: "3ff14136-e09f-4df9-80ea-000000000002", + Ref: "3ff14136-e09f-4df9-80ea-000000000001", Dependencies: &[]string{ - "3ff14136-e09f-4df9-80ea-000000000003", - "3ff14136-e09f-4df9-80ea-000000000004", - "pkg:maven/org.springframework/spring-web@5.3.22?file_path=spring-web-5.3.22.jar", + "3ff14136-e09f-4df9-80ea-000000000002", + "3ff14136-e09f-4df9-80ea-000000000005", + "pkg:maven/org.springframework/spring-web@5.3.22", }, }, { - Ref: "3ff14136-e09f-4df9-80ea-000000000003", + Ref: "3ff14136-e09f-4df9-80ea-000000000002", Dependencies: &[]string{ "pkg:gem/actioncable@6.1.4.1", }, }, { - Ref: "3ff14136-e09f-4df9-80ea-000000000004", + Ref: "3ff14136-e09f-4df9-80ea-000000000005", Dependencies: &[]string{ "pkg:npm/%40babel/helper-string-parser@7.23.4", }, @@ -1391,7 +1430,7 @@ func TestMarshaler_Marshal(t *testing.T) { Dependencies: lo.ToPtr([]string{}), }, { - Ref: "pkg:maven/org.springframework/spring-web@5.3.22?file_path=spring-web-5.3.22.jar", + Ref: "pkg:maven/org.springframework/spring-web@5.3.22", Dependencies: lo.ToPtr([]string{}), }, { @@ -1407,26 +1446,6 @@ func TestMarshaler_Marshal(t *testing.T) { SchemaVersion: report.SchemaVersion, ArtifactName: "./report.cdx.json", ArtifactType: ftypes.ArtifactCycloneDX, - CycloneDX: &ftypes.CycloneDX{ - BOMFormat: "CycloneDX", - SpecVersion: 6, - SerialNumber: "urn:uuid:ea7360be-19a5-4f61-98dd-d4e170eb6737", - Version: 1, - Metadata: ftypes.Metadata{ - Timestamp: "2024-02-16T06:05:53+00:00", - Component: ftypes.Component{ - BOMRef: "aff65b54-6009-4c32-968d-748949ef46e8", - Type: "application", - Name: "jackson-databind-2.13.4.1.jar", - Properties: []ftypes.Property{ - { - Name: "aquasecurity:trivy:SchemaVersion", - Value: "2", - }, - }, - }, - }, - }, Results: types.Results{ { Target: "Java", @@ -1495,13 +1514,14 @@ func TestMarshaler_Marshal(t *testing.T) { }, }, }, + BOM: testSBOM, }, want: &cdx.BOM{ XMLNS: "http://cyclonedx.org/schema/bom/1.5", BOMFormat: "CycloneDX", SpecVersion: cdx.SpecVersion1_5, JSONSchema: "http://cyclonedx.org/schema/bom-1.5.schema.json", - SerialNumber: "urn:uuid:3ff14136-e09f-4df9-80ea-000000000001", + SerialNumber: "urn:uuid:3ff14136-e09f-4df9-80ea-000000000002", Version: 1, Metadata: &cdx.Metadata{ Timestamp: "2021-08-25T12:20:30+00:00", @@ -1516,7 +1536,7 @@ func TestMarshaler_Marshal(t *testing.T) { }, }, Component: &cdx.Component{ - BOMRef: "3ff14136-e09f-4df9-80ea-000000000002", + BOMRef: "aff65b54-6009-4c32-968d-748949ef46e8", // The original bom-ref is used Type: cdx.ComponentTypeApplication, Name: "jackson-databind-2.13.4.1.jar", Properties: &[]cdx.Property{ @@ -1529,7 +1549,7 @@ func TestMarshaler_Marshal(t *testing.T) { }, Components: &[]cdx.Component{ { - BOMRef: "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.4.1?file_path=jackson-databind-2.13.4.1.jar", + BOMRef: "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.4.1", Type: cdx.ComponentTypeLibrary, Group: "com.fasterxml.jackson.core", Name: "jackson-databind", @@ -1579,7 +1599,7 @@ func TestMarshaler_Marshal(t *testing.T) { Updated: "2022-12-20T10:15:00+00:00", Affects: &[]cdx.Affects{ { - Ref: "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.4.1?file_path=jackson-databind-2.13.4.1.jar", + Ref: "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.4.1", Range: &[]cdx.AffectedVersions{ { Version: "2.13.4.1", @@ -1592,13 +1612,13 @@ func TestMarshaler_Marshal(t *testing.T) { }, Dependencies: &[]cdx.Dependency{ { - Ref: "3ff14136-e09f-4df9-80ea-000000000002", + Ref: "aff65b54-6009-4c32-968d-748949ef46e8", Dependencies: &[]string{ - "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.4.1?file_path=jackson-databind-2.13.4.1.jar", + "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.4.1", }, }, { - Ref: "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.4.1?file_path=jackson-databind-2.13.4.1.jar", + Ref: "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.4.1", Dependencies: lo.ToPtr([]string{}), }, }, @@ -1753,7 +1773,7 @@ func TestMarshaler_Marshal(t *testing.T) { BOMFormat: "CycloneDX", SpecVersion: cdx.SpecVersion1_5, JSONSchema: "http://cyclonedx.org/schema/bom-1.5.schema.json", - SerialNumber: "urn:uuid:3ff14136-e09f-4df9-80ea-000000000001", + SerialNumber: "urn:uuid:3ff14136-e09f-4df9-80ea-000000000004", Version: 1, Metadata: &cdx.Metadata{ Timestamp: "2021-08-25T12:20:30+00:00", @@ -1768,7 +1788,7 @@ func TestMarshaler_Marshal(t *testing.T) { }, }, Component: &cdx.Component{ - BOMRef: "3ff14136-e09f-4df9-80ea-000000000002", + BOMRef: "3ff14136-e09f-4df9-80ea-000000000001", Type: cdx.ComponentTypeApplication, Name: "CVE-2023-34468", Properties: &[]cdx.Property{ @@ -1781,7 +1801,7 @@ func TestMarshaler_Marshal(t *testing.T) { }, Components: &[]cdx.Component{ { - BOMRef: "pkg:maven/org.apache.nifi/nifi-dbcp-base@1.20.0?file_path=nifi-dbcp-base-1.20.0.jar", + BOMRef: "pkg:maven/org.apache.nifi/nifi-dbcp-base@1.20.0", Type: "library", Name: "nifi-dbcp-base", Group: "org.apache.nifi", @@ -1799,7 +1819,7 @@ func TestMarshaler_Marshal(t *testing.T) { }, }, { - BOMRef: "pkg:maven/org.apache.nifi/nifi-hikari-dbcp-service@1.20.0?file_path=nifi-hikari-dbcp-service-1.20.0.jar", + BOMRef: "pkg:maven/org.apache.nifi/nifi-hikari-dbcp-service@1.20.0", Type: "library", Name: "nifi-hikari-dbcp-service", Group: "org.apache.nifi", @@ -1819,18 +1839,18 @@ func TestMarshaler_Marshal(t *testing.T) { }, Dependencies: &[]cdx.Dependency{ { - Ref: "3ff14136-e09f-4df9-80ea-000000000002", + Ref: "3ff14136-e09f-4df9-80ea-000000000001", Dependencies: &[]string{ - "pkg:maven/org.apache.nifi/nifi-dbcp-base@1.20.0?file_path=nifi-dbcp-base-1.20.0.jar", - "pkg:maven/org.apache.nifi/nifi-hikari-dbcp-service@1.20.0?file_path=nifi-hikari-dbcp-service-1.20.0.jar", + "pkg:maven/org.apache.nifi/nifi-dbcp-base@1.20.0", + "pkg:maven/org.apache.nifi/nifi-hikari-dbcp-service@1.20.0", }, }, { - Ref: "pkg:maven/org.apache.nifi/nifi-dbcp-base@1.20.0?file_path=nifi-dbcp-base-1.20.0.jar", + Ref: "pkg:maven/org.apache.nifi/nifi-dbcp-base@1.20.0", Dependencies: lo.ToPtr([]string{}), }, { - Ref: "pkg:maven/org.apache.nifi/nifi-hikari-dbcp-service@1.20.0?file_path=nifi-hikari-dbcp-service-1.20.0.jar", + Ref: "pkg:maven/org.apache.nifi/nifi-hikari-dbcp-service@1.20.0", Dependencies: lo.ToPtr([]string{}), }, }, @@ -1879,7 +1899,7 @@ func TestMarshaler_Marshal(t *testing.T) { Updated: "2023-06-21T02:20:00+00:00", Affects: &[]cdx.Affects{ { - Ref: "pkg:maven/org.apache.nifi/nifi-dbcp-base@1.20.0?file_path=nifi-dbcp-base-1.20.0.jar", + Ref: "pkg:maven/org.apache.nifi/nifi-dbcp-base@1.20.0", Range: &[]cdx.AffectedVersions{ { Version: "1.20.0", @@ -1888,7 +1908,7 @@ func TestMarshaler_Marshal(t *testing.T) { }, }, { - Ref: "pkg:maven/org.apache.nifi/nifi-hikari-dbcp-service@1.20.0?file_path=nifi-hikari-dbcp-service-1.20.0.jar", + Ref: "pkg:maven/org.apache.nifi/nifi-hikari-dbcp-service@1.20.0", Range: &[]cdx.AffectedVersions{ { Version: "1.20.0", @@ -1939,7 +1959,7 @@ func TestMarshaler_Marshal(t *testing.T) { BOMFormat: "CycloneDX", SpecVersion: cdx.SpecVersion1_5, JSONSchema: "http://cyclonedx.org/schema/bom-1.5.schema.json", - SerialNumber: "urn:uuid:3ff14136-e09f-4df9-80ea-000000000001", + SerialNumber: "urn:uuid:3ff14136-e09f-4df9-80ea-000000000003", Version: 1, Metadata: &cdx.Metadata{ Timestamp: "2021-08-25T12:20:30+00:00", @@ -1956,7 +1976,7 @@ func TestMarshaler_Marshal(t *testing.T) { Component: &cdx.Component{ Type: cdx.ComponentTypeApplication, Name: "test-aggregate", - BOMRef: "3ff14136-e09f-4df9-80ea-000000000002", + BOMRef: "3ff14136-e09f-4df9-80ea-000000000001", Properties: &[]cdx.Property{ { Name: "aquasecurity:trivy:SchemaVersion", @@ -1967,7 +1987,7 @@ func TestMarshaler_Marshal(t *testing.T) { }, Components: &[]cdx.Component{ { - BOMRef: "pkg:npm/ruby-typeprof@0.20.1?file_path=usr%2Flocal%2Flib%2Fruby%2Fgems%2F3.1.0%2Fgems%2Ftypeprof-0.21.1%2Fvscode%2Fpackage.json", + BOMRef: "pkg:npm/ruby-typeprof@0.20.1", Type: "library", Name: "ruby-typeprof", Version: "0.20.1", @@ -2002,13 +2022,13 @@ func TestMarshaler_Marshal(t *testing.T) { Vulnerabilities: &[]cdx.Vulnerability{}, Dependencies: &[]cdx.Dependency{ { - Ref: "3ff14136-e09f-4df9-80ea-000000000002", + Ref: "3ff14136-e09f-4df9-80ea-000000000001", Dependencies: &[]string{ - "pkg:npm/ruby-typeprof@0.20.1?file_path=usr%2Flocal%2Flib%2Fruby%2Fgems%2F3.1.0%2Fgems%2Ftypeprof-0.21.1%2Fvscode%2Fpackage.json", + "pkg:npm/ruby-typeprof@0.20.1", }, }, { - Ref: "pkg:npm/ruby-typeprof@0.20.1?file_path=usr%2Flocal%2Flib%2Fruby%2Fgems%2F3.1.0%2Fgems%2Ftypeprof-0.21.1%2Fvscode%2Fpackage.json", + Ref: "pkg:npm/ruby-typeprof@0.20.1", Dependencies: lo.ToPtr([]string{}), }, }, @@ -2027,7 +2047,7 @@ func TestMarshaler_Marshal(t *testing.T) { BOMFormat: "CycloneDX", SpecVersion: cdx.SpecVersion1_5, JSONSchema: "http://cyclonedx.org/schema/bom-1.5.schema.json", - SerialNumber: "urn:uuid:3ff14136-e09f-4df9-80ea-000000000001", + SerialNumber: "urn:uuid:3ff14136-e09f-4df9-80ea-000000000002", Version: 1, Metadata: &cdx.Metadata{ Timestamp: "2021-08-25T12:20:30+00:00", @@ -2044,7 +2064,7 @@ func TestMarshaler_Marshal(t *testing.T) { Component: &cdx.Component{ Type: cdx.ComponentTypeApplication, Name: "empty/path", - BOMRef: "3ff14136-e09f-4df9-80ea-000000000002", + BOMRef: "3ff14136-e09f-4df9-80ea-000000000001", Properties: &[]cdx.Property{ { Name: "aquasecurity:trivy:SchemaVersion", @@ -2053,11 +2073,11 @@ func TestMarshaler_Marshal(t *testing.T) { }, }, }, - Components: lo.ToPtr([]cdx.Component{}), + Components: &[]cdx.Component{}, Vulnerabilities: &[]cdx.Vulnerability{}, Dependencies: &[]cdx.Dependency{ { - Ref: "3ff14136-e09f-4df9-80ea-000000000002", + Ref: "3ff14136-e09f-4df9-80ea-000000000001", Dependencies: lo.ToPtr([]string{}), }, }, @@ -2071,7 +2091,7 @@ func TestMarshaler_Marshal(t *testing.T) { uuid.SetFakeUUID(t, "3ff14136-e09f-4df9-80ea-%012d") marshaler := cyclonedx.NewMarshaler("dev") - got, err := marshaler.Marshal(ctx, tt.inputReport) + got, err := marshaler.MarshalReport(ctx, tt.inputReport) require.NoError(t, err) assert.Equal(t, tt.want, got) }) diff --git a/pkg/sbom/cyclonedx/testdata/sad/invalid-purl.json b/pkg/sbom/cyclonedx/testdata/happy/invalid-purl.json similarity index 100% rename from pkg/sbom/cyclonedx/testdata/sad/invalid-purl.json rename to pkg/sbom/cyclonedx/testdata/happy/invalid-purl.json diff --git a/pkg/sbom/cyclonedx/testdata/happy/kbom.json b/pkg/sbom/cyclonedx/testdata/happy/kbom.json index 3219cf7efaf6..7d4a5b82b65e 100644 --- a/pkg/sbom/cyclonedx/testdata/happy/kbom.json +++ b/pkg/sbom/cyclonedx/testdata/happy/kbom.json @@ -54,17 +54,7 @@ { "bom-ref": "a62abb1f-cb38-4fde-90f3-2bda3b87ddb2", "type": "application", - "name": "node-core-components", - "properties": [ - { - "name": "aquasecurity:trivy:Class", - "value": "lang-pkgs" - }, - { - "name": "aquasecurity:trivy:Type", - "value": "golang" - } - ] + "name": "node-core-components" }, { "bom-ref": "a6350ac3-52f6-4c5f-a3e3-184b9a634bef", diff --git a/pkg/sbom/cyclonedx/testdata/sad/invalid-serial.json b/pkg/sbom/cyclonedx/testdata/sad/invalid-serial.json new file mode 100644 index 000000000000..4edeb3f3ec13 --- /dev/null +++ b/pkg/sbom/cyclonedx/testdata/sad/invalid-serial.json @@ -0,0 +1,6 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "serialNumber": 1000000, + "version": 1 +} \ No newline at end of file diff --git a/pkg/sbom/cyclonedx/unmarshal.go b/pkg/sbom/cyclonedx/unmarshal.go index 59c36bde5226..b234b85fd637 100644 --- a/pkg/sbom/cyclonedx/unmarshal.go +++ b/pkg/sbom/cyclonedx/unmarshal.go @@ -3,33 +3,26 @@ package cyclonedx import ( "bytes" "errors" - "fmt" "io" - "sort" - "strconv" + "strings" cdx "github.com/CycloneDX/cyclonedx-go" "github.com/package-url/packageurl-go" "github.com/samber/lo" - "golang.org/x/exp/maps" + "go.uber.org/zap" "golang.org/x/xerrors" - ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/digest" "github.com/aquasecurity/trivy/pkg/log" - "github.com/aquasecurity/trivy/pkg/purl" - "github.com/aquasecurity/trivy/pkg/sbom/cyclonedx/core" - "github.com/aquasecurity/trivy/pkg/types" + "github.com/aquasecurity/trivy/pkg/sbom/core" ) var ( - ErrPURLEmpty = errors.New("purl empty error") + ErrUnsupportedType = errors.New("unsupported type") ) type BOM struct { - *types.SBOM - - dependencies map[string][]string - components map[string]cdx.Component + *core.BOM } func DecodeJSON(r io.Reader) (*cdx.BOM, error) { @@ -41,417 +34,151 @@ func DecodeJSON(r io.Reader) (*cdx.BOM, error) { return bom, nil } -func (c *BOM) UnmarshalJSON(b []byte) error { - log.Logger.Debug("Unmarshaling CycloneDX JSON...") - if c.SBOM == nil { - c.SBOM = &types.SBOM{} +func (b *BOM) UnmarshalJSON(data []byte) error { + log.Logger.Debug("Unmarshalling CycloneDX JSON...") + if b.BOM == nil { + b.BOM = core.NewBOM() } - bom, err := DecodeJSON(bytes.NewReader(b)) + + cdxBOM, err := DecodeJSON(bytes.NewReader(data)) if err != nil { return xerrors.Errorf("CycloneDX decode error: %w", err) } - if !core.IsTrivySBOM(bom) { + if !IsTrivySBOM(cdxBOM) { log.Logger.Warnf("Third-party SBOM may lead to inaccurate vulnerability detection") log.Logger.Warnf("Recommend using Trivy to generate SBOMs") } - if err = c.parseSBOM(bom); err != nil { + if err = b.parseBOM(cdxBOM); err != nil { return xerrors.Errorf("failed to parse sbom: %w", err) } - sort.Slice(c.Applications, func(i, j int) bool { - if c.Applications[i].Type != c.Applications[j].Type { - return c.Applications[i].Type < c.Applications[j].Type - } - return c.Applications[i].FilePath < c.Applications[j].FilePath - }) - - var metadata ftypes.Metadata - if bom.Metadata != nil { - metadata.Timestamp = bom.Metadata.Timestamp - if bom.Metadata.Component != nil { - metadata.Component = toTrivyCdxComponent(lo.FromPtr(bom.Metadata.Component)) - } - } - - var components []ftypes.Component - for _, component := range lo.FromPtr(bom.Components) { - components = append(components, toTrivyCdxComponent(component)) - } + // Store the original metadata + b.BOM.SerialNumber = cdxBOM.SerialNumber + b.BOM.Version = cdxBOM.Version - // Keep the original SBOM - c.CycloneDX = &ftypes.CycloneDX{ - BOMFormat: bom.BOMFormat, - SpecVersion: ftypes.SpecVersion(bom.SpecVersion), - SerialNumber: bom.SerialNumber, - Version: bom.Version, - Metadata: metadata, - Components: components, - } return nil } -func (c *BOM) parseSBOM(bom *cdx.BOM) error { - c.dependencies = dependencyMap(bom.Dependencies) - c.components = componentMap(bom.Metadata, bom.Components) - var seen = make(map[string]struct{}) - for bomRef := range c.dependencies { - component := c.components[bomRef] - switch component.Type { - case cdx.ComponentTypeOS: // OS info and OS packages - seen[component.BOMRef] = struct{}{} - c.OS = toOS(component) - pkgInfo, err := c.parseOSPkgs(component, seen) - if err != nil { - return xerrors.Errorf("failed to parse os packages: %w", err) - } - c.Packages = append(c.Packages, pkgInfo) - case cdx.ComponentTypeApplication: // It would be a lock file in a CycloneDX report generated by Trivy - if core.LookupProperty(component.Properties, PropertyType) == "" { - continue - } - app, err := c.parseLangPkgs(component, seen) - if err != nil { - return xerrors.Errorf("failed to parse language packages: %w", err) - } - c.Applications = append(c.Applications, *app) - case cdx.ComponentTypeLibrary: - // It is an individual package not associated with any lock files and should be processed later. - // e.g. .gemspec, .egg and .wheel - continue - } +func (b *BOM) parseBOM(bom *cdx.BOM) error { + // Convert all CycloneDX components into Trivy components + components := b.parseComponents(bom.Components) + + // Convert the metadata component into Trivy component + mComponent, err := b.parseMetadataComponent(bom) + if err != nil { + return xerrors.Errorf("failed to parse root component: %w", err) + } else if mComponent != nil { + components[mComponent.PkgID.BOMRef] = mComponent } - var libComponents []cdx.Component - for ref, component := range c.components { - if _, ok := seen[ref]; ok { + // Parse dependencies and build relationships + for _, dep := range lo.FromPtr(bom.Dependencies) { + ref, ok := components[dep.Ref] + if !ok { continue } - if component.Type == cdx.ComponentTypeLibrary || component.PackageURL != "" { - libComponents = append(libComponents, component) - } - - // For third-party SBOMs. - // If there are no operating-system dependent libraries, make them implicitly dependent. - if component.Type == cdx.ComponentTypeOS { - if lo.IsNotEmpty(c.OS) { - return xerrors.New("multiple OSes are not supported") + for _, depRef := range lo.FromPtr(dep.Dependencies) { + dependency, ok := components[depRef] + if !ok { + continue } - c.OS = toOS(component) + b.BOM.AddRelationship(ref, dependency, core.RelationshipDependsOn) } } - - pkgInfos, aggregatedApps, err := aggregatePkgs(libComponents) - if err != nil { - return xerrors.Errorf("failed to aggregate packages: %w", err) - } - - // For third party SBOMs. - // If a package that depends on the operating-system did not exist, - // but an os package is found during aggregate, it is used. - if len(c.Packages) == 0 && len(pkgInfos) != 0 { - if !c.OS.Detected() { - log.Logger.Warnf("Ignore the OS package as no OS information is found.") - } else { - c.Packages = pkgInfos - } - } - c.Applications = append(c.Applications, aggregatedApps...) - return nil } -func (c *BOM) parseOSPkgs(component cdx.Component, seen map[string]struct{}) (ftypes.PackageInfo, error) { - components := c.walkDependencies(component.BOMRef, make(map[string]struct{})) - pkgs, err := parsePkgs(components, seen) - if err != nil { - return ftypes.PackageInfo{}, xerrors.Errorf("failed to parse os package: %w", err) +func (b *BOM) parseMetadataComponent(bom *cdx.BOM) (*core.Component, error) { + if bom.Metadata == nil || bom.Metadata.Component == nil { + return nil, nil } - - return ftypes.PackageInfo{ - Packages: pkgs, - }, nil -} - -func (c *BOM) parseLangPkgs(component cdx.Component, seen map[string]struct{}) (*ftypes.Application, error) { - components := c.walkDependencies(component.BOMRef, make(map[string]struct{})) - components = lo.UniqBy(components, func(c cdx.Component) string { - return c.BOMRef - }) - - app := toApplication(component) - pkgs, err := parsePkgs(components, seen) + root, err := b.parseComponent(*bom.Metadata.Component) if err != nil { - return nil, xerrors.Errorf("failed to parse language-specific packages: %w", err) + return nil, xerrors.Errorf("failed to parse metadata component: %w", err) } - app.Libraries = pkgs - - return app, nil + root.Root = true + b.BOM.AddComponent(root) + return root, nil } -func parsePkgs(components []cdx.Component, seen map[string]struct{}) ([]ftypes.Package, error) { - var pkgs []ftypes.Package - for _, com := range components { - seen[com.BOMRef] = struct{}{} - pkgURL, pkg, err := toPackage(com) - if errors.Is(err, ErrPURLEmpty) { +func (b *BOM) parseComponents(cdxComponents *[]cdx.Component) map[string]*core.Component { + components := make(map[string]*core.Component) + for _, component := range lo.FromPtr(cdxComponents) { + c, err := b.parseComponent(component) + if errors.Is(err, ErrUnsupportedType) { + log.Logger.Infow("Skipping the component with the unsupported type", + zap.String("bom-ref", component.BOMRef), zap.String("type", string(component.Type))) continue } else if err != nil { - return nil, xerrors.Errorf("failed to parse language package: %w", err) - } - - // Skip unsupported package types - if pkgURL.Class() == types.ClassUnknown { + log.Logger.Warnw("Failed to parse component: %s", zap.Error(err)) continue } - pkgs = append(pkgs, *pkg) - } - return pkgs, nil -} - -// walkDependencies takes all nested dependencies of the root component. -func (c *BOM) walkDependencies(rootRef string, uniqComponents map[string]struct{}) []cdx.Component { - // e.g. Library A, B, C, D and E will be returned as dependencies of Application 1. - // type: Application 1 - // - type: Library A - // - type: Library B - // - type: Application 2 - // - type: Library C - // - type: Application 3 - // - type: Library D - // - type: Library E - var components []cdx.Component - for _, dep := range c.dependencies[rootRef] { - component, ok := c.components[dep] - if !ok { - continue - } - - // there are cases of looped components: - // type: Application 1 - // - type: Library A - // - type: Library B - // - type: Library A - // ... - // use uniqComponents to fix infinite loop - if _, ok = uniqComponents[dep]; ok { - continue - } - uniqComponents[dep] = struct{}{} - - // Take only 'Libraries' - if component.Type == cdx.ComponentTypeLibrary { - components = append(components, component) - } - components = append(components, c.walkDependencies(dep, uniqComponents)...) + b.BOM.AddComponent(c) + components[component.BOMRef] = c } return components } -func componentMap(metadata *cdx.Metadata, components *[]cdx.Component) map[string]cdx.Component { - cmap := make(map[string]cdx.Component) - - for _, component := range lo.FromPtr(components) { - cmap[component.BOMRef] = component - } - if metadata != nil && metadata.Component != nil { - cmap[metadata.Component.BOMRef] = *metadata.Component - } - return cmap -} - -func dependencyMap(deps *[]cdx.Dependency) map[string][]string { - depMap := make(map[string][]string) - - for _, dep := range lo.FromPtr(deps) { - if _, ok := depMap[dep.Ref]; ok { - continue - } - var refs []string - if dep.Dependencies != nil { - refs = append(refs, *dep.Dependencies...) - } - - depMap[dep.Ref] = refs - } - return depMap -} - -func aggregatePkgs(libs []cdx.Component) ([]ftypes.PackageInfo, []ftypes.Application, error) { - osPkgMap := make(map[string]ftypes.Packages) - langPkgMap := make(map[ftypes.LangType]ftypes.Packages) - for _, lib := range libs { - pkgURL, pkg, err := toPackage(lib) - if errors.Is(err, ErrPURLEmpty) { - continue - } else if err != nil { - return nil, nil, xerrors.Errorf("failed to parse the component: %w", err) - } - - switch pkgURL.Class() { - case types.ClassOSPkg: - osPkgMap[pkgURL.Type] = append(osPkgMap[pkgURL.Type], *pkg) - case types.ClassLangPkg: - langType := pkgURL.LangType() - langPkgMap[langType] = append(langPkgMap[langType], *pkg) - } - } - - if len(osPkgMap) > 1 { - return nil, nil, xerrors.Errorf("multiple types of OS packages in SBOM are not supported (%q)", - maps.Keys(osPkgMap)) - } - - var osPkgs ftypes.PackageInfo - for _, pkgs := range osPkgMap { - // Just take the first element - sort.Sort(pkgs) - osPkgs = ftypes.PackageInfo{Packages: pkgs} - break - } - - var apps []ftypes.Application - for pkgType, pkgs := range langPkgMap { - sort.Sort(pkgs) - apps = append(apps, ftypes.Application{ - Type: pkgType, - Libraries: pkgs, - }) - } - return []ftypes.PackageInfo{osPkgs}, apps, nil -} - -func toOS(component cdx.Component) ftypes.OS { - return ftypes.OS{ - Family: ftypes.OSType(component.Name), - Name: component.Version, - } -} - -func toApplication(component cdx.Component) *ftypes.Application { - return &ftypes.Application{ - Type: ftypes.LangType(core.LookupProperty(component.Properties, PropertyType)), - FilePath: component.Name, - } -} - -func toPackage(component cdx.Component) (*purl.PackageURL, *ftypes.Package, error) { - if component.PackageURL == "" { - log.Logger.Warnf("Skip the component (BOM-Ref: %s) as the PURL is empty", component.BOMRef) - return nil, nil, ErrPURLEmpty - } - p, err := purl.FromString(component.PackageURL) +func (b *BOM) parseComponent(c cdx.Component) (*core.Component, error) { + componentType, err := b.unmarshalType(c.Type) if err != nil { - return nil, nil, xerrors.Errorf("failed to parse purl: %w", err) + return nil, xerrors.Errorf("failed to unmarshal component type: %w", err) } - pkg := p.Package() - // Trivy's marshall loses case-sensitivity in PURL used in SBOM for packages (Go, Npm, PyPI), - // so we have to use an original package name - pkg.Name = packageName(p.Type, pkg.Name, component) - pkg.Licenses = parsePackageLicenses(component.Licenses) - - for key, value := range core.UnmarshalProperties(component.Properties) { - switch key { - case PropertyPkgID: - pkg.ID = value - case PropertySrcName: - pkg.SrcName = value - case PropertySrcVersion: - pkg.SrcVersion = value - case PropertySrcRelease: - pkg.SrcRelease = value - case PropertySrcEpoch: - pkg.SrcEpoch, err = strconv.Atoi(value) - if err != nil { - return nil, nil, xerrors.Errorf("failed to parse source epoch: %w", err) - } - case PropertyModularitylabel: - pkg.Modularitylabel = value - case PropertyLayerDiffID: - pkg.Layer.DiffID = value - case PropertyLayerDigest: - pkg.Layer.Digest = value - case PropertyFilePath: - pkg.FilePath = value + // Parse PURL + var purl packageurl.PackageURL + if c.PackageURL != "" { + purl, err = packageurl.FromString(c.PackageURL) + if err != nil { + return nil, xerrors.Errorf("failed to parse PURL: %w", err) } } - if pkg.FilePath != "" { - p.FilePath = pkg.FilePath - } - pkg.Identifier.BOMRef = component.BOMRef - - if p.Class() == types.ClassOSPkg { - fillSrcPkg(pkg) - } - - return p, pkg, nil + component := &core.Component{ + Type: componentType, + Name: c.Name, + Group: c.Group, + Version: c.Version, + Licenses: b.unmarshalLicenses(c.Licenses), + Files: lo.Map(b.unmarshalHashes(c.Hashes), func(d digest.Digest, _ int) core.File { + return core.File{Hash: d} // CycloneDX doesn't have a file path for the hash + }), + PkgID: core.PkgID{ + PURL: &purl, + BOMRef: c.BOMRef, + }, + Supplier: b.unmarshalSupplier(c.Supplier), + Properties: b.unmarshalProperties(c.Properties), + } + + return component, nil } -func fillSrcPkg(pkg *ftypes.Package) { - // Fill source package information for components in third-party SBOMs . - if pkg.SrcName == "" { - pkg.SrcName = pkg.Name - } - if pkg.SrcVersion == "" { - pkg.SrcVersion = pkg.Version - } - if pkg.SrcRelease == "" { - pkg.SrcRelease = pkg.Release - } - if pkg.SrcEpoch == 0 { - pkg.SrcEpoch = pkg.Epoch - } -} - -func toTrivyCdxComponent(component cdx.Component) ftypes.Component { - var props []ftypes.Property - for _, prop := range lo.FromPtr(component.Properties) { - props = append(props, ftypes.Property{ - Name: prop.Name, - Value: prop.Value, - }) - } - return ftypes.Component{ - BOMRef: component.BOMRef, - MIMEType: component.MIMEType, - Type: ftypes.ComponentType(component.Type), - Name: component.Name, - Group: component.Group, - Version: component.Version, - PackageURL: component.PackageURL, - Properties: props, - } -} - -func packageName(typ, pkgNameFromPurl string, component cdx.Component) string { - if typ == packageurl.TypeMaven || typ == packageurl.TypeNPM { - // Maven uses `Group` field for `GroupID` - // Npm uses `Group` field for `Scope` - if component.Group != "" { - return fmt.Sprintf("%s%s%s", component.Group, packageNameSeparator(typ), component.Name) - } else { - // use name derived from purl if `Group` doesn't exist - return pkgNameFromPurl - } - } - return component.Name -} - -// packageNameSeparator selects separator to join `group` and `name` fields of the component -func packageNameSeparator(typ string) string { - if typ == packageurl.TypeMaven { - return ":" - } - return "/" +func (b *BOM) unmarshalType(t cdx.ComponentType) (core.ComponentType, error) { + var ctype core.ComponentType + switch t { + case cdx.ComponentTypeContainer: + ctype = core.TypeContainer + case cdx.ComponentTypeApplication: + ctype = core.TypeApplication + case cdx.ComponentTypeLibrary: + ctype = core.TypeLibrary + case cdx.ComponentTypeOS: + ctype = core.TypeOS + case cdx.ComponentTypePlatform: + ctype = core.TypePlatform + default: + return "", ErrUnsupportedType + } + return ctype, nil } // parsePackageLicenses checks all supported license fields and returns a list of licenses. // https://cyclonedx.org/docs/1.5/json/#components_items_licenses -func parsePackageLicenses(l *cdx.Licenses) []string { +func (b *BOM) unmarshalLicenses(l *cdx.Licenses) []string { var licenses []string for _, license := range lo.FromPtr(l) { if license.License != nil { @@ -475,3 +202,61 @@ func parsePackageLicenses(l *cdx.Licenses) []string { } return licenses } + +func (b *BOM) unmarshalHashes(hashes *[]cdx.Hash) []digest.Digest { + var digests []digest.Digest + for _, h := range lo.FromPtr(hashes) { + var alg digest.Algorithm + switch h.Algorithm { + case cdx.HashAlgoSHA1: + alg = digest.SHA1 + case cdx.HashAlgoSHA256: + alg = digest.SHA256 + case cdx.HashAlgoMD5: + alg = digest.MD5 + default: + log.Logger.Warnf("Unsupported hash algorithm: %s", h.Algorithm) + } + digests = append(digests, digest.NewDigestFromString(alg, h.Value)) + } + return digests +} + +func (b *BOM) unmarshalSupplier(supplier *cdx.OrganizationalEntity) string { + if supplier == nil { + return "" + } + return supplier.Name +} + +func (b *BOM) unmarshalProperties(properties *[]cdx.Property) []core.Property { + var props []core.Property + for _, p := range lo.FromPtr(properties) { + props = append(props, core.Property{ + Name: strings.TrimPrefix(p.Name, Namespace), + Value: p.Value, + }) + } + return props +} + +func IsTrivySBOM(c *cdx.BOM) bool { + if c == nil || c.Metadata == nil || c.Metadata.Tools == nil { + return false + } + + for _, component := range lo.FromPtr(c.Metadata.Tools.Components) { + if component.Group == ToolVendor && component.Name == ToolName { + return true + } + } + + // Metadata.Tools array is deprecated (as of CycloneDX v1.5). We check this field for backward compatibility. + // cf. https://github.com/CycloneDX/cyclonedx-go/blob/b9654ae9b4705645152d20eb9872b5f3d73eac49/cyclonedx.go#L988 + for _, tool := range lo.FromPtr(c.Metadata.Tools.Tools) { + if tool.Vendor == ToolVendor && tool.Name == ToolName { + return true + } + } + return false +} diff --git a/pkg/sbom/cyclonedx/unmarshal_test.go b/pkg/sbom/cyclonedx/unmarshal_test.go index 61b24082343e..6ffd9ce89ad9 100644 --- a/pkg/sbom/cyclonedx/unmarshal_test.go +++ b/pkg/sbom/cyclonedx/unmarshal_test.go @@ -3,6 +3,8 @@ package cyclonedx_test import ( "encoding/json" "github.com/aquasecurity/trivy/pkg/purl" + sbomio "github.com/aquasecurity/trivy/pkg/sbom/io" + "github.com/aquasecurity/trivy/pkg/types" "github.com/package-url/packageurl-go" "os" "testing" @@ -12,7 +14,6 @@ import ( ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/sbom/cyclonedx" - "github.com/aquasecurity/trivy/pkg/types" ) func TestUnmarshaler_Unmarshal(t *testing.T) { @@ -26,14 +27,25 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { name: "happy path", inputFile: "testdata/happy/bom.json", want: types.SBOM{ - OS: ftypes.OS{ - Family: "alpine", - Name: "3.16.0", + Metadata: types.Metadata{ + ImageID: "sha256:49193a2310dbad4c02382da87ac624a80a92387a4f7536235f9ba590e5bcd7b5", + DiffIDs: []string{ + "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", + "sha256:dd565ff850e7003356e2b252758f9bdc1ff2803f61e995e24c7844f6297f8fc3", + }, + RepoTags: []string{ + "maven-test-project:latest", + }, + OS: &ftypes.OS{ + Family: "alpine", + Name: "3.16.0", + }, }, Packages: []ftypes.PackageInfo{ { Packages: ftypes.Packages{ { + ID: "musl@1.2.3-r0", Name: "musl", Version: "1.2.3-r0", SrcName: "musl", @@ -67,6 +79,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { FilePath: "app/composer/composer.lock", Libraries: ftypes.Packages{ { + ID: "pear/log@1.13.1", Name: "pear/log", Version: "1.13.1", Identifier: ftypes.PkgIdentifier{ @@ -83,7 +96,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { }, }, { - + ID: "pear/pear_exception@v1.0.0", Name: "pear/pear_exception", Version: "v1.0.0", Identifier: ftypes.PkgIdentifier{ @@ -106,6 +119,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { FilePath: "app/gobinary/gobinary", Libraries: ftypes.Packages{ { + ID: "github.com/package-url/packageurl-go@v0.1.1-0.20220203205134-d70459300c8a", Name: "github.com/package-url/packageurl-go", Version: "v0.1.1-0.20220203205134-d70459300c8a", Identifier: ftypes.PkgIdentifier{ @@ -128,6 +142,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { FilePath: "app/gradle/target/gradle.lockfile", Libraries: ftypes.Packages{ { + ID: "com.example:example:0.0.1", Name: "com.example:example", Identifier: ftypes.PkgIdentifier{ PURL: &packageurl.PackageURL{ @@ -149,6 +164,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { Type: ftypes.Jar, Libraries: ftypes.Packages{ { + ID: "org.codehaus.mojo:child-project:1.0", Name: "org.codehaus.mojo:child-project", Identifier: ftypes.PkgIdentifier{ PURL: &packageurl.PackageURL{ @@ -172,6 +188,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { FilePath: "", Libraries: ftypes.Packages{ { + ID: "@example/bootstrap@5.0.2", Name: "@example/bootstrap", Version: "5.0.2", Identifier: ftypes.PkgIdentifier{ @@ -198,13 +215,10 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { name: "happy path KBOM", inputFile: "testdata/happy/kbom.json", want: types.SBOM{ - OS: ftypes.OS{ - Family: "ubuntu", - Name: "22.04.2", - }, - Packages: []ftypes.PackageInfo{ - { - FilePath: "", + Metadata: types.Metadata{ + OS: &ftypes.OS{ + Family: "ubuntu", + Name: "22.04.2", }, }, Applications: []ftypes.Application{ @@ -212,6 +226,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { Type: ftypes.GoBinary, Libraries: ftypes.Packages{ { + ID: "docker@v24.0.4", Name: "docker", Version: "24.0.4", Identifier: ftypes.PkgIdentifier{ @@ -225,14 +240,11 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { }, }, }, - { - Type: "golang", - FilePath: "node-core-components", - }, { Type: ftypes.K8sUpstream, Libraries: ftypes.Packages{ { + ID: "k8s.io/apiserver@1.27.4", Name: "k8s.io/apiserver", Version: "1.27.4", Identifier: ftypes.PkgIdentifier{ @@ -245,6 +257,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { }, }, { + ID: "k8s.io/controller-manager@1.27.4", Name: "k8s.io/controller-manager", Version: "1.27.4", Identifier: ftypes.PkgIdentifier{ @@ -257,6 +270,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { }, }, { + ID: "k8s.io/kube-proxy@1.27.4", Name: "k8s.io/kube-proxy", Version: "1.27.4", Identifier: ftypes.PkgIdentifier{ @@ -269,6 +283,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { }, }, { + ID: "k8s.io/kube-scheduler@1.27.4", Name: "k8s.io/kube-scheduler", Version: "1.27.4", Identifier: ftypes.PkgIdentifier{ @@ -281,6 +296,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { }, }, { + ID: "k8s.io/kubelet@1.27.4", Name: "k8s.io/kubelet", Version: "1.27.4", Identifier: ftypes.PkgIdentifier{ @@ -293,6 +309,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { }, }, { + ID: "k8s.io/kubernetes@1.27.4", Name: "k8s.io/kubernetes", Version: "1.27.4", Identifier: ftypes.PkgIdentifier{ @@ -303,6 +320,12 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { }, BOMRef: "pkg:k8s/k8s.io%2Fkubernetes@1.27.4", }, + DependsOn: []string{ + "k8s.io/apiserver@1.27.4", + "k8s.io/controller-manager@1.27.4", + "k8s.io/kube-proxy@1.27.4", + "k8s.io/kube-scheduler@1.27.4", + }, }, }, }, @@ -313,9 +336,21 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { name: "happy path with infinity loop", inputFile: "testdata/happy/infinite-loop-bom.json", want: types.SBOM{ - OS: ftypes.OS{ - Family: "ubuntu", - Name: "22.04", + Metadata: types.Metadata{ + ImageID: "sha256:08d22c0ceb150ddeb2237c5fa3129c0183f3cc6f5eeb2e7aa4016da3ad02140a", + DiffIDs: []string{ + "sha256:b93c1bd012ab8fda60f5b4f5906bf244586e0e3292d84571d3abb56472248466", + }, + RepoTags: []string{ + "ubuntu:latest", + }, + RepoDigests: []string{ + "ubuntu@sha256:67211c14fa74f070d27cc59d69a7fa9aeff8e28ea118ef3babc295a0428a6d21", + }, + OS: &ftypes.OS{ + Family: "ubuntu", + Name: "22.04", + }, }, Packages: []ftypes.PackageInfo{ { @@ -347,6 +382,9 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { }, BOMRef: "pkg:deb/ubuntu/libc6@2.35-0ubuntu3.1?distro=ubuntu-22.04", }, + DependsOn: []string{ + "libcrypt1@1:4.4.27-1", + }, Layer: ftypes.Layer{ Digest: "sha256:74ac377868f863e123f24c409f79709f7563fa464557c36a09cf6f85c8b92b7f", DiffID: "sha256:b93c1bd012ab8fda60f5b4f5906bf244586e0e3292d84571d3abb56472248466", @@ -380,6 +418,9 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { }, BOMRef: "pkg:deb/ubuntu/libcrypt1@4.4.27-1?epoch=1&distro=ubuntu-22.04", }, + DependsOn: []string{ + "libc6@2.35-0ubuntu3.1", + }, Layer: ftypes.Layer{ Digest: "sha256:74ac377868f863e123f24c409f79709f7563fa464557c36a09cf6f85c8b92b7f", DiffID: "sha256:b93c1bd012ab8fda60f5b4f5906bf244586e0e3292d84571d3abb56472248466", @@ -394,14 +435,17 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { name: "happy path for third party sbom", inputFile: "testdata/happy/third-party-bom.json", want: types.SBOM{ - OS: ftypes.OS{ - Family: "alpine", - Name: "3.16.0", + Metadata: types.Metadata{ + OS: &ftypes.OS{ + Family: "alpine", + Name: "3.16.0", + }, }, Packages: []ftypes.PackageInfo{ { Packages: ftypes.Packages{ { + ID: "musl@1.2.3-r0", Name: "musl", Version: "1.2.3-r0", SrcName: "musl", @@ -432,6 +476,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { FilePath: "", Libraries: ftypes.Packages{ { + ID: "pear/log@1.13.1", Name: "pear/log", Version: "1.13.1", Identifier: ftypes.PkgIdentifier{ @@ -446,7 +491,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { Licenses: []string{"MIT"}, }, { - + ID: "pear/pear_exception@v1.0.0", Name: "pear/pear_exception", Version: "v1.0.0", Identifier: ftypes.PkgIdentifier{ @@ -474,6 +519,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { FilePath: "", Libraries: ftypes.Packages{ { + ID: "pear/log@1.13.1", Name: "pear/log", Version: "1.13.1", Identifier: ftypes.PkgIdentifier{ @@ -501,6 +547,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { FilePath: "", Libraries: ftypes.Packages{ { + ID: "pear/log@1.13.1", Name: "pear/log", Version: "1.13.1", Identifier: ftypes.PkgIdentifier{ @@ -514,7 +561,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { }, }, { - + ID: "pear/pear_exception@v1.0.0", Name: "pear/pear_exception", Version: "v1.0.0", Identifier: ftypes.PkgIdentifier{ @@ -542,6 +589,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { FilePath: "", Libraries: ftypes.Packages{ { + ID: "pear/core@1.13.1", Name: "pear/core", Version: "1.13.1", Identifier: ftypes.PkgIdentifier{ @@ -553,8 +601,13 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { }, BOMRef: "pkg:composer/pear/core@1.13.1", }, + DependsOn: []string{ + "pear/log@1.13.1", + "pear/pear_exception@v1.0.0", + }, }, { + ID: "pear/log@1.13.1", Name: "pear/log", Version: "1.13.1", Identifier: ftypes.PkgIdentifier{ @@ -568,7 +621,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { }, }, { - + ID: "pear/pear_exception@v1.0.0", Name: "pear/pear_exception", Version: "v1.0.0", Identifier: ftypes.PkgIdentifier{ @@ -595,6 +648,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { Type: "jar", Libraries: ftypes.Packages{ { + ID: "org.springframework:spring-web:5.3.22", Name: "org.springframework:spring-web", Version: "5.3.22", Identifier: ftypes.PkgIdentifier{ @@ -617,19 +671,37 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { name: "happy path only os component", inputFile: "testdata/happy/os-only-bom.json", want: types.SBOM{ - OS: ftypes.OS{ - Family: "alpine", - Name: "3.16.0", - }, - Packages: []ftypes.PackageInfo{ - {}, + Metadata: types.Metadata{ + ImageID: "sha256:49193a2310dbad4c02382da87ac624a80a92387a4f7536235f9ba590e5bcd7b5", + DiffIDs: []string{ + "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", + "sha256:dd565ff850e7003356e2b252758f9bdc1ff2803f61e995e24c7844f6297f8fc3", + }, + RepoTags: []string{ + "maven-test-project:latest", + }, + OS: &ftypes.OS{ + Family: "alpine", + Name: "3.16.0", + }, }, }, }, { name: "happy path empty component", inputFile: "testdata/happy/empty-bom.json", - want: types.SBOM{}, + want: types.SBOM{ + Metadata: types.Metadata{ + ImageID: "sha256:49193a2310dbad4c02382da87ac624a80a92387a4f7536235f9ba590e5bcd7b5", + DiffIDs: []string{ + "sha256:3c79e832b1b4891a1cb4a326ef8524e0bd14a2537150ac0e203a5677176c1ca1", + "sha256:dd565ff850e7003356e2b252758f9bdc1ff2803f61e995e24c7844f6297f8fc3", + }, + RepoTags: []string{ + "maven-test-project:latest", + }, + }, + }, }, { name: "happy path empty metadata component", @@ -637,9 +709,36 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { want: types.SBOM{}, }, { - name: "sad path invalid purl", - inputFile: "testdata/sad/invalid-purl.json", - wantErr: "failed to parse purl", + name: "invalid purl", + inputFile: "testdata/happy/invalid-purl.json", + want: types.SBOM{ + Applications: []ftypes.Application{ + { + Type: ftypes.Composer, + Libraries: ftypes.Packages{ + { + ID: "pear/core@1.13.1", + Name: "pear/core", + Version: "1.13.1", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeComposer, + Namespace: "pear", + Name: "core", + Version: "1.13.1", + }, + BOMRef: "pkg:composer/pear/core@1.13.1", + }, + }, + }, + }, + }, + }, + }, + { + name: "invalid serial", + inputFile: "testdata/sad/invalid-serial.json", + wantErr: "CycloneDX decode error", }, } @@ -652,16 +751,16 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { var cdx cyclonedx.BOM err = json.NewDecoder(f).Decode(&cdx) if tt.wantErr != "" { - require.Error(t, err) - assert.Contains(t, err.Error(), tt.wantErr) + require.ErrorContains(t, err, tt.wantErr) return } require.NoError(t, err) - // Not compare the CycloneDX field - got := *cdx.SBOM - got.CycloneDX = nil + var got types.SBOM + err = sbomio.NewDecoder(cdx.BOM).Decode(&got) + require.NoError(t, err) + got.BOM = nil assert.Equal(t, tt.want, got) }) } diff --git a/pkg/sbom/io/decode.go b/pkg/sbom/io/decode.go new file mode 100644 index 000000000000..f0385ddedc26 --- /dev/null +++ b/pkg/sbom/io/decode.go @@ -0,0 +1,336 @@ +package io + +import ( + "errors" + "sort" + "strconv" + + "github.com/package-url/packageurl-go" + "go.uber.org/zap" + "golang.org/x/exp/maps" + "golang.org/x/xerrors" + + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/purl" + "github.com/aquasecurity/trivy/pkg/sbom/core" + "github.com/aquasecurity/trivy/pkg/types" + "github.com/aquasecurity/trivy/pkg/uuid" +) + +var ( + ErrPURLEmpty = errors.New("purl empty error") + ErrUnsupportedType = errors.New("unsupported type") +) + +type Decoder struct { + bom *core.BOM + + osID uuid.UUID + pkgs map[uuid.UUID]*ftypes.Package + apps map[uuid.UUID]*ftypes.Application +} + +func NewDecoder(bom *core.BOM) *Decoder { + return &Decoder{ + bom: bom, + pkgs: make(map[uuid.UUID]*ftypes.Package), + apps: make(map[uuid.UUID]*ftypes.Application), + } +} + +func (m *Decoder) Decode(sbom *types.SBOM) error { + // Parse the root component + if err := m.decodeRoot(sbom); err != nil { + return xerrors.Errorf("failed to decode root component: %w", err) + } + + // Parse all components + if err := m.decodeComponents(sbom); err != nil { + return xerrors.Errorf("failed to decode components: %w", err) + } + + // Build dependency graph between packages + m.buildDependencyGraph() + + // Add OS packages + m.addOSPkgs(sbom) + + // Add language-specific packages + m.addLangPkgs(sbom) + + // Add remaining packages + if err := m.addOrphanPkgs(sbom); err != nil { + return xerrors.Errorf("failed to aggregate packages: %w", err) + } + + sort.Slice(sbom.Applications, func(i, j int) bool { + if sbom.Applications[i].Type != sbom.Applications[j].Type { + return sbom.Applications[i].Type < sbom.Applications[j].Type + } + return sbom.Applications[i].FilePath < sbom.Applications[j].FilePath + }) + + sbom.BOM = m.bom + + return nil +} + +func (m *Decoder) decodeRoot(s *types.SBOM) error { + root := m.bom.Root() + if root == nil { + return nil // No root found + } + + var err error + for _, prop := range root.Properties { + switch prop.Name { + case core.PropertyImageID: + s.Metadata.ImageID = prop.Value + case core.PropertySize: + if s.Metadata.Size, err = strconv.ParseInt(prop.Value, 10, 64); err != nil { + return xerrors.Errorf("failed to convert size: %w", err) + } + case core.PropertyRepoDigest: + s.Metadata.RepoDigests = append(s.Metadata.RepoDigests, prop.Value) + case core.PropertyDiffID: + s.Metadata.DiffIDs = append(s.Metadata.DiffIDs, prop.Value) + case core.PropertyRepoTag: + s.Metadata.RepoTags = append(s.Metadata.RepoTags, prop.Value) + } + } + return nil +} + +func (m *Decoder) decodeComponents(sbom *types.SBOM) error { + for id, c := range m.bom.Components() { + switch c.Type { + case core.TypeOS: + if m.osID != uuid.Nil { + return xerrors.New("multiple OS components are not supported") + } + m.osID = id + sbom.Metadata.OS = &ftypes.OS{ + Family: ftypes.OSType(c.Name), + Name: c.Version, + } + continue + case core.TypeApplication: + if app := m.decodeApplication(c); app.Type != "" { + m.apps[id] = app + continue + } + } + + // Third-party SBOMs may contain packages in types other than "Library" + if c.Type == core.TypeLibrary || c.PkgID.PURL != nil { + pkg, err := m.decodeLibrary(c) + if errors.Is(err, ErrUnsupportedType) { + continue + } else if err != nil { + return xerrors.Errorf("failed to decode library: %w", err) + } + m.pkgs[id] = pkg + } + } + + return nil +} + +// buildDependencyGraph builds a dependency graph between packages +func (m *Decoder) buildDependencyGraph() { + for id, rels := range m.bom.Relationships() { + pkg, ok := m.pkgs[id] + if !ok { + continue + } + for _, rel := range rels { + dep, ok := m.pkgs[rel.Dependency] + if !ok { + continue + } + pkg.DependsOn = append(pkg.DependsOn, dep.ID) + } + continue + } +} + +func (m *Decoder) decodeApplication(c *core.Component) *ftypes.Application { + app := &ftypes.Application{ + FilePath: c.Name, + } + for _, prop := range c.Properties { + if prop.Name == core.PropertyType { + app.Type = ftypes.LangType(prop.Value) + } + } + return app +} + +func (m *Decoder) decodeLibrary(c *core.Component) (*ftypes.Package, error) { + p := (*purl.PackageURL)(c.PkgID.PURL) + if p == nil { + log.Logger.Debugw("Skipping a component without PURL", + zap.String("name", c.Name), zap.String("version", c.Version)) + return nil, ErrPURLEmpty + } + + pkg := p.Package() + if p.Class() == types.ClassUnknown { + log.Logger.Debugw("Skipping a component with an unsupported type", + zap.String("name", c.Name), zap.String("version", c.Version), zap.String("type", p.Type)) + return nil, ErrUnsupportedType + } + pkg.Name = m.pkgName(pkg, c) + + var err error + for _, prop := range c.Properties { + switch prop.Name { + case core.PropertyPkgID: + pkg.ID = prop.Value + case core.PropertyFilePath: + pkg.FilePath = prop.Value + case core.PropertySrcName: + pkg.SrcName = prop.Value + case core.PropertySrcVersion: + pkg.SrcVersion = prop.Value + case core.PropertySrcRelease: + pkg.SrcRelease = prop.Value + case core.PropertySrcEpoch: + if pkg.SrcEpoch, err = strconv.Atoi(prop.Value); err != nil { + return nil, xerrors.Errorf("invalid src epoch: %w", err) + } + case core.PropertyModularitylabel: + pkg.Modularitylabel = prop.Value + case core.PropertyLayerDigest: + pkg.Layer.Digest = prop.Value + case core.PropertyLayerDiffID: + pkg.Layer.DiffID = prop.Value + } + } + + pkg.Identifier.BOMRef = c.PkgID.BOMRef + pkg.Licenses = c.Licenses + if len(c.Files) > 0 { + pkg.Digest = c.Files[0].Hash + } + + if p.Class() == types.ClassOSPkg { + m.fillSrcPkg(pkg) + } + + return pkg, nil +} + +// pkgName returns the package name. +// PURL loses case-sensitivity (e.g. Go, Npm, PyPI), so we have to use an original package name. +func (m *Decoder) pkgName(pkg *ftypes.Package, c *core.Component) string { + p := c.PkgID.PURL + + // A name from PURL takes precedence for CocoaPods since it has subpath. + if c.PkgID.PURL.Type == packageurl.TypeCocoapods { + return pkg.Name + } + + if c.Group != "" { + if p.Type == packageurl.TypeMaven || p.Type == packageurl.TypeGradle { + return c.Group + ":" + c.Name + } + return c.Group + "/" + c.Name + } + return c.Name +} + +func (m *Decoder) fillSrcPkg(pkg *ftypes.Package) { + // Fill source package information for components in third-party SBOMs . + if pkg.SrcName == "" { + pkg.SrcName = pkg.Name + } + if pkg.SrcVersion == "" { + pkg.SrcVersion = pkg.Version + } + if pkg.SrcRelease == "" { + pkg.SrcRelease = pkg.Release + } + if pkg.SrcEpoch == 0 { + pkg.SrcEpoch = pkg.Epoch + } +} + +// addOSPkgs traverses relationships and adds OS packages +func (m *Decoder) addOSPkgs(sbom *types.SBOM) { + var pkgs []ftypes.Package + for _, rel := range m.bom.Relationships()[m.osID] { + pkg, ok := m.pkgs[rel.Dependency] + if !ok { + continue + } + pkgs = append(pkgs, *pkg) + delete(m.pkgs, rel.Dependency) // Delete the added package + } + if len(pkgs) == 0 { + return + } + sbom.Packages = []ftypes.PackageInfo{{Packages: pkgs}} +} + +// addLangPkgs traverses relationships and adds language-specific packages +func (m *Decoder) addLangPkgs(sbom *types.SBOM) { + for id, app := range m.apps { + for _, rel := range m.bom.Relationships()[id] { + pkg, ok := m.pkgs[rel.Dependency] + if !ok { + continue + } + app.Libraries = append(app.Libraries, *pkg) + delete(m.pkgs, rel.Dependency) // Delete the added package + } + sbom.Applications = append(sbom.Applications, *app) + } +} + +// addOrphanPkgs adds orphan packages. +// Orphan packages are packages that are not related to any components. +func (m *Decoder) addOrphanPkgs(sbom *types.SBOM) error { + osPkgMap := make(map[string]ftypes.Packages) + langPkgMap := make(map[ftypes.LangType]ftypes.Packages) + for _, pkg := range m.pkgs { + p := (*purl.PackageURL)(pkg.Identifier.PURL) + switch p.Class() { + case types.ClassOSPkg: + osPkgMap[p.Type] = append(osPkgMap[p.Type], *pkg) + case types.ClassLangPkg: + langType := p.LangType() + langPkgMap[langType] = append(langPkgMap[langType], *pkg) + } + } + + if len(osPkgMap) > 1 { + return xerrors.Errorf("multiple types of OS packages in SBOM are not supported (%q)", maps.Keys(osPkgMap)) + } + + // Add OS packages only when OS is detected. + for _, pkgs := range osPkgMap { + if sbom.Metadata.OS == nil || !sbom.Metadata.OS.Detected() { + log.Logger.Warn("Ignore the OS package as no OS is detected.") + break + } + + // TODO: mismatch between the OS and the packages should be rejected. + // e.g. OS: debian, Packages: rpm + sort.Sort(pkgs) + sbom.Packages = append(sbom.Packages, ftypes.PackageInfo{Packages: pkgs}) + + break // Just take the first element + } + + // Add language-specific packages + for pkgType, pkgs := range langPkgMap { + sort.Sort(pkgs) + sbom.Applications = append(sbom.Applications, ftypes.Application{ + Type: pkgType, + Libraries: pkgs, + }) + } + return nil +} diff --git a/pkg/sbom/io/encode.go b/pkg/sbom/io/encode.go new file mode 100644 index 000000000000..73c0d4fef3dc --- /dev/null +++ b/pkg/sbom/io/encode.go @@ -0,0 +1,343 @@ +package io + +import ( + "fmt" + "strconv" + + "github.com/package-url/packageurl-go" + "github.com/samber/lo" + "golang.org/x/xerrors" + + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/purl" + "github.com/aquasecurity/trivy/pkg/sbom/core" + "github.com/aquasecurity/trivy/pkg/types" +) + +type Encoder struct { + bom *core.BOM +} + +func NewEncoder() *Encoder { + return &Encoder{} +} + +func (m *Encoder) Encode(report types.Report) (*core.BOM, error) { + // Metadata component + root, err := m.rootComponent(report) + if err != nil { + return nil, xerrors.Errorf("failed to create root component: %w", err) + } + + m.bom = core.NewBOM() + m.bom.AddComponent(root) + + for _, result := range report.Results { + m.encodeResult(root, report.Metadata, result) + } + + // Components that do not have their own dependencies MUST be declared as empty elements within the graph. + if _, ok := m.bom.Relationships()[root.ID()]; !ok { + m.bom.AddRelationship(root, nil, "") + } + return m.bom, nil +} + +func (m *Encoder) rootComponent(r types.Report) (*core.Component, error) { + root := &core.Component{ + Root: true, + Name: r.ArtifactName, + } + + props := []core.Property{ + { + Name: core.PropertySchemaVersion, + Value: strconv.Itoa(r.SchemaVersion), + }, + } + + switch r.ArtifactType { + case ftypes.ArtifactContainerImage: + root.Type = core.TypeContainer + props = append(props, core.Property{ + Name: core.PropertyImageID, + Value: r.Metadata.ImageID, + }) + + p, err := purl.New(purl.TypeOCI, r.Metadata, ftypes.Package{}) + if err != nil { + return nil, xerrors.Errorf("failed to new package url for oci: %w", err) + } + if p != nil { + root.PkgID.PURL = p.Unwrap() + } + + case ftypes.ArtifactVM: + root.Type = core.TypeContainer + case ftypes.ArtifactFilesystem, ftypes.ArtifactRepository: + root.Type = core.TypeApplication + case ftypes.ArtifactCycloneDX: + return r.BOM.Root(), nil + } + + if r.Metadata.Size != 0 { + props = append(props, core.Property{ + Name: core.PropertySize, + Value: strconv.FormatInt(r.Metadata.Size, 10), + }) + } + + for _, d := range r.Metadata.RepoDigests { + props = append(props, core.Property{ + Name: core.PropertyRepoDigest, + Value: d, + }) + } + + for _, id := range r.Metadata.DiffIDs { + props = append(props, core.Property{ + Name: core.PropertyDiffID, + Value: id, + }) + } + + for _, tag := range r.Metadata.RepoTags { + props = append(props, core.Property{ + Name: core.PropertyRepoTag, + Value: tag, + }) + } + + root.Properties = filterProperties(props) + + return root, nil +} + +func (m *Encoder) encodeResult(root *core.Component, metadata types.Metadata, result types.Result) { + if result.Type == ftypes.NodePkg || result.Type == ftypes.PythonPkg || + result.Type == ftypes.GemSpec || result.Type == ftypes.Jar || result.Type == ftypes.CondaPkg { + // If a package is language-specific package that isn't associated with a lock file, + // it will be a dependency of a component under "metadata". + // e.g. + // Container component (alpine:3.15) ----------------------- #1 + // -> Library component (npm package, express-4.17.3) ---- #2 + // -> Library component (python package, django-4.0.2) --- #2 + // -> etc. + // ref. https://cyclonedx.org/use-cases/#inventory + + // Dependency graph from #1 to #2 + m.encodePackages(root, result) + } else if result.Class == types.ClassOSPkg || result.Class == types.ClassLangPkg { + // If a package is OS package, it will be a dependency of "Operating System" component. + // e.g. + // Container component (alpine:3.15) --------------------- #1 + // -> Operating System Component (Alpine Linux 3.15) --- #2 + // -> Library component (bash-4.12) ------------------ #3 + // -> Library component (vim-8.2) ------------------ #3 + // -> etc. + // + // Else if a package is language-specific package associated with a lock file, + // it will be a dependency of "Application" component. + // e.g. + // Container component (alpine:3.15) ------------------------ #1 + // -> Application component (/app/package-lock.json) ------ #2 + // -> Library component (npm package, express-4.17.3) --- #3 + // -> Library component (npm package, lodash-4.17.21) --- #3 + // -> etc. + + // #2 + appComponent := m.resultComponent(root, result, metadata.OS) + + // #3 + m.encodePackages(appComponent, result) + } +} + +func (m *Encoder) encodePackages(parent *core.Component, result types.Result) { + // Get dependency parents first + parents := ftypes.Packages(result.Packages).ParentDeps() + + // Group vulnerabilities by package ID + vulns := make(map[string][]core.Vulnerability) + for _, vuln := range result.Vulnerabilities { + v := m.vulnerability(vuln) + vulns[v.PkgID] = append(vulns[v.PkgID], v) + } + + // Convert packages into components and add them to the BOM + components := make(map[string]*core.Component, len(result.Packages)) + for i, pkg := range result.Packages { + pkgID := lo.Ternary(pkg.ID == "", fmt.Sprintf("%s@%s", pkg.Name, pkg.Version), pkg.ID) + result.Packages[i].ID = pkgID + + // Convert packages to components + c := m.component(result.Type, pkg) + components[pkgID] = c + + // Add a component + m.bom.AddComponent(c) + + // Add vulnerabilities + if vv := vulns[pkgID]; vv != nil { + m.bom.AddVulnerabilities(c, vv) + } + } + + // Build a dependency graph + for _, pkg := range result.Packages { + // Skip indirect dependencies + if pkg.Indirect && len(parents[pkg.ID]) != 0 { + continue + } + + directPkg := components[pkg.ID] + m.bom.AddRelationship(parent, directPkg, core.RelationshipContains) + + for _, dep := range pkg.DependsOn { + indirectPkg, ok := components[dep] + if !ok { + continue + } + m.bom.AddRelationship(directPkg, indirectPkg, core.RelationshipDependsOn) + } + + // Components that do not have their own dependencies MUST be declared as empty elements within the graph. + // TODO: Should check if the component has actually no dependencies or the dependency graph is not supported. + if len(pkg.DependsOn) == 0 { + m.bom.AddRelationship(directPkg, nil, "") + } + } +} + +func (m *Encoder) resultComponent(root *core.Component, r types.Result, osFound *ftypes.OS) *core.Component { + component := &core.Component{ + Name: r.Target, + Properties: []core.Property{ + { + Name: core.PropertyType, + Value: string(r.Type), + }, + { + Name: core.PropertyClass, + Value: string(r.Class), + }, + }, + } + + switch r.Class { + case types.ClassOSPkg: + if osFound != nil { + component.Name = string(osFound.Family) + component.Version = osFound.Name + } + component.Type = core.TypeOS + case types.ClassLangPkg: + component.Type = core.TypeApplication + } + + m.bom.AddRelationship(root, component, core.RelationshipContains) + return component +} + +func (*Encoder) component(pkgType ftypes.TargetType, pkg ftypes.Package) *core.Component { + name := pkg.Name + version := pkg.Version + var group string + // there are cases when we can't build purl + // e.g. local Go packages + if pu := pkg.Identifier.PURL; pu != nil { + version = pu.Version + // Use `group` field for GroupID and `name` for ArtifactID for java files + // https://github.com/aquasecurity/trivy/issues/4675 + // Use `group` field for npm scopes + // https://github.com/aquasecurity/trivy/issues/5908 + if pu.Type == packageurl.TypeMaven || pu.Type == packageurl.TypeNPM { + name = pu.Name + group = pu.Namespace + } + } + + properties := []core.Property{ + { + Name: core.PropertyPkgID, + Value: pkg.ID, + }, + { + Name: core.PropertyPkgType, + Value: string(pkgType), + }, + { + Name: core.PropertyFilePath, + Value: pkg.FilePath, + }, + { + Name: core.PropertySrcName, + Value: pkg.SrcName, + }, + { + Name: core.PropertySrcVersion, + Value: pkg.SrcVersion, + }, + { + Name: core.PropertySrcRelease, + Value: pkg.SrcRelease, + }, + { + Name: core.PropertySrcEpoch, + Value: strconv.Itoa(pkg.SrcEpoch), + }, + { + Name: core.PropertyModularitylabel, + Value: pkg.Modularitylabel, + }, + { + Name: core.PropertyLayerDigest, + Value: pkg.Layer.Digest, + }, + { + Name: core.PropertyLayerDiffID, + Value: pkg.Layer.DiffID, + }, + } + + var files []core.File + if pkg.FilePath != "" || pkg.Digest != "" { + files = append(files, core.File{ + Path: pkg.FilePath, + Hash: pkg.Digest, + }) + } + + return &core.Component{ + Type: core.TypeLibrary, + Name: name, + Group: group, + Version: version, + PkgID: core.PkgID{ + PURL: pkg.Identifier.PURL, + }, + Supplier: pkg.Maintainer, + Licenses: pkg.Licenses, + Files: files, + Properties: filterProperties(properties), + } +} + +func (*Encoder) vulnerability(vuln types.DetectedVulnerability) core.Vulnerability { + return core.Vulnerability{ + Vulnerability: vuln.Vulnerability, + ID: vuln.VulnerabilityID, + PkgID: lo.Ternary(vuln.PkgID == "", fmt.Sprintf("%s@%s", vuln.PkgName, vuln.InstalledVersion), vuln.PkgID), + PkgName: vuln.PkgName, + InstalledVersion: vuln.InstalledVersion, + FixedVersion: vuln.FixedVersion, + PrimaryURL: vuln.PrimaryURL, + DataSource: vuln.DataSource, + } +} + +func filterProperties(props []core.Property) []core.Property { + return lo.Filter(props, func(property core.Property, _ int) bool { + return !(property.Value == "" || (property.Name == core.PropertySrcEpoch && property.Value == "0")) + }) +} diff --git a/pkg/sbom/io/encode_test.go b/pkg/sbom/io/encode_test.go new file mode 100644 index 000000000000..5c2af5b54d9f --- /dev/null +++ b/pkg/sbom/io/encode_test.go @@ -0,0 +1,339 @@ +package io_test + +import ( + dtypes "github.com/aquasecurity/trivy-db/pkg/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/sbom/core" + sbomio "github.com/aquasecurity/trivy/pkg/sbom/io" + "github.com/aquasecurity/trivy/pkg/types" + "github.com/aquasecurity/trivy/pkg/uuid" + "github.com/package-url/packageurl-go" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "testing" +) + +func TestEncoder_Encode(t *testing.T) { + tests := []struct { + name string + report types.Report + wantComponents map[uuid.UUID]*core.Component + wantRels map[uuid.UUID][]core.Relationship + wantVulns map[uuid.UUID][]core.Vulnerability + wantErr string + }{ + { + name: "container image", + report: types.Report{ + SchemaVersion: 2, + ArtifactName: "debian:12", + ArtifactType: ftypes.ArtifactContainerImage, + Metadata: types.Metadata{ + OS: &ftypes.OS{ + Family: ftypes.Debian, + Name: "12", + }, + RepoTags: []string{ + "debian:latest", + "debian:12", + }, + RepoDigests: []string{ + "debian@sha256:4482958b4461ff7d9fabc24b3a9ab1e9a2c85ece07b2db1840c7cbc01d053e90", + }, + }, + Results: []types.Result{ + { + Target: "debian:12", + Type: ftypes.Debian, + Class: types.ClassOSPkg, + Packages: []ftypes.Package{ + { + ID: "libc6@2.37-15.1", + Name: "libc6", + Version: "2.37-15.1", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeDebian, + Name: "libc6", + Version: "2.37-15.1", + }, + }, + }, + { + ID: "curl@7.50.3-1", + Name: "curl", + Version: "7.50.3-1", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeDebian, + Name: "curl", + Version: "7.50.3-1", + }, + }, + DependsOn: []string{ + "libc6@2.37-15.1", + }, + }, + }, + Vulnerabilities: []types.DetectedVulnerability{ + { + PkgName: "curl", + PkgID: "curl@7.50.3-1", + VulnerabilityID: "CVE-2021-22876", + InstalledVersion: "7.50.3-1", + FixedVersion: "7.50.3-1+deb9u1", + Vulnerability: dtypes.Vulnerability{ + Severity: "HIGH", + }, + }, + }, + }, + { + Target: "Java", + Type: ftypes.Jar, + Class: types.ClassLangPkg, + Packages: []ftypes.Package{ + { + ID: "org.apache.xmlgraphics/batik-anim:1.9.1", + Name: "org.apache.xmlgraphics/batik-anim", + Version: "1.9.1", + FilePath: "/app/batik-anim-1.9.1.jar", + Identifier: ftypes.PkgIdentifier{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "org.apache.xmlgraphics", + Name: "batik-anim", + Version: "1.9.1", + }, + }, + }, + }, + }, + }, + }, + wantComponents: map[uuid.UUID]*core.Component{ + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000001"): { + Type: core.TypeContainer, + Name: "debian:12", + Root: true, + PkgID: core.PkgID{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeOCI, + Name: "debian", + Version: "sha256:4482958b4461ff7d9fabc24b3a9ab1e9a2c85ece07b2db1840c7cbc01d053e90", + Qualifiers: packageurl.Qualifiers{ + { + Key: "repository_url", + Value: "index.docker.io/library/debian", + }, + }, + }, + BOMRef: "pkg:oci/debian@sha256%3A4482958b4461ff7d9fabc24b3a9ab1e9a2c85ece07b2db1840c7cbc01d053e90?repository_url=index.docker.io%2Flibrary%2Fdebian", + }, + Properties: []core.Property{ + { + Name: core.PropertyRepoDigest, + Value: "debian@sha256:4482958b4461ff7d9fabc24b3a9ab1e9a2c85ece07b2db1840c7cbc01d053e90", + }, + { + Name: core.PropertyRepoTag, + Value: "debian:12", + }, + { + Name: core.PropertyRepoTag, + Value: "debian:latest", + }, + { + Name: core.PropertySchemaVersion, + Value: "2", + }, + }, + }, + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000002"): { + Type: core.TypeOS, + Name: "debian", + Version: "12", + Properties: []core.Property{ + { + Name: core.PropertyClass, + Value: "os-pkgs", + }, + { + Name: core.PropertyType, + Value: "debian", + }, + }, + PkgID: core.PkgID{ + BOMRef: "3ff14136-e09f-4df9-80ea-000000000002", + }, + }, + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000003"): { + Type: core.TypeLibrary, + Name: "libc6", + Version: "2.37-15.1", + Properties: []core.Property{ + { + Name: core.PropertyPkgID, + Value: "libc6@2.37-15.1", + }, + { + Name: core.PropertyPkgType, + Value: "debian", + }, + }, + PkgID: core.PkgID{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeDebian, + Name: "libc6", + Version: "2.37-15.1", + }, + BOMRef: "pkg:deb/libc6@2.37-15.1", + }, + }, + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000004"): { + Type: core.TypeLibrary, + Name: "curl", + Version: "7.50.3-1", + Properties: []core.Property{ + { + Name: core.PropertyPkgID, + Value: "curl@7.50.3-1", + }, + { + Name: core.PropertyPkgType, + Value: "debian", + }, + }, + PkgID: core.PkgID{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeDebian, + Name: "curl", + Version: "7.50.3-1", + }, + BOMRef: "pkg:deb/curl@7.50.3-1", + }, + }, + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000005"): { + Type: core.TypeLibrary, + Group: "org.apache.xmlgraphics", + Name: "batik-anim", + Version: "1.9.1", + Files: []core.File{ + { + Path: "/app/batik-anim-1.9.1.jar", + }, + }, + Properties: []core.Property{ + { + Name: core.PropertyFilePath, + Value: "/app/batik-anim-1.9.1.jar", + }, + { + Name: core.PropertyPkgID, + Value: "org.apache.xmlgraphics/batik-anim:1.9.1", + }, + { + Name: core.PropertyPkgType, + Value: "jar", + }, + }, + PkgID: core.PkgID{ + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "org.apache.xmlgraphics", + Name: "batik-anim", + Version: "1.9.1", + }, + BOMRef: "pkg:maven/org.apache.xmlgraphics/batik-anim@1.9.1", + }, + }, + }, + wantRels: map[uuid.UUID][]core.Relationship{ + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000001"): { + { + Dependency: uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000002"), + Type: core.RelationshipContains, + }, + { + Dependency: uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000005"), + Type: core.RelationshipContains, + }, + }, + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000002"): { + { + Dependency: uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000003"), + Type: core.RelationshipContains, + }, + { + Dependency: uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000004"), + Type: core.RelationshipContains, + }, + }, + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000003"): nil, + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000004"): { + { + Dependency: uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000003"), + Type: core.RelationshipDependsOn, + }, + }, + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000005"): nil, + }, + wantVulns: map[uuid.UUID][]core.Vulnerability{ + uuid.MustParse("3ff14136-e09f-4df9-80ea-000000000004"): { + { + ID: "CVE-2021-22876", + PkgID: "curl@7.50.3-1", + PkgName: "curl", + InstalledVersion: "7.50.3-1", + FixedVersion: "7.50.3-1+deb9u1", + Vulnerability: dtypes.Vulnerability{ + Severity: "HIGH", + }, + }, + }, + }, + }, + { + name: "invalid digest", + report: types.Report{ + SchemaVersion: 2, + ArtifactName: "debian:12", + ArtifactType: ftypes.ArtifactContainerImage, + Metadata: types.Metadata{ + OS: &ftypes.OS{ + Family: ftypes.Debian, + Name: "12", + }, + RepoTags: []string{ + "debian:12", + }, + RepoDigests: []string{ + "debian@sha256:123", + }, + }, + }, + wantErr: "failed to parse digest", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + uuid.SetFakeUUID(t, "3ff14136-e09f-4df9-80ea-%012d") + + got, err := sbomio.NewEncoder().Encode(tt.report) + if tt.wantErr != "" { + require.ErrorContains(t, err, tt.wantErr) + return + } + require.NoError(t, err) + + require.Len(t, got.Components(), len(tt.wantComponents)) + for id, want := range tt.wantComponents { + assert.EqualExportedValues(t, *want, *got.Components()[id]) + } + + assert.Equal(t, tt.wantRels, got.Relationships()) + assert.Equal(t, tt.wantVulns, got.Vulnerabilities()) + }) + } +} diff --git a/pkg/sbom/sbom.go b/pkg/sbom/sbom.go index 481f18da3639..2d8d74b267a0 100644 --- a/pkg/sbom/sbom.go +++ b/pkg/sbom/sbom.go @@ -11,7 +11,9 @@ import ( "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/attestation" + "github.com/aquasecurity/trivy/pkg/sbom/core" "github.com/aquasecurity/trivy/pkg/sbom/cyclonedx" + sbomio "github.com/aquasecurity/trivy/pkg/sbom/io" "github.com/aquasecurity/trivy/pkg/sbom/spdx" "github.com/aquasecurity/trivy/pkg/types" ) @@ -181,20 +183,21 @@ func decodeAttestCycloneDXJSONFormat(r io.ReadSeeker) (Format, bool) { func Decode(f io.Reader, format Format) (types.SBOM, error) { var ( v interface{} - bom types.SBOM + bom = core.NewBOM() + sbom types.SBOM decoder interface{ Decode(any) error } ) switch format { case FormatCycloneDXJSON: - v = &cyclonedx.BOM{SBOM: &bom} + v = &cyclonedx.BOM{BOM: bom} decoder = json.NewDecoder(f) case FormatAttestCycloneDXJSON: // dsse envelope // => in-toto attestation // => CycloneDX JSON v = &attestation.Statement{ - Predicate: &cyclonedx.BOM{SBOM: &bom}, + Predicate: &cyclonedx.BOM{BOM: bom}, } decoder = json.NewDecoder(f) case FormatLegacyCosignAttestCycloneDXJSON: @@ -204,26 +207,34 @@ func Decode(f io.Reader, format Format) (types.SBOM, error) { // => CycloneDX JSON v = &attestation.Statement{ Predicate: &attestation.CosignPredicate{ - Data: &cyclonedx.BOM{SBOM: &bom}, + Data: &cyclonedx.BOM{BOM: bom}, }, } decoder = json.NewDecoder(f) case FormatSPDXJSON: - v = &spdx.SPDX{SBOM: &bom} + v = &spdx.SPDX{SBOM: &sbom} decoder = json.NewDecoder(f) case FormatSPDXTV: - v = &spdx.SPDX{SBOM: &bom} + v = &spdx.SPDX{SBOM: &sbom} decoder = spdx.NewTVDecoder(f) - default: return types.SBOM{}, xerrors.Errorf("%s scanning is not yet supported", format) } - // Decode a file content into sbom.SBOM + // Decode a file content into core.BOM if err := decoder.Decode(v); err != nil { return types.SBOM{}, xerrors.Errorf("failed to decode: %w", err) } - return bom, nil + // TODO: use BOM in SPDX + if format == FormatSPDXJSON || format == FormatSPDXTV { + return sbom, nil + } + + if err := sbomio.NewDecoder(bom).Decode(&sbom); err != nil { + return types.SBOM{}, xerrors.Errorf("failed to decode: %w", err) + } + + return sbom, nil } diff --git a/pkg/sbom/spdx/marshal.go b/pkg/sbom/spdx/marshal.go index b88f92c1c23f..ceb9a1ae24ce 100644 --- a/pkg/sbom/spdx/marshal.go +++ b/pkg/sbom/spdx/marshal.go @@ -247,7 +247,7 @@ func (m *Marshaler) rootPackage(r types.Report, pkgDownloadLocation string) (*sp if p, err := purl.New(purl.TypeOCI, r.Metadata, ftypes.Package{}); err != nil { return nil, xerrors.Errorf("failed to new package url for oci: %w", err) } else if p != nil { - externalReferences = append(externalReferences, purlExternalReference(p.ToString())) + externalReferences = append(externalReferences, purlExternalReference(p.String())) } if r.Metadata.ImageID != "" { diff --git a/pkg/sbom/spdx/unmarshal.go b/pkg/sbom/spdx/unmarshal.go index 1723476a323d..718bdd608886 100644 --- a/pkg/sbom/spdx/unmarshal.go +++ b/pkg/sbom/spdx/unmarshal.go @@ -105,7 +105,7 @@ func (s *SPDX) unmarshal(spdxDocument *spdx.Document) error { switch { // Relationship: root package => OS case isOperatingSystem(pkgB.PackageSPDXIdentifier): - s.SBOM.OS = parseOS(*pkgB) + s.SBOM.Metadata.OS = parseOS(*pkgB) delete(orphanPkgs, pkgB.PackageSPDXIdentifier) // Relationship: OS => OS package case isOperatingSystem(pkgA.PackageSPDXIdentifier): @@ -157,8 +157,6 @@ func (s *SPDX) unmarshal(spdxDocument *spdx.Document) error { return err } - // Keep the original document - s.SPDX = spdxDocument return nil } @@ -239,8 +237,8 @@ func initApplication(pkg spdx.Package) *ftypes.Application { return app } -func parseOS(pkg spdx.Package) ftypes.OS { - return ftypes.OS{ +func parseOS(pkg spdx.Package) *ftypes.OS { + return &ftypes.OS{ Family: ftypes.OSType(pkg.PackageName), Name: pkg.PackageVersion, } diff --git a/pkg/sbom/spdx/unmarshal_test.go b/pkg/sbom/spdx/unmarshal_test.go index 2d6ee258c378..cee50461508e 100644 --- a/pkg/sbom/spdx/unmarshal_test.go +++ b/pkg/sbom/spdx/unmarshal_test.go @@ -26,9 +26,11 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { name: "happy path", inputFile: "testdata/happy/bom.json", want: types.SBOM{ - OS: ftypes.OS{ - Family: "alpine", - Name: "3.16.0", + Metadata: types.Metadata{ + OS: &ftypes.OS{ + Family: "alpine", + Name: "3.16.0", + }, }, Packages: []ftypes.PackageInfo{ { @@ -298,9 +300,11 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { name: "happy path only os component", inputFile: "testdata/happy/os-only-bom.json", want: types.SBOM{ - OS: ftypes.OS{ - Family: "alpine", - Name: "3.16.0", + Metadata: types.Metadata{ + OS: &ftypes.OS{ + Family: "alpine", + Name: "3.16.0", + }, }, }, }, @@ -331,7 +335,7 @@ func TestUnmarshaler_Unmarshal(t *testing.T) { } // Not compare the SPDX field - v.SPDX = nil + v.BOM = nil sort.Slice(v.Applications, func(i, j int) bool { return v.Applications[i].Type < v.Applications[j].Type diff --git a/pkg/scanner/scan.go b/pkg/scanner/scan.go index 82e900024acb..4cf647b66d13 100644 --- a/pkg/scanner/scan.go +++ b/pkg/scanner/scan.go @@ -186,8 +186,8 @@ func (s Scanner) ScanArtifact(ctx context.Context, options types.ScanOptions) (t RepoDigests: artifactInfo.ImageMetadata.RepoDigests, ImageConfig: artifactInfo.ImageMetadata.ConfigFile, }, - CycloneDX: artifactInfo.CycloneDX, - Results: results, + Results: results, + BOM: artifactInfo.BOM, }, nil } diff --git a/pkg/types/report.go b/pkg/types/report.go index 6b8aae6940b6..e6c96d9564eb 100644 --- a/pkg/types/report.go +++ b/pkg/types/report.go @@ -6,6 +6,7 @@ import ( v1 "github.com/google/go-containerregistry/pkg/v1" // nolint: goimports ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/sbom/core" ) // Report represents a scan result @@ -17,8 +18,8 @@ type Report struct { Metadata Metadata `json:",omitempty"` Results Results `json:",omitempty"` - // SBOM - CycloneDX *ftypes.CycloneDX `json:"-"` // Just for internal usage, not exported in JSON + // parsed SBOM + BOM *core.BOM `json:"-"` // Just for internal usage, not exported in JSON } // Metadata represents a metadata of artifact diff --git a/pkg/types/sbom.go b/pkg/types/sbom.go index 61d21b7d7867..82f6139e48f7 100644 --- a/pkg/types/sbom.go +++ b/pkg/types/sbom.go @@ -1,21 +1,20 @@ package types import ( - stypes "github.com/spdx/tools-golang/spdx" - - "github.com/aquasecurity/trivy/pkg/fanal/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/sbom/core" ) +type SBOMSource = string + type SBOM struct { - OS types.OS - Packages []types.PackageInfo - Applications []types.Application + Metadata Metadata - CycloneDX *types.CycloneDX - SPDX *stypes.Document -} + Packages []ftypes.PackageInfo + Applications []ftypes.Application -type SBOMSource = string + BOM *core.BOM +} const ( SBOMSourceOCI = SBOMSource("oci") diff --git a/pkg/uuid/uuid.go b/pkg/uuid/uuid.go index 2841b7f099bc..3e3ad456a805 100644 --- a/pkg/uuid/uuid.go +++ b/pkg/uuid/uuid.go @@ -1,3 +1,5 @@ +//go:build !tinygo.wasm + package uuid import ( @@ -7,7 +9,13 @@ import ( "github.com/google/uuid" ) -var newUUID func() uuid.UUID = uuid.New +type UUID = uuid.UUID + +var ( + newUUID func() uuid.UUID = uuid.New + Nil = uuid.Nil + MustParse = uuid.MustParse +) // SetFakeUUID sets a fake UUID for testing. // The 'format' is used to generate a fake UUID and @@ -23,6 +31,6 @@ func SetFakeUUID(t *testing.T, format string) { }) } -func New() uuid.UUID { +func New() UUID { return newUUID() } diff --git a/pkg/uuid/uuid_tinygo.go b/pkg/uuid/uuid_tinygo.go new file mode 100644 index 000000000000..fe328cea9282 --- /dev/null +++ b/pkg/uuid/uuid_tinygo.go @@ -0,0 +1,13 @@ +//go:build tinygo.wasm + +package uuid + +// TinyGo doesn't work with github.com/google/uuid + +type UUID string + +func (UUID) String() string { return "" } + +const Nil = "" + +func New() UUID { return "" } diff --git a/pkg/vex/cyclonedx.go b/pkg/vex/cyclonedx.go index f1a860fe7ae7..a956703da3ae 100644 --- a/pkg/vex/cyclonedx.go +++ b/pkg/vex/cyclonedx.go @@ -5,13 +5,13 @@ import ( "github.com/samber/lo" "go.uber.org/zap" - ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/sbom/core" "github.com/aquasecurity/trivy/pkg/types" ) type CycloneDX struct { - sbom *ftypes.CycloneDX + sbom *core.BOM statements []Statement logger *zap.SugaredLogger } @@ -23,7 +23,7 @@ type Statement struct { Justification string } -func newCycloneDX(cdxSBOM *ftypes.CycloneDX, vex *cdx.BOM) *CycloneDX { +func newCycloneDX(sbom *core.BOM, vex *cdx.BOM) *CycloneDX { var stmts []Statement for _, vuln := range lo.FromPtr(vex.Vulnerabilities) { affects := lo.Map(lo.FromPtr(vuln.Affects), func(item cdx.Affects, index int) string { @@ -39,7 +39,7 @@ func newCycloneDX(cdxSBOM *ftypes.CycloneDX, vex *cdx.BOM) *CycloneDX { }) } return &CycloneDX{ - sbom: cdxSBOM, + sbom: sbom, statements: stmts, logger: log.Logger.With(zap.String("VEX format", "CycloneDX")), } diff --git a/pkg/vex/vex.go b/pkg/vex/vex.go index 61a41a960246..dc2c118b56bc 100644 --- a/pkg/vex/vex.go +++ b/pkg/vex/vex.go @@ -11,6 +11,7 @@ import ( "github.com/sirupsen/logrus" "golang.org/x/xerrors" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/sbom" "github.com/aquasecurity/trivy/pkg/sbom/cyclonedx" "github.com/aquasecurity/trivy/pkg/types" @@ -66,10 +67,10 @@ func decodeCycloneDXJSON(r io.ReadSeeker, report types.Report) (VEX, error) { if err != nil { return nil, xerrors.Errorf("json decode error: %w", err) } - if report.CycloneDX == nil { + if report.ArtifactType != ftypes.ArtifactCycloneDX { return nil, xerrors.New("CycloneDX VEX can be used with CycloneDX SBOM") } - return newCycloneDX(report.CycloneDX, vex), nil + return newCycloneDX(report.BOM, vex), nil } func decodeOpenVEX(r io.ReadSeeker) (VEX, error) { diff --git a/pkg/vex/vex_test.go b/pkg/vex/vex_test.go index 08724884074d..d591ccfdc6c6 100644 --- a/pkg/vex/vex_test.go +++ b/pkg/vex/vex_test.go @@ -1,6 +1,7 @@ package vex_test import ( + "github.com/aquasecurity/trivy/pkg/sbom/core" "os" "testing" @@ -115,7 +116,8 @@ func TestVEX_Filter(t *testing.T) { fields: fields{ filePath: "testdata/cyclonedx.json", report: types.Report{ - CycloneDX: &ftypes.CycloneDX{ + ArtifactType: ftypes.ArtifactCycloneDX, + BOM: &core.BOM{ SerialNumber: "urn:uuid:3e671687-395b-41f5-a30f-a58921a69b79", Version: 1, }, @@ -197,7 +199,8 @@ func TestVEX_Filter(t *testing.T) { fields: fields{ filePath: "testdata/cyclonedx.json", report: types.Report{ - CycloneDX: &ftypes.CycloneDX{ + ArtifactType: ftypes.ArtifactCycloneDX, + BOM: &core.BOM{ SerialNumber: "urn:uuid:wrong", Version: 1, },