Commit 8a65ba1f authored by Etienne Baqué's avatar Etienne Baqué

Merge branch 'use_security_findings_to_count_vulnerabilities_238951' into 'master'

Use security findings to count vulnerabilities

See merge request gitlab-org/gitlab!53116
parents 01031763 9222ed19
...@@ -40,7 +40,12 @@ module Security ...@@ -40,7 +40,12 @@ module Security
scope :with_scan, -> { includes(:scan) } scope :with_scan, -> { includes(:scan) }
scope :with_scanner, -> { includes(:scanner) } scope :with_scanner, -> { includes(:scanner) }
scope :deduplicated, -> { where(deduplicated: true) } scope :deduplicated, -> { where(deduplicated: true) }
scope :grouped_by_scan_type, -> { joins(:scan).group('security_scans.scan_type') }
delegate :scan_type, to: :scan, allow_nil: true delegate :scan_type, to: :scan, allow_nil: true
def self.count_by_scan_type
grouped_by_scan_type.count
end
end end
end end
...@@ -13,11 +13,13 @@ module Security ...@@ -13,11 +13,13 @@ module Security
end end
def execute def execute
vulnerabilities = ::Security::PipelineVulnerabilitiesFinder.new(pipeline: @pipeline, params: { report_type: @report_types }).execute counts = @pipeline.security_findings
vulnerabilities.findings.group_by(&:report_type).compact.transform_values(&:size).reverse_merge(no_counts) .deduplicated
end .by_report_types(@report_types)
.count_by_scan_type
private counts.transform_keys { |key| Security::Scan.scan_types.key(key) }.reverse_merge(no_counts)
end
def no_counts def no_counts
@report_types.zip([0].cycle).to_h @report_types.zip([0].cycle).to_h
......
...@@ -129,4 +129,21 @@ RSpec.describe Security::Finding do ...@@ -129,4 +129,21 @@ RSpec.describe Security::Finding do
it { is_expected.to eq(expected_findings) } it { is_expected.to eq(expected_findings) }
end end
describe '.count_by_scan_type' do
let!(:sast_scan) { create(:security_scan, scan_type: :sast) }
let!(:dast_scan) { create(:security_scan, scan_type: :dast) }
let!(:finding_1) { create(:security_finding, scan: sast_scan) }
let!(:finding_2) { create(:security_finding, scan: sast_scan) }
let!(:finding_3) { create(:security_finding, scan: dast_scan) }
subject { described_class.count_by_scan_type }
it {
is_expected.to eq({
Security::Scan.scan_types['dast'] => 1,
Security::Scan.scan_types['sast'] => 2
})
}
end
end end
...@@ -5,14 +5,38 @@ require 'spec_helper' ...@@ -5,14 +5,38 @@ require 'spec_helper'
RSpec.describe 'Query.project(fullPath).pipeline(iid).securityReportSummary' do RSpec.describe 'Query.project(fullPath).pipeline(iid).securityReportSummary' do
let_it_be(:project) { create(:project, :repository) } let_it_be(:project) { create(:project, :repository) }
let_it_be(:pipeline) { create(:ci_pipeline, :success, project: project) } let_it_be(:pipeline) { create(:ci_pipeline, :success, project: project) }
let_it_be(:build_sast) { create(:ci_build, :success, name: 'sast', pipeline: pipeline) }
let_it_be(:artifact_sast) { create(:ee_ci_job_artifact, :sast, job: build_sast) }
let_it_be(:report_sast) { create(:ci_reports_security_report, type: :sast) }
let_it_be(:build_dast) { create(:ci_build, :success, name: 'dast', pipeline: pipeline) }
let_it_be(:artifact_dast) { create(:ee_ci_job_artifact, :dast_large_scanned_resources_field, job: build_dast) }
let_it_be(:report_dast) { create(:ci_reports_security_report, type: :dast) }
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
before_all do before_all do
create(:ci_build, :success, name: 'dast_job', pipeline: pipeline, project: project) do |job| sast_content = File.read(artifact_sast.file.path)
create(:ee_ci_job_artifact, :dast_large_scanned_resources_field, job: job, project: project) Gitlab::Ci::Parsers::Security::Sast.parse!(sast_content, report_sast)
report_sast.merge!(report_sast)
dast_content = File.read(artifact_dast.file.path)
Gitlab::Ci::Parsers::Security::Dast.parse!(dast_content, report_dast)
report_dast.merge!(report_dast)
{ artifact_dast => report_dast, artifact_sast => report_sast }.each do |artifact, report|
scan = create(:security_scan, scan_type: artifact.job.name, build: artifact.job)
report.findings.each_with_index do |finding, index|
create(:security_finding,
severity: finding.severity,
confidence: finding.confidence,
project_fingerprint: finding.project_fingerprint,
deduplicated: true,
position: index,
scan: scan)
end end
create(:ci_build, :success, name: 'sast_job', pipeline: pipeline, project: project) do |job|
create(:ee_ci_job_artifact, :sast, job: job, project: project)
end end
end end
......
...@@ -3,22 +3,53 @@ ...@@ -3,22 +3,53 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Security::ReportSummaryService, '#execute' do RSpec.describe Security::ReportSummaryService, '#execute' do
let_it_be(:project) { create(:project, :repository) } let_it_be(:pipeline) { create(:ci_pipeline, :success) }
let_it_be(:pipeline) { create(:ci_pipeline, :success, project: project) }
before_all do let_it_be(:build_ds) { create(:ci_build, :success, name: 'dependency_scanning', pipeline: pipeline) }
create(:ci_build, :success, name: 'dast_job', pipeline: pipeline, project: project) do |job| let_it_be(:artifact_ds) { create(:ee_ci_job_artifact, :dependency_scanning, job: build_ds) }
create(:ee_ci_job_artifact, :dast_large_scanned_resources_field, job: job, project: project) let_it_be(:report_ds) { create(:ci_reports_security_report, type: :dependency_scanning) }
end
create(:ci_build, :success, name: 'sast_job', pipeline: pipeline, project: project) do |job| let_it_be(:build_sast) { create(:ci_build, :success, name: 'sast', pipeline: pipeline) }
create(:ee_ci_job_artifact, :sast, job: job, project: project) let_it_be(:artifact_sast) { create(:ee_ci_job_artifact, :sast, job: build_sast) }
end let_it_be(:report_sast) { create(:ci_reports_security_report, type: :sast) }
let_it_be(:build_dast) { create(:ci_build, :success, name: 'dast', pipeline: pipeline) }
let_it_be(:artifact_dast) { create(:ee_ci_job_artifact, :dast_large_scanned_resources_field, job: build_dast) }
let_it_be(:report_dast) { create(:ci_reports_security_report, type: :dast) }
let_it_be(:build_cs) { create(:ci_build, :success, name: 'container_scanning', pipeline: pipeline) }
let_it_be(:artifact_cs) { create(:ee_ci_job_artifact, :container_scanning, job: build_cs) }
let_it_be(:report_cs) { create(:ci_reports_security_report, type: :container_scanning) }
before(:all) do
ds_content = File.read(artifact_ds.file.path)
Gitlab::Ci::Parsers::Security::DependencyScanning.parse!(ds_content, report_ds)
report_ds.merge!(report_ds)
sast_content = File.read(artifact_sast.file.path)
Gitlab::Ci::Parsers::Security::Sast.parse!(sast_content, report_sast)
report_sast.merge!(report_sast)
dast_content = File.read(artifact_dast.file.path)
Gitlab::Ci::Parsers::Security::Dast.parse!(dast_content, report_dast)
report_dast.merge!(report_dast)
cs_content = File.read(artifact_cs.file.path)
Gitlab::Ci::Parsers::Security::ContainerScanning.parse!(cs_content, report_cs)
report_cs.merge!(report_cs)
{ artifact_cs => report_cs, artifact_dast => report_dast, artifact_ds => report_ds, artifact_sast => report_sast }.each do |artifact, report|
scan = create(:security_scan, scan_type: artifact.job.name, build: artifact.job)
create(:ci_build, :success, name: 'cs_job', pipeline: pipeline, project: project) do |job| report.findings.each_with_index do |finding, index|
create(:ee_ci_job_artifact, :container_scanning, job: job, project: project) create(:security_finding,
severity: finding.severity,
confidence: finding.confidence,
project_fingerprint: finding.project_fingerprint,
deduplicated: true,
position: index,
scan: scan)
end end
create(:ci_build, :success, name: 'ds_job', pipeline: pipeline, project: project) do |job|
create(:ee_ci_job_artifact, :dependency_scanning, job: job, project: project)
end end
end end
...@@ -101,7 +132,7 @@ RSpec.describe Security::ReportSummaryService, '#execute' do ...@@ -101,7 +132,7 @@ RSpec.describe Security::ReportSummaryService, '#execute' do
it 'returns the scanned_resources_csv_path' do it 'returns the scanned_resources_csv_path' do
expected_path = Gitlab::Routing.url_helpers.project_security_scanned_resources_path( expected_path = Gitlab::Routing.url_helpers.project_security_scanned_resources_path(
project, pipeline.project,
format: :csv, format: :csv,
pipeline_id: pipeline.id pipeline_id: pipeline.id
) )
......
...@@ -7,27 +7,61 @@ RSpec.describe Security::VulnerabilityCountingService, '#execute' do ...@@ -7,27 +7,61 @@ RSpec.describe Security::VulnerabilityCountingService, '#execute' do
stub_licensed_features(sast: true, dependency_scanning: true, container_scanning: true, dast: true) stub_licensed_features(sast: true, dependency_scanning: true, container_scanning: true, dast: true)
end end
let_it_be(:project) { create(:project, :repository) } let_it_be(:pipeline) { create(:ci_pipeline, :success) }
let_it_be(:pipeline) { create(:ci_pipeline, :success, project: project) }
let_it_be(:build_ds) { create(:ci_build, :success, name: 'dependency_scanning', pipeline: pipeline) }
let_it_be(:artifact_ds) { create(:ee_ci_job_artifact, :dependency_scanning, job: build_ds) }
let_it_be(:report_ds) { create(:ci_reports_security_report, type: :dependency_scanning) }
let_it_be(:build_sast) { create(:ci_build, :success, name: 'sast', pipeline: pipeline) }
let_it_be(:artifact_sast) { create(:ee_ci_job_artifact, :sast, job: build_sast) }
let_it_be(:report_sast) { create(:ci_reports_security_report, type: :sast) }
let_it_be(:build_dast) { create(:ci_build, :success, name: 'dast', pipeline: pipeline) }
let_it_be(:artifact_dast) { create(:ee_ci_job_artifact, :dast, job: build_dast) }
let_it_be(:report_dast) { create(:ci_reports_security_report, type: :dast) }
let_it_be(:build_cs) { create(:ci_build, :success, name: 'container_scanning', pipeline: pipeline) }
let_it_be(:artifact_cs) { create(:ee_ci_job_artifact, :container_scanning, job: build_cs) }
let_it_be(:report_cs) { create(:ci_reports_security_report, type: :container_scanning) }
context "The pipeline has security builds" do context "The pipeline has security builds" do
before_all do before(:all) do
create(:ci_build, :success, name: 'dast_job', pipeline: pipeline, project: project) do |job| ds_content = File.read(artifact_ds.file.path)
create(:ee_ci_job_artifact, :dast, job: job, project: project) Gitlab::Ci::Parsers::Security::DependencyScanning.parse!(ds_content, report_ds)
end report_ds.merge!(report_ds)
create(:ci_build, :success, name: 'sast_job', pipeline: pipeline, project: project) do |job|
create(:ee_ci_job_artifact, :sast, job: job, project: project) sast_content = File.read(artifact_sast.file.path)
end Gitlab::Ci::Parsers::Security::Sast.parse!(sast_content, report_sast)
create(:ci_build, :success, name: 'cs_job', pipeline: pipeline, project: project) do |job| report_sast.merge!(report_sast)
create(:ee_ci_job_artifact, :container_scanning, job: job, project: project)
dast_content = File.read(artifact_dast.file.path)
Gitlab::Ci::Parsers::Security::Dast.parse!(dast_content, report_dast)
report_dast.merge!(report_dast)
cs_content = File.read(artifact_cs.file.path)
Gitlab::Ci::Parsers::Security::ContainerScanning.parse!(cs_content, report_cs)
report_cs.merge!(report_cs)
{ artifact_cs => report_cs, artifact_dast => report_dast, artifact_ds => report_ds, artifact_sast => report_sast }.each do |artifact, report|
scan = create(:security_scan, scan_type: artifact.job.name, build: artifact.job)
report.findings.each_with_index do |finding, index|
create(:security_finding,
severity: finding.severity,
confidence: finding.confidence,
project_fingerprint: finding.project_fingerprint,
deduplicated: true,
position: index,
scan: scan)
end end
create(:ci_build, :success, name: 'ds_job', pipeline: pipeline, project: project) do |job|
create(:ee_ci_job_artifact, :dependency_scanning, job: job, project: project)
end end
end end
context 'All report types are requested' do context 'All report types are requested' do
subject { described_class.new(pipeline, %w[sast dast container_scanning dependency_scanning]).execute } subject do
described_class.new(pipeline, %w[sast dast container_scanning dependency_scanning]).execute
end
it { it {
is_expected.to match(a_hash_including("sast" => 33, is_expected.to match(a_hash_including("sast" => 33,
......
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