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.

  1. No Arbitrary Limits: Handles 1 group or 1000 groups with proportional performance
  2. Efficient Processing: One evaluation per actual group, no wasted empty string generation
  3. Order Independent: Processes all groups regardless of JWT array ordering
  4. Minimal Memory: Single program vs. 25 stored templates
  5. 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

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

  1. Introduce a feature flag to guard all changes behind it.
  2. Delete MAX_GROUPS + group policy template
  • Add a new method - user_auth_login_path, "#{user_auth_mount}/cel/login"
  1. Stop expanding groups client-side in user_auth_policies
  2. 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:

  1. Enable the CEL role support in JWT/OIDC
  2. Create a CEL role for user auth, instead auth/jwt/roles/:name, use auth/jwt/cel/roles/:role_name
  3. Drop MAX_GROUPS assumption

Verification steps

OpenBao

  1. CEL role exists (auth/jwt/cel/roles/:name) with correct cel_program, audiences.
  2. Valid JWT → token with expected policies.
  3. Large group sets (>25) expand fully.

Rails

  1. UserHelper: MAX_GROUPS + group_policy_template removed; user_auth_login_path points to /cel/login.
  2. Call sites switched from /login → /cel/login
  3. JWT includes required claims (project_id, user_id, groups, etc.).
  4. Feature flag toggles between old /jwt/login and new CEL path.
  5. Valid claims succeed, >25 groups succeed, invalid claims fail clearly.
Edited by Fabien Catteau