Enforce policy force-push prevention with database policies
What does this MR do and why?
Merge request approval policies can block pushes to protected branches through their approval_settings.prevent_pushing_and_force_pushing property.
This poses a problem during project creation when you select the "Enable Static Application Security Testing", "Enable Secret Detection" or "Initialise repository with a README" options, which commit files to the newly created project's repository: A project's parent group may have a policy declared that sets approval_settings.prevent_pushing_and_force_pushing. Due to an implementation detail, when a project has an empty repository, the service that determines branches affected by a policy returns the empty set. However if the repository isn't empty, the SAST/Secret Detection commits get rejected by the policy push check.
Projects::CreateService however swallows the resulting Gitlab::Git::CommandError silently without surfacing it to the user. These exceptions also cause group policy synchronisation not to occur. As a result, Git exceptions both lead to missing files or file contents, but also to a lack of policy-project links.
The SSoT for security policies is currently a separate security policy project's Git repository, where YAML policies are read from a specific location. Each policies is mirrored to the database on change. Currently, the policy push check reads policy state from the policy project's YAML file. This merge request changes the behaviour of the service underlying the push check so that policies are read from the database. Since the policy-project links are created after the SAST/Secret Detection commits have occured, the SAST/Secret Detection setup commits during project creation are no longer rejected by the push check. Once the project is linked to the project, the behaviour of the push check remains unchanged.
References
- #588855
- #588133
- #588133 (comment 3064329841)
- Related trigger: #588855
Database
-- https://console.postgres.ai/gitlab/gitlab-production-main/sessions/48335/commands/145382
SELECT
"security_policies".*
FROM
"security_policies"
INNER JOIN "security_policy_project_links" ON "security_policies"."id" = "security_policy_project_links"."security_policy_id"
WHERE
"security_policy_project_links"."project_id" = 67057533
AND "security_policies"."type" = 0
AND "security_policies"."enabled" = TRUE
AND (content -> 'approval_settings' ->> 'prevent_pushing_and_force_pushing' = 'true')
AND (content ->> 'enforcement_type' IS DISTINCT FROM 'warn');
How to set up and validate locally
- Enable the feature flag:
echo "Feature.enable(:approval_policy_push_check_database_policies)" | rails c - Create a new group
- Navigate to
Secure > Policiesand create the following Merge request approval policy:
approval_policy:
- name: Test policy
enabled: true
enforcement_type: enforce
rules:
- type: any_merge_request
branches:
- main
- foo*
commits: any
actions:
- type: require_approval
approvals_required: 1
role_approvers:
- maintainer
- type: send_bot_message
enabled: true
approval_settings:
prevent_pushing_and_force_pushing: true
- Create a new contained project and during project creation, tick the following checkboxes:
- Initialize repository with a README
- Enable Static Application Security Testing (SAST)
- Enable Secret Detection
- Verify that the project's repository contains three commits:
- Initial commit
- Configure SAST
- Configure Secret Detection
- Verify that the policy-project links were created:
[3] pry(main)> Project.last.security_policies.count
=> 1
- Navigate to
Code > Branchesand create thefoobarbranch - Navigate to
Settings > Repositoryand protect thefoobarbranch and enable theAllowed to force pushoption - Attempt to force-push to the
foobarbranch and verify the push gets rejected:
remote: GitLab: Force push is blocked by settings overridden by a security policy
To http://gdk.test:3000/foreign-hippo-7189/test1
! [remote rejected] HEAD -> foobar (pre-receive hook declined)
Reproducing the bug
- Disable the feature flag:
echo "Feature.disable(:approval_policy_push_check_database_policies)" | rails c - Create a new contained project and during project creation, tick the following checkboxes:
- Initialize repository with a README
- Enable Static Application Security Testing (SAST)
- Enable Secret Detection
- Verify that the project's repository contains two commits (no Secret Detection):
- Initial commit
- Configure SAST
- Verify that the policy-project links weren't created:
[3] pry(main)> Project.last.security_policies.count
=> 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.
Related to #588133