Commit 3efc769f authored by James Fargher's avatar James Fargher

Merge branch '222643_use_only_new_vulnerabilities_on_approval_rules' into 'master'

Fix `Vulnerability-Check` approval rule logic

See merge request gitlab-org/gitlab!38589
parents 694aac31 fd34534a
...@@ -102,7 +102,7 @@ module EE ...@@ -102,7 +102,7 @@ module EE
end end
def security_reports def security_reports
::Gitlab::Ci::Reports::Security::Reports.new(sha).tap do |security_reports| ::Gitlab::Ci::Reports::Security::Reports.new(self).tap do |security_reports|
builds.latest.with_reports(::Ci::JobArtifact.security_reports).each do |build| builds.latest.with_reports(::Ci::JobArtifact.security_reports).each do |build|
build.collect_security_reports!(security_reports) build.collect_security_reports!(security_reports)
end end
......
...@@ -114,7 +114,7 @@ class Vulnerability < ApplicationRecord ...@@ -114,7 +114,7 @@ class Vulnerability < ApplicationRecord
end end
def active_state_values def active_state_values
states.values_at(*ACTIVE_STATES) states.values_at(*active_states)
end end
end end
......
...@@ -31,7 +31,7 @@ module Security ...@@ -31,7 +31,7 @@ module Security
sort_by_ds_analyzers! sort_by_ds_analyzers!
@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.pipeline,
@source_reports.first.created_at @source_reports.first.created_at
) )
@findings = [] @findings = []
......
...@@ -33,22 +33,31 @@ module Security ...@@ -33,22 +33,31 @@ module Security
return if report.empty? && !pipeline.complete? return if report.empty? && !pipeline.complete?
return if report.violates?(project.software_license_policies) return if report.violates?(project.software_license_policies)
remove_required_approvals_for(ApprovalMergeRequestRule.report_approver.license_scanning) remove_required_approvals_for(ApprovalMergeRequestRule.report_approver.license_scanning,
pipeline.merge_requests_as_head_pipeline)
end end
def sync_vulnerability_rules def sync_vulnerability_rules
reports = pipeline.security_reports
# If we have some reports, then we want to sync them early; # If we have some reports, then we want to sync them early;
# If we don't have reports, then we should wait until pipeline stops. # If we don't have reports, then we should wait until pipeline stops.
return if reports.empty? && !pipeline.complete? return if reports.empty? && !pipeline.complete?
return if reports.violates_default_policy?
remove_required_approvals_for(ApprovalMergeRequestRule.security_report) remove_required_approvals_for(ApprovalMergeRequestRule.security_report, sync_required_merge_requests)
end end
def remove_required_approvals_for(rules) def reports
@reports ||= pipeline.security_reports
end
def sync_required_merge_requests
pipeline.merge_requests_as_head_pipeline.reject do |merge_request|
reports.violates_default_policy_against?(merge_request.base_pipeline&.security_reports)
end
end
def remove_required_approvals_for(rules, merge_requests)
rules rules
.for_unmerged_merge_requests(pipeline.merge_requests_as_head_pipeline) .for_unmerged_merge_requests(merge_requests)
.update_all(approvals_required: 0) .update_all(approvals_required: 0)
end end
end end
......
---
title: Fix MR approval rule update logic for Vulnerability-Check
merge_request: 38589
author:
type: fixed
...@@ -5,6 +5,8 @@ module Gitlab ...@@ -5,6 +5,8 @@ module Gitlab
module Reports module Reports
module Security module Security
class Finding class Finding
UNSAFE_SEVERITIES = %w[unknown high critical].freeze
attr_reader :compare_key attr_reader :compare_key
attr_reader :confidence attr_reader :confidence
attr_reader :identifiers attr_reader :identifiers
...@@ -65,6 +67,20 @@ module Gitlab ...@@ -65,6 +67,20 @@ module Gitlab
@location = new_location @location = new_location
end end
def unsafe?
severity.in?(UNSAFE_SEVERITIES)
end
def eql?(other)
report_type == other.report_type &&
location.fingerprint == other.location.fingerprint &&
primary_identifier.fingerprint == other.primary_identifier.fingerprint
end
def hash
report_type.hash ^ location.fingerprint.hash ^ primary_identifier.fingerprint.hash
end
private private
def generate_project_fingerprint def generate_project_fingerprint
......
...@@ -5,11 +5,9 @@ module Gitlab ...@@ -5,11 +5,9 @@ module Gitlab
module Reports module Reports
module Security module Security
class Report class Report
UNSAFE_SEVERITIES = %w[unknown high critical].freeze
attr_reader :created_at attr_reader :created_at
attr_reader :type attr_reader :type
attr_reader :commit_sha attr_reader :pipeline
attr_reader :findings attr_reader :findings
attr_reader :scanners attr_reader :scanners
attr_reader :identifiers attr_reader :identifiers
...@@ -17,9 +15,9 @@ module Gitlab ...@@ -17,9 +15,9 @@ module Gitlab
attr_accessor :scanned_resources attr_accessor :scanned_resources
attr_accessor :error attr_accessor :error
def initialize(type, commit_sha, created_at) def initialize(type, pipeline, created_at)
@type = type @type = type
@commit_sha = commit_sha @pipeline = pipeline
@created_at = created_at @created_at = created_at
@findings = [] @findings = []
@scanners = {} @scanners = {}
...@@ -27,6 +25,10 @@ module Gitlab ...@@ -27,6 +25,10 @@ module Gitlab
@scanned_resources = [] @scanned_resources = []
end end
def commit_sha
pipeline.sha
end
def errored? def errored?
error.present? error.present?
end end
...@@ -44,7 +46,7 @@ module Gitlab ...@@ -44,7 +46,7 @@ module Gitlab
end end
def clone_as_blank def clone_as_blank
Report.new(type, commit_sha, created_at) Report.new(type, pipeline, created_at)
end end
def replace_with!(other) def replace_with!(other)
...@@ -56,15 +58,6 @@ module Gitlab ...@@ -56,15 +58,6 @@ module Gitlab
def merge!(other) def merge!(other)
replace_with!(::Security::MergeReportsService.new(self, other).execute) replace_with!(::Security::MergeReportsService.new(self, other).execute)
end end
def unsafe_severity?
!safe?
end
def safe?
severities = findings.map(&:severity).compact.map(&:downcase)
(severities & UNSAFE_SEVERITIES).empty?
end
end end
end end
end end
......
...@@ -5,21 +5,31 @@ module Gitlab ...@@ -5,21 +5,31 @@ module Gitlab
module Reports module Reports
module Security module Security
class Reports class Reports
attr_reader :reports, :commit_sha attr_reader :reports, :pipeline
delegate :empty?, to: :reports delegate :empty?, to: :reports
def initialize(commit_sha) def initialize(pipeline)
@reports = {} @reports = {}
@commit_sha = commit_sha @pipeline = pipeline
end end
def get_report(report_type, report_artifact) def get_report(report_type, report_artifact)
reports[report_type] ||= Report.new(report_type, commit_sha, report_artifact.created_at) reports[report_type] ||= Report.new(report_type, pipeline, report_artifact.created_at)
end end
def violates_default_policy? def findings
reports.values.any? { |report| report.unsafe_severity? } reports.values.flat_map(&:findings)
end
def violates_default_policy_against?(target_reports)
findings_diff(target_reports).any?(&:unsafe?)
end
private
def findings_diff(target_reports)
findings - target_reports&.findings.to_a
end end
end end
end end
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
FactoryBot.define do 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) } pipeline { build(:ci_pipeline) }
created_at { 2.weeks.ago } created_at { 2.weeks.ago }
scanned_resources { [] } scanned_resources { [] }
...@@ -22,7 +22,7 @@ FactoryBot.define do ...@@ -22,7 +22,7 @@ FactoryBot.define do
skip_create skip_create
initialize_with do initialize_with do
::Gitlab::Ci::Reports::Security::Report.new(type, commit_sha, created_at) ::Gitlab::Ci::Reports::Security::Report.new(type, pipeline, created_at)
end end
end end
end end
...@@ -71,12 +71,18 @@ FactoryBot.define do ...@@ -71,12 +71,18 @@ FactoryBot.define do
}.to_json }.to_json
end end
trait :confirmed do trait :detected do
after(:create) do |finding| after(:create) do |finding|
create(:vulnerability, :detected, project: finding.project, findings: [finding]) create(:vulnerability, :detected, project: finding.project, findings: [finding])
end end
end end
trait :confirmed do
after(:create) do |finding|
create(:vulnerability, :confirmed, project: finding.project, findings: [finding])
end
end
trait :resolved do trait :resolved do
after(:create) do |finding| after(:create) do |finding|
create(:vulnerability, :resolved, project: finding.project, findings: [finding]) create(:vulnerability, :resolved, project: finding.project, findings: [finding])
...@@ -85,6 +91,7 @@ FactoryBot.define do ...@@ -85,6 +91,7 @@ FactoryBot.define do
trait :dismissed do trait :dismissed do
after(:create) do |finding| after(:create) do |finding|
create(:vulnerability, :dismissed, project: finding.project, findings: [finding])
create(:vulnerability_feedback, create(:vulnerability_feedback,
:dismissal, :dismissal,
project: finding.project, project: finding.project,
......
...@@ -4,8 +4,10 @@ require 'spec_helper' ...@@ -4,8 +4,10 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Parsers::Security::Common do RSpec.describe Gitlab::Ci::Parsers::Security::Common do
describe '#parse!' do describe '#parse!' do
let_it_be(:pipeline) { create(:ci_pipeline) }
let(:artifact) { build(:ee_ci_job_artifact, :dependency_scanning) } let(:artifact) { build(:ee_ci_job_artifact, :dependency_scanning) }
let(:report) { Gitlab::Ci::Reports::Security::Report.new(artifact.file_type, 'sha', 2.weeks.ago) } let(:report) { Gitlab::Ci::Reports::Security::Report.new(artifact.file_type, pipeline, 2.weeks.ago) }
let(:parser) { described_class.new } let(:parser) { described_class.new }
before do before do
......
...@@ -6,7 +6,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::ContainerScanning do ...@@ -6,7 +6,7 @@ RSpec.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, 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 do |blob|
......
...@@ -5,7 +5,7 @@ require 'spec_helper' ...@@ -5,7 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Parsers::Security::CoverageFuzzing do 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.sha, 2.weeks.ago) } let(:report) { Gitlab::Ci::Reports::Security::Report.new(artifact.file_type, pipeline, 2.weeks.ago) }
let(:parser) { described_class.new } let(:parser) { described_class.new }
let(:artifact) { create(:ee_ci_job_artifact, :coverage_fuzzing) } let(:artifact) { create(:ee_ci_job_artifact, :coverage_fuzzing) }
......
...@@ -9,7 +9,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Dast do ...@@ -9,7 +9,7 @@ RSpec.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, 2.weeks.ago) } let(:report) { Gitlab::Ci::Reports::Security::Report.new(artifact.file_type, pipeline, 2.weeks.ago) }
let(:parser) { described_class.new } let(:parser) { described_class.new }
where(:report_format, where(:report_format,
......
...@@ -9,7 +9,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::DependencyScanning do ...@@ -9,7 +9,7 @@ RSpec.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, 2.weeks.ago) } let(:report) { Gitlab::Ci::Reports::Security::Report.new(artifact.file_type, pipeline, 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
......
...@@ -4,16 +4,17 @@ require 'spec_helper' ...@@ -4,16 +4,17 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Parsers::Security::Sast do RSpec.describe Gitlab::Ci::Parsers::Security::Sast do
describe '#parse!' do describe '#parse!' do
subject(:parser) { described_class.new } let_it_be(:pipeline) { create(:ci_pipeline) }
let(:commit_sha) { "d8978e74745e18ce44d88814004d4255ac6a65bb" }
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(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, created_at) } let(:report) { Gitlab::Ci::Reports::Security::Report.new(artifact.file_type, pipeline, created_at) }
let(:artifact) { create(:ee_ci_job_artifact, report_format) } let(:artifact) { create(:ee_ci_job_artifact, report_format) }
before do before do
...@@ -48,7 +49,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Sast do ...@@ -48,7 +49,7 @@ RSpec.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, 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(parser.parse!(blob, report)).to be_empty }
......
...@@ -4,16 +4,17 @@ require 'spec_helper' ...@@ -4,16 +4,17 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Parsers::Security::SecretDetection do RSpec.describe Gitlab::Ci::Parsers::Security::SecretDetection do
describe '#parse!' do describe '#parse!' do
subject(:parser) { described_class.new } let_it_be(:pipeline) { create(:ci_pipeline) }
let(:commit_sha) { "d8978e74745e18ce44d88814004d4255ac6a65bb" }
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))
with_them do with_them do
let(:report) { Gitlab::Ci::Reports::Security::Report.new(artifact.file_type, commit_sha, created_at) } let(:report) { Gitlab::Ci::Reports::Security::Report.new(artifact.file_type, pipeline, created_at) }
let(:artifact) { create(:ee_ci_job_artifact, report_format) } let(:artifact) { create(:ee_ci_job_artifact, report_format) }
before do before do
...@@ -48,7 +49,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::SecretDetection do ...@@ -48,7 +49,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::SecretDetection 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('secret_detection', commit_sha, 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(parser.parse!(blob, report)).to be_empty }
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Gitlab::Ci::Reports::Security::Finding do RSpec.describe Gitlab::Ci::Reports::Security::Finding do
using RSpec::Parameterized::TableSyntax
describe '#initialize' do describe '#initialize' do
subject { described_class.new(**params) } subject { described_class.new(**params) }
...@@ -128,4 +130,102 @@ RSpec.describe Gitlab::Ci::Reports::Security::Finding do ...@@ -128,4 +130,102 @@ RSpec.describe Gitlab::Ci::Reports::Security::Finding do
expect(occurrence.old_location).to eq(old_location) expect(occurrence.old_location).to eq(old_location)
end end
end end
describe '#unsafe?' do
where(:severity, :unsafe?) do
'critical' | true
'high' | true
'medium' | false
'low' | false
'info' | false
'unknown' | true
end
with_them do
let(:finding) { create(:ci_reports_security_finding, severity: severity) }
subject { finding.unsafe? }
it { is_expected.to be(unsafe?) }
end
end
describe '#eql?' do
let(:identifier) { build(:ci_reports_security_identifier) }
let(:location) { build(:ci_reports_security_locations_sast) }
let(:finding) { build(:ci_reports_security_finding, severity: 'low', report_type: :sast, identifiers: [identifier], location: location) }
let(:report_type) { :secret_detection }
let(:identifier_external_id) { 'foo' }
let(:location_start_line) { 0 }
let(:other_identifier) { build(:ci_reports_security_identifier, external_id: identifier_external_id) }
let(:other_location) { build(:ci_reports_security_locations_sast, start_line: location_start_line) }
let(:other_finding) do
build(:ci_reports_security_finding,
severity: 'low',
report_type: report_type,
identifiers: [other_identifier],
location: other_location)
end
subject { finding.eql?(other_finding) }
context 'when the other finding has same `report_type`' do
let(:report_type) { :sast }
context 'when the other finding has same primary identifier fingerprint' do
let(:identifier_external_id) { identifier.external_id }
context 'when the other finding has same location fingerprint' do
let(:location_start_line) { location.start_line }
it { is_expected.to be(true) }
end
context 'when the other finding does not have same location fingerprint' do
it { is_expected.to be(false) }
end
end
context 'when the other finding does not have same primary identifier fingerprint' do
context 'when the other finding has same location fingerprint' do
let(:location_start_line) { location.start_line }
it { is_expected.to be(false) }
end
context 'when the other finding does not have same location fingerprint' do
it { is_expected.to be(false) }
end
end
end
context 'when the other finding does not have same `report_type`' do
context 'when the other finding has same primary identifier fingerprint' do
let(:identifier_external_id) { identifier.external_id }
context 'when the other finding has same location fingerprint' do
let(:location_start_line) { location.start_line }
it { is_expected.to be(false) }
end
context 'when the other finding does not have same location fingerprint' do
it { is_expected.to be(false) }
end
end
context 'when the other finding does not have same primary identifier fingerprint' do
context 'when the other finding has same location fingerprint' do
let(:location_start_line) { location.start_line }
it { is_expected.to be(false) }
end
context 'when the other finding does not have same location fingerprint' do
it { is_expected.to be(false) }
end
end
end
end
end end
...@@ -3,8 +3,9 @@ ...@@ -3,8 +3,9 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Gitlab::Ci::Reports::Security::Report do RSpec.describe Gitlab::Ci::Reports::Security::Report do
let(:report) { described_class.new('sast', commit_sha, created_at) } let_it_be(:pipeline) { create(:ci_pipeline) }
let(:commit_sha) { "d8978e74745e18ce44d88814004d4255ac6a65bb" }
let(:report) { described_class.new('sast', pipeline, created_at) }
let(:created_at) { 2.weeks.ago } let(:created_at) { 2.weeks.ago }
it { expect(report.type).to eq('sast') } it { expect(report.type).to eq('sast') }
...@@ -61,11 +62,11 @@ RSpec.describe Gitlab::Ci::Reports::Security::Report do ...@@ -61,11 +62,11 @@ RSpec.describe Gitlab::Ci::Reports::Security::Report do
) )
end end
it 'creates a blank report with copied type and commit SHA' do it 'creates a blank report with copied type and pipeline' do
clone = report.clone_as_blank clone = report.clone_as_blank
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.pipeline).to eq(report.pipeline)
expect(clone.created_at).to eq(report.created_at) expect(clone.created_at).to eq(report.created_at)
expect(clone.findings).to eq([]) expect(clone.findings).to eq([])
expect(clone.scanners).to eq({}) expect(clone.scanners).to eq({})
...@@ -114,7 +115,7 @@ RSpec.describe Gitlab::Ci::Reports::Security::Report do ...@@ -114,7 +115,7 @@ RSpec.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, created_at)) } subject { report.merge!(described_class.new('sast', pipeline, 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
...@@ -122,63 +123,4 @@ RSpec.describe Gitlab::Ci::Reports::Security::Report do ...@@ -122,63 +123,4 @@ RSpec.describe Gitlab::Ci::Reports::Security::Report do
expect(report).to have_received(:replace_with!).with(merged_report) expect(report).to have_received(:replace_with!).with(merged_report)
end end
end end
describe "#safe?" do
subject { described_class.new('sast', commit_sha, created_at) }
context "when the sast report has an unsafe vulnerability" do
where(severity: %w[unknown Unknown high High critical Critical])
with_them do
let(:finding) { build(:ci_reports_security_finding, severity: severity) }
before do
subject.add_finding(finding)
end
it { expect(subject.unsafe_severity?).to be(true) }
it { expect(subject).not_to be_safe }
end
end
context "when the sast report has a medium to low severity vulnerability" do
where(severity: %w[medium Medium low Low])
with_them do
let(:finding) { build(:ci_reports_security_finding, severity: severity) }
before do
subject.add_finding(finding)
end
it { expect(subject.unsafe_severity?).to be(false) }
it { expect(subject).to be_safe }
end
end
context "when the sast report has a vulnerability with a `nil` severity" do
let(:finding) { build(:ci_reports_security_finding, severity: nil) }
before do
subject.add_finding(finding)
end
it { expect(subject.unsafe_severity?).to be(false) }
it { expect(subject).to be_safe }
end
context "when the sast report has a vulnerability with a blank severity" do
let(:finding) { build(:ci_reports_security_finding, severity: '') }
before do
subject.add_finding(finding)
end
it { expect(subject.unsafe_severity?).to be(false) }
it { expect(subject).to be_safe }
end
context "when the sast report has zero vulnerabilities" do
it { expect(subject.unsafe_severity?).to be(false) }
it { expect(subject).to be_safe }
end
end
end end
...@@ -3,9 +3,10 @@ ...@@ -3,9 +3,10 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Gitlab::Ci::Reports::Security::Reports do RSpec.describe Gitlab::Ci::Reports::Security::Reports do
let(:commit_sha) { '20410773a37f49d599e5f0d45219b39304763538' } let_it_be(:pipeline) { create(:ci_pipeline) }
let(:security_reports) { described_class.new(commit_sha) } let_it_be(:artifact) { create(:ee_ci_job_artifact, :sast) }
let(:artifact) { create(:ee_ci_job_artifact, :sast) }
let(:security_reports) { described_class.new(pipeline) }
describe '#get_report' do describe '#get_report' do
subject { security_reports.get_report(report_type, artifact) } subject { security_reports.get_report(report_type, artifact) }
...@@ -14,12 +15,11 @@ RSpec.describe Gitlab::Ci::Reports::Security::Reports do ...@@ -14,12 +15,11 @@ RSpec.describe Gitlab::Ci::Reports::Security::Reports 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.created_at).to eq(artifact.created_at) } 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, artifact.created_at).and_call_original .with('sast', pipeline, 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
...@@ -38,29 +38,69 @@ RSpec.describe Gitlab::Ci::Reports::Security::Reports do ...@@ -38,29 +38,69 @@ RSpec.describe Gitlab::Ci::Reports::Security::Reports do
end end
end end
describe "#violates_default_policy?" do describe '#findings' do
subject { described_class.new(commit_sha) } let(:finding_1) { build(:ci_reports_security_finding, severity: 'low') }
let(:finding_2) { build(:ci_reports_security_finding, severity: 'high') }
let!(:expected_findings) { [finding_1, finding_2] }
subject { security_reports.findings }
before do
security_reports.get_report('sast', artifact).add_finding(finding_1)
security_reports.get_report('dependency_scanning', artifact).add_finding(finding_2)
end
it { is_expected.to match_array(expected_findings) }
end
describe "#violates_default_policy_against?" do
let(:low_severity_sast) { build(:ci_reports_security_finding, severity: 'low', report_type: :sast) }
let(:high_severity_dast) { build(:ci_reports_security_finding, severity: 'high', report_type: :dast) }
subject { security_reports.violates_default_policy_against?(target_reports) }
context 'when the target_reports is `nil`' do
let(:target_reports) { nil }
context "when a report has unsafe vulnerability" do
before do
security_reports.get_report('sast', artifact).add_finding(high_severity_dast)
end
it { is_expected.to be(true) }
end
context "when none of the reports have an unsafe vulnerability" do
before do
security_reports.get_report('sast', artifact).add_finding(low_severity_sast)
end
it { is_expected.to be(false) }
end
end
let(:low_severity) { build(:ci_reports_security_finding, severity: 'low') } context 'when the target_reports is not `nil`' do
let(:high_severity) { build(:ci_reports_security_finding, severity: 'high') } let(:target_reports) { described_class.new(pipeline) }
context "when a report has a high severity vulnerability" do context "when a report has a new unsafe vulnerability" do
before do before do
subject.get_report('sast', artifact).add_finding(high_severity) security_reports.get_report('sast', artifact).add_finding(high_severity_dast)
subject.get_report('dependency_scanning', artifact).add_finding(low_severity) security_reports.get_report('dependency_scanning', artifact).add_finding(low_severity_sast)
target_reports.get_report('dependency_scanning', artifact).add_finding(low_severity_sast)
end end
it { expect(subject.violates_default_policy?).to be(true) } it { is_expected.to be(true) }
end end
context "when none of the reports have a high severity vulnerability" do context "when none of the reports have a new unsafe vulnerability" do
before do before do
subject.get_report('sast', artifact).add_finding(low_severity) security_reports.get_report('sast', artifact).add_finding(high_severity_dast)
subject.get_report('sast', artifact).add_finding(low_severity) security_reports.get_report('sast', artifact).add_finding(low_severity_sast)
subject.get_report('dependency_scanning', artifact).add_finding(low_severity) target_reports.get_report('sast', artifact).add_finding(high_severity_dast)
end end
it { expect(subject.violates_default_policy?).to be(false) } it { is_expected.to be(false) }
end
end end
end end
end end
...@@ -157,7 +157,7 @@ RSpec.describe Ci::Build do ...@@ -157,7 +157,7 @@ RSpec.describe Ci::Build do
end end
describe '#collect_security_reports!' do describe '#collect_security_reports!' do
let(:security_reports) { ::Gitlab::Ci::Reports::Security::Reports.new(pipeline.sha) } let(:security_reports) { ::Gitlab::Ci::Reports::Security::Reports.new(pipeline) }
subject { job.collect_security_reports!(security_reports) } subject { job.collect_security_reports!(security_reports) }
......
...@@ -146,12 +146,9 @@ RSpec.describe Ci::Pipeline do ...@@ -146,12 +146,9 @@ RSpec.describe Ci::Pipeline do
let!(:cs1_artifact) { create(:ee_ci_job_artifact, :container_scanning, job: build_cs_1, 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) } let!(:cs2_artifact) { create(:ee_ci_job_artifact, :container_scanning, job: build_cs_2, project: project) }
before do it 'assigns pipeline to the reports' do
end expect(subject.pipeline).to eq(pipeline)
expect(subject.reports.values.map(&:pipeline).uniq).to contain_exactly(pipeline)
it 'assigns pipeline commit_sha to the reports' do
expect(subject.commit_sha).to eq(pipeline.sha)
expect(subject.reports.values.map(&:commit_sha).uniq).to contain_exactly(pipeline.sha)
end end
it 'returns security reports with collected data grouped as expected' do it 'returns security reports with collected data grouped as expected' do
......
...@@ -34,7 +34,7 @@ RSpec.describe Security::StoreReportsService do ...@@ -34,7 +34,7 @@ RSpec.describe Security::StoreReportsService do
end end
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) }
let(:sast_report) { reports.get_report('sast', sast_artifact) } let(:sast_report) { reports.get_report('sast', sast_artifact) }
let(:dast_report) { reports.get_report('dast', dast_artifact) } let(:dast_report) { reports.get_report('dast', dast_artifact) }
let(:success) { { status: :success } } let(:success) { { status: :success } }
......
...@@ -7,6 +7,7 @@ RSpec.describe Security::SyncReportsToApprovalRulesService, '#execute' do ...@@ -7,6 +7,7 @@ RSpec.describe Security::SyncReportsToApprovalRulesService, '#execute' do
let(:project) { merge_request.project } let(:project) { merge_request.project }
let(:pipeline) { create(:ee_ci_pipeline, :success, project: project, merge_requests_as_head_pipeline: [merge_request]) } let(:pipeline) { create(:ee_ci_pipeline, :success, project: project, merge_requests_as_head_pipeline: [merge_request]) }
let(:report_approver_rule) { create(:report_approver_rule, merge_request: merge_request, approvals_required: 2) } let(:report_approver_rule) { create(:report_approver_rule, merge_request: merge_request, approvals_required: 2) }
let(:base_pipeline) { create(:ee_ci_pipeline, :success, project: project, ref: merge_request.target_branch, sha: merge_request.diff_base_sha) }
subject { described_class.new(pipeline).execute } subject { described_class.new(pipeline).execute }
...@@ -23,15 +24,24 @@ RSpec.describe Security::SyncReportsToApprovalRulesService, '#execute' do ...@@ -23,15 +24,24 @@ RSpec.describe Security::SyncReportsToApprovalRulesService, '#execute' do
create(:ee_ci_build, :success, :dependency_scanning, name: 'ds_job', pipeline: pipeline, project: project) create(:ee_ci_build, :success, :dependency_scanning, name: 'ds_job', pipeline: pipeline, project: project)
end end
it "won't change approvals_required count" do context 'when high-severity vulnerabilities already present in target branch pipeline' do
expect( before do
pipeline.security_reports.reports.values.all?(&:unsafe_severity?) create(:ee_ci_build, :success, :dependency_scanning, name: 'ds_job', pipeline: base_pipeline, project: project)
).to be true end
it 'lowers approvals_required count to zero' do
expect { subject }
.to change { report_approver_rule.reload.approvals_required }.from(2).to(0)
end
end
context 'when high-severity vulnerabilities do not present in target branch pipeline' do
it "won't change approvals_required count" do
expect { subject } expect { subject }
.not_to change { report_approver_rule.reload.approvals_required } .not_to change { report_approver_rule.reload.approvals_required }
end end
end end
end
context 'when only low-severity vulnerabilities are present' do context 'when only low-severity vulnerabilities are present' do
before do before do
...@@ -39,10 +49,6 @@ RSpec.describe Security::SyncReportsToApprovalRulesService, '#execute' do ...@@ -39,10 +49,6 @@ RSpec.describe Security::SyncReportsToApprovalRulesService, '#execute' do
end end
it 'lowers approvals_required count to zero' do it 'lowers approvals_required count to zero' do
expect(
pipeline.security_reports.reports.values.none?(&:unsafe_severity?)
).to be true
expect { subject } expect { subject }
.to change { report_approver_rule.reload.approvals_required }.from(2).to(0) .to change { report_approver_rule.reload.approvals_required }.from(2).to(0)
end end
...@@ -131,15 +137,24 @@ RSpec.describe Security::SyncReportsToApprovalRulesService, '#execute' do ...@@ -131,15 +137,24 @@ RSpec.describe Security::SyncReportsToApprovalRulesService, '#execute' do
create(:ee_ci_build, :success, :dependency_scanning, name: 'ds_job', pipeline: pipeline, project: project) create(:ee_ci_build, :success, :dependency_scanning, name: 'ds_job', pipeline: pipeline, project: project)
end end
it "won't change approvals_required count" do context 'when high-severity vulnerabilities already present in target branch pipeline' do
expect( before do
pipeline.security_reports.reports.values.all?(&:unsafe_severity?) create(:ee_ci_build, :success, :dependency_scanning, name: 'ds_job', pipeline: base_pipeline, project: project)
).to be true end
it 'lowers approvals_required count to zero' do
expect { subject }
.to change { report_approver_rule.reload.approvals_required }.from(2).to(0)
end
end
context 'when high-severity vulnerabilities do not present in target branch pipeline' do
it "won't change approvals_required count" do
expect { subject } expect { subject }
.not_to change { report_approver_rule.reload.approvals_required } .not_to change { report_approver_rule.reload.approvals_required }
end end
end end
end
context 'when only low-severity vulnerabilities are present' do context 'when only low-severity vulnerabilities are present' do
before do before do
...@@ -147,12 +162,8 @@ RSpec.describe Security::SyncReportsToApprovalRulesService, '#execute' do ...@@ -147,12 +162,8 @@ RSpec.describe Security::SyncReportsToApprovalRulesService, '#execute' do
end end
it 'lowers approvals_required count to zero' do it 'lowers approvals_required count to zero' do
expect(
pipeline.security_reports.reports.values.none?(&:unsafe_severity?)
).to be true
expect { subject } expect { subject }
.to change { report_approver_rule.reload.approvals_required } .to change { report_approver_rule.reload.approvals_required }.from(2).to(0)
end end
end end
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