Commit 910ad293 authored by Sean McGivern's avatar Sean McGivern

Merge branch 'identify-secure-jobs-in-pipeline' into 'master'

Identify secure jobs in a pipeline

See merge request gitlab-org/gitlab!17568
parents 89ed43d5 a22c802a
...@@ -128,6 +128,12 @@ module Ci ...@@ -128,6 +128,12 @@ module Ci
scope :with_stale_live_trace, -> { with_live_trace.finished_before(12.hours.ago) } scope :with_stale_live_trace, -> { with_live_trace.finished_before(12.hours.ago) }
scope :finished_before, -> (date) { finished.where('finished_at < ?', date) } scope :finished_before, -> (date) { finished.where('finished_at < ?', date) }
scope :with_secure_reports_from_options, -> (job_type) { where('options like :job_type', job_type: "%:artifacts:%:reports:%:#{job_type}:%") }
scope :with_secure_reports_from_config_options, -> (job_types) do
joins(:metadata).where("ci_builds_metadata.config_options -> 'artifacts' -> 'reports' ?| array[:job_types]", job_types: job_types)
end
scope :matches_tag_ids, -> (tag_ids) do scope :matches_tag_ids, -> (tag_ids) do
matcher = ::ActsAsTaggableOn::Tagging matcher = ::ActsAsTaggableOn::Tagging
.where(taggable_type: CommitStatus.name) .where(taggable_type: CommitStatus.name)
......
# frozen_string_literal: true
# Security::JobsFinder
#
# Used to find jobs (builds) that are for Secure products, SAST, DAST, Dependency Scanning and Container Scanning.
#
# Arguments:
# params:
# pipeline: required, only jobs for the specified pipeline will be found
# job_types: required, array of job types that should be returned, defaults to all job types
module Security
class JobsFinder
attr_reader :pipeline
JOB_TYPES = [:sast, :dast, :dependency_scanning, :container_scanning].freeze
def initialize(pipeline:, job_types: JOB_TYPES)
@pipeline = pipeline
@job_types = job_types
end
def execute
return [] if @job_types.empty?
if Feature.enabled?(:ci_build_metadata_config)
find_jobs
else
find_jobs_legacy
end
end
private
def find_jobs
@pipeline.builds.with_secure_reports_from_config_options(@job_types)
end
def find_jobs_legacy
# the query doesn't guarantee accuracy, so we verify it here
legacy_jobs_query.select do |job|
@job_types.find { |job_type| job.options.dig(:artifacts, :reports, job_type) }
end
end
def legacy_jobs_query
@job_types.map do |job_type|
@pipeline.builds.with_secure_reports_from_options(job_type)
end.reduce(&:or)
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Security::JobsFinder do
let(:pipeline) { create(:ci_pipeline) }
let(:finder) { described_class.new(pipeline: pipeline, job_types: ::Security::JobsFinder::JOB_TYPES) }
describe '#execute' do
subject { finder.execute }
describe 'legacy options stored' do
before do
stub_feature_flags(ci_build_metadata_config: false)
end
context 'with no jobs' do
it { is_expected.to be_empty }
end
context 'with non secure jobs' do
before do
create(:ci_build, pipeline: pipeline)
end
it { is_expected.to be_empty }
end
context 'with jobs having report artifacts' do
before do
create(:ci_build, pipeline: pipeline, options: { artifacts: { file: 'test.file' } })
end
it { is_expected.to be_empty }
end
context 'with jobs having non secure report artifacts' do
before do
create(:ci_build, pipeline: pipeline, options: { artifacts: { reports: { file: 'test.file' } } })
end
it { is_expected.to be_empty }
end
context 'with jobs having report artifacts that are similar to secure artifacts' do
before do
create(:ci_build, pipeline: pipeline, options: { artifacts: { reports: { file: 'report:sast:result.file' } } })
end
it { is_expected.to be_empty }
end
context 'searching for all types takes precedence over excluding specific types' do
let!(:build) { create(:ci_build, :dast, pipeline: pipeline) }
let(:finder) { described_class.new(pipeline: pipeline, job_types: [:dast]) }
it { is_expected.to eq([build]) }
end
context 'with dast jobs' do
let!(:build) { create(:ci_build, :dast, pipeline: pipeline) }
it { is_expected.to eq([build]) }
end
context 'with sast jobs' do
let!(:build) { create(:ci_build, :sast, pipeline: pipeline) }
it { is_expected.to eq([build]) }
end
context 'with container scanning jobs' do
let!(:build) { create(:ci_build, :container_scanning, pipeline: pipeline) }
it { is_expected.to eq([build]) }
end
context 'with dependency scanning jobs' do
let!(:build) { create(:ci_build, :dependency_scanning, pipeline: pipeline) }
it { is_expected.to eq([build]) }
end
context 'with many secure pipelines' do
before do
create(:ci_build, :dast, pipeline: create(:ci_pipeline))
end
let!(:build) { create(:ci_build, :dast, pipeline: pipeline) }
it 'returns jobs associated with provided pipeline' do
is_expected.to eq([build])
end
end
context 'with specific secure job types' do
let!(:sast_build) { create(:ci_build, :sast, pipeline: pipeline) }
let!(:container_scanning_build) { create(:ci_build, :container_scanning, pipeline: pipeline) }
let!(:dast_build) { create(:ci_build, :dast, pipeline: pipeline) }
let(:finder) { described_class.new(pipeline: pipeline, job_types: [:sast, :container_scanning]) }
it 'returns only those requested' do
is_expected.to include(sast_build)
is_expected.to include(container_scanning_build)
is_expected.not_to include(dast_build)
end
end
end
describe 'config options stored' do
before do
stub_feature_flags(ci_build_metadata_config: true)
end
context 'with no jobs' do
it { is_expected.to be_empty }
end
context 'with non secure jobs' do
before do
create(:ci_build, pipeline: pipeline)
end
it { is_expected.to be_empty }
end
context 'with jobs having report artifacts' do
before do
create(:ci_build, pipeline: pipeline, options: { artifacts: { file: 'test.file' } })
end
it { is_expected.to be_empty }
end
context 'with jobs having non secure report artifacts' do
before do
create(:ci_build, pipeline: pipeline, options: { artifacts: { reports: { file: 'test.file' } } })
end
it { is_expected.to be_empty }
end
context 'with dast jobs' do
let!(:build) { create(:ci_build, :dast, pipeline: pipeline) }
it { is_expected.to eq([build]) }
end
context 'with sast jobs' do
let!(:build) { create(:ci_build, :sast, pipeline: pipeline) }
it { is_expected.to eq([build]) }
end
context 'with container scanning jobs' do
let!(:build) { create(:ci_build, :container_scanning, pipeline: pipeline) }
it { is_expected.to eq([build]) }
end
context 'with dependency scanning jobs' do
let!(:build) { create(:ci_build, :dependency_scanning, pipeline: pipeline) }
it { is_expected.to eq([build]) }
end
context 'with many secure pipelines' do
before do
create(:ci_build, :dast, pipeline: create(:ci_pipeline))
end
let!(:build) { create(:ci_build, :dast, pipeline: pipeline) }
it 'returns jobs associated with provided pipeline' do
is_expected.to eq([build])
end
end
context 'with specific secure job types' do
let!(:sast_build) { create(:ci_build, :sast, pipeline: pipeline) }
let!(:container_scanning_build) { create(:ci_build, :container_scanning, pipeline: pipeline) }
let!(:dast_build) { create(:ci_build, :dast, pipeline: pipeline) }
let(:finder) { described_class.new(pipeline: pipeline, job_types: [:sast, :container_scanning]) }
it 'returns only those requested' do
is_expected.to include(sast_build)
is_expected.to include(container_scanning_build)
is_expected.not_to include(dast_build)
end
end
end
end
end
...@@ -330,6 +330,38 @@ FactoryBot.define do ...@@ -330,6 +330,38 @@ FactoryBot.define do
options { {} } options { {} }
end end
trait :dast do
options do
{
artifacts: { reports: { dast: 'gl-dast-report.json' } }
}
end
end
trait :sast do
options do
{
artifacts: { reports: { sast: 'gl-sast-report.json' } }
}
end
end
trait :dependency_scanning do
options do
{
artifacts: { reports: { dependency_scanning: 'gl-dependency-scanning-report.json' } }
}
end
end
trait :container_scanning do
options do
{
artifacts: { reports: { container_scanning: 'gl-container-scanning-report.json' } }
}
end
end
trait :non_playable do trait :non_playable do
status { 'created' } status { 'created' }
self.when { 'manual' } self.when { 'manual' }
......
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