Commit f090135f authored by Kamil Trzciński's avatar Kamil Trzciński

Support new report syntax for common vulnerabilities

The new reports are always a top-level Hash with explicit `version:`
field. Old reports that are Arrays are converted to Hash representation
with indication what version is being used.

This MR also removes other reports and uses one common set of fixtures
for all test types.
parent 2efc19f8
...@@ -8,10 +8,11 @@ module Gitlab ...@@ -8,10 +8,11 @@ module Gitlab
SecurityReportParserError = Class.new(Gitlab::Ci::Parsers::ParserError) SecurityReportParserError = Class.new(Gitlab::Ci::Parsers::ParserError)
def parse!(json_data, report) def parse!(json_data, report)
vulnerabilities = JSON.parse!(json_data) report_data = parse_report(json_data)
raise SecurityReportParserError, "Invalid report format" unless report_data.is_a?(Hash)
vulnerabilities.each do |vulnerability| report_data["vulnerabilities"].each do |vulnerability|
create_vulnerability(report, vulnerability) create_vulnerability(report, vulnerability, report_data["version"])
end end
rescue JSON::ParserError rescue JSON::ParserError
raise SecurityReportParserError, 'JSON parsing failed' raise SecurityReportParserError, 'JSON parsing failed'
...@@ -22,7 +23,11 @@ module Gitlab ...@@ -22,7 +23,11 @@ module Gitlab
protected protected
def create_vulnerability(report, data) def parse_report(json_data)
JSON.parse!(json_data)
end
def create_vulnerability(report, data, version)
scanner = create_scanner(report, data['scanner'] || mutate_scanner_tool(data['tool'])) scanner = create_scanner(report, data['scanner'] || mutate_scanner_tool(data['tool']))
identifiers = create_identifiers(report, data['identifiers']) identifiers = create_identifiers(report, data['identifiers'])
...@@ -38,9 +43,7 @@ module Gitlab ...@@ -38,9 +43,7 @@ module Gitlab
scanner: scanner, scanner: scanner,
identifiers: identifiers, identifiers: identifiers,
raw_metadata: data.to_json, raw_metadata: data.to_json,
# Version is hardcoded here untill provided in the report. metadata_version: version
# See https://gitlab.com/gitlab-org/gitlab-ee/issues/8025
metadata_version: metadata_version(data)
) )
end end
......
...@@ -5,14 +5,28 @@ module Gitlab ...@@ -5,14 +5,28 @@ module Gitlab
module Parsers module Parsers
module Security module Security
class DependencyScanning < Common class DependencyScanning < Common
extend ::Gitlab::Utils::Override
DEPRECATED_REPORT_VERSION = "1.3".freeze
private private
def metadata_version(vulnerability) override :parse_report
'1.3' def parse_report(json_data)
report = super
if report.is_a?(Array)
report = {
"version" => DEPRECATED_REPORT_VERSION,
"vulnerabilities" => report
}
end
report
end end
def generate_location_fingerprint(location) def generate_location_fingerprint(location)
Digest::SHA1.hexdigest("#{location['file']}:#{location['dependency']['package']['name']}") Digest::SHA1.hexdigest("#{location['file']}:#{location.dig('dependency', 'package', 'name')}")
end end
end end
end end
......
...@@ -5,10 +5,24 @@ module Gitlab ...@@ -5,10 +5,24 @@ module Gitlab
module Parsers module Parsers
module Security module Security
class Sast < Common class Sast < Common
extend ::Gitlab::Utils::Override
DEPRECATED_REPORT_VERSION = "1.2".freeze
private private
def metadata_version(vulnerability) override :parse_report
'1.2' def parse_report(json_data)
report = super
if report.is_a?(Array)
report = {
"version" => DEPRECATED_REPORT_VERSION,
"vulnerabilities" => report
}
end
report
end end
def generate_location_fingerprint(location) def generate_location_fingerprint(location)
......
...@@ -29,15 +29,9 @@ FactoryBot.define do ...@@ -29,15 +29,9 @@ FactoryBot.define do
end end
end end
trait :license_management_report do trait :license_management_feature_branch do
after(:build) do |build| after(:build) do |build|
build.job_artifacts << create(:ee_ci_job_artifact, :license_management_report, job: build) build.job_artifacts << create(:ee_ci_job_artifact, :license_management_feature_branch, job: build)
end
end
trait :license_management_report_2 do
after(:build) do |build|
build.job_artifacts << create(:ee_ci_job_artifact, :license_management_report_2, job: build)
end end
end end
......
...@@ -8,7 +8,17 @@ FactoryBot.define do ...@@ -8,7 +8,17 @@ FactoryBot.define do
after(:build) do |artifact, evaluator| after(:build) do |artifact, evaluator|
artifact.file = fixture_file_upload( artifact.file = fixture_file_upload(
Rails.root.join('ee/spec/fixtures/reports/security/sast.json'), 'application/json') Rails.root.join('spec/fixtures/security-reports/master/gl-sast-report.json'), 'text/plain')
end
end
trait :sast_deprecated do
file_type :sast
file_format :raw
after(:build) do |artifact, evaluator|
artifact.file = fixture_file_upload(
Rails.root.join('spec/fixtures/security-reports/deprecated/gl-sast-report.json'), 'text/plain')
end end
end end
...@@ -18,27 +28,27 @@ FactoryBot.define do ...@@ -18,27 +28,27 @@ FactoryBot.define do
after(:build) do |artifact, evaluator| after(:build) do |artifact, evaluator|
artifact.file = fixture_file_upload( artifact.file = fixture_file_upload(
Rails.root.join('ee/spec/fixtures/reports/security/sast_with_corrupted_data.json'), 'application/json') Rails.root.join('spec/fixtures/trace/sample_trace'), 'application/json')
end end
end end
trait :license_management_report do trait :license_management do
file_type :license_management file_type :license_management
file_format :raw file_format :raw
after(:build) do |artifact, evaluator| after(:build) do |artifact, evaluator|
artifact.file = fixture_file_upload( artifact.file = fixture_file_upload(
Rails.root.join('ee/spec/fixtures/license_management/report.json'), 'application/json') Rails.root.join('spec/fixtures/security-reports/master/gl-license-management-report.json'), 'application/json')
end end
end end
trait :license_management_report_2 do trait :license_management_feature_branch do
file_type :license_management file_type :license_management
file_format :raw file_format :raw
after(:build) do |artifact, evaluator| after(:build) do |artifact, evaluator|
artifact.file = fixture_file_upload( artifact.file = fixture_file_upload(
Rails.root.join('ee/spec/fixtures/license_management/report2.json'), 'application/json') Rails.root.join('spec/fixtures/security-reports/feature-branch/gl-license-management-report.json'), 'application/json')
end end
end end
...@@ -48,7 +58,7 @@ FactoryBot.define do ...@@ -48,7 +58,7 @@ FactoryBot.define do
after(:build) do |artifact, evaluator| after(:build) do |artifact, evaluator|
artifact.file = fixture_file_upload( artifact.file = fixture_file_upload(
Rails.root.join('ee/spec/fixtures/license_management/report_with_corrupted_data.json'), 'application/json') Rails.root.join('spec/fixtures/trace/sample_trace'), 'application/json')
end end
end end
...@@ -82,6 +92,16 @@ FactoryBot.define do ...@@ -82,6 +92,16 @@ FactoryBot.define do
end end
end end
trait :dependency_scanning_deprecated do
file_format :raw
file_type :dependency_scanning
after(:build) do |artifact, _|
artifact.file = fixture_file_upload(
Rails.root.join('spec/fixtures/security-reports/deprecated/gl-dependency-scanning-report.json'), 'text/plain')
end
end
trait :container_scanning do trait :container_scanning do
file_format :raw file_format :raw
file_type :container_scanning file_type :container_scanning
......
...@@ -11,15 +11,15 @@ FactoryBot.define do ...@@ -11,15 +11,15 @@ FactoryBot.define do
status :success status :success
after(:build) do |pipeline, evaluator| after(:build) do |pipeline, evaluator|
pipeline.builds << build(:ee_ci_build, :license_management_report, pipeline: pipeline, project: pipeline.project) pipeline.builds << build(:ee_ci_build, :license_management, pipeline: pipeline, project: pipeline.project)
end end
end end
trait :with_license_management_report_2 do trait :with_license_management_feature_branch do
status :success status :success
after(:build) do |pipeline, evaluator| after(:build) do |pipeline, evaluator|
pipeline.builds << build(:ee_ci_build, :license_management_report_2, pipeline: pipeline, project: pipeline.project) pipeline.builds << build(:ee_ci_build, :license_management_feature_branch, pipeline: pipeline, project: pipeline.project)
end end
end end
......
This diff is collapsed.
{
"licenses": [
{
"count": 1,
"name": "WTFPL"
},
{
"count": 1,
"name": "MIT"
}
],
"dependencies": [
{
"license": {
"name": "MIT",
"url": "http://opensource.org/licenses/mit-license"
},
"dependency": {
"name": "actioncable",
"url": "http://rubyonrails.org",
"description": "WebSocket framework for Rails.",
"pathes": [
"."
]
}
},
{
"license": {
"name": "WTFPL",
"url": "http://www.wtfpl.net/"
},
"dependency": {
"name": "wtfpl_init",
"url": "https://rubygems.org/gems/wtfpl_init",
"description": "Download WTFPL license file and rename to LICENSE.md or something",
"pathes": [
"."
]
}
}
]
}
[
{
"category": "sast",
"name": "Cipher with no integrity",
"message": "Cipher with no integrity",
"description": "The cipher does not provide data integrity",
"cve": "e6449b89335daf53c0db4c0219bc1634:CIPHER_INTEGRITY",
"severity": "Medium",
"confidence": "High",
"location": {
"file": "maven/src/main/java/com/gitlab/security_products/tests/App.java",
"start_line": 29,
"end_line": 29,
"class": "com.gitlab.security_products.tests.App",
"method": "insecureCypher"
},
"scanner": {
"id": "find_sec_bugs",
"name": "Find Security Bugs"
},
"identifiers": [
{
"type": "find_sec_bugs_type",
"name": "Find Security Bugs-CIPHER_INTEGRITY",
"value": "CIPHER_INTEGRITY",
"url": "https://find-sec-bugs.github.io/bugs.htm#CIPHER_INTEGRITY"
},
{
"type": "cwe",
"name": "CWE-353",
"value": "353",
"url": "https://cwe.mitre.org/data/definitions/353.html"
}
],
"priority": "Medium",
"file": "maven/src/main/java/com/gitlab/security_products/tests/App.java",
"line": 29,
"url": "https://find-sec-bugs.github.io/bugs.htm#CIPHER_INTEGRITY",
"tool": "find_sec_bugs"
},
{
"category": "sast",
"message": "Use of insecure MD2, MD4, or MD5 hash function.",
"cve": "python/imports/imports-aliases.py:cb203b465dffb0cb3a8e8bd8910b84b93b0a5995a938e4b903dbb0cd6ffa1254:B303",
"severity": "Medium",
"confidence": "High",
"location": {
"file": "python/imports/imports-aliases.py",
"start_line": 11,
"end_line": 11
},
"scanner": {
"id": "bandit",
"name": "Bandit"
},
"identifiers": [
{
"type": "bandit_test_id",
"name": "Bandit Test ID B303",
"value": "B303"
},
{
"type": "cwe",
"name": "CWE-353",
"value": "353",
"url": "https://cwe.mitre.org/data/definitions/353.html"
}
],
"priority": "Medium",
"file": "python/imports/imports-aliases.py",
"line": 11,
"tool": "bandit"
},
{
"category": "sast",
"name": "Unescaped parameter value",
"message": "Unescaped parameter value",
"cve": "26b8b0ad586712d41ac6877e2292c6da7aa4760078add7fd23edf5b7a1bcb699",
"confidence": "High",
"location": {
"file": "rails5/app/views/widget/show.html.erb",
"start_line": 1
},
"scanner": {
"id": "brakeman",
"name": "Brakeman"
},
"identifiers": [
{
"type": "brakeman_warning_code",
"name": "Brakeman Warning Code 2",
"value": "2",
"url": "https://brakemanscanner.org/docs/warning_types/cross-site_scripting/"
}
],
"file": "rails5/app/views/widget/show.html.erb",
"line": 1,
"url": "https://brakemanscanner.org/docs/warning_types/cross-site_scripting/",
"tool": "brakeman"
}
]
...@@ -9,7 +9,7 @@ describe Gitlab::Ci::Parsers::LicenseManagement::LicenseManagement do ...@@ -9,7 +9,7 @@ describe Gitlab::Ci::Parsers::LicenseManagement::LicenseManagement do
let(:report) { Gitlab::Ci::Reports::LicenseManagement::Report.new } let(:report) { Gitlab::Ci::Reports::LicenseManagement::Report.new }
context 'when data is a JSON license management report' do context 'when data is a JSON license management report' do
let(:data) { File.read(Rails.root.join('ee/spec/fixtures/license_management/report.json')) } let(:data) { File.read(Rails.root.join('spec/fixtures/security-reports/master/gl-license-management-report.json')) }
it 'parses without error' do it 'parses without error' do
expect { subject }.not_to raise_error expect { subject }.not_to raise_error
......
...@@ -10,6 +10,11 @@ describe Gitlab::Ci::Parsers::Security::DependencyScanning do ...@@ -10,6 +10,11 @@ describe Gitlab::Ci::Parsers::Security::DependencyScanning do
let(:report) { Gitlab::Ci::Reports::Security::Report.new(artifact.file_type) } let(:report) { Gitlab::Ci::Reports::Security::Report.new(artifact.file_type) }
let(:parser) { described_class.new } let(:parser) { described_class.new }
where(report_format: %i(dependency_scanning dependency_scanning_deprecated))
with_them do
let(:artifact) { create(:ee_ci_job_artifact, report_format) }
before do before do
artifact.each_blob do |blob| artifact.each_blob do |blob|
parser.parse!(blob, report) parser.parse!(blob, report)
...@@ -30,4 +35,5 @@ describe Gitlab::Ci::Parsers::Security::DependencyScanning do ...@@ -30,4 +35,5 @@ describe Gitlab::Ci::Parsers::Security::DependencyScanning do
expect(report.occurrences.first[:metadata_version]).to eq('1.3') expect(report.occurrences.first[:metadata_version]).to eq('1.3')
end end
end end
end
end end
...@@ -6,10 +6,14 @@ describe Gitlab::Ci::Parsers::Security::Sast do ...@@ -6,10 +6,14 @@ describe Gitlab::Ci::Parsers::Security::Sast do
describe '#parse!' do describe '#parse!' do
let(:project) { artifact.project } let(:project) { artifact.project }
let(:pipeline) { artifact.job.pipeline } let(:pipeline) { artifact.job.pipeline }
let(:artifact) { create(:ee_ci_job_artifact, :sast) }
let(:report) { Gitlab::Ci::Reports::Security::Report.new(artifact.file_type) } let(:report) { Gitlab::Ci::Reports::Security::Report.new(artifact.file_type) }
let(:parser) { described_class.new } let(:parser) { described_class.new }
where(report_format: %i(sast sast_deprecated))
with_them do
let(:artifact) { create(:ee_ci_job_artifact, report_format) }
before do before do
artifact.each_blob do |blob| artifact.each_blob do |blob|
parser.parse!(blob, report) parser.parse!(blob, report)
...@@ -17,17 +21,18 @@ describe Gitlab::Ci::Parsers::Security::Sast do ...@@ -17,17 +21,18 @@ describe Gitlab::Ci::Parsers::Security::Sast do
end end
it "parses all identifiers and occurrences" do it "parses all identifiers and occurrences" do
expect(report.occurrences.length).to eq(3) expect(report.occurrences.length).to eq(33)
expect(report.identifiers.length).to eq(4) expect(report.identifiers.length).to eq(14)
expect(report.scanners.length).to eq(3) expect(report.scanners.length).to eq(3)
end end
it "generates expected location fingerprint" do it "generates expected location fingerprint" do
expect(report.occurrences.first[:location_fingerprint]).to eq('6b6bb283d43cc510d7d1e73e2882b3652cb34bd5') expect(report.occurrences.first[:location_fingerprint]).to eq('d869ba3f0b3347eb2749135a437dc07c8ae0f420')
end end
it "generates expected metadata_version" do it "generates expected metadata_version" do
expect(report.occurrences.first[:metadata_version]).to eq('1.2') expect(report.occurrences.first[:metadata_version]).to eq('1.2')
end end
end end
end
end end
...@@ -167,7 +167,7 @@ describe Ci::Build do ...@@ -167,7 +167,7 @@ describe Ci::Build do
it 'parses blobs and add the results to the report' do it 'parses blobs and add the results to the report' do
subject subject
expect(security_reports.get_report('sast').occurrences.size).to eq(3) expect(security_reports.get_report('sast').occurrences.size).to eq(33)
end end
end end
...@@ -180,7 +180,7 @@ describe Ci::Build do ...@@ -180,7 +180,7 @@ describe Ci::Build do
it 'parses blobs and add the results to the reports' do it 'parses blobs and add the results to the reports' do
subject subject
expect(security_reports.get_report('sast').occurrences.size).to eq(3) expect(security_reports.get_report('sast').occurrences.size).to eq(33)
expect(security_reports.get_report('dependency_scanning').occurrences.size).to eq(4) expect(security_reports.get_report('dependency_scanning').occurrences.size).to eq(4)
end end
end end
...@@ -236,7 +236,7 @@ describe Ci::Build do ...@@ -236,7 +236,7 @@ describe Ci::Build do
context 'when build has a license management report' do context 'when build has a license management report' do
context 'when there is a license management report' do context 'when there is a license management report' do
before do before do
create(:ee_ci_job_artifact, :license_management_report, job: job, project: job.project) create(:ee_ci_job_artifact, :license_management, job: job, project: job.project)
end end
it 'parses blobs and add the results to the report' do it 'parses blobs and add the results to the report' do
......
...@@ -239,7 +239,7 @@ describe Ci::Pipeline do ...@@ -239,7 +239,7 @@ describe Ci::Pipeline do
it 'returns security reports with collected data grouped as expected' do it 'returns security reports with collected data grouped as expected' do
expect(subject.reports.keys).to contain_exactly('sast', 'dependency_scanning') expect(subject.reports.keys).to contain_exactly('sast', 'dependency_scanning')
expect(subject.get_report('sast').occurrences.size).to eq(6) expect(subject.get_report('sast').occurrences.size).to eq(66)
expect(subject.get_report('dependency_scanning').occurrences.size).to eq(4) expect(subject.get_report('dependency_scanning').occurrences.size).to eq(4)
end end
...@@ -247,7 +247,7 @@ describe Ci::Pipeline do ...@@ -247,7 +247,7 @@ describe Ci::Pipeline do
let(:build_sast_1) { create(:ci_build, :retried, name: 'sast_1', pipeline: pipeline, project: project) } let(:build_sast_1) { create(:ci_build, :retried, name: 'sast_1', pipeline: pipeline, project: project) }
it 'does not take retried builds into account' do it 'does not take retried builds into account' do
expect(subject.get_report('sast').occurrences.size).to eq(3) expect(subject.get_report('sast').occurrences.size).to eq(33)
expect(subject.get_report('dependency_scanning').occurrences.size).to eq(4) expect(subject.get_report('dependency_scanning').occurrences.size).to eq(4)
end end
end end
...@@ -322,7 +322,7 @@ describe Ci::Pipeline do ...@@ -322,7 +322,7 @@ describe Ci::Pipeline do
context 'when pipeline has builds with license_management reports' do context 'when pipeline has builds with license_management reports' do
before do before do
create(:ee_ci_build, :license_management_report, pipeline: pipeline, project: project) create(:ee_ci_build, :license_management, pipeline: pipeline, project: project)
end end
context 'when pipeline status is running' do context 'when pipeline status is running' do
...@@ -350,7 +350,7 @@ describe Ci::Pipeline do ...@@ -350,7 +350,7 @@ describe Ci::Pipeline do
context 'when retried build has license management reports' do context 'when retried build has license management reports' do
before do before do
create(:ee_ci_build, :retried, :license_management_report, pipeline: pipeline, project: project) create(:ee_ci_build, :retried, :license_management, pipeline: pipeline, project: project)
end end
let(:pipeline) { create(:ci_pipeline, :success, project: project) } let(:pipeline) { create(:ci_pipeline, :success, project: project) }
...@@ -367,14 +367,13 @@ describe Ci::Pipeline do ...@@ -367,14 +367,13 @@ describe Ci::Pipeline do
let!(:build_2) { create(:ci_build, :success, name: 'license_management2', pipeline: pipeline, project: project) } let!(:build_2) { create(:ci_build, :success, name: 'license_management2', pipeline: pipeline, project: project) }
before do before do
create(:ee_ci_job_artifact, :license_management_report, job: build_1, project: project) create(:ee_ci_job_artifact, :license_management, job: build_1, project: project)
create(:ee_ci_job_artifact, :license_management_report_2, job: build_2, project: project) create(:ee_ci_job_artifact, :license_management_feature_branch, job: build_2, project: project)
end end
it 'returns a license management report with collected data' do it 'returns a license management report with collected data' do
expect(subject.licenses.count).to be(5) expect(subject.licenses.count).to eq(5)
expect(subject.licenses.any? { |license| license.name == 'WTFPL' } ).to be_truthy expect(subject.licenses.map(&:name)).to include('WTFPL', 'MIT')
expect(subject.licenses.any? { |license| license.name == 'MIT' } ).to be_truthy
end end
context 'when builds are retried' do context 'when builds are retried' do
...@@ -382,14 +381,14 @@ describe Ci::Pipeline do ...@@ -382,14 +381,14 @@ describe Ci::Pipeline do
let!(:build_2) { create(:ci_build, :retried, :success, name: 'license_management2', pipeline: pipeline, project: project) } let!(:build_2) { create(:ci_build, :retried, :success, name: 'license_management2', pipeline: pipeline, project: project) }
it 'does not take retried builds into account' do it 'does not take retried builds into account' do
expect(subject.licenses.count).to be(0) expect(subject.licenses).to be_empty
end end
end end
end end
context 'when pipeline does not have any builds with license management reports' do context 'when pipeline does not have any builds with license management reports' do
it 'returns an empty license management report' do it 'returns an empty license management report' do
expect(subject.licenses.count).to be(0) expect(subject.licenses).to be_empty
end end
end end
end end
......
...@@ -7,7 +7,7 @@ describe EE::Ci::JobArtifact do ...@@ -7,7 +7,7 @@ describe EE::Ci::JobArtifact do
subject { Ci::JobArtifact.license_management_reports } subject { Ci::JobArtifact.license_management_reports }
context 'when there is a license management report' do context 'when there is a license management report' do
let!(:artifact) { create(:ee_ci_job_artifact, :license_management_report) } let!(:artifact) { create(:ee_ci_job_artifact, :license_management) }
it { is_expected.to eq([artifact]) } it { is_expected.to eq([artifact]) }
end end
......
...@@ -15,32 +15,32 @@ describe Ci::CompareLicenseManagementReportsService do ...@@ -15,32 +15,32 @@ describe Ci::CompareLicenseManagementReportsService do
it 'reports new licenses' do it 'reports new licenses' do
expect(subject[:status]).to eq(:parsed) expect(subject[:status]).to eq(:parsed)
expect(subject[:data]['new_licenses'].count).to be(4) expect(subject[:data]['new_licenses'].count).to eq(4)
expect(subject[:data]['new_licenses'].any? { |license| license['name'] == 'MIT' } ).to be_truthy expect(subject[:data]['new_licenses']).to include(a_hash_including('name' => 'MIT'))
end end
end end
context 'when base and head pipelines have test reports' do context 'when base and head pipelines have test reports' do
let!(:base_pipeline) { create(:ee_ci_pipeline, :with_license_management_report, project: project) } let!(:base_pipeline) { create(:ee_ci_pipeline, :with_license_management_report, project: project) }
let!(:head_pipeline) { create(:ee_ci_pipeline, :with_license_management_report_2, project: project) } let!(:head_pipeline) { create(:ee_ci_pipeline, :with_license_management_feature_branch, project: project) }
it 'reports status as parsed' do it 'reports status as parsed' do
expect(subject[:status]).to eq(:parsed) expect(subject[:status]).to eq(:parsed)
end end
it 'reports new licenses' do it 'reports new licenses' do
expect(subject[:data]['new_licenses'].count).to be(1) expect(subject[:data]['new_licenses'].count).to eq(1)
expect(subject[:data]['new_licenses'][0]['name']).to eq('WTFPL') expect(subject[:data]['new_licenses']).to include(a_hash_including('name' => 'WTFPL'))
end end
it 'reports existing licenses' do it 'reports existing licenses' do
expect(subject[:data]['existing_licenses'].count).to be(1) expect(subject[:data]['existing_licenses'].count).to eq(1)
expect(subject[:data]['existing_licenses'][0]['name']).to eq('MIT') expect(subject[:data]['existing_licenses']).to include(a_hash_including('name' => 'MIT'))
end end
it 'reports removed licenses' do it 'reports removed licenses' do
expect(subject[:data]['removed_licenses'].count).to be(3) expect(subject[:data]['removed_licenses'].count).to eq(3)
expect(subject[:data]['removed_licenses'].any? { |license| license['name'] == 'New BSD' } ).to be_truthy expect(subject[:data]['removed_licenses']).to include(a_hash_including('name' => 'New BSD'))
end end
end end
......
...@@ -18,7 +18,7 @@ describe Security::StoreReportService, '#execute' do ...@@ -18,7 +18,7 @@ describe Security::StoreReportService, '#execute' do
using RSpec::Parameterized::TableSyntax using RSpec::Parameterized::TableSyntax
where(:case_name, :report_type, :scanners, :identifiers, :occurrences, :occurrence_identifiers, :occurrence_pipelines) do where(:case_name, :report_type, :scanners, :identifiers, :occurrences, :occurrence_identifiers, :occurrence_pipelines) do
'with SAST report' | :sast | 3 | 4 | 3 | 5 | 3 'with SAST report' | :sast | 3 | 14 | 33 | 35 | 33
'with Dependency Scanning report' | :dependency_scanning | 2 | 7 | 4 | 7 | 4 'with Dependency Scanning report' | :dependency_scanning | 2 | 7 | 4 | 7 | 4
end end
...@@ -46,8 +46,8 @@ describe Security::StoreReportService, '#execute' do ...@@ -46,8 +46,8 @@ describe Security::StoreReportService, '#execute' do
end end
context 'with existing data from previous pipeline' do context 'with existing data from previous pipeline' do
let(:scanner) { create(:vulnerabilities_scanner, project: project, external_id: 'find_sec_bugs', name: 'existing_name') } let(:scanner) { create(:vulnerabilities_scanner, project: project, external_id: 'bandit', name: 'Bandit') }
let(:identifier) { create(:vulnerabilities_identifier, project: project, fingerprint: 'f5724386167705667ae25a1390c0a516020690ba') } let(:identifier) { create(:vulnerabilities_identifier, project: project, fingerprint: 'e6dd15eda2137be0034977a85b300a94a4f243a3') }
let!(:new_artifact) { create(:ee_ci_job_artifact, :sast, job: new_build) } let!(:new_artifact) { create(:ee_ci_job_artifact, :sast, job: new_build) }
let(:new_build) { create(:ci_build, pipeline: new_pipeline) } let(:new_build) { create(:ci_build, pipeline: new_pipeline) }
let(:new_pipeline) { create(:ci_pipeline, project: project) } let(:new_pipeline) { create(:ci_pipeline, project: project) }
...@@ -61,7 +61,7 @@ describe Security::StoreReportService, '#execute' do ...@@ -61,7 +61,7 @@ describe Security::StoreReportService, '#execute' do
primary_identifier: identifier, primary_identifier: identifier,
scanner: scanner, scanner: scanner,
project: project, project: project,
location_fingerprint: '6b6bb283d43cc510d7d1e73e2882b3652cb34bd5') location_fingerprint: 'd869ba3f0b3347eb2749135a437dc07c8ae0f420')
end end
subject { described_class.new(new_pipeline, new_report).execute } subject { described_class.new(new_pipeline, new_report).execute }
...@@ -71,15 +71,15 @@ describe Security::StoreReportService, '#execute' do ...@@ -71,15 +71,15 @@ describe Security::StoreReportService, '#execute' do
end end
it 'inserts only new identifiers and reuse existing ones' do it 'inserts only new identifiers and reuse existing ones' do
expect { subject }.to change { Vulnerabilities::Identifier.count }.by(3) expect { subject }.to change { Vulnerabilities::Identifier.count }.by(13)
end end
it 'inserts only new occurrences and reuse existing ones' do it 'inserts only new occurrences and reuse existing ones' do
expect { subject }.to change { Vulnerabilities::Occurrence.count }.by(2) expect { subject }.to change { Vulnerabilities::Occurrence.count }.by(32)
end end
it 'inserts all occurrence pipelines (join model) for this new pipeline' do it 'inserts all occurrence pipelines (join model) for this new pipeline' do
expect { subject }.to change { Vulnerabilities::OccurrencePipeline.where(pipeline: new_pipeline).count }.by(3) expect { subject }.to change { Vulnerabilities::OccurrencePipeline.where(pipeline: new_pipeline).count }.by(33)
end end
end end
......
[
{
"category": "dependency_scanning",
"name": "io.netty/netty - CVE-2014-3488",
"message": "DoS by CPU exhaustion when using malicious SSL packets",
"cve": "app/pom.xml:io.netty/netty@3.9.1.Final:CVE-2014-3488",
"severity": "Unknown",
"solution": "Upgrade to the latest version",
"scanner": {
"id": "gemnasium",
"name": "Gemnasium"
},
"location": {
"file": "app/pom.xml",
"dependency": {
"package": {
"name": "io.netty/netty"
},
"version": "3.9.1.Final"
}
},
"identifiers": [
{
"type": "gemnasium",
"name": "Gemnasium-d1bf36d9-9f07-46cd-9cfc-8675338ada8f",
"value": "d1bf36d9-9f07-46cd-9cfc-8675338ada8f",
"url": "https://deps.sec.gitlab.com/packages/maven/io.netty/netty/versions/3.9.1.Final/advisories"
},
{
"type": "cve",
"name": "CVE-2014-3488",
"value": "CVE-2014-3488",
"url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-3488"
}
],
"links": [
{
"url": "https://bugzilla.redhat.com/CVE-2014-3488"
},
{
"url": "http://netty.io/news/2014/06/11/3.html"
},
{
"url": "https://github.com/netty/netty/issues/2562"
}
],
"priority": "Unknown",
"file": "app/pom.xml",
"url": "https://bugzilla.redhat.com/CVE-2014-3488",
"tool": "gemnasium"
},
{
"category": "dependency_scanning",
"name": "Django - CVE-2017-12794",
"message": "Possible XSS in traceback section of technical 500 debug page",
"cve": "app/requirements.txt:Django@1.11.3:CVE-2017-12794",
"severity": "Unknown",
"solution": "Upgrade to latest version or apply patch.",
"scanner": {
"id": "gemnasium",
"name": "Gemnasium"
},
"location": {
"file": "app/requirements.txt",
"dependency": {
"package": {
"name": "Django"
},
"version": "1.11.3"
}
},
"identifiers": [
{
"type": "gemnasium",
"name": "Gemnasium-6162a015-8635-4a15-8d7c-dc9321db366f",
"value": "6162a015-8635-4a15-8d7c-dc9321db366f",
"url": "https://deps.sec.gitlab.com/packages/pypi/Django/versions/1.11.3/advisories"
},
{
"type": "cve",
"name": "CVE-2017-12794",
"value": "CVE-2017-12794",
"url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-12794"
}
],
"links": [
{
"url": "https://www.djangoproject.com/weblog/2017/sep/05/security-releases/"
}
],
"priority": "Unknown",
"file": "app/requirements.txt",
"url": "https://www.djangoproject.com/weblog/2017/sep/05/security-releases/",
"tool": "gemnasium"
},
{
"category": "dependency_scanning",
"name": "nokogiri - USN-3424-1",
"message": "Vulnerabilities in libxml2",
"cve": "rails/Gemfile.lock:nokogiri@1.8.0:USN-3424-1",
"severity": "Unknown",
"solution": "Upgrade to latest version.",
"scanner": {
"id": "gemnasium",
"name": "Gemnasium"
},
"location": {
"file": "rails/Gemfile.lock",
"dependency": {
"package": {
"name": "nokogiri"
},
"version": "1.8.0"
}
},
"identifiers": [
{
"type": "gemnasium",
"name": "Gemnasium-06565b64-486d-4326-b906-890d9915804d",
"value": "06565b64-486d-4326-b906-890d9915804d",
"url": "https://deps.sec.gitlab.com/packages/gem/nokogiri/versions/1.8.0/advisories"
},
{
"type": "usn",
"name": "USN-3424-1",
"value": "USN-3424-1",
"url": "https://usn.ubuntu.com/3424-1/"
}
],
"links": [
{
"url": "https://github.com/sparklemotion/nokogiri/issues/1673"
}
],
"priority": "Unknown",
"file": "rails/Gemfile.lock",
"url": "https://github.com/sparklemotion/nokogiri/issues/1673",
"tool": "gemnasium"
},
{
"category": "dependency_scanning",
"name": "ffi - CVE-2018-1000201",
"message": "ruby-ffi DDL loading issue on Windows OS",
"cve": "ffi:1.9.18:CVE-2018-1000201",
"severity": "High",
"solution": "upgrade to \u003e= 1.9.24",
"scanner": {
"id": "bundler_audit",
"name": "bundler-audit"
},
"location": {
"file": "sast-sample-rails/Gemfile.lock",
"dependency": {
"package": {
"name": "ffi"
},
"version": "1.9.18"
}
},
"identifiers": [
{
"type": "cve",
"name": "CVE-2018-1000201",
"value": "CVE-2018-1000201",
"url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-1000201"
}
],
"links": [
{
"url": "https://github.com/ffi/ffi/releases/tag/1.9.24"
}
],
"priority": "High",
"file": "sast-sample-rails/Gemfile.lock",
"url": "https://github.com/ffi/ffi/releases/tag/1.9.24",
"tool": "bundler_audit"
}
]
This diff is collapsed.
[ {
"version": "1.3",
"vulnerabilities": [
{ {
"category": "dependency_scanning", "category": "dependency_scanning",
"name": "io.netty/netty - CVE-2014-3488", "name": "io.netty/netty - CVE-2014-3488",
...@@ -175,4 +177,5 @@ ...@@ -175,4 +177,5 @@
"url": "https://github.com/ffi/ffi/releases/tag/1.9.24", "url": "https://github.com/ffi/ffi/releases/tag/1.9.24",
"tool": "bundler_audit" "tool": "bundler_audit"
} }
] ]
}
{ {
"licenses": [ "licenses": [
{ {
"count": 13, "count": 1,
"name": "MIT" "name": "WTFPL"
},
{
"count": 2,
"name": "New BSD"
}, },
{ {
"count": 1, "count": 1,
"name": "LGPL" "name": "MIT"
} }
], ],
"dependencies": [ "dependencies": [
...@@ -20,107 +16,9 @@ ...@@ -20,107 +16,9 @@
"url": "http://opensource.org/licenses/mit-license" "url": "http://opensource.org/licenses/mit-license"
}, },
"dependency": { "dependency": {
"name": "bundler", "name": "actioncable",
"url": "http://bundler.io", "url": "http://rubyonrails.org",
"description": "The best way to manage your application's dependencies", "description": "WebSocket framework for Rails.",
"pathes": [
"."
]
}
},
{
"license": {
"name": "MIT",
"url": "http://opensource.org/licenses/mit-license"
},
"dependency": {
"name": "concurrent-ruby",
"url": "http://www.concurrent-ruby.com",
"description": "Modern concurrency tools for Ruby. Inspired by Erlang, Clojure, Scala, Haskell, F#, C#, Java, and classic concurrency patterns.",
"pathes": [
"."
]
}
},
{
"license": {
"name": "MIT",
"url": "http://opensource.org/licenses/mit-license"
},
"dependency": {
"name": "connection_pool",
"url": "https://github.com/mperham/connection_pool",
"description": "Generic connection pool for Ruby",
"pathes": [
"."
]
}
},
{
"license": {
"name": "MIT",
"url": "http://opensource.org/licenses/mit-license"
},
"dependency": {
"name": "mini_portile2",
"url": "http://github.com/flavorjones/mini_portile",
"description": "Simplistic port-like solution for developers",
"pathes": [
"."
]
}
},
{
"license": {
"name": "MIT",
"url": "http://opensource.org/licenses/mit-license"
},
"dependency": {
"name": "mustermann",
"url": "https://github.com/sinatra/mustermann",
"description": "Your personal string matching expert.",
"pathes": [
"."
]
}
},
{
"license": {
"name": "MIT",
"url": "http://opensource.org/licenses/mit-license"
},
"dependency": {
"name": "nokogiri",
"url": "http://nokogiri.org",
"description": "Nokogiri (鋸) is an HTML, XML, SAX, and Reader parser",
"pathes": [
"."
]
}
},
{
"license": {
"name": "New BSD",
"url": "http://opensource.org/licenses/BSD-3-Clause"
},
"dependency": {
"name": "pg",
"url": "https://bitbucket.org/ged/ruby-pg",
"description": "Pg is the Ruby interface to the {PostgreSQL RDBMS}[http://www.postgresql.org/]",
"pathes": [
"."
]
}
},
{
"license": {
"name": "New BSD",
"url": "http://opensource.org/licenses/BSD-3-Clause"
},
"dependency": {
"name": "puma",
"url": "http://puma.io",
"description": "Puma is a simple, fast, threaded, and highly concurrent HTTP 1.1 server for Ruby/Rack applications",
"pathes": [ "pathes": [
"." "."
] ]
...@@ -128,111 +26,13 @@ ...@@ -128,111 +26,13 @@
}, },
{ {
"license": { "license": {
"name": "MIT", "name": "WTFPL",
"url": "http://opensource.org/licenses/mit-license" "url": "http://www.wtfpl.net/"
},
"dependency": {
"name": "rack",
"url": "https://rack.github.io/",
"description": "a modular Ruby webserver interface",
"pathes": [
"."
]
}
},
{
"license": {
"name": "MIT",
"url": "http://opensource.org/licenses/mit-license"
},
"dependency": {
"name": "rack-protection",
"url": "http://github.com/sinatra/sinatra/tree/master/rack-protection",
"description": "Protect against typical web attacks, works with all Rack apps, including Rails.",
"pathes": [
"."
]
}
},
{
"license": {
"name": "MIT",
"url": "http://opensource.org/licenses/mit-license"
},
"dependency": {
"name": "redis",
"url": "https://github.com/redis/redis-rb",
"description": "A Ruby client library for Redis",
"pathes": [
"."
]
}
},
{
"license": {
"name": "LGPL",
"url": "http://www.gnu.org/licenses/lgpl.txt"
},
"dependency": {
"name": "sidekiq",
"url": "http://sidekiq.org",
"description": "Simple, efficient background processing for Ruby",
"pathes": [
"."
]
}
},
{
"license": {
"name": "MIT",
"url": "http://opensource.org/licenses/mit-license"
},
"dependency": {
"name": "sinatra",
"url": "http://www.sinatrarb.com/",
"description": "Classy web-development dressed in a DSL",
"pathes": [
"."
]
}
},
{
"license": {
"name": "MIT",
"url": "http://opensource.org/licenses/mit-license"
},
"dependency": {
"name": "slim",
"url": "http://slim-lang.com/",
"description": "Slim is a template language.",
"pathes": [
"."
]
}
},
{
"license": {
"name": "MIT",
"url": "http://opensource.org/licenses/mit-license"
},
"dependency": {
"name": "temple",
"url": "https://github.com/judofyr/temple",
"description": "Template compilation framework in Ruby",
"pathes": [
"."
]
}
},
{
"license": {
"name": "MIT",
"url": "http://opensource.org/licenses/mit-license"
}, },
"dependency": { "dependency": {
"name": "tilt", "name": "wtfpl_init",
"url": "http://github.com/rtomayko/tilt/", "url": "https://rubygems.org/gems/wtfpl_init",
"description": "Generic interface to multiple Ruby template engines", "description": "Download WTFPL license file and rename to LICENSE.md or something",
"pathes": [ "pathes": [
"." "."
] ]
......
[ {
"version": "1.2",
"vulnerabilities": [
{ {
"category": "sast", "category": "sast",
"message": "Probable insecure usage of temp file/directory.", "message": "Probable insecure usage of temp file/directory.",
...@@ -941,4 +943,5 @@ ...@@ -941,4 +943,5 @@
"url": "https://cwe.mitre.org/data/definitions/120.html", "url": "https://cwe.mitre.org/data/definitions/120.html",
"tool": "flawfinder" "tool": "flawfinder"
} }
] ]
}
[ {
"version": "1.3",
"vulnerabilities": [
{ {
"category": "dependency_scanning", "category": "dependency_scanning",
"name": "io.netty/netty - CVE-2014-3488", "name": "io.netty/netty - CVE-2014-3488",
...@@ -175,4 +177,5 @@ ...@@ -175,4 +177,5 @@
"url": "https://github.com/ffi/ffi/releases/tag/1.9.24", "url": "https://github.com/ffi/ffi/releases/tag/1.9.24",
"tool": "bundler_audit" "tool": "bundler_audit"
} }
] ]
}
[ {
"version": "1.2",
"vulnerabilities": [
{ {
"category": "sast", "category": "sast",
"message": "Probable insecure usage of temp file/directory.", "message": "Probable insecure usage of temp file/directory.",
...@@ -941,4 +943,5 @@ ...@@ -941,4 +943,5 @@
"url": "https://cwe.mitre.org/data/definitions/120.html", "url": "https://cwe.mitre.org/data/definitions/120.html",
"tool": "flawfinder" "tool": "flawfinder"
} }
] ]
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment