From e50f065481ae4ef212402b7d82b5a294b3675f38 Mon Sep 17 00:00:00 2001 From: jeffrey-zhang Date: Tue, 27 Jul 2021 17:13:00 -0400 Subject: [PATCH 01/10] add ruby gems specific parsing and unit tests --- lib/cyclonedx/base.rb | 15 +- lib/cyclonedx/report_ruby_gems_cyclonedx.rb | 23 +++ lib/salus.rb | 1 + .../report_ruby_gems_cyclonedx_spec.rb | 155 ++++++++++++++++++ 4 files changed, 182 insertions(+), 12 deletions(-) create mode 100644 spec/lib/cyclonedx/report_ruby_gems_cyclonedx_spec.rb diff --git a/lib/cyclonedx/base.rb b/lib/cyclonedx/base.rb index 21891145..437468c6 100644 --- a/lib/cyclonedx/base.rb +++ b/lib/cyclonedx/base.rb @@ -24,18 +24,9 @@ def build_metadata # Returns the 'components' object for a supported/unsupported scanner's report def build_components_object components = [] - @scan_report.info[:dependencies].each do |dependency| - component = { - "bom-ref": "", - "type": DEFAULT_COMPONENT_TYPE, - "group": "", - "name": dependency[:name], - "version": "", - "purl": "" - } - - # TODO: Add specific component parsing for individual scanners - components << component + info = @scan_report.to_h.fetch(:info) + info[:dependencies].each do |dependency| + components << parse_dependency(dependency) end components end diff --git a/lib/cyclonedx/report_ruby_gems_cyclonedx.rb b/lib/cyclonedx/report_ruby_gems_cyclonedx.rb index 840f7ee2..090821ca 100644 --- a/lib/cyclonedx/report_ruby_gems_cyclonedx.rb +++ b/lib/cyclonedx/report_ruby_gems_cyclonedx.rb @@ -1,7 +1,30 @@ module Cyclonedx class ReportRubyGems < Base + DEFAULT_COMPONENT_TYPE = "library".freeze + def initialize(scan_report) super(scan_report) end + + def parse_dependency(dependency) + { + "bom-ref": "", + "type": DEFAULT_COMPONENT_TYPE, + "group": "", # TODO: add group or domain name of the publisher + "name": dependency[:name], + "version": dependency[:version], + "purl": "pkg:#{dependency[:type]}/#{dependency[:name]}@#{dependency[:version]}", + "properties": [ + { + "key": "source", + "value": dependency[:source] + }, + { + "key": "dependency_file", + "value": dependency[:dependency_file] + } + ] + } + end end end diff --git a/lib/salus.rb b/lib/salus.rb index daa10b4f..6ba65e2b 100644 --- a/lib/salus.rb +++ b/lib/salus.rb @@ -8,6 +8,7 @@ require 'salus/processor' require 'salus/plugin_manager' require 'sarif/sarif_report' +require 'cyclonedx/report' module Salus VERSION = '2.11.12'.freeze diff --git a/spec/lib/cyclonedx/report_ruby_gems_cyclonedx_spec.rb b/spec/lib/cyclonedx/report_ruby_gems_cyclonedx_spec.rb new file mode 100644 index 00000000..cb14d104 --- /dev/null +++ b/spec/lib/cyclonedx/report_ruby_gems_cyclonedx_spec.rb @@ -0,0 +1,155 @@ +require_relative '../../spec_helper' +require 'json' + +describe Cyclonedx::ReportRubyGems do + describe "#run" do + it 'should report all the deps in the Gemfile if Gemfile.lock is absent in cyclonedx' do + repo = Salus::Repo.new('spec/fixtures/report_ruby_gems/gemfile_only') + scanner = Salus::Scanners::ReportRubyGems.new(repository: repo, config: {}) + scanner.run + + ruby_cyclonedx = Cyclonedx::ReportRubyGems.new(scanner.report) + expect(ruby_cyclonedx.build_components_object).to match_array( + [ + { + "bom-ref": "", + "type": "library", + "group": "", + "name": "kibana_url", + "version": "~> 1.0", + "purl": "pkg:gem/kibana_url@~> 1.0", + "properties": [ + { + "key": "source", + "value": "https://rubygems.org/" + }, + { + "key": "dependency_file", + "value": "Gemfile" + } + ] + }, + { + "bom-ref": "", + "type": "library", + "group": "", + "name": "rails", + "version": ">= 0", + "purl": "pkg:gem/rails@>= 0", + "properties": [ + { + "key": "source", + "value": "https://rubygems.org/" + }, + { + "key": "dependency_file", + "value": "Gemfile" + } + ] + }, + { + "bom-ref": "", + "type": "library", + "group": "", + "name": "master_lock", + "version": ">= 0", + "purl": "pkg:gem/master_lock@>= 0", + "properties": [ + { + "key": "source", + "value": "git@github.com:coinbase/master_lock.git" + }, + { + "key": "dependency_file", + "value": "Gemfile" + } + ] + } + ] + ) + end + + it 'should report all deps in Gemfile.lock in cyclonedx' do + repo = Salus::Repo.new('spec/fixtures/report_ruby_gems/lockfile') + scanner = Salus::Scanners::ReportRubyGems.new(repository: repo, config: {}) + scanner.run + + ruby_cyclonedx = Cyclonedx::ReportRubyGems.new(scanner.report) + expected = [ + { + "bom-ref": "", + "type": "library", + "group": "", + "name": "actioncable", + "version": "5.1.2", + "purl": "pkg:gem/actioncable@5.1.2", + "properties": [ + { + "key": "source", + "value": "rubygems repository https://rubygems.org/ or installed locally" + }, + { + "key": "dependency_file", + "value": "Gemfile.lock" + } + ] + }, + { + "bom-ref": "", + "type": "library", + "group": "", + "name": "actionmailer", + "version": "5.1.2", + "purl": "pkg:gem/actionmailer@5.1.2", + "properties": [ + { + "key": "source", + "value": "rubygems repository https://rubygems.org/ or installed locally" + }, + { + "key": "dependency_file", + "value": "Gemfile.lock" + } + ] + }, + { + "bom-ref": "", + "type": "library", + "group": "", + "name": "kibana_url", + "version": "1.0.1", + "purl": "pkg:gem/kibana_url@1.0.1", + "properties": [ + { + "key": "source", + "value": "rubygems repository https://rubygems.org/ or installed locally" + }, + { + "key": "dependency_file", + "value": "Gemfile.lock" + } + ] + }, + { + "bom-ref": "", + "type": "library", + "group": "", + "name": "master_lock", + "version": "0.9.1", + "purl": "pkg:gem/master_lock@0.9.1", + "properties": [ + { + "key": "source", + "value": "git@github.com:coinbase/master_lock.git" + }, + { + "key": "dependency_file", + "value": "Gemfile.lock" + } + ] + } + ] + expect(ruby_cyclonedx.build_components_object).to include(*expected) + end + end +end From 103d7dea2b9bc70adbe97b07a97b7aabbe22eae1 Mon Sep 17 00:00:00 2001 From: jeffrey-zhang Date: Wed, 28 Jul 2021 17:45:39 -0400 Subject: [PATCH 02/10] add in versioning for purl ruby --- lib/cyclonedx/report_ruby_gems_cyclonedx.rb | 12 +++++++++++- .../lib/cyclonedx/report_ruby_gems_cyclonedx_spec.rb | 6 +++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/cyclonedx/report_ruby_gems_cyclonedx.rb b/lib/cyclonedx/report_ruby_gems_cyclonedx.rb index 090821ca..a6b90798 100644 --- a/lib/cyclonedx/report_ruby_gems_cyclonedx.rb +++ b/lib/cyclonedx/report_ruby_gems_cyclonedx.rb @@ -13,7 +13,7 @@ def parse_dependency(dependency) "group": "", # TODO: add group or domain name of the publisher "name": dependency[:name], "version": dependency[:version], - "purl": "pkg:#{dependency[:type]}/#{dependency[:name]}@#{dependency[:version]}", + "purl": "pkg:#{dependency[:type]}/#{dependency[:name]}#{version_string(dependency)}", "properties": [ { "key": "source", @@ -26,5 +26,15 @@ def parse_dependency(dependency) ] } end + + # Return version string to be used in purl + def version_string(dependency) + if dependency[:dependency_file] == 'Gemfile.lock' + # Return empty string if concrete dependency version specified in Gemfile.lock + "@#{dependency[:version]}" + else + "" + end + end end end diff --git a/spec/lib/cyclonedx/report_ruby_gems_cyclonedx_spec.rb b/spec/lib/cyclonedx/report_ruby_gems_cyclonedx_spec.rb index cb14d104..a98a0b5a 100644 --- a/spec/lib/cyclonedx/report_ruby_gems_cyclonedx_spec.rb +++ b/spec/lib/cyclonedx/report_ruby_gems_cyclonedx_spec.rb @@ -17,7 +17,7 @@ "group": "", "name": "kibana_url", "version": "~> 1.0", - "purl": "pkg:gem/kibana_url@~> 1.0", + "purl": "pkg:gem/kibana_url", "properties": [ { "key": "source", @@ -35,7 +35,7 @@ "group": "", "name": "rails", "version": ">= 0", - "purl": "pkg:gem/rails@>= 0", + "purl": "pkg:gem/rails", "properties": [ { "key": "source", @@ -53,7 +53,7 @@ "group": "", "name": "master_lock", "version": ">= 0", - "purl": "pkg:gem/master_lock@>= 0", + "purl": "pkg:gem/master_lock", "properties": [ { "key": "source", From 195dda1c1b56f80afd0247b7909be6b6f60e4b41 Mon Sep 17 00:00:00 2001 From: jeffrey-zhang Date: Tue, 27 Jul 2021 17:13:00 -0400 Subject: [PATCH 03/10] add ruby gems specific parsing and unit tests --- lib/cyclonedx/base.rb | 15 +- lib/cyclonedx/report_ruby_gems_cyclonedx.rb | 23 +++ lib/salus.rb | 1 + .../report_ruby_gems_cyclonedx_spec.rb | 155 ++++++++++++++++++ 4 files changed, 182 insertions(+), 12 deletions(-) create mode 100644 spec/lib/cyclonedx/report_ruby_gems_cyclonedx_spec.rb diff --git a/lib/cyclonedx/base.rb b/lib/cyclonedx/base.rb index 21891145..437468c6 100644 --- a/lib/cyclonedx/base.rb +++ b/lib/cyclonedx/base.rb @@ -24,18 +24,9 @@ def build_metadata # Returns the 'components' object for a supported/unsupported scanner's report def build_components_object components = [] - @scan_report.info[:dependencies].each do |dependency| - component = { - "bom-ref": "", - "type": DEFAULT_COMPONENT_TYPE, - "group": "", - "name": dependency[:name], - "version": "", - "purl": "" - } - - # TODO: Add specific component parsing for individual scanners - components << component + info = @scan_report.to_h.fetch(:info) + info[:dependencies].each do |dependency| + components << parse_dependency(dependency) end components end diff --git a/lib/cyclonedx/report_ruby_gems_cyclonedx.rb b/lib/cyclonedx/report_ruby_gems_cyclonedx.rb index 840f7ee2..090821ca 100644 --- a/lib/cyclonedx/report_ruby_gems_cyclonedx.rb +++ b/lib/cyclonedx/report_ruby_gems_cyclonedx.rb @@ -1,7 +1,30 @@ module Cyclonedx class ReportRubyGems < Base + DEFAULT_COMPONENT_TYPE = "library".freeze + def initialize(scan_report) super(scan_report) end + + def parse_dependency(dependency) + { + "bom-ref": "", + "type": DEFAULT_COMPONENT_TYPE, + "group": "", # TODO: add group or domain name of the publisher + "name": dependency[:name], + "version": dependency[:version], + "purl": "pkg:#{dependency[:type]}/#{dependency[:name]}@#{dependency[:version]}", + "properties": [ + { + "key": "source", + "value": dependency[:source] + }, + { + "key": "dependency_file", + "value": dependency[:dependency_file] + } + ] + } + end end end diff --git a/lib/salus.rb b/lib/salus.rb index 3420dc7b..47b69531 100644 --- a/lib/salus.rb +++ b/lib/salus.rb @@ -8,6 +8,7 @@ require 'salus/processor' require 'salus/plugin_manager' require 'sarif/sarif_report' +require 'cyclonedx/report' module Salus VERSION = '2.11.13'.freeze diff --git a/spec/lib/cyclonedx/report_ruby_gems_cyclonedx_spec.rb b/spec/lib/cyclonedx/report_ruby_gems_cyclonedx_spec.rb new file mode 100644 index 00000000..cb14d104 --- /dev/null +++ b/spec/lib/cyclonedx/report_ruby_gems_cyclonedx_spec.rb @@ -0,0 +1,155 @@ +require_relative '../../spec_helper' +require 'json' + +describe Cyclonedx::ReportRubyGems do + describe "#run" do + it 'should report all the deps in the Gemfile if Gemfile.lock is absent in cyclonedx' do + repo = Salus::Repo.new('spec/fixtures/report_ruby_gems/gemfile_only') + scanner = Salus::Scanners::ReportRubyGems.new(repository: repo, config: {}) + scanner.run + + ruby_cyclonedx = Cyclonedx::ReportRubyGems.new(scanner.report) + expect(ruby_cyclonedx.build_components_object).to match_array( + [ + { + "bom-ref": "", + "type": "library", + "group": "", + "name": "kibana_url", + "version": "~> 1.0", + "purl": "pkg:gem/kibana_url@~> 1.0", + "properties": [ + { + "key": "source", + "value": "https://rubygems.org/" + }, + { + "key": "dependency_file", + "value": "Gemfile" + } + ] + }, + { + "bom-ref": "", + "type": "library", + "group": "", + "name": "rails", + "version": ">= 0", + "purl": "pkg:gem/rails@>= 0", + "properties": [ + { + "key": "source", + "value": "https://rubygems.org/" + }, + { + "key": "dependency_file", + "value": "Gemfile" + } + ] + }, + { + "bom-ref": "", + "type": "library", + "group": "", + "name": "master_lock", + "version": ">= 0", + "purl": "pkg:gem/master_lock@>= 0", + "properties": [ + { + "key": "source", + "value": "git@github.com:coinbase/master_lock.git" + }, + { + "key": "dependency_file", + "value": "Gemfile" + } + ] + } + ] + ) + end + + it 'should report all deps in Gemfile.lock in cyclonedx' do + repo = Salus::Repo.new('spec/fixtures/report_ruby_gems/lockfile') + scanner = Salus::Scanners::ReportRubyGems.new(repository: repo, config: {}) + scanner.run + + ruby_cyclonedx = Cyclonedx::ReportRubyGems.new(scanner.report) + expected = [ + { + "bom-ref": "", + "type": "library", + "group": "", + "name": "actioncable", + "version": "5.1.2", + "purl": "pkg:gem/actioncable@5.1.2", + "properties": [ + { + "key": "source", + "value": "rubygems repository https://rubygems.org/ or installed locally" + }, + { + "key": "dependency_file", + "value": "Gemfile.lock" + } + ] + }, + { + "bom-ref": "", + "type": "library", + "group": "", + "name": "actionmailer", + "version": "5.1.2", + "purl": "pkg:gem/actionmailer@5.1.2", + "properties": [ + { + "key": "source", + "value": "rubygems repository https://rubygems.org/ or installed locally" + }, + { + "key": "dependency_file", + "value": "Gemfile.lock" + } + ] + }, + { + "bom-ref": "", + "type": "library", + "group": "", + "name": "kibana_url", + "version": "1.0.1", + "purl": "pkg:gem/kibana_url@1.0.1", + "properties": [ + { + "key": "source", + "value": "rubygems repository https://rubygems.org/ or installed locally" + }, + { + "key": "dependency_file", + "value": "Gemfile.lock" + } + ] + }, + { + "bom-ref": "", + "type": "library", + "group": "", + "name": "master_lock", + "version": "0.9.1", + "purl": "pkg:gem/master_lock@0.9.1", + "properties": [ + { + "key": "source", + "value": "git@github.com:coinbase/master_lock.git" + }, + { + "key": "dependency_file", + "value": "Gemfile.lock" + } + ] + } + ] + expect(ruby_cyclonedx.build_components_object).to include(*expected) + end + end +end From 853b6668c030c5fc9663517541ec365bd430313b Mon Sep 17 00:00:00 2001 From: jeffrey-zhang Date: Wed, 28 Jul 2021 17:45:39 -0400 Subject: [PATCH 04/10] add in versioning for purl ruby --- lib/cyclonedx/report_ruby_gems_cyclonedx.rb | 12 +++++++++++- .../lib/cyclonedx/report_ruby_gems_cyclonedx_spec.rb | 6 +++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/cyclonedx/report_ruby_gems_cyclonedx.rb b/lib/cyclonedx/report_ruby_gems_cyclonedx.rb index 090821ca..a6b90798 100644 --- a/lib/cyclonedx/report_ruby_gems_cyclonedx.rb +++ b/lib/cyclonedx/report_ruby_gems_cyclonedx.rb @@ -13,7 +13,7 @@ def parse_dependency(dependency) "group": "", # TODO: add group or domain name of the publisher "name": dependency[:name], "version": dependency[:version], - "purl": "pkg:#{dependency[:type]}/#{dependency[:name]}@#{dependency[:version]}", + "purl": "pkg:#{dependency[:type]}/#{dependency[:name]}#{version_string(dependency)}", "properties": [ { "key": "source", @@ -26,5 +26,15 @@ def parse_dependency(dependency) ] } end + + # Return version string to be used in purl + def version_string(dependency) + if dependency[:dependency_file] == 'Gemfile.lock' + # Return empty string if concrete dependency version specified in Gemfile.lock + "@#{dependency[:version]}" + else + "" + end + end end end diff --git a/spec/lib/cyclonedx/report_ruby_gems_cyclonedx_spec.rb b/spec/lib/cyclonedx/report_ruby_gems_cyclonedx_spec.rb index cb14d104..a98a0b5a 100644 --- a/spec/lib/cyclonedx/report_ruby_gems_cyclonedx_spec.rb +++ b/spec/lib/cyclonedx/report_ruby_gems_cyclonedx_spec.rb @@ -17,7 +17,7 @@ "group": "", "name": "kibana_url", "version": "~> 1.0", - "purl": "pkg:gem/kibana_url@~> 1.0", + "purl": "pkg:gem/kibana_url", "properties": [ { "key": "source", @@ -35,7 +35,7 @@ "group": "", "name": "rails", "version": ">= 0", - "purl": "pkg:gem/rails@>= 0", + "purl": "pkg:gem/rails", "properties": [ { "key": "source", @@ -53,7 +53,7 @@ "group": "", "name": "master_lock", "version": ">= 0", - "purl": "pkg:gem/master_lock@>= 0", + "purl": "pkg:gem/master_lock", "properties": [ { "key": "source", From e3926f37ea768c64869392591c613c8041e93ad1 Mon Sep 17 00:00:00 2001 From: jeffrey-zhang Date: Wed, 28 Jul 2021 17:55:09 -0400 Subject: [PATCH 05/10] set cyclonedx spec version to 1.3 --- lib/cyclonedx/report.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cyclonedx/report.rb b/lib/cyclonedx/report.rb index b0f3321b..c1d5ea58 100644 --- a/lib/cyclonedx/report.rb +++ b/lib/cyclonedx/report.rb @@ -14,7 +14,7 @@ def initialize(scan_reports, config = {}) @config = config end - CYCLONEDX_SPEC_VERSION = "1.2.0".freeze + CYCLONEDX_SPEC_VERSION = "1.3.0".freeze CYCLONEDX_VERSION = "1".freeze CYCLONEDX_FORMAT = "CycloneDX".freeze From 83445a3d8fb2c00d5ae01e459d380c7e354894f0 Mon Sep 17 00:00:00 2001 From: jeffrey-zhang Date: Thu, 29 Jul 2021 12:23:07 -0400 Subject: [PATCH 06/10] move common parsing logic to base report and add in bom-ref --- lib/cyclonedx/base.rb | 22 ++++++++++++++++++ lib/cyclonedx/report_ruby_gems_cyclonedx.rb | 23 ++----------------- .../report_ruby_gems_cyclonedx_spec.rb | 14 +++++------ 3 files changed, 31 insertions(+), 28 deletions(-) diff --git a/lib/cyclonedx/base.rb b/lib/cyclonedx/base.rb index 437468c6..5c441c64 100644 --- a/lib/cyclonedx/base.rb +++ b/lib/cyclonedx/base.rb @@ -1,6 +1,7 @@ module Cyclonedx class Base DEFAULT_COMPONENT_TYPE = "application".freeze + DEFAULT_DEP_COMPONENT_TYPE = "library".freeze def initialize(scan_report, config = {}) @scan_report = scan_report @@ -30,5 +31,26 @@ def build_components_object end components end + + def parse_dependency(dependency) + { + "bom-ref": package_url(dependency), + "type": DEFAULT_DEP_COMPONENT_TYPE, + "group": "", # TODO: add group or domain name of the publisher + "name": dependency[:name], + "version": dependency[:version], + "purl": package_url(dependency), + "properties": [ + { + "key": "source", + "value": dependency[:source] + }, + { + "key": "dependency_file", + "value": dependency[:dependency_file] + } + ] + } + end end end diff --git a/lib/cyclonedx/report_ruby_gems_cyclonedx.rb b/lib/cyclonedx/report_ruby_gems_cyclonedx.rb index a6b90798..7ace8a25 100644 --- a/lib/cyclonedx/report_ruby_gems_cyclonedx.rb +++ b/lib/cyclonedx/report_ruby_gems_cyclonedx.rb @@ -1,30 +1,11 @@ module Cyclonedx class ReportRubyGems < Base - DEFAULT_COMPONENT_TYPE = "library".freeze - def initialize(scan_report) super(scan_report) end - def parse_dependency(dependency) - { - "bom-ref": "", - "type": DEFAULT_COMPONENT_TYPE, - "group": "", # TODO: add group or domain name of the publisher - "name": dependency[:name], - "version": dependency[:version], - "purl": "pkg:#{dependency[:type]}/#{dependency[:name]}#{version_string(dependency)}", - "properties": [ - { - "key": "source", - "value": dependency[:source] - }, - { - "key": "dependency_file", - "value": dependency[:dependency_file] - } - ] - } + def package_url(dependency) + "pkg:#{dependency[:type]}/#{dependency[:name]}#{version_string(dependency)}" end # Return version string to be used in purl diff --git a/spec/lib/cyclonedx/report_ruby_gems_cyclonedx_spec.rb b/spec/lib/cyclonedx/report_ruby_gems_cyclonedx_spec.rb index a98a0b5a..43018f2b 100644 --- a/spec/lib/cyclonedx/report_ruby_gems_cyclonedx_spec.rb +++ b/spec/lib/cyclonedx/report_ruby_gems_cyclonedx_spec.rb @@ -12,7 +12,7 @@ expect(ruby_cyclonedx.build_components_object).to match_array( [ { - "bom-ref": "", + "bom-ref": "pkg:gem/kibana_url", "type": "library", "group": "", "name": "kibana_url", @@ -30,7 +30,7 @@ ] }, { - "bom-ref": "", + "bom-ref": "pkg:gem/rails", "type": "library", "group": "", "name": "rails", @@ -48,7 +48,7 @@ ] }, { - "bom-ref": "", + "bom-ref": "pkg:gem/master_lock", "type": "library", "group": "", "name": "master_lock", @@ -77,7 +77,7 @@ ruby_cyclonedx = Cyclonedx::ReportRubyGems.new(scanner.report) expected = [ { - "bom-ref": "", + "bom-ref": "pkg:gem/actioncable@5.1.2", "type": "library", "group": "", "name": "actioncable", @@ -95,7 +95,7 @@ ] }, { - "bom-ref": "", + "bom-ref": "pkg:gem/actionmailer@5.1.2", "type": "library", "group": "", "name": "actionmailer", @@ -113,7 +113,7 @@ ] }, { - "bom-ref": "", + "bom-ref": "pkg:gem/kibana_url@1.0.1", "type": "library", "group": "", "name": "kibana_url", @@ -131,7 +131,7 @@ ] }, { - "bom-ref": "", + "bom-ref": "pkg:gem/master_lock@0.9.1", "type": "library", "group": "", "name": "master_lock", From 87fa75803d18dc74b88f5a4da6bbc8d2ec099388 Mon Sep 17 00:00:00 2001 From: jeffrey-zhang Date: Thu, 29 Jul 2021 12:43:39 -0400 Subject: [PATCH 07/10] fixed version string to be either an absolute version or empty string --- lib/cyclonedx/base.rb | 2 +- lib/cyclonedx/report_ruby_gems_cyclonedx.rb | 7 ++++--- spec/lib/cyclonedx/report_ruby_gems_cyclonedx_spec.rb | 6 +++--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/cyclonedx/base.rb b/lib/cyclonedx/base.rb index 5c441c64..8c7ea08c 100644 --- a/lib/cyclonedx/base.rb +++ b/lib/cyclonedx/base.rb @@ -38,7 +38,7 @@ def parse_dependency(dependency) "type": DEFAULT_DEP_COMPONENT_TYPE, "group": "", # TODO: add group or domain name of the publisher "name": dependency[:name], - "version": dependency[:version], + "version": version_string(dependency), "purl": package_url(dependency), "properties": [ { diff --git a/lib/cyclonedx/report_ruby_gems_cyclonedx.rb b/lib/cyclonedx/report_ruby_gems_cyclonedx.rb index 7ace8a25..e9e2bcd5 100644 --- a/lib/cyclonedx/report_ruby_gems_cyclonedx.rb +++ b/lib/cyclonedx/report_ruby_gems_cyclonedx.rb @@ -5,14 +5,15 @@ def initialize(scan_report) end def package_url(dependency) - "pkg:#{dependency[:type]}/#{dependency[:name]}#{version_string(dependency)}" + "pkg:#{dependency[:type]}/#{dependency[:name]}#{version_string(dependency, true)}" end # Return version string to be used in purl - def version_string(dependency) + def version_string(dependency, is_purl_version = false) + prefix = is_purl_version ? "@" : "" if dependency[:dependency_file] == 'Gemfile.lock' # Return empty string if concrete dependency version specified in Gemfile.lock - "@#{dependency[:version]}" + "#{prefix}#{dependency[:version]}" else "" end diff --git a/spec/lib/cyclonedx/report_ruby_gems_cyclonedx_spec.rb b/spec/lib/cyclonedx/report_ruby_gems_cyclonedx_spec.rb index 43018f2b..72430be5 100644 --- a/spec/lib/cyclonedx/report_ruby_gems_cyclonedx_spec.rb +++ b/spec/lib/cyclonedx/report_ruby_gems_cyclonedx_spec.rb @@ -16,7 +16,7 @@ "type": "library", "group": "", "name": "kibana_url", - "version": "~> 1.0", + "version": "", "purl": "pkg:gem/kibana_url", "properties": [ { @@ -34,7 +34,7 @@ "type": "library", "group": "", "name": "rails", - "version": ">= 0", + "version": "", "purl": "pkg:gem/rails", "properties": [ { @@ -52,7 +52,7 @@ "type": "library", "group": "", "name": "master_lock", - "version": ">= 0", + "version": "", "purl": "pkg:gem/master_lock", "properties": [ { From 8530109b2c673c61e45d07d5c5f98647a0f67db7 Mon Sep 17 00:00:00 2001 From: jeffrey-zhang Date: Fri, 30 Jul 2021 10:19:23 -0400 Subject: [PATCH 08/10] fix version spec --- lib/cyclonedx/report.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cyclonedx/report.rb b/lib/cyclonedx/report.rb index c1d5ea58..2074eb07 100644 --- a/lib/cyclonedx/report.rb +++ b/lib/cyclonedx/report.rb @@ -14,7 +14,7 @@ def initialize(scan_reports, config = {}) @config = config end - CYCLONEDX_SPEC_VERSION = "1.3.0".freeze + CYCLONEDX_SPEC_VERSION = "1.3".freeze CYCLONEDX_VERSION = "1".freeze CYCLONEDX_FORMAT = "CycloneDX".freeze From 5b5485026ba4e5a688492a3cc5b62a7f1406d4f3 Mon Sep 17 00:00:00 2001 From: jeffrey-zhang Date: Fri, 30 Jul 2021 10:23:49 -0400 Subject: [PATCH 09/10] fix comment --- lib/cyclonedx/report_ruby_gems_cyclonedx.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cyclonedx/report_ruby_gems_cyclonedx.rb b/lib/cyclonedx/report_ruby_gems_cyclonedx.rb index e9e2bcd5..c8d56f40 100644 --- a/lib/cyclonedx/report_ruby_gems_cyclonedx.rb +++ b/lib/cyclonedx/report_ruby_gems_cyclonedx.rb @@ -12,7 +12,7 @@ def package_url(dependency) def version_string(dependency, is_purl_version = false) prefix = is_purl_version ? "@" : "" if dependency[:dependency_file] == 'Gemfile.lock' - # Return empty string if concrete dependency version specified in Gemfile.lock + # Return concrete dependency version specified in Gemfile.lock "#{prefix}#{dependency[:version]}" else "" From 587fe8b9f52a105f00f34f472a0f7f2cd87c852a Mon Sep 17 00:00:00 2001 From: jeffrey-zhang Date: Mon, 2 Aug 2021 11:57:58 -0400 Subject: [PATCH 10/10] add unit tests for multiple sources, guard statement --- lib/cyclonedx/report_ruby_gems_cyclonedx.rb | 13 +-- .../lockfile_multiple_sources/Gemfile.lock | 91 +++++++++++++++ .../lockfile_multiple_sources/Gemfile.rb | 4 + .../report_ruby_gems_cyclonedx_spec.rb | 107 +++++++++++++++++- 4 files changed, 205 insertions(+), 10 deletions(-) create mode 100644 spec/fixtures/report_ruby_gems/lockfile_multiple_sources/Gemfile.lock create mode 100644 spec/fixtures/report_ruby_gems/lockfile_multiple_sources/Gemfile.rb diff --git a/lib/cyclonedx/report_ruby_gems_cyclonedx.rb b/lib/cyclonedx/report_ruby_gems_cyclonedx.rb index c8d56f40..353a669c 100644 --- a/lib/cyclonedx/report_ruby_gems_cyclonedx.rb +++ b/lib/cyclonedx/report_ruby_gems_cyclonedx.rb @@ -8,15 +8,14 @@ def package_url(dependency) "pkg:#{dependency[:type]}/#{dependency[:name]}#{version_string(dependency, true)}" end - # Return version string to be used in purl + # Return version string to be used in purl or component def version_string(dependency, is_purl_version = false) + # If the dependency is specified in the Gemfile and an absolute version is needed for + # the purl return empty + return "" if dependency[:dependency_file] == 'Gemfile' && is_purl_version + prefix = is_purl_version ? "@" : "" - if dependency[:dependency_file] == 'Gemfile.lock' - # Return concrete dependency version specified in Gemfile.lock - "#{prefix}#{dependency[:version]}" - else - "" - end + "#{prefix}#{dependency[:version]}" end end end diff --git a/spec/fixtures/report_ruby_gems/lockfile_multiple_sources/Gemfile.lock b/spec/fixtures/report_ruby_gems/lockfile_multiple_sources/Gemfile.lock new file mode 100644 index 00000000..dce6ce83 --- /dev/null +++ b/spec/fixtures/report_ruby_gems/lockfile_multiple_sources/Gemfile.lock @@ -0,0 +1,91 @@ +GEM + remote: https://cool_rubygems.org/ + specs: + dep1 (0.0.47) + activesupport + dep2 (0.15.3) + activesupport + google-protobuf (~> 3.14) + googleapis-common-protos-types (~> 1.0) + +GEM + remote: https://rubygems.org/ + specs: + activesupport (6.1.4) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 1.6, < 2) + minitest (>= 5.1) + tzinfo (~> 2.0) + zeitwerk (~> 2.3) + aws-eventstream (1.1.1) + aws-partitions (1.472.0) + aws-sdk-core (3.115.0) + aws-eventstream (~> 1, >= 1.0.2) + aws-partitions (~> 1, >= 1.239.0) + aws-sigv4 (~> 1.1) + jmespath (~> 1.0) + aws-sdk-sns (1.41.0) + aws-sdk-core (~> 3, >= 3.112.0) + aws-sigv4 (~> 1.1) + aws-sdk-sqs (1.39.0) + aws-sdk-core (~> 3, >= 3.112.0) + aws-sigv4 (~> 1.1) + aws-sdk-xray (1.4.0) + aws-sdk-core (~> 3) + aws-sigv4 (~> 1.0) + aws-sigv4 (1.2.3) + aws-eventstream (~> 1, >= 1.0.2) + aws-xray-sdk (0.11.5) + aws-sdk-xray (~> 1.4.0) + multi_json (~> 1) + bugsnag (6.21.0) + concurrent-ruby (~> 1.0) + concurrent-ruby (1.1.9) + datadog-lambda (1.12.0) + aws-xray-sdk (~> 0.11.3) + ddtrace (0.50.0) + ffi (~> 1.0) + msgpack + faraday (1.4.3) + faraday-em_http (~> 1.0) + faraday-em_synchrony (~> 1.0) + faraday-excon (~> 1.1) + faraday-net_http (~> 1.0) + faraday-net_http_persistent (~> 1.1) + multipart-post (>= 1.2, < 3) + ruby2_keywords (>= 0.0.4) + faraday-em_http (1.0.0) + faraday-em_synchrony (1.0.0) + faraday-excon (1.1.0) + faraday-net_http (1.0.1) + faraday-net_http_persistent (1.1.0) + ffi (1.15.3) + google-protobuf (3.17.3-universal-darwin) + googleapis-common-protos-types (1.0.6) + google-protobuf (~> 3.14) + grpc-tools (1.38.0) + i18n (1.8.10) + concurrent-ruby (~> 1.0) + jmespath (1.4.0) + jwt (2.2.3) + minitest (5.14.4) + msgpack (1.4.2) + multi_json (1.15.0) + multipart-post (2.1.1) + oj (3.11.7) + rack (2.2.3) + ruby2_keywords (0.0.4) + tzinfo (2.0.4) + concurrent-ruby (~> 1.0) + zeitwerk (2.4.2) + +PLATFORMS + universal-darwin-20 + x86_64-darwin-19 + +DEPENDENCIES + dep1! + dep2! + +BUNDLED WITH + 2.2.17 \ No newline at end of file diff --git a/spec/fixtures/report_ruby_gems/lockfile_multiple_sources/Gemfile.rb b/spec/fixtures/report_ruby_gems/lockfile_multiple_sources/Gemfile.rb new file mode 100644 index 00000000..e0a73a95 --- /dev/null +++ b/spec/fixtures/report_ruby_gems/lockfile_multiple_sources/Gemfile.rb @@ -0,0 +1,4 @@ +source 'https://rubygems.org'.freeze + +gem 'dep1', source: 'https://cool_rubygems.org' +gem 'dep2', source: 'https://cool_rubygems.org' diff --git a/spec/lib/cyclonedx/report_ruby_gems_cyclonedx_spec.rb b/spec/lib/cyclonedx/report_ruby_gems_cyclonedx_spec.rb index 72430be5..05a8cd02 100644 --- a/spec/lib/cyclonedx/report_ruby_gems_cyclonedx_spec.rb +++ b/spec/lib/cyclonedx/report_ruby_gems_cyclonedx_spec.rb @@ -16,7 +16,7 @@ "type": "library", "group": "", "name": "kibana_url", - "version": "", + "version": "~> 1.0", "purl": "pkg:gem/kibana_url", "properties": [ { @@ -34,7 +34,7 @@ "type": "library", "group": "", "name": "rails", - "version": "", + "version": ">= 0", "purl": "pkg:gem/rails", "properties": [ { @@ -52,7 +52,7 @@ "type": "library", "group": "", "name": "master_lock", - "version": "", + "version": ">= 0", "purl": "pkg:gem/master_lock", "properties": [ { @@ -112,6 +112,42 @@ } ] }, + { + "bom-ref": "pkg:gem/actionpack@5.1.2", + "type": "library", + "group": "", + "name": "actionpack", + "version": "5.1.2", + "purl": "pkg:gem/actionpack@5.1.2", + "properties": [ + { + "key": "source", + "value": "rubygems repository https://rubygems.org/ or installed locally" + }, + { + "key": "dependency_file", + "value": "Gemfile.lock" + } + ] + }, + { + "bom-ref": "pkg:gem/nio4r@2.1.0", + "type": "library", + "group": "", + "name": "nio4r", + "version": "2.1.0", + "purl": "pkg:gem/nio4r@2.1.0", + "properties": [ + { + "key": "source", + "value": "rubygems repository https://rubygems.org/ or installed locally" + }, + { + "key": "dependency_file", + "value": "Gemfile.lock" + } + ] + }, { "bom-ref": "pkg:gem/kibana_url@1.0.1", "type": "library", @@ -151,5 +187,70 @@ ] expect(ruby_cyclonedx.build_components_object).to include(*expected) end + + it 'should report all deps from multiple sources in Gemfile.lock in cyclonedx' do + repo = Salus::Repo.new('spec/fixtures/report_ruby_gems/lockfile_multiple_sources') + scanner = Salus::Scanners::ReportRubyGems.new(repository: repo, config: {}) + scanner.run + + ruby_cyclonedx = Cyclonedx::ReportRubyGems.new(scanner.report) + expected = [ + { + "bom-ref": "pkg:gem/dep1@0.0.47", + "type": "library", + "group": "", + "name": "dep1", + "version": "0.0.47", + "purl": "pkg:gem/dep1@0.0.47", + "properties": [ + { + "key": "source", + "value": "rubygems repository https://cool_rubygems.org/ or installed locally" + }, + { + "key": "dependency_file", + "value": "Gemfile.lock" + } + ] + }, + { + "bom-ref": "pkg:gem/dep2@0.15.3", + "type": "library", + "group": "", + "name": "dep2", + "version": "0.15.3", + "purl": "pkg:gem/dep2@0.15.3", + "properties": [ + { + "key": "source", + "value": "rubygems repository https://cool_rubygems.org/ or installed locally" + }, + { + "key": "dependency_file", + "value": "Gemfile.lock" + } + ] + }, + { + "bom-ref": "pkg:gem/minitest@5.14.4", + "type": "library", + "group": "", + "name": "minitest", + "version": "5.14.4", + "purl": "pkg:gem/minitest@5.14.4", + "properties": [ + { + "key": "source", + "value": "rubygems repository https://rubygems.org/ or installed locally" + }, + { + "key": "dependency_file", + "value": "Gemfile.lock" + } + ] + } + ] + expect(ruby_cyclonedx.build_components_object).to include(*expected) + end end end