security/tests: add GBAC group claim edge case and nested group tests#29747
security/tests: add GBAC group claim edge case and nested group tests#29747nguyen-andrew wants to merge 1 commit intoredpanda-data:devfrom
Conversation
Unit tests cover group claim parsing edge cases (object-type claims, semicolon-delimited strings, empty CSV entries, unicode, special characters, duplicates, control chars) and authorizer group name matching (case sensitivity, whitespace). A new ducktape test verifies that nested_group_behavior=none requires the full Keycloak group path to match the ACL principal exactly.
There was a problem hiding this comment.
Pull request overview
Adds automated coverage for GBAC (Group-Based Access Control) edge cases that were previously manually validated, focusing on OIDC group-claim parsing, group name matching behavior, and nested group path handling.
Changes:
- Add a new end-to-end test verifying
nested_group_behavior=nonerequires the full group path to match ACL principals. - Add a Keycloak admin helper for adding a service account user to a group by group-id (supports nested-group setup).
- Expand C++ unit tests for OIDC group-claim parsing edge cases and authorizer group principal matching behavior.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
tests/rptest/tests/redpanda_oauth_test.py |
Adds ACL propagation helper and an E2E test for nested group behavior when full paths are required. |
tests/rptest/services/keycloak.py |
Adds a Keycloak admin utility to add the service account user to a group via group-id. |
src/v/security/tests/oidc_principal_mapping_test.cc |
Adds unit tests covering additional group claim parsing edge cases and nested suffix collision behavior. |
src/v/security/tests/authorizer_test.cc |
Adds tests validating case-sensitive group matching and whitespace characters in group names. |
CI test resultstest results on build#81359
|
| "groups": "admin,,users" | ||
| })"); | ||
| BOOST_REQUIRE(jwt.has_value()); | ||
|
|
||
| json::Pointer group_pointer("/groups"); | ||
| auto result = security::oidc::detail::get_group_claim( | ||
| group_pointer, jwt.assume_value()); | ||
|
|
||
| BOOST_REQUIRE(result.has_value()); | ||
| auto groups = std::move(result).value(); | ||
| // absl::StrSplit keeps empty entries — "admin,,users" produces | ||
| // ["admin", "", "users"]. Empty group names are not filtered. | ||
| BOOST_REQUIRE_EQUAL(groups.size(), 3); | ||
| BOOST_CHECK_EQUAL(groups[0], "admin"); | ||
| BOOST_CHECK_EQUAL(groups[1], ""); | ||
| BOOST_CHECK_EQUAL(groups[2], "users"); |
There was a problem hiding this comment.
What does "Ignored or denied (documented behavior)" from the test wiki mean here? From what I can tell it is not ignored / denied here, no?
There was a problem hiding this comment.
Right, this is another good point of discussion. The current implementation does not filter out groups (e.g. empty groups or duplicate groups, as mentioned in the other comment thread) - it's produces a list of groups that is 1-1 to the groups that we receive in the token. Generally, I think that's simpler both in terms of implementation and operations (e.g. if a user is having issues with GBAC, they can call the ResolveOidcIdentity/whoami endpoint and see their list of groups and know that it corresponds 1-1 to the groups that the IdP gave them in their bearer token).
In terms of empty groups in the authz path, I believe they'll be ignored/no-ops since we cannot create ACLs with empty names.
What do ya'll think about keeping this behavior and updating the wiki to reflect? @cjayani
There was a problem hiding this comment.
@nguyen-andrew Yeah makes sense, some of the expected results on the tests wiki were assumptions we made due to unavailability of the required implementation details at the time. Let's update the tests wiki to reflect the actual behavior
| // Implementation does not deduplicate | ||
| BOOST_REQUIRE_EQUAL(groups.size(), 3); | ||
| BOOST_CHECK_EQUAL(groups[0], "admin"); | ||
| BOOST_CHECK_EQUAL(groups[1], "admin"); | ||
| BOOST_CHECK_EQUAL(groups[2], "users"); |
There was a problem hiding this comment.
Should these be deduplicated based on the wiki?
There was a problem hiding this comment.
I wonder if the wiki should be updated instead (to say that it does not deduplicate). From an authz standpoint, I don't think duplicates matter. If a group is duplicated and also has an acl match, the first duplicate will trigger the match. If a group is duplicated and does not have an acl match, neither the first nor any of the duplicates will trigger the match. What do you think @cjayani ?
There was a problem hiding this comment.
Yeah agreed, let's update the wiki.
| // Multiple nested groups collide to same suffix | ||
| BOOST_AUTO_TEST_CASE(test_apply_nested_group_policy_suffix_collision) { | ||
| auto p1 = security::oidc::detail::apply_nested_group_policy( | ||
| "deptA/admin", security::oidc::nested_group_behavior::suffix); | ||
| auto p2 = security::oidc::detail::apply_nested_group_policy( | ||
| "deptB/admin", security::oidc::nested_group_behavior::suffix); | ||
|
|
||
| // Both resolve to "admin" | ||
| BOOST_CHECK_EQUAL(p1.name(), "admin"); | ||
| BOOST_CHECK_EQUAL(p2.name(), "admin"); | ||
| BOOST_CHECK_EQUAL(p1.name(), p2.name()); | ||
| } |
There was a problem hiding this comment.
nit: Should this test be at the security::oidc::detail::get_group_claim API level or some higher level?
This PR adds automated test coverage for GBAC (Group-Based Access Control) scenarios that were previously only manually verified. It covers edge cases in OIDC group claim parsing, group name ACL matching, and nested group path behavior.
The scenarios that these cover include:
Backports Required
Release Notes