Fix OpenBao JWT auth limited to MAX_GROUPS, use CEL
Related: #548398 (closed)
What does this MR do and why?
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
Remove the following:
-
MAX_GROUPSconstant and arbitrary 25-group constraint -
group_policy_templatemethod and positional template generation - Client-side group expansion in
user_auth_policies
Replace with the following:
- CEL role creation via
auth/jwt/cel/roles/:name - Single CEL program for dynamic policy assignment based on actual group membership
-
auth/jwt/cel/loginendpoint usage
References
- Recap generated by Duo: !203172 (comment 2727209048)
- https://openbao.org/docs/rfcs/cel-jwt/#cel-role-format
How to set up and validate locally
- Enable openbao on gdk.
- Enable the
secrets_managerfeature flag. - Create a new project.
- In project's general Settings -> Visibility, project features, permissions,
Enable the Secrets Manager to securely store and manage sensitive information for this project. - Check CE role – see below.
- Validate secrets manager functionality.
- Add secrets.
- Delete secrets.
Shell command to check CE role:
bao read auth/user_1/user_jwt/cel/role/project_32
bao read auth/user_1/user_jwt/cel/role/project_54
Command output
Key Value
--- -----
bound_audiences [http://gdk.test:8200]
cel_program map[expression:pid == "" ? "missing project_id" :
pid != expected_pid ? "token project_id does not match role base" :
aud == "" ? "missing audience" :
aud != expected_aud ? "audience validation failed" :
uid == "" ? "missing user_id" :
pb.Auth{
display_name: who,
alias: logical.Alias { name: who },
policies:
(uid != "" ? [base + "/users/direct/user_" + uid] : []) +
(mrid != "" ? [base + "/users/direct/member_role_" + mrid] : []) +
grps.map(g, base + "/users/direct/group_" + string(g)) +
(rid != "" ? [base + "/users/roles/" + rid] : [])
} variables:[map[expression:"project_54" name:base] map[expression:('user_id' in claims) ? string(claims['user_id']) : "" name:uid] map[expression:('member_role_id' in claims && claims['member_role_id'] != null) ?
string(claims['member_role_id']) : "" name:mrid] map[expression:('role_id' in claims) ? string(claims['role_id']) : "" name:rid] map[expression:"54" name:expected_pid] map[expression:('project_id' in claims) ? string(claims['project_id']) : "" name:pid] map[expression:('groups' in claims) ? claims['groups'] : [] name:grps] map[expression:uid != "" ? "gitlab-user:" + uid : "gitlab-user:anonymous" name:who] map[expression:('aud' in claims) ? claims['aud'] : "" name:aud] map[expression:"http://gdk.test:8200" name:expected_aud]]]
clock_skew_leeway 60
expiration_leeway 150
message n/a
name project_54
not_before_leeway 150
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.
Edited by Fabien Catteau