Fix OpenBao JWT auth limited to MAX_GROUPS, use CEL
Why are we doing this work
Replace MAX_GROUPS and group_policy_template with a dynamic Common Expression Language (CEL) expression.
- No Arbitrary Limits: Handles 1 group or 1000 groups with proportional performance
- Efficient Processing: One evaluation per actual group, no wasted empty string generation
- Order Independent: Processes all groups regardless of JWT array ordering
- Minimal Memory: Single program vs. 25 stored templates
- Server-Side Logic: Moves complexity from Rails template expansion to OpenBao CEL evaluation
openbao-internal needs to be updated to support JWT CEL.
See https://openbao.org/docs/rfcs/cel-jwt/
Relevant links
Non-functional requirements
-
Documentation: -
Feature flag: [FF] rollout `secrets_manager_jwt_cel` (#566793 - closed) -
Testing: See verification steps below. 👇
Implementation plan
Replace our hard cap MAX_GROUPS=25 with OpenBao’s new JWT CEL–based validation so we can enforce richer claim rules and assign policies dynamically during OIDC/JWT login.
What to change:
Rails part
- Introduce a feature flag to guard all changes behind it.
- Delete MAX_GROUPS + group policy template
- Add a new method -
user_auth_login_path, "#{user_auth_mount}/cel/login"
- Stop expanding groups client-side in user_auth_policies
- Switch login to CEL endpoint in https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/app/services/secrets_management/project_secrets_managers/provision_service.rb?ref_type=heads
- Use auth/jwt/cel/login with role=:name instead of the plain jwt logi
- Don’t call update_jwt_role for user auth
- call the CEL equivalent API in OpenBao
Openbao internal repo part:
- Enable the CEL role support in JWT/OIDC
- Create a CEL role for user auth, instead
auth/jwt/roles/:name, useauth/jwt/cel/roles/:role_name - Drop MAX_GROUPS assumption
Verification steps
OpenBao
- CEL role exists (auth/jwt/cel/roles/:name) with correct cel_program, audiences.
- Valid JWT → token with expected policies.
- Large group sets (>25) expand fully.
Rails
- UserHelper: MAX_GROUPS + group_policy_template removed; user_auth_login_path points to /cel/login.
- Call sites switched from /login → /cel/login
- JWT includes required claims (project_id, user_id, groups, etc.).
- Feature flag toggles between old /jwt/login and new CEL path.
- Valid claims succeed, >25 groups succeed, invalid claims fail clearly.
Edited by Fabien Catteau