Commit a35c8f7c authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge branch '32765-vuln-check-approvals-required' into 'master'

Resolve sentry errors related to parsing security reports

See merge request gitlab-org/gitlab!18423
parents 2aec11b3 1f0ac78e
...@@ -37,12 +37,13 @@ module Security ...@@ -37,12 +37,13 @@ module Security
end end
def sync_vulnerability_rules def sync_vulnerability_rules
reports = pipeline.security_reports.reports reports = pipeline.security_reports
safe = reports.any? && reports.none? do |_report_type, report| # If we have some reports, then we want to sync them early;
report.unsafe_severity? # If we don't have reports, then we should wait until pipeline stops.
end return if reports.empty? && !pipeline.complete?
return if reports.violates_default_policy?
remove_required_approvals_for(ApprovalMergeRequestRule.security_report) if safe remove_required_approvals_for(ApprovalMergeRequestRule.security_report)
end end
def remove_required_approvals_for(rules) def remove_required_approvals_for(rules)
......
---
title: Add default empty values to prevent parser errors from approving the Vulnerability-Check rule
merge_request: 18423
author:
type: fixed
...@@ -29,7 +29,7 @@ module Gitlab ...@@ -29,7 +29,7 @@ module Gitlab
# map remediations to relevant vulnerabilities # map remediations to relevant vulnerabilities
def collate_remediations(report_data) def collate_remediations(report_data)
return report_data["vulnerabilities"] unless report_data["remediations"] return report_data["vulnerabilities"] || [] unless report_data["remediations"]
report_data["vulnerabilities"].map do |vulnerability| report_data["vulnerabilities"].map do |vulnerability|
# Grab the first available remediation. # Grab the first available remediation.
...@@ -49,8 +49,8 @@ module Gitlab ...@@ -49,8 +49,8 @@ module Gitlab
uuid: SecureRandom.uuid, uuid: SecureRandom.uuid,
report_type: report.type, report_type: report.type,
name: data['message'], name: data['message'],
compare_key: data['cve'], compare_key: data['cve'] || '',
location: create_location(data['location']), location: create_location(data['location'] || {}),
severity: parse_level(data['severity']), severity: parse_level(data['severity']),
confidence: parse_level(data['confidence']), confidence: parse_level(data['confidence']),
scanner: scanner, scanner: scanner,
......
...@@ -54,7 +54,12 @@ module Gitlab ...@@ -54,7 +54,12 @@ module Gitlab
end end
def unsafe_severity? def unsafe_severity?
occurrences.any? { |occurrence| UNSAFE_SEVERITIES.include?(occurrence.severity) } !safe?
end
def safe?
severities = occurrences.map(&:severity).compact.map(&:downcase)
(severities & UNSAFE_SEVERITIES).empty?
end end
end end
end end
......
...@@ -7,6 +7,8 @@ module Gitlab ...@@ -7,6 +7,8 @@ module Gitlab
class Reports class Reports
attr_reader :reports, :commit_sha attr_reader :reports, :commit_sha
delegate :empty?, to: :reports
def initialize(commit_sha) def initialize(commit_sha)
@reports = {} @reports = {}
@commit_sha = commit_sha @commit_sha = commit_sha
...@@ -15,6 +17,10 @@ module Gitlab ...@@ -15,6 +17,10 @@ module Gitlab
def get_report(report_type) def get_report(report_type)
reports[report_type] ||= Report.new(report_type, commit_sha) reports[report_type] ||= Report.new(report_type, commit_sha)
end end
def violates_default_policy?
reports.values.any? { |report| report.unsafe_severity? }
end
end end
end end
end end
......
...@@ -49,6 +49,26 @@ describe Gitlab::Ci::Parsers::Security::DependencyScanning do ...@@ -49,6 +49,26 @@ describe Gitlab::Ci::Parsers::Security::DependencyScanning do
end end
end end
context "when parsing a vulnerability with a missing location" do
let(:report_hash) { JSON.parse(fixture_file('security_reports/master/gl-sast-report.json', dir: 'ee'), symbolize_names: true) }
before do
report_hash[:vulnerabilities][0][:location] = nil
end
it { expect { parser.parse!(report_hash.to_json, report) }.not_to raise_error }
end
context "when parsing a vulnerability with a missing cve" do
let(:report_hash) { JSON.parse(fixture_file('security_reports/master/gl-sast-report.json', dir: 'ee'), symbolize_names: true) }
before do
report_hash[:vulnerabilities][0][:cve] = nil
end
it { expect { parser.parse!(report_hash.to_json, report) }.not_to raise_error }
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) }
......
...@@ -4,14 +4,15 @@ require 'spec_helper' ...@@ -4,14 +4,15 @@ require 'spec_helper'
describe Gitlab::Ci::Parsers::Security::Sast do describe Gitlab::Ci::Parsers::Security::Sast do
describe '#parse!' do describe '#parse!' do
let(:project) { artifact.project } subject(:parser) { described_class.new }
let(:pipeline) { artifact.job.pipeline }
let(:report) { Gitlab::Ci::Reports::Security::Report.new(artifact.file_type, pipeline.sha) }
let(:parser) { described_class.new }
let(:commit_sha) { "d8978e74745e18ce44d88814004d4255ac6a65bb" }
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(:artifact) { create(:ee_ci_job_artifact, report_format) } let(:artifact) { create(:ee_ci_job_artifact, report_format) }
before do before do
...@@ -44,4 +45,12 @@ describe Gitlab::Ci::Parsers::Security::Sast do ...@@ -44,4 +45,12 @@ describe Gitlab::Ci::Parsers::Security::Sast do
end end
end end
end end
context "when parsing an empty report" do
let(:report) { Gitlab::Ci::Reports::Security::Report.new('sast', commit_sha) }
let(:blob) { JSON.generate({}) }
it { expect(parser.parse!(blob, report)).to be_empty }
end
end
end end
...@@ -3,8 +3,8 @@ ...@@ -3,8 +3,8 @@
require 'spec_helper' require 'spec_helper'
describe Gitlab::Ci::Reports::Security::Report do describe Gitlab::Ci::Reports::Security::Report do
let(:pipeline) { create(:ci_pipeline) } let(:report) { described_class.new('sast', commit_sha) }
let(:report) { described_class.new('sast', pipeline.sha) } let(:commit_sha) { "d8978e74745e18ce44d88814004d4255ac6a65bb" }
it { expect(report.type).to eq('sast') } it { expect(report.type).to eq('sast') }
...@@ -111,7 +111,7 @@ describe Gitlab::Ci::Reports::Security::Report do ...@@ -111,7 +111,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', pipeline.sha)) } subject { report.merge!(described_class.new('sast', commit_sha)) }
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
...@@ -119,4 +119,63 @@ describe Gitlab::Ci::Reports::Security::Report do ...@@ -119,4 +119,63 @@ 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) }
context "when the sast report has an unsafe vulnerability" do
where(severity: %w[unknown Unknown high High critical Critical])
with_them do
let(:occurrence) { build(:ci_reports_security_occurrence, severity: severity) }
before do
subject.add_occurrence(occurrence)
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(:occurrence) { build(:ci_reports_security_occurrence, severity: severity) }
before do
subject.add_occurrence(occurrence)
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(:occurrence) { build(:ci_reports_security_occurrence, severity: nil) }
before do
subject.add_occurrence(occurrence)
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(:occurrence) { build(:ci_reports_security_occurrence, severity: '') }
before do
subject.add_occurrence(occurrence)
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
...@@ -35,4 +35,29 @@ describe Gitlab::Ci::Reports::Security::Reports do ...@@ -35,4 +35,29 @@ describe Gitlab::Ci::Reports::Security::Reports do
end end
end end
end end
describe "#violates_default_policy?" do
subject { described_class.new(commit_sha) }
let(:low_severity) { build(:ci_reports_security_occurrence, severity: 'low') }
let(:high_severity) { build(:ci_reports_security_occurrence, severity: 'high') }
context "when a report has a high severity vulnerability" do
before do
subject.get_report('sast').add_occurrence(high_severity)
subject.get_report('dependency_scanning').add_occurrence(low_severity)
end
it { expect(subject.violates_default_policy?).to be(true) }
end
context "when none of the reports have a high severity vulnerability" do
before do
subject.get_report('sast').add_occurrence(low_severity)
subject.get_report('dependency_scanning').add_occurrence(low_severity)
end
it { expect(subject.violates_default_policy?).to be(false) }
end
end
end end
...@@ -131,13 +131,14 @@ describe Security::SyncReportsToApprovalRulesService, '#execute' do ...@@ -131,13 +131,14 @@ describe Security::SyncReportsToApprovalRulesService, '#execute' do
end end
context 'without reports' do context 'without reports' do
let(:pipeline) { create(:ci_pipeline, :running, project: project, merge_requests_as_head_pipeline: [merge_request]) }
it "won't change approvals_required count" 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
context "license compliance policy" do context "license compliance policy" do
let(:pipeline) { create(:ee_ci_pipeline, :running, project: project, merge_requests_as_head_pipeline: [merge_request]) }
let!(:software_license_policy) { create(:software_license_policy, :blacklist, project: project, software_license: blacklisted_license) } let!(:software_license_policy) { create(:software_license_policy, :blacklist, project: project, software_license: blacklisted_license) }
let!(:license_compliance_rule) { create(:report_approver_rule, :license_management, merge_request: merge_request, approvals_required: 1) } let!(:license_compliance_rule) { create(:report_approver_rule, :license_management, merge_request: merge_request, approvals_required: 1) }
let!(:blacklisted_license) { create(:software_license) } let!(:blacklisted_license) { create(:software_license) }
......
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