diff --git a/doc/user/application_security/policies/scan-execution-policies.md b/doc/user/application_security/policies/scan-execution-policies.md index 7865a884397a1ce8711c7ce4f0c9efc318eea679..b1ca644a66558b32b9b4d90173a7a30cc45bb4c1 100644 --- a/doc/user/application_security/policies/scan-execution-policies.md +++ b/doc/user/application_security/policies/scan-execution-policies.md @@ -475,11 +475,12 @@ Note the following: - `custom` scans are being executed for triggered rules only. - Jobs variables from `custom` scans take precedence over the project's CI/CD configuration. - Users triggering a pipeline must have at least read access to CI files specified in the `ci_configuration_path` or included in the CI/CD configuration. -- It is not possible to define custom stages using the `stages` keyword in a custom scan action. Instead three default stages will be added to the pipeline: +- It is not possible to define custom stages using the `stages` keyword in a custom scan action. Instead three reserved stages will be added to the pipeline: - `.pipeline-policy-pre`at the beginning of the pipeline, before the `.pre` stage. - `.pipeline-policy-test` after the `test` stage. If the `test` stage does not exist, it will be injected after the `build` stage. If the `build` stage does not exist, it will be injected at the beginning of the pipeline after the `.pre` stage. - `.pipeline-policy-post` at the very end of the pipeline, after the .post stage. - Jobs without a stage are assigned to the `.pipeline-policy-test` stage by default. +- It is not possible to assign jobs to reserved stages outside of a custom scan action. #### Example security policies project 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 9dd9fb8c04c925dc13655b7882306f32bac1e8bd..852fbc3b9d78bc5298ee8db5e35c2d7c10119f21 100644 --- a/ee/lib/gitlab/ci/config/security_orchestration_policies/processor.rb +++ b/ee/lib/gitlab/ci/config/security_orchestration_policies/processor.rb @@ -13,6 +13,7 @@ class Processor DEFAULT_POLICY_PRE_STAGE = '.pipeline-policy-pre' DEFAULT_POLICY_TEST_STAGE = '.pipeline-policy-test' DEFAULT_POLICY_POST_STAGE = '.pipeline-policy-post' + RESERVED_STAGES = [DEFAULT_POLICY_PRE_STAGE, DEFAULT_POLICY_TEST_STAGE, DEFAULT_POLICY_POST_STAGE].freeze DEFAULT_STAGES = Gitlab::Ci::Config::Entry::Stages.default def initialize(config, context, ref, source) @@ -34,6 +35,8 @@ def perform merged_config = @config.deep_merge(merged_security_policy_config) if custom_scan_actions_enabled? && active_scan_custom_actions.any? + merged_config = clean_up_reserved_stages_jobs(merged_config) + merged_config = merged_config.deep_merge(scan_custom_actions[:pipeline_scan]) merged_config[:stages] = insert_custom_scan_stages(merged_config[:stages]) @@ -137,6 +140,14 @@ def merge_pipeline_scan_template(merged_config, defined_stages) end end + def clean_up_reserved_stages_jobs(config) + jobs_to_reject = config.except(*Config::Entry::Root.reserved_nodes_names).select do |_, content| + RESERVED_STAGES.include?(content[:stage]) + end.keys + + config.except(*jobs_to_reject) + end + def insert_custom_scan_stages(config_stages) config_stages.append(DEFAULT_POLICY_POST_STAGE) 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 163ab8d91405f4d6c9ec18f4ebe97f432bc832b1..f62e6a90fc118df6bdeed538dc14033d871db1a2 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 @@ -391,6 +391,30 @@ it 'does not includes the custom job' do expect(perform_service[:custom_job]).to be_nil end + + context 'and project has jobs in reserved stages' do + let(:config) do + { + stages: %w[.pipeline-policy-test], + reserved_stage_test_job: { + stage: '.pipeline-policy-test', + script: [ + 'echo "Hello World"' + ] + } + } + end + + it 'does not remove the reserved stages and jobs', :aggregate_failures do + expect(perform_service[:reserved_stage_test_job]).to eq( + { + script: ['echo "Hello World"'], + stage: ".pipeline-policy-test" + } + ) + expect(perform_service[:stages]).to include('.pipeline-policy-test') + end + end end it 'does not include the custom job' do @@ -411,6 +435,24 @@ ) end + context 'and project has jobs in reserved stages' do + let(:config) do + { + stages: %w[.pipeline-policy-test], + test_job: { + stage: '.pipeline-policy-test', + script: [ + 'echo "Hello World"' + ] + } + } + end + + it 'removes project jobs in reserved stages' do + expect(perform_service.key?(:test_job)).to eq(false) + end + end + context 'when test stage does not exist' do let(:config) { { stages: %w[build deploy] } }