Scheduled DAST execution policy scans can fail silently due to insufficient permissions
In this Security MR in GitLab 16.3, we changed the way in which scan execution policies run. This changes the 'user' to a security project bot which now runs scheduled jobs under their own permissions.
A customer has shared with Support that they've noticed scheduled pipelines not running. Through investigation we've narrowed this down to being DAST jobs specifically, where other scan execution policies run without issue.
The problem seems to arise because a DAST scan invokes a check to see if the user is permitted to do-so. We can see that for security bot users, this is not the case:
irb(main):145:0> p = Project.find_by(id: 32)
=> #<Project id:32 sep-2/project>>
# Project Owner
irb(main):146:0> u = User.find_by(id: 1)
=> #<User id:1 @root>
irb(main):147:0> Ability.allowed?(u, :create_on_demand_dast_scan, p)
=> true
# Security Bot
# This returns the Security Policy Bot for Project ID 32.
irb(main):148:0> u = User.find_by(id: 33)
=> #<User id:33 @gitlab_security_policy_project_32_bot_bd5beb4dab90d1013dd19a120758d902>
irb(main):149:0> Ability.allowed?(u, :create_on_demand_dast_scan, p)
=> false
We can see that create_on_demand_dast_scan is defined in ProjectPolicy, and running a check against this shows:
irb(main):151:0> policy = ProjectPolicy.new(u,p)
=> #<ProjectPolicy (@gitlab_security_policy_project_32_bot_bd5beb4dab90d1013dd19a120758d902 : Project/32)>
irb(main):152:0> policy.debug(:create_on_demand_dast_scan)
- [0] prevent when all?(anonymous, ~public_project) ((@gitlab_security_policy_project_32_bot_bd5beb4dab90d1013dd19a120758d902 : Project/32))
- [7] prevent when all?(ip_enforcement_prevents_access, ~admin, ~auditor) ((@gitlab_security_policy_project_32_bot_bd5beb4dab90d1013dd19a120758d902 : Project/32))
- [14] prevent when user_banned_from_namespace ((@gitlab_security_policy_project_32_bot_bd5beb4dab90d1013dd19a120758d902 : Project/32))
- [28] prevent when all?(~public_project, ~internal_access, ~project_allowed_for_job_token) ((@gitlab_security_policy_project_32_bot_bd5beb4dab90d1013dd19a120758d902 : Project/32))
- [96] enable when all?(on_demand_scans_enabled, can?(:developer_access)) ((@gitlab_security_policy_project_32_bot_bd5beb4dab90d1013dd19a120758d902 : Project/32))
=>
#<DeclarativePolicy::Runner::State:0x00007f9565666c80
@called_conditions=
#<Set:
{"/dp/condition/DeclarativePolicy::Base/anonymous/User:33",
"/dp/condition/BasePolicy/admin/User:33",
"/dp/condition/BasePolicy/auditor/User:33",
"/dp/condition/ProjectPolicy/ip_enforcement_prevents_access/Project:32",
"/dp/condition/ProjectPolicy/user_banned_from_namespace/User:33,Project:32",
"/dp/condition/ProjectPolicy/public_project/Project:32",
"/dp/condition/ProjectPolicy/internal_access/User:33,Project:32",
"/dp/condition/ProjectPolicy/project_allowed_for_job_token/User:33,Project:32",
"/dp/condition/ProjectPolicy/on_demand_scans_enabled/Project:32",
"/dp/condition/ProjectPolicy/developer/User:33,Project:32",
"/dp/condition/ProjectPolicy/needs_new_sso_session/Project:32",
"/dp/condition/BasePolicy/visual_review_bot/User:33",
"/dp/condition/BasePolicy/security_bot/User:33",
"/dp/condition/BasePolicy/alert_bot/User:33",
"/dp/condition/BasePolicy/support_bot/User:33",
"/dp/condition/ProjectPolicy/owner/User:33,Project:32"}>,
@enabled=false,
@prevented=true>
This is preventing the bot user from having permissions. For context:
### For bot user
irb(main):159:0> policy.debug(:create_on_demand_dast_scan)
- [0] prevent when all?(anonymous, ~public_project) ((@gitlab_security_policy_project_32_bot_bd5beb4dab90d1013dd19a120758d902 : Project/32))
- [7] prevent when all?(ip_enforcement_prevents_access, ~admin, ~auditor) ((@gitlab_security_policy_project_32_bot_bd5beb4dab90d1013dd19a120758d902 : Project/32))
- [14] prevent when user_banned_from_namespace ((@gitlab_security_policy_project_32_bot_bd5beb4dab90d1013dd19a120758d902 : Project/32))
- [28] prevent when all?(~public_project, ~internal_access, ~project_allowed_for_job_token) ((@gitlab_security_policy_project_32_bot_bd5beb4dab90d1013dd19a120758d902 : Project/32))
- [96] enable when all?(on_demand_scans_enabled, can?(:developer_access)) ((@gitlab_security_policy_project_32_bot_bd5beb4dab90d1013dd19a120758d902 : Project/32))
### For normal user (technically Root)
=> #<ProjectPolicy (@root : Project/32)>
irb(main):162:0> policy.debug(:create_on_demand_dast_scan)
- [0] prevent when all?(anonymous, ~public_project) ((@root : Project/32))
- [7] prevent when all?(ip_enforcement_prevents_access, ~admin, ~auditor) ((@root : Project/32))
- [14] prevent when user_banned_from_namespace ((@root : Project/32))
- [28] prevent when all?(~public_project, ~internal_access, ~project_allowed_for_job_token) ((@root : Project/32))
+ [132] enable when all?(on_demand_scans_enabled, can?(:developer_access)) ((@root : Project/32))
From a user perspective, the only way we know that the scheduled job has failed is in Sidekiq logs. Here's a sanitized example from the customer ticket:
{
"component": "gitlab",
"subcomponent": "application_json",
"severity": "WARN",
"time": "2023-11-20T22:00:07.776Z",
"meta.caller_id": "Security::ScanExecutionPolicies::RuleScheduleWorker",
"correlation_id": "9bc3245b48b1021256ca908f991e826e",
"meta.root_caller_id": "Cronjob",
"meta.feature_category": "security_policy_management",
"meta.user": "john_smith",
"meta.user_id": 123,
"meta.project": "john_smith/project",
"meta.root_namespace": "john_smith",
"meta.client_id": "user/123",
"class": "Security::SecurityOrchestrationPolicies::RuleScheduleService",
"security_orchestration_policy_configuration_id": 68,
"user_id": 9999,
"message": "Insufficient permissions"
}
In this, we can see that the RuleSchedulerWorker tried to run, but failed promptly with Insufficient permissions
. This is because the user.id
is tied to the security bot which isn't approved in the project policy.
Implementation plan
This can be fixed by allowing security policy bots to create_on_demand_dast_scan
. I'm not sure if it needs the permission to read
and edit
diff --git a/ee/app/policies/ee/project_policy.rb b/ee/app/policies/ee/project_policy.rb
index 63c952b7a6df..2a3a8890fb5b 100644
--- a/ee/app/policies/ee/project_policy.rb
+++ b/ee/app/policies/ee/project_policy.rb
@@ -435,7 +435,7 @@ module ProjectPolicy
enable :create_coverage_fuzzing_corpus
end
- rule { on_demand_scans_enabled & can?(:developer_access) }.policy do
+ rule { on_demand_scans_enabled & (can?(:developer_access) | security_policy_bot) }.policy do
enable :read_on_demand_dast_scan
enable :create_on_demand_dast_scan
enable :edit_on_demand_dast_scan
Verification steps
- Create new project
- Create new Scan Execution Policy (Secure -> Policies -> New Policy -> Scan Execution Policy) with the following content. You might need to create a
site_profile
and ascanner_profile
as well.
type: scan_execution_policy
name: Scheduled DAST
description: ''
enabled: true
rules:
- type: schedule
cadence: "*/5 * * * *"
branches:
- '*'
actions:
- scan: dast
site_profile: Test GraphQL
scanner_profile: Test GraphQL
- Save the policy and wait up to 15 min for the scheduled scan.
- Scan should be triggered by the security project bot.
- Disable or delete the policy to prevent a new pipeline from being triggered every 15 minutes.