This is an automated email from the ASF dual-hosted git repository.
smolnar82 pushed a commit to branch v3.0.0
in repository https://gitbox.apache.org/repos/asf/knox.git
The following commit(s) were added to refs/heads/v3.0.0 by this push:
new 7f9b68670 KNOX-3386: LDAP Proxy pages backends (#1331)
7f9b68670 is described below
commit 7f9b686708aa2e9aa379d48fd4da27fce1ad6266
Author: David Han <[email protected]>
AuthorDate: Fri Jul 31 09:11:08 2026 -0500
KNOX-3386: LDAP Proxy pages backends (#1331)
---
.github/workflows/build/gateway-site.xml | 13 ++
.../compose/single-eku-no-mtls/gateway-site.xml | 13 ++
.../workflows/compose/single-eku/gateway-site.xml | 13 ++
.../gateway/config/impl/GatewayConfigImpl.java | 27 +++-
.../services/ldap/KnoxLDAPServerManager.java | 18 ++-
.../knox/gateway/services/ldap/LdapMessages.java | 20 ++-
.../services/ldap/backend/LdapProxyBackend.java | 160 ++++++++++++-------
.../src/main/resources/conf/gateway-site.xml | 12 ++
.../services/ldap/KnoxLDAPServerManagerTest.java | 21 +++
.../gateway/services/ldap/KnoxLDAPServiceTest.java | 2 +
.../ldap/backend/LdapProxyBackendSslTest.java | 4 +-
.../ldap/backend/LdapProxyBackendTest.java | 170 ++++++++++++++++++---
.../test/resources/ldap-proxy-backend-test.ldif | 19 +++
.../org/apache/knox/gateway/GatewayTestConfig.java | 10 ++
.../apache/knox/gateway/config/GatewayConfig.java | 12 ++
knox-site/docs/service_ldap_server.md | 6 +
16 files changed, 436 insertions(+), 84 deletions(-)
diff --git a/.github/workflows/build/gateway-site.xml
b/.github/workflows/build/gateway-site.xml
index 00698bebd..002fadea5 100644
--- a/.github/workflows/build/gateway-site.xml
+++ b/.github/workflows/build/gateway-site.xml
@@ -145,6 +145,14 @@ limitations under the License.
<name>gateway.ldap.base.dn</name>
<value>dc=proxy,dc=org</value>
</property>
+ <property>
+ <name>gateway.ldap.max.size.limit</name>
+ <value>1000</value>
+ </property>
+ <property>
+ <name>gateway.ldap.max.time.limit</name>
+ <value>60000</value>
+ </property>
<property>
<name>gateway.ldap.recursive.group.resolution</name>
<value>true</value>
@@ -211,5 +219,10 @@ limitations under the License.
<name>gateway.ldap.interceptor.demoldap.groupMemberAttribute</name>
<value>member</value>
</property>
+ <!-- Set an unnaturally low page size to ensure that paging is used in
tests -->
+ <property>
+ <name>gateway.ldap.interceptor.demoldap.pageSize</name>
+ <value>3</value>
+ </property>
</configuration>
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 94d613ff9..791a3b2a5 100644
--- a/.github/workflows/compose/single-eku-no-mtls/gateway-site.xml
+++ b/.github/workflows/compose/single-eku-no-mtls/gateway-site.xml
@@ -157,6 +157,14 @@ limitations under the License.
<name>gateway.ldap.base.dn</name>
<value>dc=proxy,dc=org</value>
</property>
+ <property>
+ <name>gateway.ldap.max.size.limit</name>
+ <value>1000</value>
+ </property>
+ <property>
+ <name>gateway.ldap.max.time.limit</name>
+ <value>60000</value>
+ </property>
<property>
<name>gateway.ldap.recursive.group.resolution</name>
<value>true</value>
@@ -203,5 +211,10 @@ limitations under the License.
<name>gateway.ldap.interceptor.demoldap.groupMemberAttribute</name>
<value>member</value>
</property>
+ <!-- Set an unnaturally low page size to ensure that paging is used in
tests -->
+ <property>
+ <name>gateway.ldap.interceptor.demoldap.pageSize</name>
+ <value>3</value>
+ </property>
</configuration>
diff --git a/.github/workflows/compose/single-eku/gateway-site.xml
b/.github/workflows/compose/single-eku/gateway-site.xml
index 4272c7b73..76f488b85 100644
--- a/.github/workflows/compose/single-eku/gateway-site.xml
+++ b/.github/workflows/compose/single-eku/gateway-site.xml
@@ -211,6 +211,14 @@ limitations under the License.
<name>gateway.ldap.base.dn</name>
<value>dc=proxy,dc=org</value>
</property>
+ <property>
+ <name>gateway.ldap.max.size.limit</name>
+ <value>1000</value>
+ </property>
+ <property>
+ <name>gateway.ldap.max.time.limit</name>
+ <value>60000</value>
+ </property>
<property>
<name>gateway.ldap.recursive.group.resolution</name>
<value>true</value>
@@ -257,5 +265,10 @@ limitations under the License.
<name>gateway.ldap.interceptor.demoldap.groupMemberAttribute</name>
<value>member</value>
</property>
+ <!-- Set an unnaturally low page size to ensure that paging is used in
tests -->
+ <property>
+ <name>gateway.ldap.interceptor.demoldap.pageSize</name>
+ <value>3</value>
+ </property>
</configuration>
diff --git
a/gateway-server/src/main/java/org/apache/knox/gateway/config/impl/GatewayConfigImpl.java
b/gateway-server/src/main/java/org/apache/knox/gateway/config/impl/GatewayConfigImpl.java
index 061518537..d0e34d0fc 100644
---
a/gateway-server/src/main/java/org/apache/knox/gateway/config/impl/GatewayConfigImpl.java
+++
b/gateway-server/src/main/java/org/apache/knox/gateway/config/impl/GatewayConfigImpl.java
@@ -392,6 +392,13 @@ public class GatewayConfigImpl extends Configuration
implements GatewayConfig {
public static final String STRICT_TRANSPORT_ENABLED =
GATEWAY_CONFIG_FILE_PREFIX + ".strict.transport.enabled";
public static final String STRICT_TRANSPORT_OPTION =
GATEWAY_CONFIG_FILE_PREFIX + ".strict.transport.option";
+ // Gateway LDAP Properties
+ public static final int DEFAULT_LDAP_PORT = 3890;
+ public static final String DEFAULT_LDAP_BASE_DN = "dc=proxy,dc=com";
+ public static final int DEFAULT_LDAP_MAX_SIZE_LIMIT = 1000;
+ /* The default max time for LDAP search in milliseconds */
+ public static final int DEFAULT_LDAP_MAX_TIME_LIMIT = 60 * 1000;
+
public GatewayConfigImpl() {
init();
}
@@ -1775,17 +1782,17 @@ public class GatewayConfigImpl extends Configuration
implements GatewayConfig {
// LDAP Service Configuration
@Override
public boolean isLDAPEnabled() {
- return Boolean.parseBoolean(get(LDAP_ENABLED, "false"));
+ return getBoolean(LDAP_ENABLED, false);
}
@Override
public int getLDAPPort() {
- return Integer.parseInt(get(LDAP_PORT, "3890"));
+ return getInt(LDAP_PORT, DEFAULT_LDAP_PORT);
}
@Override
public String getLDAPBaseDN() {
- return get(LDAP_BASE_DN, "dc=proxy,dc=com");
+ return get(LDAP_BASE_DN, DEFAULT_LDAP_BASE_DN);
}
@Override
@@ -1840,7 +1847,7 @@ public class GatewayConfigImpl extends Configuration
implements GatewayConfig {
@Override
public boolean isLDAPRecursiveGroupResolutionEnabled() {
- return Boolean.parseBoolean(get(LDAP_RECURSIVE_GROUP_RESOLUTION, "false"));
+ return getBoolean(LDAP_RECURSIVE_GROUP_RESOLUTION, false);
}
@Override
@@ -1865,7 +1872,7 @@ public class GatewayConfigImpl extends Configuration
implements GatewayConfig {
@Override
public boolean isLDAPSSLEnabled() {
- return Boolean.parseBoolean(get(LDAP_SSL_ENABLED, "false"));
+ return getBoolean(LDAP_SSL_ENABLED, false);
}
@Override
@@ -1884,6 +1891,16 @@ public class GatewayConfigImpl extends Configuration
implements GatewayConfig {
return cipherSuites == null ? Collections.emptyList() : cipherSuites;
}
+ @Override
+ public int getLDAPMaxSizeLimit() {
+ return getInt(LDAP_MAX_SIZE_LIMIT, DEFAULT_LDAP_MAX_SIZE_LIMIT);
+ }
+
+ @Override
+ public int getLDAPMaxTimeLimit() {
+ return getInt(LDAP_MAX_TIME_LIMIT, DEFAULT_LDAP_MAX_TIME_LIMIT);
+ }
+
@Override
public boolean getGroupUIServicesOnHomepage() {
return getBoolean(KNOX_HOMEPAGE_GROUP_UI_SERVICES,
DEFAULT_GROUP_UI_SERVICES);
diff --git
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManager.java
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManager.java
index 3e42c2117..e4445276e 100644
---
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManager.java
+++
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManager.java
@@ -72,7 +72,8 @@ public class KnoxLDAPServerManager {
@VisibleForTesting
DirectoryService directoryService;
- private LdapServer ldapServer;
+ @VisibleForTesting
+ LdapServer ldapServer;
private GatewayConfig gatewayConfig;
private List<Interceptor> interceptors;
private boolean hasRolesLookupInterceptor;
@@ -87,6 +88,8 @@ public class KnoxLDAPServerManager {
private List<String> sslEnabledCipherSuites;
// Collection of DNs for the proxied backend LDAP servers
private Set<String> baseDns;
+ private int maxSizeLimit;
+ private int maxTimeLimit;
KnoxLDAPServerManager(AliasService aliasService) {
this(aliasService, null);
@@ -114,6 +117,9 @@ public class KnoxLDAPServerManager {
this.baseDn = config.getLDAPBaseDN();
this.bindUser = config.getLDAPBindUser();
+ maxSizeLimit = config.getLDAPMaxSizeLimit();
+ maxTimeLimit = config.getLDAPMaxTimeLimit();
+
// Secure (LDAPS) transport configuration. When enabled but no
dedicated keystore is
// configured, fall back to the gateway identity keystore so the
embedded server can
// reuse the gateway's own TLS material out of the box.
@@ -148,6 +154,13 @@ public class KnoxLDAPServerManager {
// Add common configuration
interceptorConfig.put("baseDn", baseDn);
+ if (!interceptorConfig.containsKey("maxResultSetSize")) {
+ // Set the backend to return more results than the proxy's
size limit.
+ // This will ensure that the proxy will return "Size limit
exceeded"
+ if (maxSizeLimit != 0) {
+ interceptorConfig.put("maxResultSetSize",
Integer.toString(maxSizeLimit + 1));
+ }
+ }
// Add common LDAP Proxy configurations to backends
if
("backend".equalsIgnoreCase(interceptorConfig.get("interceptorType"))) {
@@ -241,6 +254,9 @@ public class KnoxLDAPServerManager {
ldapServer.setTransports(transport);
ldapServer.setDirectoryService(directoryService);
+ ldapServer.setMaxSizeLimit(maxSizeLimit);
+ ldapServer.setMaxTimeLimit(maxTimeLimit);
+
ldapServer.start();
LOG.ldapServiceStarted(port);
diff --git
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/LdapMessages.java
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/LdapMessages.java
index 816fef003..8683f4645 100644
---
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/LdapMessages.java
+++
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/LdapMessages.java
@@ -73,6 +73,10 @@ public interface LdapMessages {
text = "Creating LDAP interceptor: {0} (via {1})")
void ldapInterceptorCreating(String interceptorName, String source);
+ @Message(level = MessageLevel.INFO,
+ text = "Configuring LDAP interceptor {0}: {1} = {2}")
+ void ldapInterceptorConfiguring(String interceptorName, String configName,
String configValue);
+
@Message(level = MessageLevel.INFO,
text = "Loading backend: {0} (via {1})")
void ldapBackendLoading(String backendName, String source);
@@ -101,6 +105,18 @@ public interface LdapMessages {
text = "LDAP Search: {0} | {1}")
void ldapSearch(String baseDn, String filter);
+ @Message(level = MessageLevel.DEBUG,
+ text = "LDAP Paged Search: {0} | {1}, page size {2}, page {3}")
+ void ldapPagedSearch(String baseDn, String filter, int pageSize, int
pageNumber);
+
+ @Message(level = MessageLevel.ERROR,
+ text = "LDAP Paged Search Exceeded Max Result Set Size: {0} | {1}")
+ void ldapPagedSearchExceededMaxResultSetSize(int resultSetSize, int
maxResultSetSize);
+
+ @Message(level = MessageLevel.DEBUG,
+ text = "LDAP Paged Search Completed: {0} | {1}")
+ void ldapPagedSearchCompleted(String baseDn, String filter);
+
@Message(level = MessageLevel.ERROR,
text = "LDAP Search failed: {0} | {1}, {2}")
void ldapSearchFailed(String baseDn, String filter, @StackTrace(level =
MessageLevel.DEBUG) Exception e);
@@ -133,9 +149,9 @@ public interface LdapMessages {
text = "Backend user not found: {0}")
void ldapUserNull(String username);
- @Message(level = MessageLevel.ERROR,
+ @Message(level = MessageLevel.DEBUG,
text = "Failed to copy attribute: {0}")
- void ldapAttributeCopyError(@StackTrace(level = MessageLevel.DEBUG)
Exception e);
+ void ldapAttributeCopyError(@StackTrace(level = MessageLevel.TRACE)
Exception e);
@Message(level = MessageLevel.DEBUG, text = "LDAP authentication succeeded
for user: {0}")
void ldapAuthSucceeded(String user);
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 0c5616852..32fd033b2 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
@@ -21,12 +21,20 @@ 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.cursor.SearchCursor;
import org.apache.directory.api.ldap.model.entry.Attribute;
import org.apache.directory.api.ldap.model.entry.DefaultEntry;
import org.apache.directory.api.ldap.model.entry.Entry;
import org.apache.directory.api.ldap.model.entry.Value;
import org.apache.directory.api.ldap.model.exception.LdapException;
+import org.apache.directory.api.ldap.model.message.Response;
+import org.apache.directory.api.ldap.model.message.SearchRequest;
+import org.apache.directory.api.ldap.model.message.SearchRequestImpl;
+import org.apache.directory.api.ldap.model.message.SearchResultDone;
+import org.apache.directory.api.ldap.model.message.SearchResultEntry;
import org.apache.directory.api.ldap.model.message.SearchScope;
+import org.apache.directory.api.ldap.model.message.controls.PagedResults;
+import org.apache.directory.api.ldap.model.message.controls.PagedResultsImpl;
import org.apache.directory.api.ldap.model.name.Dn;
import org.apache.directory.api.ldap.model.schema.SchemaManager;
import org.apache.directory.ldap.client.api.DefaultLdapConnectionFactory;
@@ -50,7 +58,6 @@ import java.security.KeyStore;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -96,6 +103,8 @@ public class LdapProxyBackend implements LdapBackend {
private boolean useMemberOf; // Use memberOf attribute for group lookup
(efficient for AD)
private boolean recursiveGroupResolution;
private int recursiveGroupResolutionMaxDepth;
+ private int pageSize;
+ private int maxResultSetSize;
private final String proxyEntryGroupMembershipAttributeType = "memberOf";
@@ -184,6 +193,11 @@ public class LdapProxyBackend implements LdapBackend {
recursiveGroupResolution =
Boolean.parseBoolean(config.getOrDefault("recursiveGroupResolution", "false"));
recursiveGroupResolutionMaxDepth =
Integer.parseInt(config.getOrDefault("recursiveGroupResolutionMaxDepth", "3"));
+ // Configure search parameters
+ pageSize = Integer.parseInt(config.getOrDefault("pageSize", "1000"));
+ maxResultSetSize =
Integer.parseInt(config.getOrDefault("maxResultSetSize", "0")); // 0 means
unlimited
+ LOG.ldapInterceptorConfiguring(name, "maxResultSetSize",
Integer.toString(maxResultSetSize));
+
// Configure secure transport (LDAPS) to the remote server. An
ldaps:// URL enables it
// by default; an explicit useSsl setting always wins.
final boolean ldapsFromUrl = ldapUrl != null &&
ldapUrl.toLowerCase(Locale.ROOT).startsWith("ldaps://");
@@ -490,15 +504,14 @@ public class LdapProxyBackend implements LdapBackend {
return List.of();
}
- LdapConnection connection = null;
- try {
- connection = getConnection();
- List<Entry> groups = getUserGroupsEntries(connection, user,
createEntryCache(), createResolvedParentsCache());
- List<String> cns = getCnsFromEntries(groups);
- return cns;
- } finally {
- releaseConnection(connection);
+ List<String> groups = new ArrayList<>();
+ Attribute groupsAttribute =
user.get(proxyEntryGroupMembershipAttributeType);
+ if (groupsAttribute != null) {
+ for (Value value : groupsAttribute) {
+ groups.add(new Dn(value.getString()).getRdn().getValue());
+ }
}
+ return groups;
}
@Override
@@ -511,12 +524,10 @@ public class LdapProxyBackend implements LdapBackend {
try {
connection = getConnection();
String ldapFilter = "(" + remoteUserIdentifierAttribute + "=" +
filter.trim() + ")";
- try (EntryCursor cursor = connection.search(remoteUserSearchBase,
ldapFilter, SearchScope.SUBTREE, "*")) {
- while (cursor.next()) {
- Entry sourceEntry = cursor.get();
- addGroupMemberships(sourceEntry, connection, entryCache,
resolvedParentsCache);
-
results.add(remoteSchemaConverter.convertRemoteEntryToProxyEntry(sourceEntry,
schemaManager));
- }
+ List<Entry> searchResults = performPagedSearch(connection,
remoteUserSearchBase, ldapFilter, SearchScope.SUBTREE, "*");
+ for (Entry sourceEntry : searchResults) {
+ addGroupMemberships(sourceEntry, connection, entryCache,
resolvedParentsCache);
+
results.add(remoteSchemaConverter.convertRemoteEntryToProxyEntry(sourceEntry,
schemaManager));
}
return results;
} finally {
@@ -535,14 +546,10 @@ public class LdapProxyBackend implements LdapBackend {
try {
connection = getConnection();
List<Entry> results = new ArrayList<>();
- try (EntryCursor cursor = connection.search(remoteSearchBase,
remoteFilter, searchScope, "*")) {
- while (cursor.next()) {
- Entry entry = cursor.get();
- addGroupMemberships(entry, connection, entryCache,
resolvedParentsCache);
-
results.add(remoteSchemaConverter.convertRemoteEntryToProxyEntry(entry,
schemaManager));
- }
- } catch (LdapException e) {
- LOG.ldapSearchFailed(remoteSearchBase, remoteFilter, e);
+ List<Entry> searchResults = performPagedSearch(connection,
remoteSearchBase, remoteFilter, searchScope, "*");
+ for (Entry entry : searchResults) {
+ addGroupMemberships(entry, connection, entryCache,
resolvedParentsCache);
+
results.add(remoteSchemaConverter.convertRemoteEntryToProxyEntry(entry,
schemaManager));
}
return results;
} finally {
@@ -721,21 +728,19 @@ public class LdapProxyBackend implements LdapBackend {
}
String filter =
buildMultipleGroupMemberFilter(groupDns.toArray(new Dn[0]));
- try (EntryCursor cursor =
connection.search(remoteGroupSearchBase, filter, SearchScope.SUBTREE, "cn",
"memberUid", "member", "uniqueMember")) {
- while (cursor.next()) {
- Entry parentGroup = cursor.get();
- String parentDn = parentGroup.getDn().getNormName();
-
- // Update cache for all groups found in this search
- updateCache(entryCache, resolvedParentsCache,
groupsToSearch, parentGroup);
-
- if (!allGroupDns.contains(parentDn)) {
- allGroupDns.add(parentDn);
- allGroups.add(parentGroup);
- nextLevelGroups.add(parentGroup);
- } else {
-
LOG.ldapRecursiveGroupSearchCycleDetected(entryName, parentDn);
- }
+ List<Entry> searchResults = performPagedSearch(connection,
remoteGroupSearchBase, filter, SearchScope.SUBTREE, "cn", "memberUid",
"member", "uniqueMember");
+ for (Entry parentGroup : searchResults) {
+ String parentDn = parentGroup.getDn().getNormName();
+
+ // Update cache for all groups found in this search
+ updateCache(entryCache, resolvedParentsCache,
groupsToSearch, parentGroup);
+
+ if (!allGroupDns.contains(parentDn)) {
+ allGroupDns.add(parentDn);
+ allGroups.add(parentGroup);
+ nextLevelGroups.add(parentGroup);
+ } else {
+ LOG.ldapRecursiveGroupSearchCycleDetected(entryName,
parentDn);
}
}
@@ -839,13 +844,69 @@ public class LdapProxyBackend implements LdapBackend {
String filter = buildMultipleGroupMemberFilter(dns);
- try (EntryCursor cursor = connection.search(remoteGroupSearchBase,
filter, SearchScope.SUBTREE, "cn")) {
- while (cursor.next()) {
- groups.add(cursor.get());
+ groups.addAll(performPagedSearch(connection, remoteGroupSearchBase,
filter, SearchScope.SUBTREE, "cn"));
+
+ return groups;
+ }
+
+ protected List<Entry> performPagedSearch(LdapConnection connection, String
baseDn, String filter, SearchScope scope, String... attributes ) throws
LdapException, CursorException, IOException {
+ List<Entry> results = new ArrayList<>();
+
+ // 1. Setup basic search parameters
+ SearchRequest searchRequest = new SearchRequestImpl();
+ searchRequest.setBase(new Dn(baseDn));
+ searchRequest.setFilter(filter);
+ searchRequest.setScope(scope);
+ searchRequest.addAttributes(attributes);
+
+ // 2. Initialize the PagedResults control
+ PagedResults pagedControl = new PagedResultsImpl();
+ pagedControl.setSize(pageSize);
+ searchRequest.addControl(pagedControl);
+
+ byte[] cookie = null;
+
+ // 3. Loop until no more pages remain
+ int pageNumber = 1;
+ do {
+ // Update cookie for the subsequent pages
+ if (cookie != null) {
+ pagedControl.setCookie(cookie);
}
+
+ try (SearchCursor cursor = connection.search(searchRequest)) {
+ LOG.ldapPagedSearch(baseDn, filter, pageSize, pageNumber);
+ while (cursor.next()) {
+ Response response = cursor.get();
+
+ // Process matching entries
+ if (response instanceof SearchResultEntry) {
+ Entry entry = ((SearchResultEntry)
response).getEntry();
+ results.add(entry);
+ }
+ }
+ if (cursor.isDone()) {
+ SearchResultDone done = cursor.getSearchResultDone();
+ PagedResults responseControl = (PagedResults)
done.getControl(PagedResults.OID);
+
+ if (responseControl != null) {
+ cookie = responseControl.getCookie();
+ } else {
+ cookie = null;
+ }
+ }
+ pageNumber++;
+ }
+ } while (cookie != null && cookie.length > 0 &&
+ (maxResultSetSize == 0 || results.size() < maxResultSetSize));
+
+ if (maxResultSetSize != 0 && results.size() >= maxResultSetSize) {
+ LOG.ldapPagedSearchExceededMaxResultSetSize(results.size(),
maxResultSetSize);
+ } else {
+ LOG.ldapPagedSearchCompleted(baseDn, filter);
}
- return groups;
+ return results;
}
private String buildMultipleGroupMemberFilter(Dn... dns) {
@@ -877,21 +938,6 @@ public class LdapProxyBackend implements LdapBackend {
return filterBuilder.toString();
}
- private List<String> getCnsFromEntries(Collection<Entry> entries) throws
LdapException {
- List<String> cns = new ArrayList<>();
- for (Entry entry : entries) {
- Attribute cnAttr = entry.get("cn");
- if (cnAttr != null) {
- cns.add(cnAttr.getString());
- } else if (entry.getDn() != null && entry.getDn().getRdn() !=
null) {
- // Fall back to the CN carried in the DN when the entry was
fetched without the
- // cn attribute, so resolved groups are not silently dropped
from the result.
- cns.add(entry.getDn().getRdn().getValue());
- }
- }
- return cns;
- }
-
protected Map<String, Entry> createEntryCache() {
return new HashMap<>();
}
diff --git a/gateway-server/src/main/resources/conf/gateway-site.xml
b/gateway-server/src/main/resources/conf/gateway-site.xml
index fda674c17..f549c413f 100644
--- a/gateway-server/src/main/resources/conf/gateway-site.xml
+++ b/gateway-server/src/main/resources/conf/gateway-site.xml
@@ -56,6 +56,18 @@ limitations under the License.
<description>Base DN for LDAP entries in the proxy server. Default is
dc=proxy,dc=com.</description>
</property>
+ <property>
+ <name>gateway.ldap.max.size.limit</name>
+ <value>1000</value>
+ <description>Maximum number of entries returned by a search
request.</description>
+ </property>
+
+ <property>
+ <name>gateway.ldap.max.time.limit</name>
+ <value>60000</value>
+ <description>Maximum time for a search request in
milliseconds.</description>
+ </property>
+
<property>
<name>gateway.ldap.recursive.group.resolution</name>
<value>false</value>
diff --git
a/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManagerTest.java
b/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManagerTest.java
index 327d701c3..a5e7df664 100644
---
a/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManagerTest.java
+++
b/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManagerTest.java
@@ -465,6 +465,27 @@ public class KnoxLDAPServerManagerTest {
List.of("analysts"), groups);
}
+ @Test
+ public void testStartSetsMaxSizeAndTime() throws Exception {
+ final int expectedMaxSize = 3158;
+ final int expectedMaxTime = 245000;
+
+ GatewayConfig mockConfig =
EasyMock.createNiceMock(GatewayConfig.class);
+
expect(mockConfig.getGatewayDataDir()).andReturn(tempWorkDir.getParent()).anyTimes();
+ expect(mockConfig.getLDAPPort()).andReturn(port).anyTimes();
+
expect(mockConfig.getLDAPBaseDN()).andReturn("dc=test,dc=com").anyTimes();
+
expect(mockConfig.getLDAPInterceptorNames()).andReturn(List.of()).anyTimes();
+
expect(mockConfig.getLDAPMaxSizeLimit()).andReturn(expectedMaxSize).anyTimes();
+
expect(mockConfig.getLDAPMaxTimeLimit()).andReturn(expectedMaxTime).anyTimes();
+ replay(mockConfig);
+
+ serverManager.initialize(mockConfig);
+ serverManager.start();
+
+ assertEquals(expectedMaxSize,
serverManager.ldapServer.getMaxSizeLimit());
+ assertEquals(expectedMaxTime,
serverManager.ldapServer.getMaxTimeLimit());
+ }
+
@Test(expected = LdapException.class)
public void testBindRequiredRejectsAnonymous() throws Exception {
useBindPassword(BIND_PASSWORD);
diff --git
a/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServiceTest.java
b/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServiceTest.java
index ce681a561..f32c036f8 100644
---
a/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServiceTest.java
+++
b/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServiceTest.java
@@ -180,6 +180,8 @@ public class KnoxLDAPServiceTest {
expect(mockConfig.getLDAPBindUser()).andReturn(null).anyTimes();
expect(mockConfig.getLDAPInterceptorNames()).andReturn(List.of("testbackend")).atLeastOnce();
expect(mockConfig.getLDAPInterceptorConfig("testbackend")).andReturn(buildBackendConfig(backendType)).atLeastOnce();
+ expect(mockConfig.getLDAPMaxSizeLimit()).andReturn(1000).atLeastOnce();
+
expect(mockConfig.getLDAPMaxTimeLimit()).andReturn(60000).atLeastOnce();
replay(mockConfig);
}
diff --git
a/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/backend/LdapProxyBackendSslTest.java
b/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/backend/LdapProxyBackendSslTest.java
index 96b8e5a8f..3ae14d812 100644
---
a/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/backend/LdapProxyBackendSslTest.java
+++
b/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/backend/LdapProxyBackendSslTest.java
@@ -158,7 +158,8 @@ public class LdapProxyBackendSslTest {
assertEquals("ldaptest1", entry.get("uid").getString());
validateMemberOf(entry, Set.of(
"cn=group1,ou=groups,dc=hadoop,dc=apache,dc=org",
- "cn=group2,ou=groups,dc=hadoop,dc=apache,dc=org"));
+ "cn=group2,ou=groups,dc=hadoop,dc=apache,dc=org",
+ "cn=group3,ou=groups,dc=hadoop,dc=apache,dc=org"));
}
@Test
@@ -168,6 +169,7 @@ public class LdapProxyBackendSslTest {
List<String> groups = ldapProxyBackend.getUserGroups("ldaptest1",
schemaManager);
assertTrue(groups.contains("group1"));
assertTrue(groups.contains("group2"));
+ assertTrue(groups.contains("group3"));
}
@Test
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 58432f137..7264527db 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
@@ -25,6 +25,7 @@ import static org.junit.Assert.assertTrue;
import org.apache.directory.api.ldap.model.entry.Entry;
import org.apache.directory.api.ldap.model.entry.Value;
+import org.apache.directory.api.ldap.model.message.SearchRequest;
import org.apache.directory.api.ldap.model.message.SearchScope;
import org.apache.directory.api.ldap.model.name.Dn;
import org.apache.directory.api.ldap.model.schema.SchemaManager;
@@ -37,6 +38,8 @@ import
org.apache.directory.server.core.factory.JdbmPartitionFactory;
import org.apache.directory.server.core.factory.PartitionFactory;
import org.apache.directory.server.core.partition.ldif.LdifPartition;
import org.apache.directory.server.ldap.LdapServer;
+import org.apache.directory.server.ldap.LdapSession;
+import org.apache.directory.server.ldap.handlers.LdapRequestHandler;
import org.apache.directory.server.protocol.shared.store.LdifFileLoader;
import org.apache.directory.server.protocol.shared.transport.TcpTransport;
import org.apache.knox.gateway.security.ldap.SimpleDirectoryService;
@@ -47,6 +50,8 @@ import org.junit.BeforeClass;
import org.junit.Test;
import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -57,12 +62,15 @@ import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicInteger;
public class LdapProxyBackendTest {
+ private static final int PAGE_SIZE = 2;
+
private static Map<String, String> ldapBackendConfig;
private static TcpTransport transport;
private static DirectoryService directoryService;
private static LdapServer ldapServer;
private static SchemaManager schemaManager;
+ private static CapturingSearchRequestHandler capturingSearchRequestHandler;
private LdapProxyBackend ldapProxyBackend;
@@ -110,10 +118,19 @@ public class LdapProxyBackendTest {
// Create and start the LDAP server
ldapServer = new LdapServer();
+
ldapServer.setTransports(transport);
ldapServer.setDirectoryService(directoryService);
+
ldapServer.start();
+ capturingSearchRequestHandler = new
CapturingSearchRequestHandler(ldapServer.getSearchRequestHandler());
+ ldapServer.setSearchHandlers(
+ capturingSearchRequestHandler,
+ ldapServer.getSearchResultEntryHandler(),
+ ldapServer.getSearchResultReferenceHandler(),
+ ldapServer.getSearchResultDoneHandler());
+
// Setup common backend config values for tests
ldapBackendConfig = Map.of(
"baseDn", "dc=hadoop,dc=apache,dc=org",
@@ -143,6 +160,7 @@ public class LdapProxyBackendTest {
@After
public void tearDown() throws Exception {
+ capturingSearchRequestHandler.reset();
if (ldapProxyBackend != null) {
ldapProxyBackend.close();
}
@@ -157,7 +175,8 @@ public class LdapProxyBackendTest {
validateUserEntry(entry, "ldaptest1", "TestCn1",
"[email protected]", "Test user ldaptest1");
validateMemberOf(entry, Set.of(
"cn=group1,ou=groups,dc=hadoop,dc=apache,dc=org",
- "cn=group2,ou=groups,dc=hadoop,dc=apache,dc=org"));
+ "cn=group2,ou=groups,dc=hadoop,dc=apache,dc=org",
+ "cn=group3,ou=groups,dc=hadoop,dc=apache,dc=org"));
}
@Test
@@ -177,7 +196,8 @@ public class LdapProxyBackendTest {
validateUserEntry(entry, "ldaptest1", "TestCn1",
"[email protected]", "Test user ldaptest1");
validateMemberOf(entry, Set.of(
"cn=group1,ou=groups,dc=hadoop,dc=apache,dc=org",
- "cn=group2,ou=groups,dc=hadoop,dc=apache,dc=org"));
+ "cn=group2,ou=groups,dc=hadoop,dc=apache,dc=org",
+ "cn=group3,ou=groups,dc=hadoop,dc=apache,dc=org"));
}
@Test
@@ -189,7 +209,8 @@ public class LdapProxyBackendTest {
validateUserEntry(entry, "ldaptest1", "TestCn1",
"[email protected]", "Test user ldaptest1");
validateMemberOf(entry, Set.of(
"cn=group1,ou=groups,dc=hadoop,dc=apache,dc=org",
- "cn=group2,ou=groups,dc=hadoop,dc=apache,dc=org"));
+ "cn=group2,ou=groups,dc=hadoop,dc=apache,dc=org",
+ "cn=group3,ou=groups,dc=hadoop,dc=apache,dc=org"));
}
@Test
@@ -211,7 +232,8 @@ public class LdapProxyBackendTest {
assertEquals("TestSam1", entry.get("sAMAccountName").getString());
validateMemberOf(entry, Set.of(
"cn=group1,ou=groups,dc=hadoop,dc=apache,dc=org",
- "cn=group2,ou=groups,dc=hadoop,dc=apache,dc=org"));
+ "cn=group2,ou=groups,dc=hadoop,dc=apache,dc=org",
+ "cn=group3,ou=groups,dc=hadoop,dc=apache,dc=org"));
}
@Test
@@ -230,8 +252,8 @@ public class LdapProxyBackendTest {
config.put("useMemberOf", "true");
ldapProxyBackend = new LdapProxyBackend("testbackend", config);
- Entry entry = ldapProxyBackend.getUser("ldaptest2", schemaManager);
- validateUserEntry(entry, "ldaptest2", "TestCn2",
"[email protected]", "Test user ldaptest2");
+ Entry entry = ldapProxyBackend.getUser("ldapmemberof", schemaManager);
+ validateUserEntry(entry, "ldapmemberof", "TestMemberOf",
"[email protected]", "Test user ldapmemberof");
validateMemberOf(entry, Set.of(
"cn=groupMemberOf1,ou=groups,dc=hadoop,dc=apache,dc=org",
"cn=groupMemberOf2,ou=groups,dc=hadoop,dc=apache,dc=org"));
@@ -244,6 +266,38 @@ public class LdapProxyBackendTest {
List<String> userGroups = ldapProxyBackend.getUserGroups("ldaptest1",
schemaManager);
assertTrue(userGroups.contains("group1"));
assertTrue(userGroups.contains("group2"));
+ assertTrue(userGroups.contains("group3"));
+ }
+
+ @Test
+ public void testGetUserGroupsPaging() throws Exception {
+ Map<String, String> config = new HashMap<>(ldapBackendConfig);
+ config.put("pageSize", Integer.toString(PAGE_SIZE));
+ ldapProxyBackend = new LdapProxyBackend("testbackend", config);
+
+ List<String> userGroups = ldapProxyBackend.getUserGroups("ldaptest1",
schemaManager);
+ assertEquals(3, userGroups.size());
+ int matchingRequests = (int)
capturingSearchRequestHandler.getRequests().stream()
+ .filter(request ->
request.getBase().getName().equals("ou=groups,dc=hadoop,dc=apache,dc=org") &&
+ request.getFilter().toString().contains("ldaptest1"))
+ .count();
+ assertEquals(2, matchingRequests);
+ }
+
+ @Test
+ public void testGetUserGroupsPagingExceedsMaxResultSetSize() throws
Exception {
+ Map<String, String> config = new HashMap<>(ldapBackendConfig);
+ config.put("pageSize", Integer.toString(PAGE_SIZE));
+ config.put("maxResultSetSize", "1");
+ ldapProxyBackend = new LdapProxyBackend("testbackend", config);
+
+ List<String> userGroups = ldapProxyBackend.getUserGroups("ldaptest1",
schemaManager);
+ assertEquals(PAGE_SIZE, userGroups.size()); // only retrieve 1 page
because that will exceed the maxResultSetSize
+ int matchingRequests = (int)
capturingSearchRequestHandler.getRequests().stream()
+ .filter(request ->
request.getBase().getName().equals("ou=groups,dc=hadoop,dc=apache,dc=org") &&
+ request.getFilter().toString().contains("ldaptest1"))
+ .count();
+ assertEquals(1, matchingRequests);
}
@Test
@@ -268,7 +322,7 @@ public class LdapProxyBackendTest {
config.put("useMemberOf", "true");
ldapProxyBackend = new LdapProxyBackend("testbackend", config);
- List<String> userGroups = ldapProxyBackend.getUserGroups("ldaptest2",
schemaManager);
+ List<String> userGroups =
ldapProxyBackend.getUserGroups("ldapmemberof", schemaManager);
assertTrue(userGroups.contains("groupMemberOf1"));
assertTrue(userGroups.contains("groupMemberOf2"));
}
@@ -323,13 +377,42 @@ public class LdapProxyBackendTest {
@Test
public void testSearchUsers() throws Exception {
ldapProxyBackend = new LdapProxyBackend("testbackend",
ldapBackendConfig);
- validateUserSearch("*", 3, Set.of("ldaptest1", "ldaptest2", "guest"));
+ validateUserSearch("*", 4, Set.of("ldaptest1", "ldaptest2",
"ldapmemberof", "guest"));
+ }
+
+ @Test
+ public void testSearchUsersWithPaging() throws Exception {
+ Map<String, String> config = new HashMap<>(ldapBackendConfig);
+ config.put("pageSize", Integer.toString(PAGE_SIZE));
+ ldapProxyBackend = new LdapProxyBackend("testbackend", config);
+ validateUserSearch("*", 4, Set.of("ldaptest1", "ldaptest2",
"ldapmemberof", "guest"));
+ int matchingRequests = (int)
capturingSearchRequestHandler.getRequests().stream()
+ .filter(request ->
request.getBase().getName().equals("ou=people,dc=hadoop,dc=apache,dc=org") &&
+ request.getFilter().toString().contains("uid=*"))
+ .count();
+ assertEquals(2, matchingRequests);
+ }
+
+ @Test
+ public void testSearchUsersWithPagingExceedsMaxResultSetSize() throws
Exception {
+ Map<String, String> config = new HashMap<>(ldapBackendConfig);
+ config.put("pageSize", Integer.toString(PAGE_SIZE));
+ config.put("maxResultSetSize", "1");
+ ldapProxyBackend = new LdapProxyBackend("testbackend", config);
+
+ List<Entry> entries = ldapProxyBackend.searchUsers("*", schemaManager);
+ assertEquals(PAGE_SIZE, entries.size()); // only expect 1 page of
results
+ int matchingRequests = (int)
capturingSearchRequestHandler.getRequests().stream()
+ .filter(request ->
request.getBase().getName().equals("ou=people,dc=hadoop,dc=apache,dc=org") &&
+ request.getFilter().toString().contains("uid=*"))
+ .count();
+ assertEquals(1, matchingRequests);
}
@Test
public void testSearchUsersPartial() throws Exception {
ldapProxyBackend = new LdapProxyBackend("testbackend",
ldapBackendConfig);
- validateUserSearch("ldap*", 2, Set.of("ldaptest1", "ldaptest2"));
+ validateUserSearch("ldap*", 3, Set.of("ldaptest1", "ldaptest2",
"ldapmemberof"));
}
@Test
@@ -343,7 +426,7 @@ public class LdapProxyBackendTest {
public void testSearchUsersByCn() throws Exception {
Map<String, String> config = createConfigWithUserAttr("cn");
ldapProxyBackend = new LdapProxyBackend("testbackend", config);
- validateUserSearch("*", 4, Set.of("ldaptest1", "ldaptest2", "Guest",
"TestCn3"));
+ validateUserSearch("*", 5, Set.of("ldaptest1", "ldaptest2",
"ldapmemberof", "Guest", "TestCn3"));
}
@Test
@@ -365,7 +448,7 @@ public class LdapProxyBackendTest {
public void testSearchUsersBySAMAccountName() throws Exception {
Map<String, String> config =
createConfigWithUserAttr("sAMAccountName");
ldapProxyBackend = new LdapProxyBackend("testbackend", config);
- validateUserSearch("*", 3, Set.of("ldaptest1", "ldaptest2",
"TestSam3"));
+ validateUserSearch("*", 4, Set.of("ldaptest1", "ldaptest2",
"ldapmemberof", "TestSam3"));
}
@Test
@@ -451,7 +534,7 @@ public class LdapProxyBackendTest {
@Test
public void testSearchObjectClassInetOrgPerson() throws Exception {
ldapProxyBackend = new LdapProxyBackend("testbackend",
ldapBackendConfig);
- validateSearch("ou=people,dc=hadoop,dc=apache,dc=org",
"(objectClass=inetOrgPerson)", 4, Set.of("ldaptest1", "ldaptest2", "guest",
"TestCn3"));
+ validateSearch("ou=people,dc=hadoop,dc=apache,dc=org",
"(objectClass=inetOrgPerson)", 5, Set.of("ldaptest1", "ldaptest2",
"ldapmemberof", "guest", "TestCn3"));
}
@Test
@@ -463,19 +546,47 @@ public class LdapProxyBackendTest {
@Test
public void testSearchByUidWildcard() throws Exception {
ldapProxyBackend = new LdapProxyBackend("testbackend",
ldapBackendConfig);
- validateSearch("ou=people,dc=hadoop,dc=apache,dc=org", "(uid=*)", 3,
Set.of("ldaptest1", "ldaptest2", "guest"));
+ validateSearch("ou=people,dc=hadoop,dc=apache,dc=org", "(uid=*)", 4,
Set.of("ldaptest1", "ldaptest2", "ldapmemberof", "guest"));
+ }
+
+ @Test
+ public void testSearchWithPaging() throws Exception {
+ Map<String, String> config = new HashMap<>(ldapBackendConfig);
+ config.put("pageSize", Integer.toString(PAGE_SIZE));
+ ldapProxyBackend = new LdapProxyBackend("testbackend", config);
+ validateSearch("ou=people,dc=hadoop,dc=apache,dc=org", "(uid=*)", 4,
Set.of("ldaptest1", "ldaptest2", "ldapmemberof", "guest"));
+ int matchingRequests = (int)
capturingSearchRequestHandler.getRequests().stream()
+ .filter(request ->
request.getBase().getName().equals("ou=people,dc=hadoop,dc=apache,dc=org") &&
+ request.getFilter().toString().contains("uid=*"))
+ .count();
+ assertEquals(2, matchingRequests);
+ }
+
+ @Test
+ public void testSearchWithPagingExceedsMaxResultSize() throws Exception {
+ Map<String, String> config = new HashMap<>(ldapBackendConfig);
+ config.put("pageSize", Integer.toString(PAGE_SIZE));
+ config.put("maxResultSetSize", "1");
+ ldapProxyBackend = new LdapProxyBackend("testbackend", config);
+ List<Entry> entries =
ldapProxyBackend.search("ou=people,dc=hadoop,dc=apache,dc=org",
SearchScope.SUBTREE, "(uid=*)", schemaManager);
+ assertEquals(PAGE_SIZE, entries.size()); // only expect 1 page because
that will exceed the maxResultSetSize
+ int matchingRequests = (int)
capturingSearchRequestHandler.getRequests().stream()
+ .filter(request ->
request.getBase().getName().equals("ou=people,dc=hadoop,dc=apache,dc=org") &&
+ request.getFilter().toString().contains("uid=*"))
+ .count();
+ assertEquals(1, matchingRequests);
}
@Test
public void testSearchByUidSubstringWildcard() throws Exception {
ldapProxyBackend = new LdapProxyBackend("testbackend",
ldapBackendConfig);
- validateSearch("ou=people,dc=hadoop,dc=apache,dc=org", "(uid=ldap*)",
2, Set.of("ldaptest1", "ldaptest2"));
+ validateSearch("ou=people,dc=hadoop,dc=apache,dc=org", "(uid=ldap*)",
3, Set.of("ldaptest1", "ldaptest2", "ldapmemberof"));
}
@Test
public void testSearchObjectClassGroupOfNames() throws Exception {
ldapProxyBackend = new LdapProxyBackend("testbackend",
ldapBackendConfig);
- validateSearch("ou=groups,dc=hadoop,dc=apache,dc=org",
"(objectClass=groupOfNames)", 3, Set.of("group1", "group2",
"nameddifferently"));
+ validateSearch("ou=groups,dc=hadoop,dc=apache,dc=org",
"(objectClass=groupOfNames)", 4, Set.of("group1", "group2", "group3",
"nameddifferently"));
}
@Test
@@ -487,19 +598,19 @@ public class LdapProxyBackendTest {
@Test
public void testSearchByCnWildcard() throws Exception {
ldapProxyBackend = new LdapProxyBackend("testbackend",
ldapBackendConfig);
- validateSearch("ou=groups,dc=hadoop,dc=apache,dc=org", "(cn=*)", 3,
Set.of("group1", "group2", "nameddifferently"));
+ validateSearch("ou=groups,dc=hadoop,dc=apache,dc=org", "(cn=*)", 4,
Set.of("group1", "group2", "group3", "nameddifferently"));
}
@Test
public void testSearchByCnWSubstringildcard() throws Exception {
ldapProxyBackend = new LdapProxyBackend("testbackend",
ldapBackendConfig);
- validateSearch("ou=groups,dc=hadoop,dc=apache,dc=org", "(cn=group*)",
2, Set.of("group1", "group2"));
+ validateSearch("ou=groups,dc=hadoop,dc=apache,dc=org", "(cn=group*)",
3, Set.of("group1", "group2", "group3"));
}
@Test
public void testSearchByUidOrCnWildcard() throws Exception {
ldapProxyBackend = new LdapProxyBackend("testbackend",
ldapBackendConfig);
- validateSearch("dc=hadoop,dc=apache,dc=org",
"(|(uid=ldap*)(cn=group*))", 4, Set.of("ldaptest1", "ldaptest2", "group1",
"group2"));
+ validateSearch("dc=hadoop,dc=apache,dc=org",
"(|(uid=ldap*)(cn=group*))", 6, Set.of("ldaptest1", "ldaptest2",
"ldapmemberof", "group1", "group2", "group3"));
}
@Test
@@ -809,4 +920,27 @@ public class LdapProxyBackendTest {
// For the second user, many groups should have been found in the
cache.
assertEquals("Expected " + expectedCacheHits + " cache hits for shared
groups, but got " + cacheHits.get(), expectedCacheHits, cacheHits.get());
}
+
+ private static class CapturingSearchRequestHandler extends
LdapRequestHandler<SearchRequest> {
+ private final LdapRequestHandler<SearchRequest> delegate;
+ private final List<SearchRequest> requests =
Collections.synchronizedList(new ArrayList<>());
+
+ CapturingSearchRequestHandler(LdapRequestHandler<SearchRequest>
delegate) {
+ this.delegate = delegate;
+ }
+
+ public void reset() {
+ requests.clear();
+ }
+
+ public List<SearchRequest> getRequests() {
+ return List.copyOf(requests);
+ }
+
+ @Override
+ public void handle(LdapSession session, SearchRequest message) throws
Exception {
+ requests.add(message);
+ delegate.handle(session, message);
+ }
+ }
}
diff --git a/gateway-server/src/test/resources/ldap-proxy-backend-test.ldif
b/gateway-server/src/test/resources/ldap-proxy-backend-test.ldif
index 91fb4136c..456105f98 100644
--- a/gateway-server/src/test/resources/ldap-proxy-backend-test.ldif
+++ b/gateway-server/src/test/resources/ldap-proxy-backend-test.ldif
@@ -58,6 +58,12 @@ objectclass:groupOfNames
cn: group2
member: uid=ldaptest1,ou=people,dc=hadoop,dc=apache,dc=org
+dn: cn=group3,ou=groups,dc=hadoop,dc=apache,dc=org
+objectclass:top
+objectclass:groupOfNames
+cn: group3
+member: uid=ldaptest1,ou=people,dc=hadoop,dc=apache,dc=org
+
dn: cn=nameddifferently,ou=groups,dc=hadoop,dc=apache,dc=org
objectclass:top
objectclass:groupOfNames
@@ -89,6 +95,19 @@ sAMAccountName: TestSam2
userPassword: 12345
mail: [email protected]
description: Test user ldaptest2
+
+dn: uid=ldapmemberof,ou=people,dc=hadoop,dc=apache,dc=org
+objectclass:top
+objectclass:person
+objectclass:organizationalPerson
+objectclass:inetOrgPerson
+cn: TestMemberOf
+sn: Ldap
+uid: ldapmemberof
+sAMAccountName: TestMemberOf
+userPassword: 12345
+mail: [email protected]
+description: Test user ldapmemberof
memberOf: cn=groupMemberOf1,ou=groups,dc=hadoop,dc=apache,dc=org
memberOf: cn=groupMemberOf2,ou=groups,dc=hadoop,dc=apache,dc=org
diff --git
a/gateway-spi-common/src/main/java/org/apache/knox/gateway/GatewayTestConfig.java
b/gateway-spi-common/src/main/java/org/apache/knox/gateway/GatewayTestConfig.java
index a3b3d0d9a..e2dbd08d5 100644
---
a/gateway-spi-common/src/main/java/org/apache/knox/gateway/GatewayTestConfig.java
+++
b/gateway-spi-common/src/main/java/org/apache/knox/gateway/GatewayTestConfig.java
@@ -1355,6 +1355,16 @@ public class GatewayTestConfig extends Configuration
implements GatewayConfig {
return Collections.emptyList();
}
+ @Override
+ public int getLDAPMaxSizeLimit() {
+ return 0;
+ }
+
+ @Override
+ public int getLDAPMaxTimeLimit() {
+ return 0;
+ }
+
@Override
public boolean getGroupUIServicesOnHomepage() {
return false;
diff --git
a/gateway-spi/src/main/java/org/apache/knox/gateway/config/GatewayConfig.java
b/gateway-spi/src/main/java/org/apache/knox/gateway/config/GatewayConfig.java
index 6b36e729b..4aec1fba4 100644
---
a/gateway-spi/src/main/java/org/apache/knox/gateway/config/GatewayConfig.java
+++
b/gateway-spi/src/main/java/org/apache/knox/gateway/config/GatewayConfig.java
@@ -155,6 +155,8 @@ public interface GatewayConfig {
String LDAP_SSL_KEYSTORE_PATH = "gateway.ldap.ssl.keystore.path";
String LDAP_SSL_KEYSTORE_PASSWORD_ALIAS =
"gateway.ldap.ssl.keystore.password.alias";
String LDAP_SSL_ENABLED_CIPHER_SUITES =
"gateway.ldap.ssl.enabled.cipher.suites";
+ String LDAP_MAX_SIZE_LIMIT = "gateway.ldap.max.size.limit";
+ String LDAP_MAX_TIME_LIMIT = "gateway.ldap.max.time.limit";
/**
* The location of the gateway configuration.
@@ -1211,6 +1213,16 @@ public interface GatewayConfig {
*/
List<String> getLDAPSSLEnabledCipherSuites();
+ /**
+ * @return the maximum size limit for LDAP search
+ */
+ int getLDAPMaxSizeLimit();
+
+ /**
+ * @return the maximum time limit for LDAP search in milliseconds
+ */
+ int getLDAPMaxTimeLimit();
+
/**
* @return set of all property names in the configuration
*/
diff --git a/knox-site/docs/service_ldap_server.md
b/knox-site/docs/service_ldap_server.md
index 8d524395b..347c40aa8 100644
--- a/knox-site/docs/service_ldap_server.md
+++ b/knox-site/docs/service_ldap_server.md
@@ -44,6 +44,8 @@ The service is configured in `gateway-site.xml`.
| `gateway.ldap.roles.lookup.strategy` | N/A | The LDAP roles lookup strategy
(`file` or `rest`). |
| `gateway.ldap.roles.lookup.rest.api.endpoint` | N/A | The LDAP roles lookup
REST API endpoint. |
| `gateway.ldap.roles.lookup.file.path` | N/A | The LDAP roles lookup file
path. |
+| `gateway.ldap.max.size.limit` | 1000 | The maximum size limit of the result
set returned by search requests. |
+| `gateway.ldap.max.time.limit` | 60000 | The maximum time limit for search
requests in milliseconds. |
### Bind Credentials
@@ -198,6 +200,10 @@ The proxy backend delegates lookups to a remote LDAP or
Active Directory server.
| `gateway.ldap.interceptor.<name>.groupMemberAttribute` | `memberUid` |
Attribute used for group membership (e.g., `member` for AD). |
| `gateway.ldap.interceptor.<name>.useMemberOf` | `false` | If `true`, use the
`memberOf` attribute for efficient group lookups. |
| `gateway.ldap.interceptor.<name>.proxy.poolMaxActive` | `8` | Maximum number
of active connections in the pool. |
+| `gateway.ldap.interceptor.<name>.pageSize` | `1000` | Page size for search
requests. |
+| `gateway.ldap.interceptor.<name>.maxResultSetSize` | `0` | Maximum number of
results to return from a search, regardless of paging. 0 means unlimited. |
+
+NOTE: If this value is undefined and the interceptor was created by the
KnoxLDAPServerManager, the KnoxLDAPServerManager will set the
`gateway.ldap.interceptor.<name>.maxResultSetSize` value to be 1 greater than
the proxy's `gateway.ldap.max.size.limit` configuration. This will ensure that
the proxy returns a "Size limit exceeded" result if the backend has more
results than the proxy's limit.
## Active Directory (AD) Integration