This is an automated email from the ASF dual-hosted git repository.

smolnar82 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/knox.git


The following commit(s) were added to refs/heads/master by this push:
     new fcab7e2f8 KNOX-3385: Ldap Proxy accepts search using either proxy or 
remote base dn (#1314)
fcab7e2f8 is described below

commit fcab7e2f8a2ce77172923368a3d585871c4b0580
Author: David Han <[email protected]>
AuthorDate: Wed Jul 22 03:06:14 2026 -0500

    KNOX-3385: Ldap Proxy accepts search using either proxy or remote base dn 
(#1314)
---
 .../workflows/build/conf/topologies/knoxldap.xml   |  2 +-
 .github/workflows/build/gateway-site.xml           |  2 +-
 .../compose/single-eku-no-mtls/gateway-site.xml    |  2 +-
 .../workflows/compose/single-eku/gateway-site.xml  |  2 +-
 .../workflows/tests/test_knox_ldap_proxy_search.py | 26 ++++++++++++++++++++++
 .../gateway/services/ldap/backend/FileBackend.java | 11 ++++++++-
 .../gateway/services/ldap/backend/LdapBackend.java |  7 ++++++
 .../services/ldap/backend/LdapProxyBackend.java    | 14 ++++++++++++
 .../ldap/interceptor/UserSearchInterceptor.java    | 12 +---------
 .../ldap/backend/LdapProxyBackendTest.java         | 22 ++++++++++++++++++
 knox-site/docs/service_ldap_server.md              |  2 +-
 11 files changed, 85 insertions(+), 17 deletions(-)

diff --git a/.github/workflows/build/conf/topologies/knoxldap.xml 
b/.github/workflows/build/conf/topologies/knoxldap.xml
index 3b886b777..c89e9ee94 100644
--- a/.github/workflows/build/conf/topologies/knoxldap.xml
+++ b/.github/workflows/build/conf/topologies/knoxldap.xml
@@ -31,7 +31,7 @@ limitations under the License.
                        </param>
                        <param>
                                <name>main.ldapRealm.userDnTemplate</name>
-                               
<value>uid={0},ou=people,dc=hadoop,dc=apache,dc=org</value>
+                               <value>uid={0},ou=people,dc=proxy,dc=org</value>
                        </param>
                        <param>
                                <name>main.ldapRealm.contextFactory.url</name>
diff --git a/.github/workflows/build/gateway-site.xml 
b/.github/workflows/build/gateway-site.xml
index 36e884a3e..00698bebd 100644
--- a/.github/workflows/build/gateway-site.xml
+++ b/.github/workflows/build/gateway-site.xml
@@ -143,7 +143,7 @@ limitations under the License.
     </property>
     <property>
         <name>gateway.ldap.base.dn</name>
-        <value>dc=hadoop,dc=apache,dc=org</value>
+        <value>dc=proxy,dc=org</value>
     </property>
     <property>
         <name>gateway.ldap.recursive.group.resolution</name>
diff --git a/.github/workflows/compose/single-eku-no-mtls/gateway-site.xml 
b/.github/workflows/compose/single-eku-no-mtls/gateway-site.xml
index bdc90706f..94d613ff9 100644
--- a/.github/workflows/compose/single-eku-no-mtls/gateway-site.xml
+++ b/.github/workflows/compose/single-eku-no-mtls/gateway-site.xml
@@ -155,7 +155,7 @@ limitations under the License.
     </property>
     <property>
         <name>gateway.ldap.base.dn</name>
-        <value>dc=hadoop,dc=apache,dc=org</value>
+        <value>dc=proxy,dc=org</value>
     </property>
     <property>
         <name>gateway.ldap.recursive.group.resolution</name>
diff --git a/.github/workflows/compose/single-eku/gateway-site.xml 
b/.github/workflows/compose/single-eku/gateway-site.xml
index 27eaf9501..4272c7b73 100644
--- a/.github/workflows/compose/single-eku/gateway-site.xml
+++ b/.github/workflows/compose/single-eku/gateway-site.xml
@@ -209,7 +209,7 @@ limitations under the License.
     </property>
     <property>
         <name>gateway.ldap.base.dn</name>
-        <value>dc=hadoop,dc=apache,dc=org</value>
+        <value>dc=proxy,dc=org</value>
     </property>
     <property>
         <name>gateway.ldap.recursive.group.resolution</name>
diff --git a/.github/workflows/tests/test_knox_ldap_proxy_search.py 
b/.github/workflows/tests/test_knox_ldap_proxy_search.py
index bb42336ad..064d42d3b 100644
--- a/.github/workflows/tests/test_knox_ldap_proxy_search.py
+++ b/.github/workflows/tests/test_knox_ldap_proxy_search.py
@@ -43,6 +43,10 @@ BASE_DN = "dc=hadoop,dc=apache,dc=org"
 PEOPLE_BASE = f"ou=people,{BASE_DN}"
 GROUPS_BASE = f"ou=groups,{BASE_DN}"
 
+PROXY_BASE_DN = "dc=proxy,dc=org"
+PROXY_PEOPLE_BASE = f"ou=people,{PROXY_BASE_DN}"
+PROXY_GROUPS_BASE = f"ou=groups,{PROXY_BASE_DN}"
+
 # A valid backend user used to bind to the proxy before searching.
 BIND_DN = f"uid=guest,{PEOPLE_BASE}"
 BIND_PASSWORD = "guest-password"
@@ -102,6 +106,28 @@ class TestKnoxLdapProxySearch(unittest.TestCase):
         users = self.rdn_values(PEOPLE_BASE, "(uid=sam)")
         self.assertIn("sam", users)
 
+    def test_search_all_users_by_objectclass_proxy_dn(self) -> None:
+        """All inetOrgPerson entries under ou=people are returned."""
+        users = self.rdn_values(PROXY_PEOPLE_BASE, 
"(objectClass=inetOrgPerson)")
+        for expected in ("guest", "admin", "sam", "tom", "recursiveUser"):
+            self.assertIn(expected, users)
+
+    def test_search_all_groups_by_objectclass_proxy_dn(self) -> None:
+        """All groupOfNames entries under ou=groups are returned."""
+        groups = self.rdn_values(PROXY_GROUPS_BASE, 
"(objectClass=groupOfNames)")
+        for expected in ("analyst", "scientist", "admin", "level1", "level2", 
"level3"):
+            self.assertIn(expected, groups)
+
+    def test_search_groups_by_cn_wildcard_proxy_dn(self) -> None:
+        """A cn wildcard filter returns only the matching groups."""
+        groups = self.rdn_values(PROXY_GROUPS_BASE, "(cn=level*)")
+        self.assertEqual({"level1", "level2", "level3"}, set(groups))
+
+    def test_search_user_by_uid_proxy_dn(self) -> None:
+        """A single user can still be looked up by uid."""
+        users = self.rdn_values(PROXY_PEOPLE_BASE, "(uid=sam)")
+        self.assertIn("sam", users)
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/FileBackend.java
 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/FileBackend.java
index 872d1c764..7d7660963 100644
--- 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/FileBackend.java
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/FileBackend.java
@@ -17,6 +17,8 @@
  */
 package org.apache.knox.gateway.services.ldap.backend;
 
+import static java.util.Locale.ROOT;
+
 import com.google.gson.Gson;
 import org.apache.directory.api.ldap.model.entry.DefaultEntry;
 import org.apache.directory.api.ldap.model.entry.Entry;
@@ -54,6 +56,7 @@ public class FileBackend implements LdapBackend {
     private Map<String, UserData> users = new HashMap<>();
     private final String dataFile;
     private final String baseDn;
+    private final String userSearchBase;
     private final String name;
 
     static class UserData {
@@ -73,6 +76,7 @@ public class FileBackend implements LdapBackend {
         this.name = name;
         dataFile = config.getOrDefault("dataFile", "ldap-users.json");
         baseDn = config.getOrDefault("baseDn", "dc=proxy,dc=com");
+        userSearchBase = "ou=people," + baseDn;
         loadData();
     }
 
@@ -91,6 +95,11 @@ public class FileBackend implements LdapBackend {
         return baseDn;
     }
 
+    @Override
+    public boolean isSupportedSearchBase(String searchBase) {
+        return searchBase != null && 
searchBase.toLowerCase(ROOT).endsWith(userSearchBase.toLowerCase(ROOT));
+    }
+
     private void loadData() throws Exception {
         Path path = Paths.get(dataFile);
 
@@ -119,7 +128,7 @@ public class FileBackend implements LdapBackend {
         }
 
         Entry entry = new DefaultEntry(schemaManager);
-        entry.setDn("uid=" + userData.username + ",ou=Users," + baseDn);
+        entry.setDn("uid=" + userData.username + "," + userSearchBase);
         entry.add("objectClass", "top");
         entry.add("objectClass", "person");
         entry.add("objectClass", "organizationalPerson");
diff --git 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapBackend.java
 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapBackend.java
index 51ef8e4f8..f3c8e5f71 100644
--- 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapBackend.java
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapBackend.java
@@ -49,6 +49,13 @@ public interface LdapBackend {
      */
     String getBaseDn();
 
+    /**
+     * Returns whether a search base is supported by this backend
+     * @param searchBase the base dn for a search
+     * @return True if the base dn is supported by this backend
+     */
+    boolean isSupportedSearchBase(String searchBase);
+
     /**
      * Get a user entry by username
      * @param username The username to look up
diff --git 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapProxyBackend.java
 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapProxyBackend.java
index 8466f5b12..0c5616852 100644
--- 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapProxyBackend.java
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapProxyBackend.java
@@ -17,6 +17,8 @@
  */
 package org.apache.knox.gateway.services.ldap.backend;
 
+import static java.util.Locale.ROOT;
+
 import org.apache.directory.api.ldap.model.cursor.CursorException;
 import org.apache.directory.api.ldap.model.cursor.EntryCursor;
 import org.apache.directory.api.ldap.model.entry.Attribute;
@@ -227,6 +229,18 @@ public class LdapProxyBackend implements LdapBackend {
         return remoteBaseDn;
     }
 
+    @Override
+    public boolean isSupportedSearchBase(String searchBase) {
+        // System/operational searches (ou=schema, cn=config, root-DSE) must 
not be forwarded.
+        // Only support user and group searches against either the proxy or 
remote base Dn.
+        if (searchBase == null) {
+            return false;
+        }
+        String searchBaseLowerCase = searchBase.toLowerCase(ROOT);
+        return searchBaseLowerCase.endsWith(proxyBaseDn.toLowerCase(ROOT)) ||
+                searchBaseLowerCase.endsWith(remoteBaseDn.toLowerCase(ROOT));
+    }
+
     /**
      * Initializes the LDAP connection pool with configurable parameters.
      * Uses a validating pool to ensure connections remain healthy.
diff --git 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/interceptor/UserSearchInterceptor.java
 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/interceptor/UserSearchInterceptor.java
index 1c9edb988..6fb823f59 100644
--- 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/interceptor/UserSearchInterceptor.java
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/interceptor/UserSearchInterceptor.java
@@ -39,8 +39,6 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
-import static java.util.Locale.ROOT;
-
 /**
  * Interceptor for LDAP operations to proxy user searches to backends when not 
found locally
  */
@@ -100,7 +98,7 @@ public class UserSearchInterceptor extends BaseInterceptor {
 
         // Only forward to the backend when the search base is under the 
backend's namespace.
         // System/operational searches (ou=schema, cn=config, root-DSE) must 
not be forwarded.
-        if (isUnderBackendBaseDn(baseDn)) {
+        if (backend.isSupportedSearchBase(baseDn)) {
             try {
                 entries.addAll(backend.search(baseDn, ctx.getScope(), filter, 
schemaManager));
             } catch (Exception e) {
@@ -112,14 +110,6 @@ public class UserSearchInterceptor extends BaseInterceptor 
{
         return new EntryFilteringCursorImpl(new ListCursor<>(entries), ctx, 
schemaManager);
     }
 
-    private boolean isUnderBackendBaseDn(String searchBase) {
-        final String backendBase = backend.getBaseDn();
-        if (searchBase == null || searchBase.isEmpty() || backendBase == null 
|| backendBase.isEmpty()) {
-            return false;
-        }
-        return 
searchBase.toLowerCase(ROOT).endsWith(backendBase.toLowerCase(ROOT));
-    }
-
     @Override
     public void bind(BindOperationContext ctx) throws LdapException {
         LOG.ldapBind(ctx.getDn() != null ? ctx.getDn().toString() : 
"anonymous");
diff --git 
a/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/backend/LdapProxyBackendTest.java
 
b/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/backend/LdapProxyBackendTest.java
index e814735f4..58432f137 100644
--- 
a/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/backend/LdapProxyBackendTest.java
+++ 
b/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/backend/LdapProxyBackendTest.java
@@ -662,6 +662,28 @@ public class LdapProxyBackendTest {
         assertTrue(ldapProxyBackend.authenticate(dn, 
"memberOfUser2-password"));
     }
 
+    @Test
+    public void testIsSupportedSearchBase() {
+        Map<String, String> config = new HashMap<>(ldapBackendConfig);
+        config.put("baseDn", "dc=proxy,dc=org");
+        ldapProxyBackend = new LdapProxyBackend("testbackend", config);
+
+        // Test searching remote dn
+        
assertTrue(ldapProxyBackend.isSupportedSearchBase("ou=people,dc=hadoop,dc=apache,dc=org"));
+        
assertTrue(ldapProxyBackend.isSupportedSearchBase("ou=groups,dc=hadoop,dc=apache,dc=org"));
+
+        // Test searching proxy dn
+        
assertTrue(ldapProxyBackend.isSupportedSearchBase("ou=people,dc=proxy,dc=org"));
+        
assertTrue(ldapProxyBackend.isSupportedSearchBase("ou=groups,dc=proxy,dc=org"));
+
+        // Test searching arbitrary dn
+        assertFalse(ldapProxyBackend.isSupportedSearchBase("cn=schema"));
+        assertFalse(ldapProxyBackend.isSupportedSearchBase("cn=config"));
+        assertFalse(ldapProxyBackend.isSupportedSearchBase(null));
+        assertFalse(ldapProxyBackend.isSupportedSearchBase(""));
+        
assertFalse(ldapProxyBackend.isSupportedSearchBase("dc=other,dc=base,dc=org"));
+    }
+
     // Helper methods for refactoring
 
     private Map<String, String> createConfigWithUserAttr(String attr) {
diff --git a/knox-site/docs/service_ldap_server.md 
b/knox-site/docs/service_ldap_server.md
index 63495195c..8d524395b 100644
--- a/knox-site/docs/service_ldap_server.md
+++ b/knox-site/docs/service_ldap_server.md
@@ -135,7 +135,7 @@ The interceptor will skip role mapping for a search request 
if the RolesLookupBy
 | Byte | Value | Description |
 | :--- | :--- | :--- |
 | Tag | 0x01 | The Boolean Tag value |
-| Length | 0x03 | The length of the value in bytes |
+| Length | 0x01 | The length of the value in bytes |
 | Bypass | 0x00 or Oxff | 0x00 corresponds to `false` and 0xff corresponds to 
`true |
 
 

Reply via email to