KNOX-3328: Implement recursive group resolution for LDAP proxy#1236
KNOX-3328: Implement recursive group resolution for LDAP proxy#1236smolnar82 wants to merge 5 commits into
Conversation
|
Cc. @handavid |
Test Results21 tests 21 ✅ 1s ⏱️ Results for commit 529e44d. ♻️ This comment has been updated with latest results. |
lmccay
left a comment
There was a problem hiding this comment.
@smolnar82 - thanks for this improvement!
Mostly looks good.
I question the default depth of 10 and am also wondering about cursor leaks.
Cursor leaks may have been an issue prior to this PR but we should be sure that things do get cleaned up properly.
| cursor.close(); | ||
|
|
||
| // 2. Try search in group base if not found in user base | ||
| String groupFilter = "(cn=" + username + ")"; |
There was a problem hiding this comment.
Do we know for sure that this will always be common name?
Also, I think a little more description of what this groupFilter's affect on the search will be in the comment would go a long way for future readers.
There was a problem hiding this comment.
I'll introduce a configurable groupIdentifierAttribute, defaulting to cn, and add descriptive comments to clarify this search.
| } | ||
| cursor.close(); | ||
|
|
||
| return null; |
There was a problem hiding this comment.
do we not need any logging here?
What affect does not getting an Entry for the getUser method have?
| @@ -83,19 +83,20 @@ public String getName() { | |||
| @Override | |||
| public void initialize(Map<String, String> config) throws Exception { | |||
| // Proxy base DN is for entries created in the proxy LDAP server | |||
There was a problem hiding this comment.
nit: comment redundant. you can remove the old comment.
| // This allows the proxy to enrich group entries with recursive 'memberOf' attributes | ||
| // when a client searches for a group name. | ||
| String groupFilter = "(" + groupIdentifierAttribute + "=" + username + ")"; | ||
| return fetchEntry(username, schemaManager, connection, groupSearchBase, groupFilter); |
There was a problem hiding this comment.
I find it strange to search for a group in a method called getUser. This is a limitation of the GroupSearchInterceptor that will only call this method for users. I have a change forthcoming to the interceptors and another planned for handling general searches better. I understand that there is a problem that you can't search for groups directly using the current implementation and hope to solve that in a better way.
| Attribute memberOfAttr = userEntry.get("memberOf"); | ||
| if (!nextLevelDns.isEmpty()) { | ||
| allGroupDns.addAll(nextLevelDns); | ||
| resolveRecursiveGroupDnsViaMemberOf(connection, allGroupDns, depth + 1); |
There was a problem hiding this comment.
I think we only need to recursively resolve the nextLevelDns and not the full allGroupDns. Otherwise we are redundantly fetching the same groups repeatedly. You'll probably need to pass in both the nextLevelDns and allGroupDns if you want to filter out seen Dns in fetchNextLevelDNs
| private Set<String> fetchNextLevelDNs(LdapConnection connection, Set<String> allGroupDns) throws IOException, LdapException, CursorException { | ||
| final Set<String> nextLevelDns = new HashSet<>(); | ||
| for (String dn : new HashSet<>(allGroupDns)) { | ||
| try (EntryCursor cursor = connection.search(dn, "(objectClass=*)", SearchScope.OBJECT, MEMBER_OF, "+")) { |
There was a problem hiding this comment.
can we combine the groups into a single search instead of executing one search per group? We can try constructing a search filter like connection.search(groupSearchBase, "(|(cn=Group1)(cn=Group2)(cn=Group3))", SearchScope.SUBTREE, MEMBER_OF, "+").
| return null; | ||
| } | ||
|
|
||
| private List<Entry> getUserGroupsInternal(LdapConnection connection, String userDn, String username) throws LdapException, CursorException, IOException { |
There was a problem hiding this comment.
this isn't only applicable to users after your change. you should rename the userDn param to something more general
| } | ||
|
|
||
| private void resolveRecursiveGroupDnsInternal(LdapConnection connection, Set<String> allGroupDns, Set<String> visited, int depth) throws LdapException, CursorException, IOException { | ||
| if (depth >= groupMaxDepth) { |
There was a problem hiding this comment.
optimization: you can convert this from recursion to iteration to shorten the call stack.
| } | ||
|
|
||
| private void resolveRecursiveGroupDnsViaMemberOf(LdapConnection connection, Set<String> allGroupDns, int depth) throws LdapException, CursorException, IOException { | ||
| if (depth >= groupMaxDepth) { |
There was a problem hiding this comment.
optimization: you can convert this from recursion to iteration to shorten the call stack.
| String groupName = extractGroupNameFromDn(groupDn); | ||
| if (groupName != null) { | ||
| groups.add(groupName); | ||
| private Set<String> fetchNextLevelDNs(LdapConnection connection, Set<String> allGroupDns) throws IOException, LdapException, CursorException { |
There was a problem hiding this comment.
can we rename this to fetchNextLevelDNsViaMemberOf to preserve the usage of this method in the memberOf codepath only?
| Entry sourceEntry = cursor.get(); | ||
| Attribute idAttr = sourceEntry.get(userIdentifierAttribute); | ||
| if (idAttr != null) { | ||
| Entry entry = createProxyEntry(sourceEntry, idAttr.getString(), connection, schemaManager); |
There was a problem hiding this comment.
this can lead to many redundant calls to the backend because the groups are retrieved on a per-user basis. If we have 10 users that are members of the same group, we'll do the same recursive group lookup 10 times.


KNOX-3328 - Support recursive group resolution in LDAP Proxy Service
What changes were proposed in this pull request?
This PR introduces recursive group resolution to the
LdapProxyBackend. Key changes include:LdapProxyBackend.getUserto search both the user and group search bases. This allows group entries to be returned as "proxy entries" enriched with their ownmemberOfattributes.(*, +)to ensure complete profile data, while recursive steps specifically request only thememberOfand operational attributes(+)to minimize network payload and processing overhead.How was this patch tested?
The patch was tested using the LdapProxyBackendTest suite with an embedded ApacheDS server.
testGetUserGroupsRecursive: Verifies 3-level deep nesting is resolved correctly.testGetUserGroupsRecursiveCircular: Verifies that circular references are handled without errors.testGetUserGroupsRecursiveMaxDepth: Verifies that the recursion stops at the configured depth.testGetUserGroupsRecursiveUseMemberOf: Verifies recursive resolution whenuseMemberOfis set totrue.ldap-proxy-backend-test.ldifto include nested groups, circular groups, and groups withmemberOfattributes.Integration Tests
Automated unit tests were added to gateway-server. These tests use an embedded LDAP server to simulate a real-world proxy scenario, which provides high-fidelity verification of the recursive logic and LDAP protocol handling. No changes were required to .github/workflows/tests as the standard test suite covers these new unit tests.
UI changes
N/A