Make `fallback_behavior` support async pipeline creation
What does this MR do and why?
Fixes fail-open approval policy rules getting prematurely unblocked during MR creation when the async_mr_pipeline_creation feature flag is enabled.
fallback_behavior: { fail: open } controls what happens when scan reports are unavailable. If a pipeline is created but lacks the required security reports (e.g., scanner misconfiguration or pipeline failure), the approval rule is unblocked. If no pipeline is created at all, the rule is also unblocked. If the reports are available, the rule is evaluated normally and requires approval when violations are found. The default fail: closed keeps the rule enforced in all cases where reports are missing.
When pipeline creation is async, schedule_policy_synchronization runs before CreatePipelineWorker has created the pipeline. It sees diff_head_pipeline as nil, incorrectly concludes no pipeline will ever exist, and enqueues UnenforceablePolicyRulesNotificationWorker. This worker deletes the running violation and marks the fail_open rule as passed, making the MR mergeable while the pipeline is still being created and running.
With this change, when an async pipeline creation is in flight, the premature unblock is skipped.
References
- Related to #578352 (closed)
Code references
AfterCreateService#executecallsprepare_for_mergeabilitythenprepare_merge_requestprepare_for_mergeabilityenqueuesCreatePipelineWorkerwhen asyncprepare_merge_requestcallsschedule_policy_synchronization(runs after, in the same worker)
The premature unblock:
schedule_policy_synchronizationchecksdiff_head_pipeline(nil), takes no-pipeline branch, enqueuesUnenforceablePolicyRulesNotificationWorkerUnenforceablePolicyRulesNotificationService#update_for_report_typecallsunblock_fail_open_rules
How to set up and validate locally
Setup
-
Create a new project
-
Add a second
Ownermember to the project -
Commit a
.gitlab-ci.ymlto the default branch:variables: AST_ENABLE_MR_PIPELINES: "true" CS_IMAGE: "nginx:1" test: image: busybox # For checking violation state while pipeline is running script: sleep 120 rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" include: - template: Jobs/Container-Scanning.gitlab-ci.yml rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" -
Navigate to
Secure > Policiesand create the following approval policy:type: approval_policy name: Container Scanning Fail Open enabled: true fallback_behavior: fail: open rules: - type: scan_finding scanners: - container_scanning vulnerabilities_allowed: 0 severity_levels: [] vulnerability_states: [] branch_type: protected actions: - type: require_approval approvals_required: 1 role_approvers: - owner -
Enable the feature flag in Rails console:
Feature.enable(:async_mr_pipeline_creation)
Reproduce the bug on master
-
Open a new MR (add any file change)
-
Immediately check the violation status in Rails console and verify it was prematurely set to
warn(mergeable):mr = MergeRequest.last mr.scan_result_policy_violations.pluck(:id, :status) # => [[<id>, "warn"]] -
Note the MR can be merged while the pipeline is still running
Verify the fix
-
Repeat steps 7-8 on this branch
-
The violation should remain
running:mr = MergeRequest.last mr.scan_result_policy_violations.pluck(:id, :status) # => [[<id>, "running"]] -
The MR should NOT be mergeable while the pipeline is running
-
After the pipeline completes and container scanning reports findings, the rule should require approval (violation transitions to
failed) -
After the pipeline completes without findings, the rule should be unblocked (violation deleted,
approvals_requiredset to 0)
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.