Draft: Add pipeline execution policy bot access settings for CI config projects
What does this MR do and why?
This MR allows CI config project owners to grant security policy bots access to specific CI/CD configuration files. This enables scheduled pipeline execution policies to reference CI configs stored outside the security policy project.
Problem
Currently, security policy bots can only access CI/CD configuration files stored in the security policy project itself. When a scheduled pipeline execution policy references a CI config from a different project, the bot doesn't have access and the pipeline fails.
Solution
Add new project settings that allow CI config project owners to explicitly grant access to security policy bots:
pipeline_execution_policy_bot_access_enabled: Toggle to enable bot accesspipeline_execution_policy_bot_access_file_patterns: Glob patterns specifying which files can be accessed (e.g.,policy-ci.yml,security/**/*.yml)pipeline_execution_policy_bot_access_group_id: Restrict access to bots from projects within a specific group (defaults to the CI config project's root group)
Security Model
- CI config project owner controls access - They explicitly opt-in and decide which files are exposed
- File-level granularity - Only specific files matching the patterns, not the entire repository
- Group restriction - Only bots from projects within the allowed group hierarchy can access
- Fail closed - Ambiguous cases (bot with no project, multiple projects, etc.) deny access
Edge Cases Handled
| Edge Case | Behavior |
|---|---|
| Bot has no project membership | Deny access |
| Bot has multiple project memberships | Deny access |
| CI config project in personal namespace | Deny access (no default group) |
| Allowed group deleted | Fall back to project's root group |
| Empty file patterns with feature enabled | Deny access |
Path with .. or leading / |
Deny access |
Here's a step-by-step guide to test this locally using the Rails console:
Local testing steps
1. Run migrations
bundle exec rails db:migrate2. Set up test data
Open the Rails console:
bundle exec rails consoleCreate the test setup:
# Create a group and subgroup
group = Group.find_or_create_by!(name: 'test-group', path: 'test-group')
subgroup = Group.find_or_create_by!(name: 'test-subgroup', path: 'test-subgroup', parent: group)
# Create the CI config project (where the CI config files live)
ci_config_project = Project.find_by_full_path('test-group/ci-config-project') ||
Projects::CreateService.new(User.first, { name: 'ci-config-project', namespace: group }).execute
# Create a target project (where the policy is enforced)
target_project = Project.find_by_full_path('test-group/test-subgroup/target-project') ||
Projects::CreateService.new(User.first, { name: 'target-project', namespace: subgroup }).execute
# Create a security policy bot for the target project
bot = User.create!(
name: 'Security Policy Bot',
username: "gitlab_security_policy_project_#{target_project.id}_bot",
email: "security_bot_#{target_project.id}@noreply.gitlab.com",
user_type: :security_policy_bot,
skip_confirmation: true,
projects_limit: 0
)
# Add bot as member of target project
target_project.add_guest(bot)3. Configure CI config project to allow bot access
# Enable bot access with file patterns
ci_config_project.project_setting.update!(
pipeline_execution_policy_bot_access_enabled: true,
pipeline_execution_policy_bot_access_file_patterns: ['policy-ci.yml', 'security/**/*.yml']
)4. Test the access check
# This should return true
ci_config_project.project_setting.allows_pipeline_execution_policy_bot_access?(
file_path: 'policy-ci.yml',
bot_project: target_project
)
# This should return true (glob pattern)
ci_config_project.project_setting.allows_pipeline_execution_policy_bot_access?(
file_path: 'security/scans/sast.yml',
bot_project: target_project
)
# This should return false (file not in patterns)
ci_config_project.project_setting.allows_pipeline_execution_policy_bot_access?(
file_path: 'other-file.yml',
bot_project: target_project
)
# This should return false (path traversal)
ci_config_project.project_setting.allows_pipeline_execution_policy_bot_access?(
file_path: '../secret.yml',
bot_project: target_project
)5. Test group restriction
# Create a project outside the allowed group
other_group = Group.find_or_create_by!(name: 'other-group', path: 'other-group')
other_project = Project.find_by_full_path('other-group/other-project') ||
Projects::CreateService.new(User.first, { name: 'other-project', namespace: other_group }).execute
# Create bot for other project
other_bot = User.create!(
name: 'Other Security Policy Bot',
username: "gitlab_security_policy_project_#{other_project.id}_bot",
email: "security_bot_#{other_project.id}@noreply.gitlab.com",
user_type: :security_policy_bot,
skip_confirmation: true,
projects_limit: 0
)
other_project.add_guest(other_bot)
# This should return false (bot's project not in allowed group)
ci_config_project.project_setting.allows_pipeline_execution_policy_bot_access?(
file_path: 'policy-ci.yml',
bot_project: other_project
)6. Test custom group restriction
# Restrict to subgroup only
ci_config_project.project_setting.update!(
pipeline_execution_policy_bot_access_group_id: subgroup.id
)
# This should still return true (target_project is in subgroup)
ci_config_project.project_setting.allows_pipeline_execution_policy_bot_access?(
file_path: 'policy-ci.yml',
bot_project: target_project
)
# Create project directly in parent group
parent_group_project = Project.find_by_full_path('test-group/parent-project') ||
Projects::CreateService.new(User.first, { name: 'parent-project', namespace: group }).execute
parent_bot = User.create!(
name: 'Parent Security Policy Bot',
username: "gitlab_security_policy_project_#{parent_group_project.id}_bot",
email: "security_bot_#{parent_group_project.id}@noreply.gitlab.com",
user_type: :security_policy_bot,
skip_confirmation: true,
projects_limit: 0
)
parent_group_project.add_guest(parent_bot)
# This should return false (parent_group_project is not in subgroup)
ci_config_project.project_setting.allows_pipeline_execution_policy_bot_access?(
file_path: 'policy-ci.yml',
bot_project: parent_group_project
)MR acceptance checklist
Evaluate this MR against the MR acceptance checklist.