Commit a0ffffd0 authored by Mehmet Emin INAC's avatar Mehmet Emin INAC Committed by Stan Hu

Change the interface of Security artifact parsers

Previously we were instantiating the parser objects without passing any
initialization arguments. Because of this, we were passing the report
and the JSON text to the parse method and all the other utility methods
which was causing inflation on method signatures.

By just passing the report and the JSON text as the initialization args
to the instances, we can avoid passing them in between utility methods.
parent a523866d
...@@ -164,7 +164,7 @@ module EE ...@@ -164,7 +164,7 @@ module EE
def parse_security_artifact_blob(security_report, blob) def parse_security_artifact_blob(security_report, blob)
report_clone = security_report.clone_as_blank report_clone = security_report.clone_as_blank
::Gitlab::Ci::Parsers.fabricate!(security_report.type).parse!(blob, report_clone) ::Gitlab::Ci::Parsers.fabricate!(security_report.type, blob, report_clone).parse!
security_report.merge!(report_clone) security_report.merge!(report_clone)
end end
......
...@@ -120,7 +120,7 @@ module EE ...@@ -120,7 +120,7 @@ module EE
report = ::Gitlab::Ci::Reports::Security::Report.new(file_type, job.pipeline, nil).tap do |report| report = ::Gitlab::Ci::Reports::Security::Report.new(file_type, job.pipeline, nil).tap do |report|
each_blob do |blob| each_blob do |blob|
::Gitlab::Ci::Parsers.fabricate!(file_type).parse!(blob, report) ::Gitlab::Ci::Parsers.fabricate!(file_type, blob, report).parse!
end end
end end
......
...@@ -7,8 +7,16 @@ module Gitlab ...@@ -7,8 +7,16 @@ module Gitlab
class Common class Common
SecurityReportParserError = Class.new(Gitlab::Ci::Parsers::ParserError) SecurityReportParserError = Class.new(Gitlab::Ci::Parsers::ParserError)
def parse!(json_data, report) def self.parse!(json_data, report)
report_data = parse_report(json_data) new(json_data, report).parse!
end
def initialize(json_data, report)
@json_data = json_data
@report = report
end
def parse!
raise SecurityReportParserError, "Invalid report format" unless report_data.is_a?(Hash) raise SecurityReportParserError, "Invalid report format" unless report_data.is_a?(Hash)
create_scanner(report, report_data.dig('scan', 'scanner')) create_scanner(report, report_data.dig('scan', 'scanner'))
...@@ -26,10 +34,12 @@ module Gitlab ...@@ -26,10 +34,12 @@ module Gitlab
raise SecurityReportParserError, "#{report.type} security report parsing failed" raise SecurityReportParserError, "#{report.type} security report parsing failed"
end end
protected private
attr_reader :json_data, :report
def parse_report(json_data) def report_data
Gitlab::Json.parse!(json_data) @report_data ||= Gitlab::Json.parse!(json_data)
end end
# map remediations to relevant vulnerabilities # map remediations to relevant vulnerabilities
...@@ -151,8 +161,6 @@ module Gitlab ...@@ -151,8 +161,6 @@ module Gitlab
raise NotImplementedError raise NotImplementedError
end end
private
def finding_name(data, identifiers, location) def finding_name(data, identifiers, location)
return data['message'] if data['message'].present? return data['message'] if data['message'].present?
return data['name'] if data['name'].present? return data['name'] if data['name'].present?
......
...@@ -14,17 +14,19 @@ module Gitlab ...@@ -14,17 +14,19 @@ module Gitlab
override :parse_report override :parse_report
end end
def parse_report(json_data) def report_data
report = super @report_data ||= begin
data = super
if report.is_a?(Array) if data.is_a?(Array)
report = { data = {
"version" => self.class::DEPRECATED_REPORT_VERSION, "version" => self.class::DEPRECATED_REPORT_VERSION,
"vulnerabilities" => report "vulnerabilities" => data
} }
end end
report data
end
end end
end end
end end
......
...@@ -5,20 +5,20 @@ module Gitlab ...@@ -5,20 +5,20 @@ module Gitlab
module Parsers module Parsers
module Security module Security
class Dast < Common class Dast < Common
def parse!(json_data, report) def parse!
report_data = super super
report.scanned_resources = create_scanned_resources(report_data.dig('scan', 'scanned_resources')) report.scanned_resources = create_scanned_resources(report_data.dig('scan', 'scanned_resources'))
end end
private private
def parse_report(json_data) def report_data
report = super @report_data ||= begin
super.then do |data|
return Formatters::Dast.new(report).format if Formatters::Dast.satisfies?(report) Formatters::Dast.satisfies?(data) ? Formatters::Dast.new(data).format : data
end
report end
end end
def create_scanned_resources(scanned_resources) def create_scanned_resources(scanned_resources)
......
...@@ -56,10 +56,10 @@ RSpec.describe Security::FindingsFinder do ...@@ -56,10 +56,10 @@ RSpec.describe Security::FindingsFinder do
before(:all) do before(:all) do
ds_content = File.read(artifact_ds.file.path) ds_content = File.read(artifact_ds.file.path)
Gitlab::Ci::Parsers::Security::DependencyScanning.new.parse!(ds_content, report_ds) Gitlab::Ci::Parsers::Security::DependencyScanning.parse!(ds_content, report_ds)
report_ds.merge!(report_ds) report_ds.merge!(report_ds)
sast_content = File.read(artifact_sast.file.path) sast_content = File.read(artifact_sast.file.path)
Gitlab::Ci::Parsers::Security::Sast.new.parse!(sast_content, report_sast) Gitlab::Ci::Parsers::Security::Sast.parse!(sast_content, report_sast)
report_sast.merge!(report_sast) report_sast.merge!(report_sast)
{ artifact_ds => report_ds, artifact_sast => report_sast }.each do |artifact, report| { artifact_ds => report_ds, artifact_sast => report_sast }.each do |artifact, report|
......
...@@ -8,13 +8,14 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do ...@@ -8,13 +8,14 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do
let(:artifact) { build(:ee_ci_job_artifact, :common_security_report) } let(:artifact) { build(:ee_ci_job_artifact, :common_security_report) }
let(:report) { Gitlab::Ci::Reports::Security::Report.new(artifact.file_type, pipeline, 2.weeks.ago) } let(:report) { Gitlab::Ci::Reports::Security::Report.new(artifact.file_type, pipeline, 2.weeks.ago) }
let(:parser) { described_class.new }
let(:location) { ::Gitlab::Ci::Reports::Security::Locations::DependencyScanning.new(file_path: 'yarn/yarn.lock', package_version: 'v2', package_name: 'saml2') } let(:location) { ::Gitlab::Ci::Reports::Security::Locations::DependencyScanning.new(file_path: 'yarn/yarn.lock', package_version: 'v2', package_name: 'saml2') }
before do before do
allow(parser).to receive(:create_location).and_return(location) allow_next_instance_of(described_class) do |parser|
allow(parser).to receive(:create_location).and_return(location)
end
artifact.each_blob { |blob| parser.parse!(blob, report) } artifact.each_blob { |blob| described_class.parse!(blob, report) }
end end
describe 'parsing finding.name' do describe 'parsing finding.name' do
...@@ -142,9 +143,8 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do ...@@ -142,9 +143,8 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common do
end end
it 'returns nil when scan is not a hash' do it 'returns nil when scan is not a hash' do
parser = described_class.new
empty_report = Gitlab::Ci::Reports::Security::Report.new(artifact.file_type, pipeline, 2.weeks.ago) empty_report = Gitlab::Ci::Reports::Security::Report.new(artifact.file_type, pipeline, 2.weeks.ago)
parser.parse!({}.to_json, empty_report) described_class.parse!({}.to_json, empty_report)
expect(empty_report.scan).to be(nil) expect(empty_report.scan).to be(nil)
end end
......
...@@ -3,15 +3,12 @@ ...@@ -3,15 +3,12 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Gitlab::Ci::Parsers::Security::ContainerScanning do RSpec.describe Gitlab::Ci::Parsers::Security::ContainerScanning do
let(:parser) { described_class.new }
let(:project) { artifact.project } let(:project) { artifact.project }
let(:pipeline) { artifact.job.pipeline } let(:pipeline) { artifact.job.pipeline }
let(:report) { Gitlab::Ci::Reports::Security::Report.new(artifact.file_type, pipeline, 2.weeks.ago) } let(:report) { Gitlab::Ci::Reports::Security::Report.new(artifact.file_type, pipeline, 2.weeks.ago) }
before do before do
artifact.each_blob do |blob| artifact.each_blob { |blob| described_class.parse!(blob, report) }
parser.parse!(blob, report)
end
end end
describe '#parse!' do describe '#parse!' do
......
...@@ -6,14 +6,11 @@ RSpec.describe Gitlab::Ci::Parsers::Security::CoverageFuzzing do ...@@ -6,14 +6,11 @@ RSpec.describe Gitlab::Ci::Parsers::Security::CoverageFuzzing do
let(:project) { artifact.project } let(:project) { artifact.project }
let(:pipeline) { artifact.job.pipeline } let(:pipeline) { artifact.job.pipeline }
let(:report) { Gitlab::Ci::Reports::Security::Report.new(artifact.file_type, pipeline, 2.weeks.ago) } let(:report) { Gitlab::Ci::Reports::Security::Report.new(artifact.file_type, pipeline, 2.weeks.ago) }
let(:parser) { described_class.new }
let(:artifact) { create(:ee_ci_job_artifact, :coverage_fuzzing) } let(:artifact) { create(:ee_ci_job_artifact, :coverage_fuzzing) }
describe '#parse!' do describe '#parse!' do
before do before do
artifact.each_blob do |blob| artifact.each_blob { |blob| described_class.parse!(blob, report) }
parser.parse!(blob, report)
end
end end
it 'parses all identifiers and findings' do it 'parses all identifiers and findings' do
......
...@@ -10,7 +10,6 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Dast do ...@@ -10,7 +10,6 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Dast do
let(:pipeline) { artifact.job.pipeline } let(:pipeline) { artifact.job.pipeline }
let(:artifact) { create(:ee_ci_job_artifact, :dast) } let(:artifact) { create(:ee_ci_job_artifact, :dast) }
let(:report) { Gitlab::Ci::Reports::Security::Report.new(artifact.file_type, pipeline, 2.weeks.ago) } let(:report) { Gitlab::Ci::Reports::Security::Report.new(artifact.file_type, pipeline, 2.weeks.ago) }
let(:parser) { described_class.new }
where(:report_format, where(:report_format,
:occurrence_count, :occurrence_count,
...@@ -32,9 +31,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Dast do ...@@ -32,9 +31,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Dast do
let(:artifact) { create(:ee_ci_job_artifact, report_format) } let(:artifact) { create(:ee_ci_job_artifact, report_format) }
before do before do
artifact.each_blob do |blob| artifact.each_blob { |blob| described_class.parse!(blob, report) }
parser.parse!(blob, report)
end
end end
it 'parses all identifiers, findings and scanned resources' do it 'parses all identifiers, findings and scanned resources' do
...@@ -75,9 +72,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Dast do ...@@ -75,9 +72,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Dast do
let(:artifact) { create(:ee_ci_job_artifact, 'dast') } let(:artifact) { create(:ee_ci_job_artifact, 'dast') }
before do before do
artifact.each_blob do |blob| artifact.each_blob { |blob| described_class.parse!(blob, report) }
parser.parse!(blob, report)
end
end end
let(:raw_json) do let(:raw_json) do
...@@ -98,7 +93,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Dast do ...@@ -98,7 +93,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Dast do
end end
it 'skips invalid URLs' do it 'skips invalid URLs' do
parser.parse!(raw_json.to_json, report) described_class.parse!(raw_json.to_json, report)
expect(report.scanned_resources).to be_empty expect(report.scanned_resources).to be_empty
end end
......
...@@ -10,7 +10,6 @@ RSpec.describe Gitlab::Ci::Parsers::Security::DependencyScanning do ...@@ -10,7 +10,6 @@ RSpec.describe Gitlab::Ci::Parsers::Security::DependencyScanning do
let(:pipeline) { artifact.job.pipeline } let(:pipeline) { artifact.job.pipeline }
let(:artifact) { create(:ee_ci_job_artifact, :dependency_scanning) } let(:artifact) { create(:ee_ci_job_artifact, :dependency_scanning) }
let(:report) { Gitlab::Ci::Reports::Security::Report.new(artifact.file_type, pipeline, 2.weeks.ago) } let(:report) { Gitlab::Ci::Reports::Security::Report.new(artifact.file_type, pipeline, 2.weeks.ago) }
let(:parser) { described_class.new }
where(:report_format, :occurrence_count, :identifier_count, :scanner_count, :file_path, :package_name, :package_version, :version) do where(:report_format, :occurrence_count, :identifier_count, :scanner_count, :file_path, :package_name, :package_version, :version) do
:dependency_scanning | 4 | 7 | 3 | 'app/pom.xml' | 'io.netty/netty' | '3.9.1.Final' | '1.3' :dependency_scanning | 4 | 7 | 3 | 'app/pom.xml' | 'io.netty/netty' | '3.9.1.Final' | '1.3'
...@@ -22,9 +21,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::DependencyScanning do ...@@ -22,9 +21,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::DependencyScanning do
let(:artifact) { create(:ee_ci_job_artifact, report_format) } let(:artifact) { create(:ee_ci_job_artifact, report_format) }
before do before do
artifact.each_blob do |blob| artifact.each_blob { |blob| described_class.parse!(blob, report) }
parser.parse!(blob, report)
end
end end
it "parses all identifiers and findings" do it "parses all identifiers and findings" do
...@@ -56,7 +53,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::DependencyScanning do ...@@ -56,7 +53,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::DependencyScanning do
report_hash[:vulnerabilities][0][:location] = nil report_hash[:vulnerabilities][0][:location] = nil
end end
it { expect { parser.parse!(report_hash.to_json, report) }.not_to raise_error } it { expect { described_class.parse!(report_hash.to_json, report) }.not_to raise_error }
end end
context "when parsing a vulnerability with a missing cve" do context "when parsing a vulnerability with a missing cve" do
...@@ -66,16 +63,14 @@ RSpec.describe Gitlab::Ci::Parsers::Security::DependencyScanning do ...@@ -66,16 +63,14 @@ RSpec.describe Gitlab::Ci::Parsers::Security::DependencyScanning do
report_hash[:vulnerabilities][0][:cve] = nil report_hash[:vulnerabilities][0][:cve] = nil
end end
it { expect { parser.parse!(report_hash.to_json, report) }.not_to raise_error } it { expect { described_class.parse!(report_hash.to_json, report) }.not_to raise_error }
end end
context "when vulnerabilities have remediations" do context "when vulnerabilities have remediations" do
let(:artifact) { create(:ee_ci_job_artifact, :dependency_scanning_remediation) } let(:artifact) { create(:ee_ci_job_artifact, :dependency_scanning_remediation) }
before do before do
artifact.each_blob do |blob| artifact.each_blob { |blob| described_class.parse!(blob, report) }
parser.parse!(blob, report)
end
end end
it "generates occurrence with expected remediation" do it "generates occurrence with expected remediation" do
......
...@@ -10,8 +10,6 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Sast do ...@@ -10,8 +10,6 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Sast do
let(:created_at) { 2.weeks.ago } let(:created_at) { 2.weeks.ago }
subject(:parser) { described_class.new }
context "when parsing valid reports" do context "when parsing valid reports" do
where(:report_format, :scanner_length) do where(:report_format, :scanner_length) do
:sast | 4 :sast | 4
...@@ -23,9 +21,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Sast do ...@@ -23,9 +21,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Sast do
let(:artifact) { create(:ee_ci_job_artifact, report_format) } let(:artifact) { create(:ee_ci_job_artifact, report_format) }
before do before do
artifact.each_blob do |blob| artifact.each_blob { |blob| described_class.parse!(blob, report) }
parser.parse!(blob, report)
end
end end
it "parses all identifiers and findings" do it "parses all identifiers and findings" do
...@@ -57,7 +53,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Sast do ...@@ -57,7 +53,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Sast do
let(:report) { Gitlab::Ci::Reports::Security::Report.new('sast', pipeline, created_at) } let(:report) { Gitlab::Ci::Reports::Security::Report.new('sast', pipeline, created_at) }
let(:blob) { Gitlab::Json.generate({}) } let(:blob) { Gitlab::Json.generate({}) }
it { expect(parser.parse!(blob, report)).to be_empty } it { expect(described_class.parse!(blob, report)).to be_empty }
end end
end end
end end
...@@ -8,8 +8,6 @@ RSpec.describe Gitlab::Ci::Parsers::Security::SecretDetection do ...@@ -8,8 +8,6 @@ RSpec.describe Gitlab::Ci::Parsers::Security::SecretDetection do
let(:created_at) { 2.weeks.ago } let(:created_at) { 2.weeks.ago }
subject(:parser) { described_class.new }
context "when parsing valid reports" do context "when parsing valid reports" do
where(report_format: %i(secret_detection)) where(report_format: %i(secret_detection))
...@@ -18,9 +16,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::SecretDetection do ...@@ -18,9 +16,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::SecretDetection do
let(:artifact) { create(:ee_ci_job_artifact, report_format) } let(:artifact) { create(:ee_ci_job_artifact, report_format) }
before do before do
artifact.each_blob do |blob| artifact.each_blob { |blob| described_class.parse!(blob, report) }
parser.parse!(blob, report)
end
end end
it "parses all identifiers and findings" do it "parses all identifiers and findings" do
...@@ -52,7 +48,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::SecretDetection do ...@@ -52,7 +48,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::SecretDetection do
let(:report) { Gitlab::Ci::Reports::Security::Report.new('secret_detection', pipeline, created_at) } let(:report) { Gitlab::Ci::Reports::Security::Report.new('secret_detection', pipeline, created_at) }
let(:blob) { Gitlab::Json.generate({}) } let(:blob) { Gitlab::Json.generate({}) }
it { expect(parser.parse!(blob, report)).to be_empty } it { expect(described_class.parse!(blob, report)).to be_empty }
end end
end end
end end
...@@ -15,8 +15,8 @@ module Gitlab ...@@ -15,8 +15,8 @@ module Gitlab
} }
end end
def self.fabricate!(file_type) def self.fabricate!(file_type, *args)
parsers.fetch(file_type.to_sym).new parsers.fetch(file_type.to_sym).new(*args)
rescue KeyError rescue KeyError
raise ParserNotFoundError, "Cannot find any parser matching file type '#{file_type}'" raise ParserNotFoundError, "Cannot find any parser matching file type '#{file_type}'"
end end
......
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