Granular Personal Access Tokens
Granular PATs allow fine-grained access control by requiring specific permissions for each API endpoint, adhering to the principle of least privilege. This documentation is designed for community contributors and GitLab developers who want to make API endpoints compliant with granular PATs.
Background
Granular PATs enable users to create personal access tokens with specific, limited permissions rather than broad access. Each API endpoint is protected by explicit permissions that must be granted to the token. This improves security by ensuring tokens only have access to the resources they explicitly need.
Prerequisites
Before starting, ensure you:
- Have a local GitLab development environment set up
- Are familiar with GitLab API structure (
lib/api/) - Understand Ruby on Rails and RSpec testing
- Have read the Permission Naming Conventions documentation
Step-by-Step Implementation Guide
Step 1: Identify API Endpoints for the Resource
Goal: Find all REST API endpoints for the resource you’re working on.
- Go to the API file for your resource:
lib/api/<resource_name>.rbExample: For jobs, open lib/api/jobs.rb
- Review all endpoints defined in the file. Look for HTTP methods and routes:
get ':id/jobs'
get ':id/jobs/:job_id'
post ':id/jobs/:job_id/cancel'
post ':id/jobs/:job_id/retry'
delete ':id/jobs/:job_id/artifacts'Step 2: Determine Permissions Needed
Goal: Define granular permissions following GitLab naming conventions.
Permission Granularity Guidelines
- Use specific action verbs:
create,read,update,delete,push,download - Do not use broad verbs like:
admin_*,manage_*,access_* - Be as granular as possible while remaining practical
Permission Naming Pattern
Follow the pattern: action_resource(_subresource)
Use singular instead of plural for the resource and subresource. Example: read_job (instead of read_jobs regardless of which boundary it applies to) and create_pipeline_schedule_variable.
Examples:
| Endpoint | HTTP Method | Permission Name |
|---|---|---|
GET /projects/:id/jobs | GET | read_job |
GET /projects/:id/jobs/:job_id | GET | read_job |
POST /projects/:id/jobs/:job_id/cancel | POST | cancel_job |
POST /projects/:id/jobs/:job_id/retry | POST | retry_job |
DELETE /projects/:id/jobs/:job_id/artifacts | DELETE | delete_job_artifact |
POST /projects/:id/pipeline_schedules/:schedule_id/variables | POST | create_pipeline_schedule_variable |
DELETE /projects/:id/pipeline_schedules/:schedule_id/variables/:key | DELETE | delete_pipeline_schedule_variable |
Common Patterns
List and Show operations: Use a single
read_resourcepermission for bothGET /projects/:id/jobs→read_jobGET /projects/:id/jobs/:job_id→read_job
Nested resources: Include the parent resource in the permission name
POST /projects/:id/pipeline_schedules/:id/variables→create_pipeline_schedule_variable
Special actions: Create specific permissions for unique operations
- Cancel, retry, download, trigger, etc. each get their own permission
Attribute updates: Do not create separate permissions for individual attributes
update_issuecovers updating title, description, assignees, etc.- Do not create
update_issue_description,update_issue_title
Step 3: Generate Permission Definition Files
Goal: Create YAML definition files for each new permission.
Run the Generator
For each permission identified in Step 2, run:
bundle exec rails generate authz:permission <permission_name>Example:
bundle exec rails generate authz:permission read_jobFor Multi-Word Actions
If the action contains multiple words, use the --action flag:
bundle exec rails generate authz:permission force_delete_ai_catalog_item --action force_deleteGenerated File Location
Files are created in: config/authz/permissions/<resource>/<action>.yml
Example:
read_job→config/authz/permissions/job/read.yml
Complete the YAML Definition
The generator creates a template. Fill in all required fields:
---
name: read_job
description: Grants the ability to read CI/CD jobs
feature_category: continuous_integration
available_for_tokens: true
boundaries:
- group
- projectRequired Fields:
| Field | Description | How to Find |
|---|---|---|
name | Permission name (auto-populated) | Matches the permission name |
description | Human-readable description of what the permission allows | Describe the capability granted |
feature_category | GitLab feature category | Found in lib/api/<resource>.rb - search for feature_category |
available_for_tokens | Indicates if this permission can be assigned to a granular token | Set to true for granular PAT permissions |
boundaries | List of organizational levels where the permission applies | Determined by the route pattern - project for /projects/:id/..., group for /groups/:id/..., user for /users/:id/..., instance for no prefix |
Step 4: Define Permission Groups (Optional)
Goal: Group related permissions together for easier token configuration.
Permission groups allow you to bundle multiple related permissions under a single logical group. This is useful when you want to maintain granularity at the API endpoint level while providing a simpler, more user-friendly experience in the UI. Instead of presenting users with many individual permissions, you can offer broader permission groups that grant multiple related capabilities at once.
You can define permission groups in config/authz/permission_groups/<resource>/<action>.yml. The following example shows a YAML file for the config/authz/permission_groups/job/run.yml permission group:
---
name: run_job
description: Grants the ability to run jobs
permissions:
- play_job
- retry_job
available_for_tokens: true
boundaries:
- group
- projectEach permission included in the group should exist as an individual permission, with a false value for the available_for_tokens key.
Step 5: Add Authorization Decorators to API Endpoints
For each endpoint, add the route_setting :authorization decorator immediately before the route definition:
route_setting :authorization, permissions: :read_job, boundary_type: :project
get ':id/jobs' do
# endpoint implementation
endImportant Notes:
- Add the decorator to every endpoint individually, even if multiple endpoints use the same permission
- The decorator goes immediately before the HTTP method definition (
get,post,put,delete) - Use the exact permission name (symbol) defined in your YAML files
Step 6: Add Test Coverage
Test files are usually located at spec/requests/api/<resource>_spec.rb. If you don’t find them there, you may need to look around a bit more for the relevant spec files.
Add Shared Examples for Each Endpoint
For each endpoint, add the 'authorizing granular token permissions' shared example:
it_behaves_like 'authorizing granular token permissions', :<permission_name> do
let(:boundary_object) { <boundary_object> }
let(:user) { <user> }
let(:request) do
<http_method> api("<endpoint_path>", personal_access_token: pat), params: <params_if_needed>
end
endBoundary Object Mapping
The boundary_object must match the boundary_type:
| Boundary Type | Boundary Object |
|---|---|
:project | project |
:group | group |
:user | user |
:instance | nil or not specified |
Step 7: Create Merge Request
Submit your changes for review following GitLab contribution process.
MR Scope
- Complete one resource per MR (Example - all Jobs endpoints in one MR)
- Do not mix multiple resources in a single MR
MR Description Template
Include the following in your MR description:
## Summary
This MR enables granular Personal Access Token (PAT) permissions for [Resource Name] REST API endpoints.
## Endpoints Modified
- `GET /projects/:id/jobs` - `read_job`
- `GET /projects/:id/jobs/:job_id` - `read_job`
- `POST /projects/:id/jobs/:job_id/cancel` - `cancel_job`
- `POST /projects/:id/jobs/:job_id/retry` - `retry_job`
- `DELETE /projects/:id/jobs/:job_id/artifacts` - `delete_job_artifact`
## Permissions Created
- `read_job` - Grants the ability to read CI/CD jobs
- `cancel_job` - Grants the ability to cancel running or pending jobs
- `retry_job` - Grants the ability to retry failed jobs
- `delete_job_artifact` - Grants the ability to delete job artifacts
## Testing
- Added authorization tests using shared examples for all endpoints
## Related Issues
Closes #[issue_number]Checklist Before Submitting
- All permission YAML files are complete with description and feature_category
- Authorization decorators added to all endpoints
- Test coverage added for all endpoints using shared examples
- Tests pass locally:
bundle exec rspec spec/requests/api/<resource>_spec.rb - MR description lists all modified endpoints
- MR is linked to the related issue
Reviewers
Tag the authorization team @GitLab-org/software-supply-chain-security/authorization/approvers for review.
Edge Cases and Special Considerations
Endpoints Requiring Multiple Permissions
Status: TBD (To Be Determined)
This scenario is still under discussion by the Authorization team. If you encounter an endpoint that logically requires multiple permissions (Example: read project and read jobs), you should:
- Document the use case in your MR description
- Tag the Authorization team for guidance
- Wait for team input before proceeding