diff --git a/ee/app/models/security/scan_execution_policy/config.rb b/ee/app/models/security/scan_execution_policy/config.rb index 100ce27d83eedefde14fdd0fda970f7c26c4af85..41892ee9c98df93d8aefd0c0538cf9947462dd30 100644 --- a/ee/app/models/security/scan_execution_policy/config.rb +++ b/ee/app/models/security/scan_execution_policy/config.rb @@ -8,16 +8,29 @@ class Config DEFAULT_SKIP_CI_STRATEGY = { allowed: true }.freeze - attr_reader :actions, :skip_ci_strategy + attr_reader :actions, :configuration, :skip_ci_strategy - def initialize(policy:) - @actions = policy.fetch(:actions) + def initialize(policy:, configuration: nil) + @configuration = configuration @skip_ci_strategy = policy[:skip_ci].presence || DEFAULT_SKIP_CI_STRATEGY + @actions = policy.fetch(:actions, []).map { |action| action.merge(metadata: action_metadata) } end def skip_ci_allowed?(user_id) skip_ci_allowed_for_strategy?(skip_ci_strategy, user_id) end + + private + + delegate :security_policy_management_project_id, :configuration_sha, to: :configuration, allow_nil: true + + def action_metadata + # Metadata used for id_tokens. It matches the attributes in `pipeline_execution_context.job_options`. + { + project_id: security_policy_management_project_id, + sha: configuration_sha + }.compact + end end end end diff --git a/ee/app/services/security/security_orchestration_policies/ci_action/template.rb b/ee/app/services/security/security_orchestration_policies/ci_action/template.rb index 19ec4041b7ebcc8bb764e2dfbd90274c9955ea5d..3bb07d5d4711213100db73ea954d5976c4e492f4 100644 --- a/ee/app/services/security/security_orchestration_policies/ci_action/template.rb +++ b/ee/app/services/security/security_orchestration_policies/ci_action/template.rb @@ -4,6 +4,8 @@ module Security module SecurityOrchestrationPolicies module CiAction class Template < Base + include CiConfigurationMetadata + SCAN_TEMPLATES = { 'secret_detection' => 'Jobs/Secret-Detection', 'container_scanning' => 'Jobs/Container-Scanning', @@ -46,6 +48,7 @@ def config apply_defaults!(job_configuration, @action[:scan_settings]) remove_extends!(job_configuration) remove_rule_to_disable_job!(job_configuration) + merge_configuration_metadata!(job_configuration, @action[:metadata]) end ci_configuration diff --git a/ee/app/services/security/security_orchestration_policies/ci_configuration_metadata.rb b/ee/app/services/security/security_orchestration_policies/ci_configuration_metadata.rb new file mode 100644 index 0000000000000000000000000000000000000000..ac7d62bddf1b93214b23505153b5dddd42467c5f --- /dev/null +++ b/ee/app/services/security/security_orchestration_policies/ci_configuration_metadata.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module Security + module SecurityOrchestrationPolicies + module CiConfigurationMetadata + def merge_configuration_metadata!(config, metadata) + return config if metadata.nil? + + # Store the policy project ID and SHA as a metadata in the job configuration, removed then in processor + config[:_metadata] = metadata + end + end + end +end diff --git a/ee/app/services/security/security_orchestration_policies/on_demand_scan_pipeline_configuration_service.rb b/ee/app/services/security/security_orchestration_policies/on_demand_scan_pipeline_configuration_service.rb index 7378cfff9b5f20a7f3f559a422d549c1bb87fa97..ffaefe8fbc9f315d217cc02e595eaa4e4831e323 100644 --- a/ee/app/services/security/security_orchestration_policies/on_demand_scan_pipeline_configuration_service.rb +++ b/ee/app/services/security/security_orchestration_policies/on_demand_scan_pipeline_configuration_service.rb @@ -4,6 +4,7 @@ module Security module SecurityOrchestrationPolicies class OnDemandScanPipelineConfigurationService include Gitlab::Utils::StrongMemoize + include CiConfigurationMetadata def initialize(project) @project = project @@ -33,7 +34,7 @@ def prepare_on_demand_scan_configuration(action) scanner_profile = dast_scanner_profile(action[:scanner_profile]) result = prepare_base_configuration(site_profile, scanner_profile) - return error_script(result.message) unless result.success? + return error_script(result.message, action) unless result.success? action_variables = action[:variables].to_h.stringify_keys ci_configuration = YAML.safe_load(result.payload[:ci_configuration]) @@ -43,10 +44,12 @@ def prepare_on_demand_scan_configuration(action) .merge(action[:tags] ? { tags: action[:tags] } : {}) .merge(ignore_default_before_after_script?(action) ? { before_script: [], after_script: [] } : {}) .deep_merge( - 'stage' => 'dast', - 'variables' => dast_on_demand_variables(template, action_variables), - 'dast_configuration' => ci_configuration['dast']['dast_configuration'] - ) + { + 'stage' => 'dast', + 'variables' => dast_on_demand_variables(template, action_variables), + 'dast_configuration' => ci_configuration['dast']['dast_configuration'] + }.compact + ).tap { |config| merge_configuration_metadata!(config, action[:metadata]) } end def dast_site_profile(site_profile_name) @@ -92,11 +95,11 @@ def dast_on_demand_variables(template, action_variables) .merge(action_variables) end - def error_script(error_message) + def error_script(error_message, action) { 'script' => "echo \"Error during On-Demand Scan execution: #{error_message}\" && false", 'allow_failure' => true - } + }.tap { |config| merge_configuration_metadata!(config, action[:metadata]) } end end end diff --git a/ee/lib/ee/gitlab/ci/pipeline/chain/set_build_sources.rb b/ee/lib/ee/gitlab/ci/pipeline/chain/set_build_sources.rb index a9ff6e0688abcc928cba517ae18c22c4d39d59d2..3e41880f25212b644be2f54957daa3ce275cbe65 100644 --- a/ee/lib/ee/gitlab/ci/pipeline/chain/set_build_sources.rb +++ b/ee/lib/ee/gitlab/ci/pipeline/chain/set_build_sources.rb @@ -9,14 +9,14 @@ module SetBuildSources extend ::Gitlab::Utils::Override override :pipeline_execution_policy_build? - def pipeline_execution_policy_build?(build) - build.options&.dig(:policy).present? + def pipeline_execution_policy_build?(_build) + command.pipeline_policy_context.pipeline_execution_context.creating_policy_pipeline? end override :scan_execution_policy_build? def scan_execution_policy_build?(build) command.pipeline_policy_context.scan_execution_context(pipeline.source_ref_path) - .job_injected?(build) + .job_injected?(build.name) end end end diff --git a/ee/lib/ee/gitlab/ci/yaml_processor/result.rb b/ee/lib/ee/gitlab/ci/yaml_processor/result.rb index 37a3a901c1c60db56181d3f8deb01cfbdb2d5502..f6bca2d76dc11834c512e1e95c99dce2d45e293e 100644 --- a/ee/lib/ee/gitlab/ci/yaml_processor/result.rb +++ b/ee/lib/ee/gitlab/ci/yaml_processor/result.rb @@ -18,15 +18,15 @@ def build_attributes(name) options: { dast_configuration: job[:dast_configuration], identity: job[:identity], - policy: execution_policy_job_options + policy: execution_policy_job_options(name) }.compact, secrets: job[:secrets] }.compact ) end - def execution_policy_job_options - ci_config&.pipeline_policy_context&.pipeline_execution_context&.job_options + def execution_policy_job_options(job_name) + ci_config.pipeline_policy_context&.job_options(ref: ci_config.source_ref_path, job_name: job_name) end end end diff --git a/ee/lib/gitlab/ci/config/security_orchestration_policies/processor.rb b/ee/lib/gitlab/ci/config/security_orchestration_policies/processor.rb index c21b1361ce076d8c7029af438cbb66f973ff79ff..fdd19d61dc78a6cad791fa83e496654286055ed9 100644 --- a/ee/lib/gitlab/ci/config/security_orchestration_policies/processor.rb +++ b/ee/lib/gitlab/ci/config/security_orchestration_policies/processor.rb @@ -92,38 +92,43 @@ def merge_policies_with_stages(config) end def merge_on_demand_scan_template(merged_config, defined_stages) - on_demand_scan_template = prepare_on_demand_scans_template - on_demand_scan_job_names = job_names(on_demand_scan_template.keys) + template = prepare_on_demand_scans_template + return if template.blank? - if on_demand_scan_template.present? - insert_stage_before_or_append(defined_stages, DEFAULT_ON_DEMAND_STAGE, ['.post']) - merged_config.except!(*on_demand_scan_job_names).deep_merge!(on_demand_scan_template) - scan_execution_policy_context.collect_injected_job_names(on_demand_scan_job_names) - end + job_names = extract_job_names(template.keys) + jobs_without_metadata = template_without_metadata(template) + + insert_stage_before_or_append(defined_stages, DEFAULT_ON_DEMAND_STAGE, ['.post']) + merged_config.except!(*job_names).deep_merge!(jobs_without_metadata) + scan_execution_policy_context.collect_injected_job_names_with_metadata(template) end def merge_pipeline_scan_template(merged_config, defined_stages) - pipeline_scan_template = prepare_pipeline_scans_template - pipeline_scan_job_names = job_names(prepare_pipeline_scans_template.keys) - - if pipeline_scan_template.present? - unless defined_stages.include?(DEFAULT_SECURITY_JOB_STAGE) - insert_stage_after_or_prepend(defined_stages, DEFAULT_SCAN_POLICY_STAGE, ['.pre', DEFAULT_BUILD_STAGE]) - pipeline_scan_template = pipeline_scan_template.transform_values do |job_config| - job_config.merge(stage: DEFAULT_SCAN_POLICY_STAGE) - end - end + template = prepare_pipeline_scans_template + return if template.blank? - merged_config.except!(*pipeline_scan_job_names).deep_merge!(pipeline_scan_template) + job_names = extract_job_names(template.keys) + jobs_without_metadata = template_without_metadata(template) - scan_execution_policy_context.collect_injected_job_names(pipeline_scan_job_names) + unless defined_stages.include?(DEFAULT_SECURITY_JOB_STAGE) + insert_stage_after_or_prepend(defined_stages, DEFAULT_SCAN_POLICY_STAGE, ['.pre', DEFAULT_BUILD_STAGE]) + jobs_without_metadata = jobs_without_metadata.transform_values do |job_config| + job_config.merge(stage: DEFAULT_SCAN_POLICY_STAGE) + end end + + merged_config.except!(*job_names).deep_merge!(jobs_without_metadata) + scan_execution_policy_context.collect_injected_job_names_with_metadata(template) end - def job_names(keys) + def extract_job_names(keys) keys - %i[variables] end + def template_without_metadata(template) + template.transform_values { |job_config| job_config.except(:_metadata) } + end + def insert_stage_after_or_prepend(stages, insert_stage_name, after_stages) stage_index = after_stages.filter_map { |stage| stages.index(stage) }.max diff --git a/ee/lib/gitlab/ci/pipeline/execution_policies/pipeline_context.rb b/ee/lib/gitlab/ci/pipeline/execution_policies/pipeline_context.rb index 4cd9cb5cf6cbcaf1f5de9f8c4f1b2ca6746c4cd7..b01f990096d0efe0a9819dc9d9a321fa62d7c080 100644 --- a/ee/lib/gitlab/ci/pipeline/execution_policies/pipeline_context.rb +++ b/ee/lib/gitlab/ci/pipeline/execution_policies/pipeline_context.rb @@ -57,6 +57,10 @@ def skip_ci_allowed?(ref:) pipeline_execution_context.skip_ci_allowed? && scan_execution_context(ref).skip_ci_allowed? end + def job_options(ref:, job_name:) + pipeline_execution_context.job_options || scan_execution_context(ref).job_options(job_name) + end + private attr_reader :project, :source, :current_user, :ref, :sha_context, :variables_attributes, :chat_data, diff --git a/ee/lib/gitlab/ci/pipeline/scan_execution_policies/pipeline_context.rb b/ee/lib/gitlab/ci/pipeline/scan_execution_policies/pipeline_context.rb index a655b9bb34e1bc2c796140d981d94c2c6dfbf0df..c681ec655626ec55adf8f8b1523d415542d38495 100644 --- a/ee/lib/gitlab/ci/pipeline/scan_execution_policies/pipeline_context.rb +++ b/ee/lib/gitlab/ci/pipeline/scan_execution_policies/pipeline_context.rb @@ -13,7 +13,7 @@ def initialize(project:, ref:, current_user:, source:) @ref = ref @current_user = current_user @source = source - @injected_job_names = [] + @injected_job_names_metadata_map = {} end def has_scan_execution_policies? @@ -21,7 +21,10 @@ def has_scan_execution_policies? end def active_scan_execution_actions - policies.flat_map { |policy| limited_actions(policy.actions) }.compact.uniq + # If there are multiple SEP policies with the same scanner, + # we may end up with duplicates due to different metadata. + # Remove the duplicates by only taking unique scans. + policies.flat_map { |policy| limited_actions(policy.actions) }.compact.uniq { |action| action[:scan] } end strong_memoize_attr :active_scan_execution_actions @@ -31,12 +34,19 @@ def skip_ci_allowed? policies.all? { |policy| policy.skip_ci_allowed?(current_user&.id) } end - def collect_injected_job_names(job_names) - @injected_job_names.concat(job_names.map(&:to_s)) + def collect_injected_job_names_with_metadata(template_with_metadata) + job_name_with_metadata = extract_job_names_and_metadata(template_with_metadata) + @injected_job_names_metadata_map.merge!(job_name_with_metadata) end - def job_injected?(job) - @injected_job_names.include?(job.name) + def job_injected?(name) + @injected_job_names_metadata_map.key?(name.to_sym) + end + + def job_options(name) + return unless job_injected?(name) + + @injected_job_names_metadata_map[name.to_sym] end private @@ -61,13 +71,19 @@ def apply_scan_execution_policies? def policies return [] if valid_security_orchestration_policy_configurations.blank? - policies = valid_security_orchestration_policy_configurations - .flat_map do |configuration| - configuration.active_pipeline_policies_for_project(ref, project, source) - end.compact + configurations_with_policies = valid_security_orchestration_policy_configurations + .filter_map do |configuration| + [ + configuration, + configuration.active_pipeline_policies_for_project(ref, project, source) + ] + end - policies.map do |policy| - ::Security::ScanExecutionPolicy::Config.new(policy: policy) + configurations_with_policies.flat_map do |configuration, policies| + policies.map do |policy| + ::Security::ScanExecutionPolicy::Config + .new(policy: policy, configuration: configuration) + end end end strong_memoize_attr :policies @@ -76,6 +92,12 @@ def valid_security_orchestration_policy_configurations @valid_security_orchestration_policy_configurations ||= ::Gitlab::Security::Orchestration::ProjectPolicyConfigurations.new(project).all end + + def extract_job_names_and_metadata(template) + template + .except(*Gitlab::Ci::Config::Entry::Root::ALLOWED_KEYS) + .transform_values { |job_config| job_config.delete(:_metadata) } + end end end end diff --git a/ee/spec/lib/ee/gitlab/ci/pipeline/chain/set_build_sources_spec.rb b/ee/spec/lib/ee/gitlab/ci/pipeline/chain/set_build_sources_spec.rb index d86e98d6c924e384ac64494c5f1f3ebf079b5ce0..2560a1f3f74c975840ca19e5e4107cb7253a5a28 100644 --- a/ee/spec/lib/ee/gitlab/ci/pipeline/chain/set_build_sources_spec.rb +++ b/ee/spec/lib/ee/gitlab/ci/pipeline/chain/set_build_sources_spec.rb @@ -50,6 +50,10 @@ end context 'with security policy' do + let(:pipeline_execution_context) do + instance_double(::Gitlab::Ci::Pipeline::PipelineExecutionPolicies::PipelineContext) + end + let(:scan_execution_context) do instance_double(::Gitlab::Ci::Pipeline::ScanExecutionPolicies::PipelineContext) end @@ -70,9 +74,17 @@ .at_least(:once) .and_return(scan_execution_context) + expect(command.pipeline_policy_context).to receive(:pipeline_execution_context) + .at_least(:once) + .and_return(pipeline_execution_context) + + allow(pipeline_execution_context).to receive(:creating_policy_pipeline?) + .exactly(7).times + .and_return(false, true, false, false, true, true, false) + pipeline_seed.stages.flat_map(&:statuses).each do |build| allow(scan_execution_context).to receive(:job_injected?) - .with(build) + .with(build.name) .and_return(expected_sources[build.name] == "scan_execution_policy") expect(build).to receive(:build_job_source).with( diff --git a/ee/spec/lib/ee/gitlab/ci/yaml_processor/result_spec.rb b/ee/spec/lib/ee/gitlab/ci/yaml_processor/result_spec.rb index 1ed1b5d47288eab958844c99e25d80c1a4843933..c52957e36e768c7ca44ce6cb5c17273c40f695f4 100644 --- a/ee/spec/lib/ee/gitlab/ci/yaml_processor/result_spec.rb +++ b/ee/spec/lib/ee/gitlab/ci/yaml_processor/result_spec.rb @@ -57,6 +57,17 @@ it 'does not mark the build as `policy` via :options' do expect(build.dig(:options, :policy)).to be_nil end + + context 'when job was injected by scan execution policies' do + before do + pipeline_policy_context.scan_execution_context('refs/heads/master') + .collect_injected_job_names_with_metadata(test: { _metadata: { sha: 'SEP SHA', project_id: 123 } }) + end + + it 'saves the policy data in :options' do + expect(build.dig(:options, :policy)).to eq(sha: 'SEP SHA', project_id: 123) + end + end end end diff --git a/ee/spec/lib/gitlab/ci/config/security_orchestration_policies/processor_spec.rb b/ee/spec/lib/gitlab/ci/config/security_orchestration_policies/processor_spec.rb index 53f727c11f27e912c1184c5c3b12c34869d26655..9f3f14cee597429d881435ca6f354e7b5d9b4b01 100644 --- a/ee/spec/lib/gitlab/ci/config/security_orchestration_policies/processor_spec.rb +++ b/ee/spec/lib/gitlab/ci/config/security_orchestration_policies/processor_spec.rb @@ -45,7 +45,8 @@ let(:policies) { {} } let_it_be(:namespace) { create(:group) } - let_it_be(:namespace_policies_repository) { create(:project, :repository) } + let_it_be(:policy_files) { { Security::OrchestrationPolicyConfiguration::POLICY_PATH => '' } } + let_it_be(:namespace_policies_repository) { create(:project, :custom_repo, files: policy_files) } let_it_be(:namespace_security_orchestration_policy_configuration) do create( :security_orchestration_policy_configuration, @@ -63,8 +64,7 @@ end let_it_be_with_refind(:project) { create(:project, :repository, group: namespace) } - - let_it_be(:policies_repository) { create(:project, :repository, group: namespace) } + let_it_be(:policies_repository) { create(:project, :custom_repo, files: policy_files, group: namespace) } let_it_be(:security_orchestration_policy_configuration) do create( :security_orchestration_policy_configuration, @@ -119,6 +119,13 @@ context "when #{ci_source} pipeline is created and affects CI status of the ref" do let(:source) { ci_source } + it 'collects the injected jobs and metadata in the pipeline context' do + perform_service + pipeline_context = pipeline_policy_context.scan_execution_context(ref) + expect(pipeline_context.job_injected?(extended_job)).to be(true) + expect(pipeline_context.job_options(extended_job)).to match a_hash_including(:project_id, :sha) + end + context 'when config already have jobs with names provided by policies' do let(:config) do { diff --git a/ee/spec/lib/gitlab/ci/pipeline/execution_policies/pipeline_context_spec.rb b/ee/spec/lib/gitlab/ci/pipeline/execution_policies/pipeline_context_spec.rb index 2d6ea7080ab49a68f18c4376847be73a8f60d87d..7e305ccefd5198a6444bd6e7dc65f905b90e03ea 100644 --- a/ee/spec/lib/gitlab/ci/pipeline/execution_policies/pipeline_context_spec.rb +++ b/ee/spec/lib/gitlab/ci/pipeline/execution_policies/pipeline_context_spec.rb @@ -170,4 +170,30 @@ end end end + + describe '#job_options' do + subject { context.job_options(ref: pipeline.ref, job_name: 'test-job') } + + it { is_expected.to be_nil } + + context 'when there are pipeline execution policies' do + before do + allow(context.pipeline_execution_context).to receive(:job_options) + .and_return({ name: 'Policy', project_id: 123, sha: 'sha' }) + end + + it { is_expected.to eq(name: 'Policy', project_id: 123, sha: 'sha') } + end + + context 'when there are scan execution policies' do + before do + scan_execution_context_double = + instance_double(::Gitlab::Ci::Pipeline::ScanExecutionPolicies::PipelineContext, + job_options: { project_id: 123, sha: 'sha' }) + allow(context).to receive(:scan_execution_context).with(pipeline.ref).and_return(scan_execution_context_double) + end + + it { is_expected.to eq(project_id: 123, sha: 'sha') } + end + end end diff --git a/ee/spec/lib/gitlab/ci/pipeline/scan_execution_policies/pipeline_context_spec.rb b/ee/spec/lib/gitlab/ci/pipeline/scan_execution_policies/pipeline_context_spec.rb index 07db79861aca994074eae25efeb46ffc4986c06d..519106bdfe1b734fdac0d32a8dcaae2934a428d2 100644 --- a/ee/spec/lib/gitlab/ci/pipeline/scan_execution_policies/pipeline_context_spec.rb +++ b/ee/spec/lib/gitlab/ci/pipeline/scan_execution_policies/pipeline_context_spec.rb @@ -13,7 +13,8 @@ let(:source) { 'push' } let(:pipeline) { build(:ci_pipeline, source: source, project: project, ref: ref, user: user) } - let_it_be(:policies_repository) { create(:project, :repository) } + let_it_be(:policy_files) { { Security::OrchestrationPolicyConfiguration::POLICY_PATH => '' } } + let_it_be(:policies_repository) { create(:project, :custom_repo, files: policy_files) } let(:feature_licensed) { true } let_it_be(:security_orchestration_policy_configuration) do create( @@ -23,6 +24,13 @@ ) end + let(:expected_metadata) do + { + sha: security_orchestration_policy_configuration.configuration_sha, + project_id: policies_repository.id + } + end + let(:policy) do build(:scan_execution_policy, actions: [ { scan: 'dast', site_profile: 'Site Profile', scanner_profile: 'Scanner Profile' }, @@ -107,7 +115,14 @@ describe '#active_scan_execution_actions' do subject(:actions) { context.active_scan_execution_actions } - it { is_expected.to match_array(policy[:actions]) } + it 'contains active actions with metadata' do + expect(actions).to match_array([ + { scan: 'dast', site_profile: 'Site Profile', scanner_profile: 'Scanner Profile', + metadata: expected_metadata }, + { scan: 'secret_detection', metadata: expected_metadata }, + { scan: 'dependency_scanning', metadata: expected_metadata } + ]) + end describe 'action limits' do let(:policies) { [policy, other_policy] } @@ -120,19 +135,35 @@ end let(:action_limit) { 2 } - let(:all_actions) { policy[:actions] + other_policy[:actions] } - let(:limited_actions) { policy[:actions].first(action_limit) + other_policy[:actions].first(action_limit) } before do allow(Gitlab::CurrentSettings).to receive(:scan_execution_policies_action_limit).and_return(action_limit) end - it { is_expected.to match_array(limited_actions) } + it 'contains active actions with metadata' do + expect(actions).to match_array([ + { scan: 'dast', site_profile: 'Site Profile', scanner_profile: 'Scanner Profile', + metadata: expected_metadata }, + { scan: 'secret_detection', metadata: expected_metadata }, + { scan: 'sast', metadata: expected_metadata }, + { scan: 'sast_iac', metadata: expected_metadata } + ]) + end context 'when value is zero' do let(:action_limit) { 0 } - it { is_expected.to match_array(all_actions) } + it 'contains active actions with metadata' do + expect(actions).to match_array([ + { scan: 'dast', site_profile: 'Site Profile', scanner_profile: 'Scanner Profile', + metadata: expected_metadata }, + { scan: 'secret_detection', metadata: expected_metadata }, + { scan: 'dependency_scanning', metadata: expected_metadata }, + { scan: 'sast', metadata: expected_metadata }, + { scan: 'sast_iac', metadata: expected_metadata }, + { scan: 'container_scanning', metadata: expected_metadata } + ]) + end end end @@ -154,7 +185,12 @@ let(:policy_pipeline_source) { source } it 'returns the active scan execution actions' do - expect(context.active_scan_execution_actions).to match_array(policy[:actions]) + expect(context.active_scan_execution_actions).to match_array([ + { scan: 'dast', site_profile: 'Site Profile', scanner_profile: 'Scanner Profile', + metadata: expected_metadata }, + { scan: 'secret_detection', metadata: expected_metadata }, + { scan: 'dependency_scanning', metadata: expected_metadata } + ]) end end @@ -218,12 +254,28 @@ end describe '#job_injected?' do - it 'stores array of job names' do - context.collect_injected_job_names([:job1, "job-2"]) + it 'returns the collected job names' do + context.collect_injected_job_names_with_metadata({ + job1: { some_key: 'value' }, + 'job-2': { some_other_key: 'other value ' } + }) + + expect(context.job_injected?('job1')).to be(true) + expect(context.job_injected?('job-2')).to be(true) + expect(context.job_injected?('job3')).to be(false) + end + end + + describe '#job_options' do + it 'returns the metadata corresponding to the collected jobs, ignoring other attributes' do + context.collect_injected_job_names_with_metadata({ + job1: { _metadata: { some_key: 'value' }, some_other_key: 'value2' }, + 'job-2': { some_other_key: 'other value' } + }) - expect(context.job_injected?(instance_double(::Ci::Build, name: 'job1'))).to be(true) - expect(context.job_injected?(instance_double(::Ci::Build, name: 'job-2'))).to be(true) - expect(context.job_injected?(instance_double(::Ci::Build, name: 'job3'))).to be(false) + expect(context.job_options('job1')).to eq(some_key: 'value') + expect(context.job_options('job-2')).to be_nil + expect(context.job_options('job3')).to be_nil end end end diff --git a/ee/spec/models/security/scan_execution_policy/config_spec.rb b/ee/spec/models/security/scan_execution_policy/config_spec.rb index 1f354f82faa33148144aaaf598196fd50658ef9e..872862cc525fc37dfa90925d00196a8ec61c76f0 100644 --- a/ee/spec/models/security/scan_execution_policy/config_spec.rb +++ b/ee/spec/models/security/scan_execution_policy/config_spec.rb @@ -3,15 +3,29 @@ require 'spec_helper' RSpec.describe Security::ScanExecutionPolicy::Config, feature_category: :security_policy_management do + let_it_be(:policy_files) { { Security::OrchestrationPolicyConfiguration::POLICY_PATH => '' } } + let_it_be(:security_policy_project) { create(:project, :custom_repo, files: policy_files) } + let_it_be(:security_orchestration_policy_configuration) do + create(:security_orchestration_policy_configuration, security_policy_management_project: security_policy_project) + end + let(:config) { described_class.new(**params) } - let(:params) { { policy: policy } } + let(:params) { { policy: policy, configuration: security_orchestration_policy_configuration } } describe '#actions' do - subject { config.actions } + subject(:actions) { config.actions } let(:policy) { build(:scan_execution_policy, actions: [{ scan: 'secret_detection' }]) } - it { is_expected.to eq([{ scan: 'secret_detection' }]) } + it 'returns actions with configuration metadata' do + expect(actions).to eq([{ + scan: 'secret_detection', + metadata: { + project_id: security_policy_project.id, + sha: security_orchestration_policy_configuration.configuration_sha + } + }]) + end end describe '#skip_ci_allowed?' do diff --git a/ee/spec/services/ci/create_pipeline_service/set_build_sources_spec.rb b/ee/spec/services/ci/create_pipeline_service/set_build_sources_spec.rb index ff51ee133b04028afdbe7ed1541c9b358f1c5dfb..6a61f0d4e35ad752fd62a4f40a54352255d88697 100644 --- a/ee/spec/services/ci/create_pipeline_service/set_build_sources_spec.rb +++ b/ee/spec/services/ci/create_pipeline_service/set_build_sources_spec.rb @@ -143,7 +143,9 @@ pipeline.builds.each do |build| source = Ci::BuildSource.find_by(build_id: build.id, project_id: project.id) - expect(source.source).to eq(expected_sources[build.name] || pipeline.source) + expected = expected_sources[build.name] || pipeline.source + expect(source.source).to eq(expected), + "expected source for build #{build.name} to match #{expected}, but it was #{source.source}" end end end diff --git a/ee/spec/services/security/security_orchestration_policies/ci_action/template_spec.rb b/ee/spec/services/security/security_orchestration_policies/ci_action/template_spec.rb index c8d317a8c962b660fd622caa6dfa7d7451929828..e0ac25fd0bd96046bd0a18949dfb1f6640c9e22e 100644 --- a/ee/spec/services/security/security_orchestration_policies/ci_action/template_spec.rb +++ b/ee/spec/services/security/security_orchestration_policies/ci_action/template_spec.rb @@ -75,6 +75,23 @@ end end + shared_examples 'with metadata added to action' do + let(:action_metadata) do + { + sha: '8dc6668b57dbeec74feb7c68119cf0c28cbe2a29', + project_id: 123 + } + end + + before do + action.merge!(metadata: action_metadata) + end + + it 'adds _metadata section to each job' do + expect(config.values).to all(include(_metadata: action_metadata)) + end + end + describe '.scan_template_path' do let(:scan_type) { 'dependency_scanning' } let(:template) { 'default' } @@ -138,6 +155,7 @@ it_behaves_like 'with template name for scan type' it_behaves_like 'removes rules which disable jobs' it_behaves_like 'with scan_settings.ignore_default_before_after_script set to true' + it_behaves_like 'with metadata added to action' it 'merges template variables with ci variables and returns them as string' do expect(config[:'secret-detection-0']).to include( @@ -190,6 +208,7 @@ it_behaves_like 'with template name for scan type' it_behaves_like 'removes rules which disable jobs' it_behaves_like 'with scan_settings.ignore_default_before_after_script set to true' + it_behaves_like 'with metadata added to action' it 'merges template variables with ci variables and returns them as string' do expect(config[:'container-scanning-0']).to include( diff --git a/ee/spec/services/security/security_orchestration_policies/on_demand_scan_pipeline_configuration_service_spec.rb b/ee/spec/services/security/security_orchestration_policies/on_demand_scan_pipeline_configuration_service_spec.rb index 5627ad9709878741837c0100d7e5ab905ddb4dde..9776d4ce8e2a2df2113c57a292d6eb75aa7c9297 100644 --- a/ee/spec/services/security/security_orchestration_policies/on_demand_scan_pipeline_configuration_service_spec.rb +++ b/ee/spec/services/security/security_orchestration_policies/on_demand_scan_pipeline_configuration_service_spec.rb @@ -168,6 +168,37 @@ end end + context 'when metadata is added to action' do + let(:action_metadata) do + { + sha: '8dc6668b57dbeec74feb7c68119cf0c28cbe2a29', + project_id: 123 + } + end + + let(:actions) do + [ + { + scan: 'dast', + site_profile: site_profile.name, + scanner_profile: scanner_profile.name, + tags: ['runner-tag'], + metadata: action_metadata + }, + { + scan: 'dast', + site_profile: 'Site Profile B', + metadata: action_metadata + } + ] + end + + it 'adds _metadata section to job configuration' do + expect(pipeline_configuration[:'dast-on-demand-0']).to include(_metadata: action_metadata) + expect(pipeline_configuration[:'dast-on-demand-1']).to include(_metadata: action_metadata) + end + end + describe "variable injection and precedence" do let(:actions) do [