Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CycloneDX Ruby Support #410

Merged
merged 12 commits into from
Aug 4, 2021
37 changes: 25 additions & 12 deletions lib/cyclonedx/base.rb
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -24,20 +25,32 @@ 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

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": version_string(dependency),
"purl": package_url(dependency),
"properties": [
{
"key": "source",
"value": dependency[:source]
jeffrey778zhan marked this conversation as resolved.
Show resolved Hide resolved
},
{
"key": "dependency_file",
"value": dependency[:dependency_file]
jeffrey778zhan marked this conversation as resolved.
Show resolved Hide resolved
}
]
}
end
end
end
2 changes: 1 addition & 1 deletion lib/cyclonedx/report.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def initialize(scan_reports, config = {})
@config = config
end

CYCLONEDX_SPEC_VERSION = "1.2.0".freeze
CYCLONEDX_SPEC_VERSION = "1.3".freeze
CYCLONEDX_VERSION = "1".freeze
CYCLONEDX_FORMAT = "CycloneDX".freeze

Expand Down
15 changes: 15 additions & 0 deletions lib/cyclonedx/report_ruby_gems_cyclonedx.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,20 @@ class ReportRubyGems < Base
def initialize(scan_report)
super(scan_report)
end

def package_url(dependency)
jeffrey778zhan marked this conversation as resolved.
Show resolved Hide resolved
"pkg:#{dependency[:type]}/#{dependency[:name]}#{version_string(dependency, true)}"
end

# Return version string to be used in purl
def version_string(dependency, is_purl_version = false)
prefix = is_purl_version ? "@" : ""
jeffrey778zhan marked this conversation as resolved.
Show resolved Hide resolved
if dependency[:dependency_file] == 'Gemfile.lock'
# Return concrete dependency version specified in Gemfile.lock
"#{prefix}#{dependency[:version]}"
else
""
end
end
end
end
1 change: 1 addition & 0 deletions lib/salus.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
155 changes: 155 additions & 0 deletions spec/lib/cyclonedx/report_ruby_gems_cyclonedx_spec.rb
Original file line number Diff line number Diff line change
@@ -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": "pkg:gem/kibana_url",
"type": "library",
"group": "",
"name": "kibana_url",
"version": "",
jeffrey778zhan marked this conversation as resolved.
Show resolved Hide resolved
"purl": "pkg:gem/kibana_url",
"properties": [
{
"key": "source",
"value": "https://rubygems.org/"
jeffrey778zhan marked this conversation as resolved.
Show resolved Hide resolved
},
{
"key": "dependency_file",
"value": "Gemfile"
}
]
},
{
"bom-ref": "pkg:gem/rails",
"type": "library",
"group": "",
"name": "rails",
"version": "",
"purl": "pkg:gem/rails",
"properties": [
{
"key": "source",
"value": "https://rubygems.org/"
},
{
"key": "dependency_file",
"value": "Gemfile"
}
]
},
{
"bom-ref": "pkg:gem/master_lock",
"type": "library",
"group": "",
"name": "master_lock",
"version": "",
"purl": "pkg:gem/master_lock",
"properties": [
{
"key": "source",
"value": "[email protected]:coinbase/master_lock.git"
jeffrey778zhan marked this conversation as resolved.
Show resolved Hide resolved
},
{
"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": "pkg:gem/[email protected]",
"type": "library",
"group": "",
"name": "actioncable",
"version": "5.1.2",
"purl": "pkg:gem/[email protected]",
"properties": [
{
"key": "source",
"value": "rubygems repository https://rubygems.org/ or installed locally"
},
{
"key": "dependency_file",
"value": "Gemfile.lock"
}
]
},
{
"bom-ref": "pkg:gem/[email protected]",
"type": "library",
"group": "",
"name": "actionmailer",
"version": "5.1.2",
"purl": "pkg:gem/[email protected]",
"properties": [
{
"key": "source",
"value": "rubygems repository https://rubygems.org/ or installed locally"
},
{
"key": "dependency_file",
"value": "Gemfile.lock"
}
]
},
{
"bom-ref": "pkg:gem/[email protected]",
"type": "library",
"group": "",
"name": "kibana_url",
"version": "1.0.1",
"purl": "pkg:gem/[email protected]",
"properties": [
{
"key": "source",
"value": "rubygems repository https://rubygems.org/ or installed locally"
},
{
"key": "dependency_file",
"value": "Gemfile.lock"
}
]
},
{
"bom-ref": "pkg:gem/[email protected]",
"type": "library",
"group": "",
"name": "master_lock",
"version": "0.9.1",
"purl": "pkg:gem/[email protected]",
"properties": [
{
"key": "source",
"value": "[email protected]:coinbase/master_lock.git"
},
{
"key": "dependency_file",
"value": "Gemfile.lock"
}
]
}
]
expect(ruby_cyclonedx.build_components_object).to include(*expected)
jeffrey778zhan marked this conversation as resolved.
Show resolved Hide resolved
end
end
end