Skip to content

Optimize permission checks in CI build policies by removing jailbreak

What does this MR do and why?

This MR optimizes permission checks in CI build policies by simplifying the authorization logic for protected refs and protected environments.

Problem:

The getPipelineDetails GraphQL query was experiencing high CPU time. Profiling revealed that permission calculations for build operations were spending significant time evaluating complex nested conditions, particularly the jailbreak pattern for protected refs.

Solution:

  • Remove the separate ~can?(:jailbreak) & protected_ref policy block from Ci::BuildPolicy
  • Simplify the reporter_has_access_to_protected_environment logic by removing the jailbreak enable/prevent pattern and using it directly in prevention rules

References

Related to #549069

Screenshots or screen recordings

Permission calculation trees

With a pipeline having many jobs.

ActiveRecord::Base.logger = nil
user = User.find(1)
pipeline = Ci::Pipeline.find(569)

Gitlab::SafeRequestStore.ensure_request_store do
  pipeline.builds.each do |build|
    policy = DeclarativePolicy.policy_for(user, build, cache: ::Gitlab::SafeRequestStore.storage)
    policy.runner(:cancel_build).debug
    puts "..."
  end
end

This will print the calculation trees of cancel_build of each build.

Before
...

- [0] prevent when placeholder_user ((@root : Ci::Build/844))
- [0] prevent when import_user ((@root : Ci::Build/844))
+ [0] enable when can?(:developer_access) ((@root : Project/20))
- [0] prevent when builds_disabled ((@root : Project/20))
- [0] prevent when repository_disabled ((@root : Project/20))
- [0] prevent when read_only ((@root : Project/20))
- [0] prevent when all?(ci_cancellation_maintainers_only, ~can?(:maintainer_access)) ((@root : Project/20))
- [0] prevent when ci_cancellation_no_one ((@root : Project/20))
- [0] prevent when ~in_current_organization ((@root : Project/20))
- [0] prevent when placeholder_user ((@root : Project/20))
- [0] prevent when import_user ((@root : Project/20))
- [0] prevent when all?(anonymous, ~public_project) ((@root : Project/20))
- [0] prevent when ~project_allowed_for_job_token ((@root : Project/20))
- [0] prevent when visual_review_bot ((@root : Project/20))
- [0] prevent when user_banned_from_namespace ((@root : Project/20))
- [0] prevent when all?(duo_workflow_token, ~duo_features_enabled) ((@root : Project/20))
- [0] prevent when builds_disabled ((@root : Project/20))
- [0] prevent when repository_disabled ((@root : Project/20))
- [7] prevent when has_outdated_deployment ((@root : Ci::Build/844))
- [7] prevent when all?(ip_enforcement_prevents_access, ~admin, ~auditor) ((@root : Project/20))
- [14] prevent when ~in_current_organization ((@root : Ci::Build/844))
- [40] prevent when all?(~can?(:jailbreak), protected_environment) ((@root : Ci::Build/844))
- [40] prevent when all?(~can?(:jailbreak), protected_ref) ((@root : Ci::Build/844))
After
...
- [0] prevent when placeholder_user ((@root : Ci::Build/841))
- [0] prevent when import_user ((@root : Ci::Build/841))
+ [0] enable when can?(:developer_access) ((@root : Project/20))
- [0] prevent when builds_disabled ((@root : Project/20))
- [0] prevent when repository_disabled ((@root : Project/20))
- [0] prevent when read_only ((@root : Project/20))
- [0] prevent when all?(ci_cancellation_maintainers_only, ~can?(:maintainer_access)) ((@root : Project/20))
- [0] prevent when ci_cancellation_no_one ((@root : Project/20))
- [0] prevent when ~in_current_organization ((@root : Project/20))
- [0] prevent when placeholder_user ((@root : Project/20))
- [0] prevent when import_user ((@root : Project/20))
- [0] prevent when all?(anonymous, ~public_project) ((@root : Project/20))
- [0] prevent when ~project_allowed_for_job_token ((@root : Project/20))
- [0] prevent when visual_review_bot ((@root : Project/20))
- [0] prevent when user_banned_from_namespace ((@root : Project/20))
- [0] prevent when all?(duo_workflow_token, ~duo_features_enabled) ((@root : Project/20))
- [0] prevent when builds_disabled ((@root : Project/20))
- [0] prevent when repository_disabled ((@root : Project/20))
- [7] prevent when has_outdated_deployment ((@root : Ci::Build/841))
- [7] prevent when all?(ip_enforcement_prevents_access, ~admin, ~auditor) ((@root : Project/20))
- [14] prevent when ~in_current_organization ((@root : Ci::Build/841))
- [28] prevent when all?(~reporter_has_access_to_protected_environment, protected_environment) ((@root : Ci::Build/841))
- [14] prevent when all?(~reporter_has_access_to_protected_environment, protected_ref) ((@root : Ci::Build/841))

GraphQL Query

Click to expand
require 'benchmark'

ActiveRecord::Base.logger = nil

query = <<~QUERY
query getPipelineDetails($projectPath: ID!, $iid: ID!) {
  project(fullPath: $projectPath) {
    pipeline(iid: $iid) {
      stages {
        nodes {
          groups {
            nodes {
              jobs {
                nodes {
                  name
                  status: detailedStatus {
                    hasDetails
                    action {
                      buttonTitle
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
QUERY

variables = {
  "projectPath" => "root/gitlab-clone",
  "iid" => 3
}

context = {
  current_user: User.find(1),
  is_sessionless_user: false,
  current_organization: Organizations::Organization.find(1)
}

operation_name = "getPipelineDetails"

n = 50

Benchmark.bm do |benchmark|
  2.times do
    Gitlab::SafeRequestStore.ensure_request_store do
      # warm up
      GitlabSchema.execute(query, variables: variables, context: context, operation_name: operation_name)
    end
  end

  benchmark.report("NEW") do
    n.times do
      Gitlab::SafeRequestStore.ensure_request_store do
        GitlabSchema.execute(query, variables: variables, context: context, operation_name: operation_name)
      end
    end
  end
end
         user     system      total        real
OLD  8.305731   0.041631   8.347362 (  8.375858)
         user     system      total        real
NEW  7.416806   0.070538   7.487344 (  7.546441)

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.

Edited by Furkan Ayhan

Merge request reports

Loading