Commit 34e8f5c6 authored by Thong Kuah's avatar Thong Kuah

Merge branch 'outdated-security-reports-backend' into 'master'

Add outdated fields to internal security reports comparison API

See merge request gitlab-org/gitlab!21220
parents d9d063b9 5fe4b7d0
...@@ -887,7 +887,7 @@ module Ci ...@@ -887,7 +887,7 @@ module Ci
def each_report(report_types) def each_report(report_types)
job_artifacts_for_types(report_types).each do |report_artifact| job_artifacts_for_types(report_types).each do |report_artifact|
report_artifact.each_blob do |blob| report_artifact.each_blob do |blob|
yield report_artifact.file_type, blob yield report_artifact.file_type, blob, report_artifact
end end
end end
end end
......
...@@ -24,13 +24,9 @@ module Security ...@@ -24,13 +24,9 @@ module Security
end end
def execute def execute
reports = pipeline_reports requested_reports = pipeline_reports.select { |report_type| requested_type?(report_type) }
return [] if reports.nil?
occurrences = reports.each_with_object([]) do |(type, report), occurrences|
next unless requested_type?(type)
occurrences = requested_reports.each_with_object([]) do |(type, report), occurrences|
raise ParseError, 'JSON parsing failed' if report.error.is_a?(Gitlab::Ci::Parsers::Security::Common::SecurityReportParserError) raise ParseError, 'JSON parsing failed' if report.error.is_a?(Gitlab::Ci::Parsers::Security::Common::SecurityReportParserError)
normalized_occurrences = normalize_report_occurrences( normalized_occurrences = normalize_report_occurrences(
...@@ -41,7 +37,7 @@ module Security ...@@ -41,7 +37,7 @@ module Security
occurrences.concat(filtered_occurrences) occurrences.concat(filtered_occurrences)
end end
sort_occurrences(occurrences) Gitlab::Ci::Reports::Security::AggregatedReport.new(requested_reports.values, sort_occurrences(occurrences))
end end
private private
...@@ -63,7 +59,7 @@ module Security ...@@ -63,7 +59,7 @@ module Security
end end
def pipeline_reports def pipeline_reports
pipeline&.security_reports&.reports pipeline&.security_reports&.reports || {}
end end
def vulnerabilities_by_finding_fingerprint(report_type, report) def vulnerabilities_by_finding_fingerprint(report_type, report)
......
...@@ -48,8 +48,8 @@ module EE ...@@ -48,8 +48,8 @@ module EE
end end
def collect_security_reports!(security_reports) def collect_security_reports!(security_reports)
each_report(::Ci::JobArtifact::SECURITY_REPORT_FILE_TYPES) do |file_type, blob| each_report(::Ci::JobArtifact::SECURITY_REPORT_FILE_TYPES) do |file_type, blob, report_artifact|
security_reports.get_report(file_type).tap do |security_report| security_reports.get_report(file_type, report_artifact).tap do |security_report|
next unless project.feature_available?(LICENSED_PARSER_FEATURES.fetch(file_type)) next unless project.feature_available?(LICENSED_PARSER_FEATURES.fetch(file_type))
parse_security_artifact_blob(security_report, blob) parse_security_artifact_blob(security_report, blob)
...@@ -75,7 +75,7 @@ module EE ...@@ -75,7 +75,7 @@ module EE
if project.feature_available?(:dependency_scanning) if project.feature_available?(:dependency_scanning)
dependency_list = ::Gitlab::Ci::Parsers::Security::DependencyList.new(project, sha) dependency_list = ::Gitlab::Ci::Parsers::Security::DependencyList.new(project, sha)
each_report(::Ci::JobArtifact::DEPENDENCY_LIST_REPORT_FILE_TYPES) do |file_type, blob| each_report(::Ci::JobArtifact::DEPENDENCY_LIST_REPORT_FILE_TYPES) do |_, blob|
dependency_list.parse!(blob, dependency_list_report) dependency_list.parse!(blob, dependency_list_report)
end end
end end
...@@ -87,7 +87,7 @@ module EE ...@@ -87,7 +87,7 @@ module EE
if project.feature_available?(:dependency_scanning) if project.feature_available?(:dependency_scanning)
dependency_list = ::Gitlab::Ci::Parsers::Security::DependencyList.new(project, sha) dependency_list = ::Gitlab::Ci::Parsers::Security::DependencyList.new(project, sha)
each_report(::Ci::JobArtifact::LICENSE_MANAGEMENT_REPORT_FILE_TYPES) do |file_type, blob| each_report(::Ci::JobArtifact::LICENSE_MANAGEMENT_REPORT_FILE_TYPES) do |_, blob|
dependency_list.parse_licenses!(blob, dependency_list_report) dependency_list.parse_licenses!(blob, dependency_list_report)
end end
end end
......
# frozen_string_literal: true # frozen_string_literal: true
class Vulnerabilities::OccurrenceReportsComparerEntity < Grape::Entity class Vulnerabilities::OccurrenceReportsComparerEntity < Grape::Entity
expose :base_report_created_at
expose :base_report_out_of_date
expose :head_report_created_at
expose :added, using: Vulnerabilities::OccurrenceEntity expose :added, using: Vulnerabilities::OccurrenceEntity
expose :fixed, using: Vulnerabilities::OccurrenceEntity expose :fixed, using: Vulnerabilities::OccurrenceEntity
expose :existing, using: Vulnerabilities::OccurrenceEntity expose :existing, using: Vulnerabilities::OccurrenceEntity
......
...@@ -20,7 +20,8 @@ module Security ...@@ -20,7 +20,8 @@ module Security
@source_reports = source_reports @source_reports = source_reports
@target_report = ::Gitlab::Ci::Reports::Security::Report.new( @target_report = ::Gitlab::Ci::Reports::Security::Report.new(
@source_reports.first.type, @source_reports.first.type,
@source_reports.first.commit_sha @source_reports.first.commit_sha,
@source_reports.first.created_at
) )
@occurrences = [] @occurrences = []
end end
......
...@@ -14,7 +14,8 @@ module API ...@@ -14,7 +14,8 @@ module API
return [] unless pipeline return [] unless pipeline
Security::PipelineVulnerabilitiesFinder.new(pipeline: pipeline, params: params).execute aggregated_report = Security::PipelineVulnerabilitiesFinder.new(pipeline: pipeline, params: params).execute
aggregated_report.occurrences
end end
end end
......
...@@ -18,7 +18,6 @@ module Gitlab ...@@ -18,7 +18,6 @@ module Gitlab
report = super report = super
if report.is_a?(Array) if report.is_a?(Array)
puts self.class
report = { report = {
"version" => self.class::DEPRECATED_REPORT_VERSION, "version" => self.class::DEPRECATED_REPORT_VERSION,
"vulnerabilities" => report "vulnerabilities" => report
......
# frozen_string_literal: true
# Used to represent combined Security Reports. This is typically done for vulnerability deduplication purposes.
module Gitlab
module Ci
module Reports
module Security
class AggregatedReport
attr_reader :occurrences
def initialize(reports, occurrences)
@reports = reports
@occurrences = occurrences
end
def created_at
@reports.map(&:created_at).compact.min
end
end
end
end
end
end
...@@ -7,6 +7,7 @@ module Gitlab ...@@ -7,6 +7,7 @@ module Gitlab
class Report class Report
UNSAFE_SEVERITIES = %w[unknown high critical].freeze UNSAFE_SEVERITIES = %w[unknown high critical].freeze
attr_reader :created_at
attr_reader :type attr_reader :type
attr_reader :commit_sha attr_reader :commit_sha
attr_reader :occurrences attr_reader :occurrences
...@@ -15,9 +16,10 @@ module Gitlab ...@@ -15,9 +16,10 @@ module Gitlab
attr_accessor :error attr_accessor :error
def initialize(type, commit_sha) def initialize(type, commit_sha, created_at)
@type = type @type = type
@commit_sha = commit_sha @commit_sha = commit_sha
@created_at = created_at
@occurrences = [] @occurrences = []
@scanners = {} @scanners = {}
@identifiers = {} @identifiers = {}
...@@ -40,7 +42,7 @@ module Gitlab ...@@ -40,7 +42,7 @@ module Gitlab
end end
def clone_as_blank def clone_as_blank
Report.new(type, commit_sha) Report.new(type, commit_sha, created_at)
end end
def replace_with!(other) def replace_with!(other)
......
...@@ -14,8 +14,8 @@ module Gitlab ...@@ -14,8 +14,8 @@ module Gitlab
@commit_sha = commit_sha @commit_sha = commit_sha
end end
def get_report(report_type) def get_report(report_type, report_artifact)
reports[report_type] ||= Report.new(report_type, commit_sha) reports[report_type] ||= Report.new(report_type, commit_sha, report_artifact.created_at)
end end
def violates_default_policy? def violates_default_policy?
......
...@@ -9,27 +9,43 @@ module Gitlab ...@@ -9,27 +9,43 @@ module Gitlab
attr_reader :base_report, :head_report attr_reader :base_report, :head_report
ACCEPTABLE_REPORT_AGE = 1.week
def initialize(base_report, head_report) def initialize(base_report, head_report)
@base_report = base_report || [] @base_report = base_report
@head_report = head_report || [] @head_report = head_report
end
def base_report_created_at
@base_report.created_at
end
def head_report_created_at
@head_report.created_at
end
def base_report_out_of_date
return false unless @base_report.created_at
ACCEPTABLE_REPORT_AGE.ago > @base_report.created_at
end end
def added def added
strong_memoize(:added) do strong_memoize(:added) do
head_report - base_report head_report.occurrences - base_report.occurrences
end end
end end
def fixed def fixed
strong_memoize(:fixed) do strong_memoize(:fixed) do
base_report - head_report base_report.occurrences - head_report.occurrences
end end
end end
def existing def existing
strong_memoize(:existing) do strong_memoize(:existing) do
# Existing vulnerabilities should point to source report for most recent information # Existing vulnerabilities should point to source report for most recent information
head_report & base_report head_report.occurrences & base_report.occurrences
end end
end end
end end
......
# frozen_string_literal: true
FactoryBot.define do
factory :ci_reports_security_aggregated_reports, class: ::Gitlab::Ci::Reports::Security::AggregatedReport do
reports { FactoryBot.build_list(:ci_reports_security_report, 1) }
occurrences { FactoryBot.build_list(:ci_reports_security_occurrence, 1) }
initialize_with do
::Gitlab::Ci::Reports::Security::AggregatedReport.new(reports, occurrences)
end
end
end
...@@ -4,6 +4,7 @@ FactoryBot.define do ...@@ -4,6 +4,7 @@ FactoryBot.define do
factory :ci_reports_security_report, class: ::Gitlab::Ci::Reports::Security::Report do factory :ci_reports_security_report, class: ::Gitlab::Ci::Reports::Security::Report do
type { :sast } type { :sast }
commit_sha { Digest::SHA1.hexdigest(SecureRandom.hex) } commit_sha { Digest::SHA1.hexdigest(SecureRandom.hex) }
created_at { 2.weeks.ago }
transient do transient do
occurrences { [] } occurrences { [] }
...@@ -20,7 +21,7 @@ FactoryBot.define do ...@@ -20,7 +21,7 @@ FactoryBot.define do
skip_create skip_create
initialize_with do initialize_with do
::Gitlab::Ci::Reports::Security::Report.new(type, commit_sha) ::Gitlab::Ci::Reports::Security::Report.new(type, commit_sha, created_at)
end end
end end
end end
...@@ -6,7 +6,7 @@ describe Gitlab::Ci::Parsers::Security::ContainerScanning do ...@@ -6,7 +6,7 @@ describe Gitlab::Ci::Parsers::Security::ContainerScanning do
let(:parser) { described_class.new } 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.sha) } let(:report) { Gitlab::Ci::Reports::Security::Report.new(artifact.file_type, pipeline.sha, 2.weeks.ago) }
before do before do
artifact.each_blob do |blob| artifact.each_blob do |blob|
......
...@@ -9,7 +9,7 @@ describe Gitlab::Ci::Parsers::Security::Dast do ...@@ -9,7 +9,7 @@ describe Gitlab::Ci::Parsers::Security::Dast 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, :dast) } let(:artifact) { create(:ee_ci_job_artifact, :dast) }
let(:report) { Gitlab::Ci::Reports::Security::Report.new(artifact.file_type, pipeline.sha) } let(:report) { Gitlab::Ci::Reports::Security::Report.new(artifact.file_type, pipeline.sha, 2.weeks.ago) }
let(:parser) { described_class.new } let(:parser) { described_class.new }
where(:report_format, where(:report_format,
......
...@@ -9,7 +9,7 @@ describe Gitlab::Ci::Parsers::Security::DependencyScanning do ...@@ -9,7 +9,7 @@ describe Gitlab::Ci::Parsers::Security::DependencyScanning 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, :dependency_scanning) } let(:artifact) { create(:ee_ci_job_artifact, :dependency_scanning) }
let(:report) { Gitlab::Ci::Reports::Security::Report.new(artifact.file_type, pipeline.sha) } let(:report) { Gitlab::Ci::Reports::Security::Report.new(artifact.file_type, pipeline.sha, 2.weeks.ago) }
let(:parser) { described_class.new } 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
......
...@@ -7,12 +7,13 @@ describe Gitlab::Ci::Parsers::Security::Sast do ...@@ -7,12 +7,13 @@ describe Gitlab::Ci::Parsers::Security::Sast do
subject(:parser) { described_class.new } subject(:parser) { described_class.new }
let(:commit_sha) { "d8978e74745e18ce44d88814004d4255ac6a65bb" } let(:commit_sha) { "d8978e74745e18ce44d88814004d4255ac6a65bb" }
let(:created_at) { 2.weeks.ago }
context "when parsing valid reports" do context "when parsing valid reports" do
where(report_format: %i(sast sast_deprecated)) where(report_format: %i(sast sast_deprecated))
with_them do with_them do
let(:report) { Gitlab::Ci::Reports::Security::Report.new(artifact.file_type, commit_sha) } let(:report) { Gitlab::Ci::Reports::Security::Report.new(artifact.file_type, commit_sha, created_at) }
let(:artifact) { create(:ee_ci_job_artifact, report_format) } let(:artifact) { create(:ee_ci_job_artifact, report_format) }
before do before do
...@@ -47,7 +48,7 @@ describe Gitlab::Ci::Parsers::Security::Sast do ...@@ -47,7 +48,7 @@ describe Gitlab::Ci::Parsers::Security::Sast do
end end
context "when parsing an empty report" do context "when parsing an empty report" do
let(:report) { Gitlab::Ci::Reports::Security::Report.new('sast', commit_sha) } let(:report) { Gitlab::Ci::Reports::Security::Report.new('sast', commit_sha, created_at) }
let(:blob) { JSON.generate({}) } let(:blob) { JSON.generate({}) }
it { expect(parser.parse!(blob, report)).to be_empty } it { expect(parser.parse!(blob, report)).to be_empty }
......
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Ci::Reports::Security::AggregatedReport do
subject { described_class.new(reports, occurrences) }
let(:reports) { build_list(:ci_reports_security_report, 1) }
let(:occurrences) { build_list(:ci_reports_security_occurrence, 1) }
describe '#created_at' do
context 'no reports' do
let(:reports) { [] }
it 'has no created date' do
expect(subject.created_at).to be_nil
end
end
context 'report with no created date' do
let(:reports) { build_list(:ci_reports_security_report, 1, created_at: nil) }
it 'has no created date' do
expect(subject.created_at).to be_nil
end
end
context 'has reports' do
let(:a_long_time_ago) { 2.months.ago }
let(:a_while_ago) { 2.weeks.ago }
let(:yesterday) { 1.day.ago }
let(:reports) do
[build(:ci_reports_security_report, created_at: a_while_ago),
build(:ci_reports_security_report, created_at: a_long_time_ago),
build(:ci_reports_security_report, created_at: nil),
build(:ci_reports_security_report, created_at: yesterday)]
end
it 'has oldest created date' do
expect(subject.created_at).to eq(a_long_time_ago)
end
end
end
end
...@@ -3,8 +3,9 @@ ...@@ -3,8 +3,9 @@
require 'spec_helper' require 'spec_helper'
describe Gitlab::Ci::Reports::Security::Report do describe Gitlab::Ci::Reports::Security::Report do
let(:report) { described_class.new('sast', commit_sha) } let(:report) { described_class.new('sast', commit_sha, created_at) }
let(:commit_sha) { "d8978e74745e18ce44d88814004d4255ac6a65bb" } let(:commit_sha) { "d8978e74745e18ce44d88814004d4255ac6a65bb" }
let(:created_at) { 2.weeks.ago }
it { expect(report.type).to eq('sast') } it { expect(report.type).to eq('sast') }
...@@ -65,6 +66,7 @@ describe Gitlab::Ci::Reports::Security::Report do ...@@ -65,6 +66,7 @@ describe Gitlab::Ci::Reports::Security::Report do
expect(clone.type).to eq(report.type) expect(clone.type).to eq(report.type)
expect(clone.commit_sha).to eq(report.commit_sha) expect(clone.commit_sha).to eq(report.commit_sha)
expect(clone.created_at).to eq(report.created_at)
expect(clone.occurrences).to eq([]) expect(clone.occurrences).to eq([])
expect(clone.scanners).to eq({}) expect(clone.scanners).to eq({})
expect(clone.identifiers).to eq({}) expect(clone.identifiers).to eq({})
...@@ -111,7 +113,7 @@ describe Gitlab::Ci::Reports::Security::Report do ...@@ -111,7 +113,7 @@ describe Gitlab::Ci::Reports::Security::Report do
allow(report).to receive(:replace_with!) allow(report).to receive(:replace_with!)
end end
subject { report.merge!(described_class.new('sast', commit_sha)) } subject { report.merge!(described_class.new('sast', commit_sha, created_at)) }
it 'invokes the merge with other report and then replaces this report contents by merge result' do it 'invokes the merge with other report and then replaces this report contents by merge result' do
subject subject
...@@ -121,7 +123,7 @@ describe Gitlab::Ci::Reports::Security::Report do ...@@ -121,7 +123,7 @@ describe Gitlab::Ci::Reports::Security::Report do
end end
describe "#safe?" do describe "#safe?" do
subject { described_class.new('sast', commit_sha) } subject { described_class.new('sast', commit_sha, created_at) }
context "when the sast report has an unsafe vulnerability" do context "when the sast report has an unsafe vulnerability" do
where(severity: %w[unknown Unknown high High critical Critical]) where(severity: %w[unknown Unknown high High critical Critical])
......
...@@ -5,19 +5,21 @@ require 'spec_helper' ...@@ -5,19 +5,21 @@ require 'spec_helper'
describe Gitlab::Ci::Reports::Security::Reports do describe Gitlab::Ci::Reports::Security::Reports do
let(:commit_sha) { '20410773a37f49d599e5f0d45219b39304763538' } let(:commit_sha) { '20410773a37f49d599e5f0d45219b39304763538' }
let(:security_reports) { described_class.new(commit_sha) } let(:security_reports) { described_class.new(commit_sha) }
let(:artifact) { create(:ee_ci_job_artifact, :sast) }
describe '#get_report' do describe '#get_report' do
subject { security_reports.get_report(report_type) } subject { security_reports.get_report(report_type, artifact) }
context 'when report type is sast' do context 'when report type is sast' do
let(:report_type) { 'sast' } let(:report_type) { 'sast' }
it { expect(subject.type).to eq('sast') } it { expect(subject.type).to eq('sast') }
it { expect(subject.commit_sha).to eq(commit_sha) } it { expect(subject.commit_sha).to eq(commit_sha) }
it { expect(subject.created_at).to eq(artifact.created_at) }
it 'initializes a new report and returns it' do it 'initializes a new report and returns it' do
expect(Gitlab::Ci::Reports::Security::Report).to receive(:new) expect(Gitlab::Ci::Reports::Security::Report).to receive(:new)
.with('sast', commit_sha).and_call_original .with('sast', commit_sha, artifact.created_at).and_call_original
is_expected.to be_a(Gitlab::Ci::Reports::Security::Report) is_expected.to be_a(Gitlab::Ci::Reports::Security::Report)
end end
...@@ -44,8 +46,8 @@ describe Gitlab::Ci::Reports::Security::Reports do ...@@ -44,8 +46,8 @@ describe Gitlab::Ci::Reports::Security::Reports do
context "when a report has a high severity vulnerability" do context "when a report has a high severity vulnerability" do
before do before do
subject.get_report('sast').add_occurrence(high_severity) subject.get_report('sast', artifact).add_occurrence(high_severity)
subject.get_report('dependency_scanning').add_occurrence(low_severity) subject.get_report('dependency_scanning', artifact).add_occurrence(low_severity)
end end
it { expect(subject.violates_default_policy?).to be(true) } it { expect(subject.violates_default_policy?).to be(true) }
...@@ -53,8 +55,8 @@ describe Gitlab::Ci::Reports::Security::Reports do ...@@ -53,8 +55,8 @@ describe Gitlab::Ci::Reports::Security::Reports do
context "when none of the reports have a high severity vulnerability" do context "when none of the reports have a high severity vulnerability" do
before do before do
subject.get_report('sast').add_occurrence(low_severity) subject.get_report('sast', artifact).add_occurrence(low_severity)
subject.get_report('dependency_scanning').add_occurrence(low_severity) subject.get_report('dependency_scanning', artifact).add_occurrence(low_severity)
end end
it { expect(subject.violates_default_policy?).to be(false) } it { expect(subject.violates_default_policy?).to be(false) }
......
...@@ -14,6 +14,7 @@ describe Ci::Build do ...@@ -14,6 +14,7 @@ describe Ci::Build do
end end
let(:job) { create(:ci_build, pipeline: pipeline) } let(:job) { create(:ci_build, pipeline: pipeline) }
let(:artifact) { create(:ee_ci_job_artifact, :sast, job: job, project: job.project) }
describe '#shared_runners_minutes_limit_enabled?' do describe '#shared_runners_minutes_limit_enabled?' do
subject { job.shared_runners_minutes_limit_enabled? } subject { job.shared_runners_minutes_limit_enabled? }
...@@ -112,58 +113,59 @@ describe Ci::Build do ...@@ -112,58 +113,59 @@ describe Ci::Build do
context 'when build has a security report' do context 'when build has a security report' do
context 'when there is a sast report' do context 'when there is a sast report' do
before do let!(:artifact) { create(:ee_ci_job_artifact, :sast, job: job, project: job.project) }
create(:ee_ci_job_artifact, :sast, job: job, project: job.project)
end
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(33) expect(security_reports.get_report('sast', artifact).occurrences.size).to eq(33)
end
it 'adds the created date to the report' do
subject
expect(security_reports.get_report('sast', artifact).created_at.to_s).to eq(artifact.created_at.to_s)
end end
end end
context 'when there are multiple reports' do context 'when there are multiple reports' do
before do let!(:sast_artifact) { create(:ee_ci_job_artifact, :sast, job: job, project: job.project) }
create(:ee_ci_job_artifact, :sast, job: job, project: job.project) let!(:ds_artifact) { create(:ee_ci_job_artifact, :dependency_scanning, job: job, project: job.project) }
create(:ee_ci_job_artifact, :dependency_scanning, job: job, project: job.project) let!(:cs_artifact) { create(:ee_ci_job_artifact, :container_scanning, job: job, project: job.project) }
create(:ee_ci_job_artifact, :container_scanning, job: job, project: job.project) let!(:dast_artifact) { create(:ee_ci_job_artifact, :dast, job: job, project: job.project) }
create(:ee_ci_job_artifact, :dast, job: job, project: job.project)
end
it 'parses blobs and adds the results to the reports' do it 'parses blobs and adds the results to the reports' do
subject subject
expect(security_reports.get_report('sast').occurrences.size).to eq(33) expect(security_reports.get_report('sast', sast_artifact).occurrences.size).to eq(33)
expect(security_reports.get_report('dependency_scanning').occurrences.size).to eq(4) expect(security_reports.get_report('dependency_scanning', ds_artifact).occurrences.size).to eq(4)
expect(security_reports.get_report('container_scanning').occurrences.size).to eq(8) expect(security_reports.get_report('container_scanning', cs_artifact).occurrences.size).to eq(8)
expect(security_reports.get_report('dast').occurrences.size).to eq(20) expect(security_reports.get_report('dast', dast_artifact).occurrences.size).to eq(20)
end end
end end
context 'when there is a corrupted sast report' do context 'when there is a corrupted sast report' do
before do let!(:artifact) { create(:ee_ci_job_artifact, :sast_with_corrupted_data, job: job, project: job.project) }
create(:ee_ci_job_artifact, :sast_with_corrupted_data, job: job, project: job.project)
end
it 'stores an error' do it 'stores an error' do
subject subject
expect(security_reports.get_report('sast')).to be_errored expect(security_reports.get_report('sast', artifact)).to be_errored
end end
end end
end end
context 'when there is unsupported file type' do context 'when there is unsupported file type' do
let!(:artifact) { create(:ee_ci_job_artifact, :codequality, job: job, project: job.project) }
before do before do
stub_const("Ci::JobArtifact::SECURITY_REPORT_FILE_TYPES", %w[codequality]) stub_const("Ci::JobArtifact::SECURITY_REPORT_FILE_TYPES", %w[codequality])
create(:ee_ci_job_artifact, :codequality, job: job, project: job.project)
end end
it 'stores an error' do it 'stores an error' do
subject subject
expect(security_reports.get_report('codequality')).to be_errored expect(security_reports.get_report('codequality', artifact)).to be_errored
end end
end end
end end
......
...@@ -159,14 +159,14 @@ describe Ci::Pipeline do ...@@ -159,14 +159,14 @@ describe Ci::Pipeline do
let(:build_ds_2) { create(:ci_build, :success, name: 'ds_2', pipeline: pipeline, project: project) } let(:build_ds_2) { create(:ci_build, :success, name: 'ds_2', pipeline: pipeline, project: project) }
let(:build_cs_1) { create(:ci_build, :success, name: 'cs_1', pipeline: pipeline, project: project) } let(:build_cs_1) { create(:ci_build, :success, name: 'cs_1', pipeline: pipeline, project: project) }
let(:build_cs_2) { create(:ci_build, :success, name: 'cs_2', pipeline: pipeline, project: project) } let(:build_cs_2) { create(:ci_build, :success, name: 'cs_2', pipeline: pipeline, project: project) }
let!(:sast1_artifact) { create(:ee_ci_job_artifact, :sast, job: build_sast_1, project: project) }
let!(:sast2_artifact) { create(:ee_ci_job_artifact, :sast, job: build_sast_2, project: project) }
let!(:ds1_artifact) { create(:ee_ci_job_artifact, :dependency_scanning, job: build_ds_1, project: project) }
let!(:ds2_artifact) { create(:ee_ci_job_artifact, :dependency_scanning, job: build_ds_2, project: project) }
let!(:cs1_artifact) { create(:ee_ci_job_artifact, :container_scanning, job: build_cs_1, project: project) }
let!(:cs2_artifact) { create(:ee_ci_job_artifact, :container_scanning, job: build_cs_2, project: project) }
before do before do
create(:ee_ci_job_artifact, :sast, job: build_sast_1, project: project)
create(:ee_ci_job_artifact, :sast, job: build_sast_2, project: project)
create(:ee_ci_job_artifact, :dependency_scanning, job: build_ds_1, project: project)
create(:ee_ci_job_artifact, :dependency_scanning, job: build_ds_2, project: project)
create(:ee_ci_job_artifact, :container_scanning, job: build_cs_1, project: project)
create(:ee_ci_job_artifact, :container_scanning, job: build_cs_2, project: project)
end end
it 'assigns pipeline commit_sha to the reports' do it 'assigns pipeline commit_sha to the reports' do
...@@ -178,18 +178,18 @@ describe Ci::Pipeline do ...@@ -178,18 +178,18 @@ describe Ci::Pipeline do
expect(subject.reports.keys).to contain_exactly('sast', 'dependency_scanning', 'container_scanning') expect(subject.reports.keys).to contain_exactly('sast', 'dependency_scanning', 'container_scanning')
# for each of report categories, we have merged 2 reports with the same data (fixture) # for each of report categories, we have merged 2 reports with the same data (fixture)
expect(subject.get_report('sast').occurrences.size).to eq(33) expect(subject.get_report('sast', sast1_artifact).occurrences.size).to eq(33)
expect(subject.get_report('dependency_scanning').occurrences.size).to eq(4) expect(subject.get_report('dependency_scanning', ds1_artifact).occurrences.size).to eq(4)
expect(subject.get_report('container_scanning').occurrences.size).to eq(8) expect(subject.get_report('container_scanning', cs1_artifact).occurrences.size).to eq(8)
end end
context 'when builds are retried' do context 'when builds are retried' 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(33) expect(subject.get_report('sast', sast1_artifact).occurrences.size).to eq(33)
expect(subject.get_report('dependency_scanning').occurrences.size).to eq(4) expect(subject.get_report('dependency_scanning', ds1_artifact).occurrences.size).to eq(4)
expect(subject.get_report('container_scanning').occurrences.size).to eq(8) expect(subject.get_report('container_scanning', cs1_artifact).occurrences.size).to eq(8)
end end
end end
end end
......
...@@ -6,8 +6,13 @@ describe Vulnerabilities::OccurrenceReportsComparerEntity do ...@@ -6,8 +6,13 @@ describe Vulnerabilities::OccurrenceReportsComparerEntity do
describe 'container scanning report comparison' do describe 'container scanning report comparison' do
set(:user) { create(:user) } set(:user) { create(:user) }
let(:base_report) { create_list(:vulnerabilities_occurrence, 2) } let(:base_occurrences) { create_list(:vulnerabilities_occurrence, 2) }
let(:head_report) { create_list(:vulnerabilities_occurrence, 1) } let(:base_combined_reports) { build_list(:ci_reports_security_report, 1, created_at: nil) }
let(:base_report) { build(:ci_reports_security_aggregated_reports, reports: base_combined_reports, occurrences: base_occurrences)}
let(:head_occurrences) { create_list(:vulnerabilities_occurrence, 1) }
let(:head_combined_reports) { build_list(:ci_reports_security_report, 1, created_at: 2.days.ago) }
let(:head_report) { build(:ci_reports_security_aggregated_reports, reports: head_combined_reports, occurrences: head_occurrences)}
let(:comparer) { Gitlab::Ci::Reports::Security::VulnerabilityReportsComparer.new(base_report, head_report) } let(:comparer) { Gitlab::Ci::Reports::Security::VulnerabilityReportsComparer.new(base_report, head_report) }
...@@ -27,7 +32,15 @@ describe Vulnerabilities::OccurrenceReportsComparerEntity do ...@@ -27,7 +32,15 @@ describe Vulnerabilities::OccurrenceReportsComparerEntity do
end end
it 'contains the added existing and fixed vulnerabilities for container scanning' do it 'contains the added existing and fixed vulnerabilities for container scanning' do
expect(subject.keys).to match_array([:added, :existing, :fixed]) expect(subject.keys).to include(:added)
expect(subject.keys).to include(:existing)
expect(subject.keys).to include(:fixed)
end
it 'contains the report out of date fields' do
expect(subject.keys).to include(:base_report_created_at)
expect(subject.keys).to include(:base_report_out_of_date)
expect(subject.keys).to include(:head_report_created_at)
end end
end end
end end
......
...@@ -6,7 +6,7 @@ describe Security::StoreReportService, '#execute' do ...@@ -6,7 +6,7 @@ describe Security::StoreReportService, '#execute' do
let(:artifact) { create(:ee_ci_job_artifact, report_type) } let(:artifact) { create(:ee_ci_job_artifact, report_type) }
let(:project) { artifact.project } let(:project) { artifact.project }
let(:pipeline) { artifact.job.pipeline } let(:pipeline) { artifact.job.pipeline }
let(:report) { pipeline.security_reports.get_report(report_type.to_s) } let(:report) { pipeline.security_reports.get_report(report_type.to_s, artifact) }
before do before do
stub_licensed_features(sast: true, dependency_scanning: true, container_scanning: true) stub_licensed_features(sast: true, dependency_scanning: true, container_scanning: true)
...@@ -52,7 +52,7 @@ describe Security::StoreReportService, '#execute' do ...@@ -52,7 +52,7 @@ describe Security::StoreReportService, '#execute' do
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) }
let(:new_report) { new_pipeline.security_reports.get_report(report_type.to_s) } let(:new_report) { new_pipeline.security_reports.get_report(report_type.to_s, artifact) }
let(:report_type) { :sast } let(:report_type) { :sast }
let!(:occurrence) do let!(:occurrence) do
......
...@@ -32,10 +32,12 @@ describe Security::StoreReportsService do ...@@ -32,10 +32,12 @@ describe Security::StoreReportsService do
context 'when StoreReportService returns an error for a report' do context 'when StoreReportService returns an error for a report' do
let(:reports) { Gitlab::Ci::Reports::Security::Reports.new(pipeline.sha) } let(:reports) { Gitlab::Ci::Reports::Security::Reports.new(pipeline.sha) }
let(:sast_report) { reports.get_report('sast') } let(:sast_report) { reports.get_report('sast', sast_artifact) }
let(:dast_report) { reports.get_report('dast') } let(:dast_report) { reports.get_report('dast', dast_artifact) }
let(:success) { { status: :success } } let(:success) { { status: :success } }
let(:error) { { status: :error, message: "something went wrong" } } let(:error) { { status: :error, message: "something went wrong" } }
let(:sast_artifact) { create(:ee_ci_job_artifact, :sast) }
let(:dast_artifact) { create(:ee_ci_job_artifact, :dast) }
before do before do
allow(pipeline).to receive(:security_reports).and_return(reports) allow(pipeline).to receive(:security_reports).and_return(reports)
......
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