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.yml lists read_pipeline_bridge next to read_pipeline_job.
  • ProjectPolicy prevents it with read_pipeline_job where read_build is not allowed, so the bypass grants it only where an anonymous user can read the pipeline jobs. Both routes still authorize read_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_bridge beside read_pipeline_job.
  • The generated doc/auth/tokens/fine_grained_access_tokens_rest.md lists 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 defines read_pipeline_bridge there) and the comparison with an anonymous request on both routes. With the role change alone, the six rows and the two refusals that need the read_build prevent fail. All of them pass with both.
  • spec/requests/api/ci/pipelines_spec.rb (591 examples) and ee/spec/policies/project_policy_spec.rb (1780) pass in full. spec/policies/project_policy_spec.rb passes except 15 clusterable policies examples, which fail the same way with the files of master: my local db_key_base is 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.rb and spec/tasks/gitlab/permissions/routes/docs_task_spec.rb pass, and so does bundle exec rake gitlab:permissions:validate.
  • RuboCop on the changed Ruby files, scripts/lint/sort_role_permissions.rb, scripts/lint/commit_linter.rb and 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

  1. Make a project public, keep Public pipelines on, and run a pipeline with a trigger job in it.
  2. 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.
  3. curl --header "PRIVATE-TOKEN: <token>" "http://gdk.test:3000/api/v4/projects/<id>/pipelines/<pipeline_id>/bridges" answers 403 insufficient_granular_scope on master and 200 on 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.

Merge request reports

Loading
Loading