Grant read_pipeline_bridge through the public access bypass
What does this MR do and why?
GET /projects/:id/pipelines/:pipeline_id/bridges and GET /projects/:id/pipelines/:pipeline_id/trigger_jobs ask a fine-grained personal access token for read_pipeline_bridge. On a public project, the public access bypass of Authz::BoundaryPolicy grants a permission the token's scopes lack when an anonymous user holds it there, but nothing enabled read_pipeline_bridge for one. A token without Pipeline: Read for the project got 403 insufficient_granular_scope on both routes, while an anonymous request and a legacy non-member token get 200.
As !261569 (merged) did for read_pipeline_job:
config/authz/roles/public_anonymous.ymllistsread_pipeline_bridgenext toread_pipeline_job.ProjectPolicyprevents it withread_pipeline_jobwhereread_buildis not allowed, so the bypass grants it only where an anonymous user can read the pipeline jobs. Both routes still authorizeread_build.- The request specs of both routes also run the granular token examples on a public project and pin the refusal with Public pipelines off. The policy tables check
read_pipeline_bridgebesideread_pipeline_job. - The generated
doc/auth/tokens/fine_grained_access_tokens_rest.mdlists both routes among the publicly accessible endpoints.
The issue's two test report routes will follow separately.
What I ran
With Ruby 3.3.11, on this branch (0eac8dfe1bb) on top of master at b3ba07f3c1f:
- The new examples fail with the role and policy of
master: the eight policy rows (no rule definesread_pipeline_bridgethere) and the comparison with an anonymous request on both routes. With the role change alone, the six rows and the two refusals that need theread_buildprevent fail. All of them pass with both. spec/requests/api/ci/pipelines_spec.rb(591 examples) andee/spec/policies/project_policy_spec.rb(1780) pass in full.spec/policies/project_policy_spec.rbpasses except 15clusterable policiesexamples, which fail the same way with the files ofmaster: my localdb_key_baseis too short for the cluster factory (key must be 32 bytes or longer).spec/policies/authz/boundary_policy_spec.rb,ee/spec/lib/authz/role_definitions_spec.rb,spec/lib/authz/role_spec.rbandspec/tasks/gitlab/permissions/routes/docs_task_spec.rbpass, and so doesbundle exec rake gitlab:permissions:validate.- RuboCop on the changed Ruby files,
scripts/lint/sort_role_permissions.rb,scripts/lint/commit_linter.rband markdownlint-cli2 in the docs-lint image report nothing.
References
Related to #633409
I found this while working on gitlab-mcp-server.
Screenshots or screen recordings
Request specs on a public project, with a non-member's fine-grained token that holds no permission for it:
| Request | Before | After |
|---|---|---|
GET /projects/:id/pipelines/:pipeline_id/bridges |
403 |
200 |
GET /projects/:id/pipelines/:pipeline_id/trigger_jobs |
403 |
200 |
| Both, with Public pipelines off | 403 |
403 |
| Both, anonymous | 200 |
200 |
How to set up and validate locally
- Make a project public, keep Public pipelines on, and run a pipeline with a trigger job in it.
- As a user who is not a member, create a fine-grained personal access token for a group that does not contain the project, with Pipeline: Read.
curl --header "PRIVATE-TOKEN: <token>" "http://gdk.test:3000/api/v4/projects/<id>/pipelines/<pipeline_id>/bridges"answers403 insufficient_granular_scopeonmasterand200on this branch, as without the header.
I did not follow these steps in a GDK. The request specs above cover the same path.
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.