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 fc2987e73 KNOX-3330: Handle interceptor name collisions (#1251)
fc2987e73 is described below
commit fc2987e73bcacf2b49b3683b6f823fccbb497c74
Author: David Han <[email protected]>
AuthorDate: Tue Jun 9 02:00:12 2026 -0500
KNOX-3330: Handle interceptor name collisions (#1251)
Partition IDs are taken from the interceptor names with whitespace removed.
This could lead to potential collisions in the Partition IDs. This change
adds a numerical suffix in the case of collision to ensure that the
Partition
IDs are unique.
---
.../services/ldap/KnoxLDAPServerManager.java | 16 ++-
.../DuplicateUserFilteringInterceptor.java | 1 -
.../ldap/interceptor/InterceptorFactory.java | 3 +-
.../src/main/resources/conf/gateway-site.xml | 158 ---------------------
.../services/ldap/KnoxLDAPServerManagerTest.java | 65 ++++++++-
knox-site/docs/service_ldap_server.md | 57 +++++++-
6 files changed, 130 insertions(+), 170 deletions(-)
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 ff4edb20e..f56b98529 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
@@ -47,6 +47,7 @@ import
org.apache.knox.gateway.services.ldap.interceptor.InterceptorFactory;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
@@ -86,7 +87,7 @@ public class KnoxLDAPServerManager {
this.port = config.getLDAPPort();
this.baseDn = config.getLDAPBaseDN();
- this.interceptors = createInterceptors(config);
+ createInterceptors(config);
// Clean up previous run if it didn't shut down cleanly
File lockFile = new File(workDir, "run/instance.lock");
@@ -98,7 +99,7 @@ public class KnoxLDAPServerManager {
workDir.mkdirs();
}
- private List<Interceptor> createInterceptors(GatewayConfig config) throws
Exception {
+ private void createInterceptors(GatewayConfig config) throws Exception {
List<String> interceptorNames = config.getLDAPInterceptorNames();
List<Interceptor> interceptors = new
ArrayList<>(interceptorNames.size());
for (String interceptorName : interceptorNames) {
@@ -123,7 +124,7 @@ public class KnoxLDAPServerManager {
interceptors.add(InterceptorFactory.createInterceptor(interceptorName,
interceptorConfig));
}
- return interceptors;
+ this.interceptors = interceptors;
}
/**
@@ -190,6 +191,7 @@ public class KnoxLDAPServerManager {
SchemaManager schemaManager = directoryService.getSchemaManager();
DnFactory dnFactory = directoryService.getDnFactory();
List<String> interceptorNames =
gatewayConfig.getLDAPInterceptorNames();
+ Map<String, Integer> idCountMap = new HashMap<>();
for (String interceptorName : interceptorNames) {
// Get backend-specific configuration using prefixed properties
Map<String, String> interceptorConfig =
gatewayConfig.getLDAPInterceptorConfig(interceptorName);
@@ -199,6 +201,14 @@ public class KnoxLDAPServerManager {
if (!baseDns.contains(remoteBaseDn)) {
//create partition
String id = interceptorName.replaceAll("\\s+", "");
+ if (idCountMap.containsKey(id)) {
+ int count = idCountMap.get(id);
+ idCountMap.put(id, count + 1);
+ // add suffix to ensure unique id
+ id = id + count;
+ } else {
+ idCountMap.put(id, 0);
+ }
JdbmPartition remotePartition = new
JdbmPartition(schemaManager, dnFactory);
remotePartition.setId(id);
remotePartition.setSuffixDn(new Dn(schemaManager,
remoteBaseDn));
diff --git
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/interceptor/DuplicateUserFilteringInterceptor.java
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/interceptor/DuplicateUserFilteringInterceptor.java
index fa7287df1..346fe21f9 100644
---
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/interceptor/DuplicateUserFilteringInterceptor.java
+++
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/interceptor/DuplicateUserFilteringInterceptor.java
@@ -51,7 +51,6 @@ public class DuplicateUserFilteringInterceptor extends
BaseInterceptor {
while (originalResults.next()) {
originalEntries.add(originalResults.get());
}
- originalResults.close();
} catch (CursorException e) {
// rethrow exception on incomplete iteration
throw new LdapException(e);
diff --git
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/interceptor/InterceptorFactory.java
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/interceptor/InterceptorFactory.java
index 6b26cc116..b2f5525a5 100644
---
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/interceptor/InterceptorFactory.java
+++
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/interceptor/InterceptorFactory.java
@@ -47,8 +47,7 @@ public class InterceptorFactory {
for (KnoxLdapInterceptorFactory interceptorFactory : loader) {
if
(interceptorFactory.getType().equalsIgnoreCase(interceptorType)) {
LOG.ldapInterceptorCreating(interceptorType, "ServiceLoader");
- Interceptor interceptor =
interceptorFactory.create(interceptorName, config);
- return interceptor;
+ return interceptorFactory.create(interceptorName, config);
}
}
diff --git a/gateway-server/src/main/resources/conf/gateway-site.xml
b/gateway-server/src/main/resources/conf/gateway-site.xml
index 70ab3b188..fda674c17 100644
--- a/gateway-server/src/main/resources/conf/gateway-site.xml
+++ b/gateway-server/src/main/resources/conf/gateway-site.xml
@@ -106,164 +106,6 @@ limitations under the License.
<description>LDAP bind password for proxy backend
authentication</description>
</property>
- <!-- Backend-specific configuration using prefixed properties -->
- <!-- Uncomment and configure based on interceptor names specified in
gateway.ldap.interceptor.names -->
-
- <!-- File backend configuration
(gateway.ldap.interceptor.<interceptorName>.backendType=file) -->
- <!--
- <property>
- <name>gateway.ldap.interceptor.ldapfile.interceptorType</name>
- <value>backend</value>
- <description>Interceptor type.</description>
- </property>
- <property>
- <name>gateway.ldap.interceptor.ldapfile.backendType</name>
- <value>file</value>
- <description>Backend type for LDAP service. Currently supported: file,
ldap. Future: jdbc, knox.</description>
- </property>
- <property>
- <name>gateway.ldap.interceptor.ldapfile.dataFile</name>
- <value>${GATEWAY_DATA_HOME}/ldap-users.json</value>
- <description>Path to JSON file containing user and group
data</description>
- </property>
- -->
-
- <!-- LDAP proxy backend configuration
(gateway.ldap.interceptor.<interceptorName>.backendType=ldap) -->
- <!-- This backend proxies to an external LDAP server (e.g., demo LDAP) -->
- <!--
- Example 1: Using Knox demo LDAP server (default port 33389)
- <property>
- <name>gateway.ldap.interceptor.demoldap.backendType</name>
- <value>ldap</value>
- <description>Backend type for LDAP service. Currently supported: file,
ldap. Future: jdbc, knox.</description>
- </property>
- <property>
- <name>gateway.ldap.interceptor.demoldap.url</name>
- <value>ldap://localhost:33389</value>
- <description>LDAP server URL</description>
- </property>
- <property>
- <name>gateway.ldap.interceptor.demoldap.remoteBaseDn</name>
- <value>dc=hadoop,dc=apache,dc=org</value>
- <description>Base DN of the remote LDAP server</description>
- </property>
- <property>
- <name>gateway.ldap.interceptor.demoldap.systemUsername</name>
- <value>uid=guest,ou=people,dc=hadoop,dc=apache,dc=org</value>
- <description>LDAP bind DN for authentication</description>
- </property>
- <property>
- <name>gateway.ldap.interceptor.demoldap.systemPassword</name>
- <value>guest-password</value>
- <description>LDAP bind password</description>
- </property>
- Note: Entries from the remote server will be re-created under
gateway.ldap.base.dn (e.g., dc=proxy,dc=com)
- -->
- <!--
- Example 2: Using external LDAP with authentication (supports both naming
conventions)
- <property>
- <name>gateway.ldap.interceptor.externalldap.backendType</name>
- <value>ldap</value>
- <description>Backend type for LDAP service. Currently supported: file,
ldap. Future: jdbc, knox.</description>
- </property>
- <property>
- <name>gateway.ldap.interceptor.externalldap.url</name>
- <value>ldap://ldap.example.com:389</value>
- <description>LDAP server URL</description>
- </property>
- <property>
- <name>gateway.ldap.interceptor.externalldap.remoteBaseDn</name>
- <value>dc=example,dc=com</value>
- <description>Base DN of the remote LDAP server</description>
- </property>
- <property>
- <name>gateway.ldap.interceptor.externalldap.systemUsername</name>
- <value>cn=admin,dc=example,dc=com</value>
- <description>LDAP bind DN for authentication (or use
bindDn)</description>
- </property>
- <property>
- <name>gateway.ldap.interceptor.externalldap.systemPassword</name>
- <value>secret</value>
- <description>LDAP bind password (or use bindPassword)</description>
- </property>
- <property>
- <name>gateway.ldap.interceptor.externalldap.userSearchBase</name>
- <value>ou=people,dc=example,dc=com</value>
- <description>Base DN for user searches on remote server (defaults to
ou=people,{remoteBaseDn})</description>
- </property>
- <property>
- <name>gateway.ldap.interceptor.externalldap.groupSearchBase</name>
- <value>ou=groups,dc=example,dc=com</value>
- <description>Base DN for group searches on remote server (defaults to
ou=groups,{remoteBaseDn})</description>
- </property>
- -->
- <!--
- Alternative: Use host and port instead of URL
- <property>
- <name>gateway.ldap.interceptor.externalldap.host</name>
- <value>localhost</value>
- <description>LDAP server hostname</description>
- </property>
- <property>
- <name>gateway.ldap.interceptor.externalldap.port</name>
- <value>33389</value>
- <description>LDAP server port</description>
- </property>
- -->
-
- <!-- Database backend configuration (gateway.ldap.backend.type=jdbc) -->
- <!--
- <property>
- <name>gateway.ldap.interceptor.jdbc.backendType</name>
- <value>jdbc</value>
- <description>Backend type for LDAP service. Currently supported: file,
ldap. Future: jdbc, knox.</description>
- </property>
- <property>
- <name>gateway.ldap.interceptor.jdbc.url</name>
- <value>jdbc:mysql://localhost:3306/knox</value>
- <description>JDBC connection URL</description>
- </property>
- <property>
- <name>gateway.ldap.interceptor.jdbc.driver</name>
- <value>com.mysql.cj.jdbc.Driver</value>
- <description>JDBC driver class name</description>
- </property>
- <property>
- <name>gateway.ldap.interceptor.jdbc.username</name>
- <value>knox_user</value>
- <description>Database username</description>
- </property>
- <property>
- <name>gateway.ldap.interceptor.jdbc.password</name>
- <value>secret</value>
- <description>Database password</description>
- </property>
- -->
-
- <!-- Knox Auth backend configuration (gateway.ldap.backend.type=knox) -->
- <!--
- <property>
- <name>gateway.ldap.interceptor.knox.backendType</name>
- <value>knox</value>
- <description>Backend type for LDAP service. Currently supported: file,
ldap. Future: jdbc, knox.</description>
- </property>
- <property>
- <name>gateway.ldap.interceptor.knox.url</name>
- <value>https://knox-server/gateway/sandbox/knoxauth/api</value>
- <description>Knox authentication service URL</description>
- </property>
- <property>
- <name>gateway.ldap.interceptor.knox.username</name>
- <value>admin</value>
- <description>Knox admin username</description>
- </property>
- <property>
- <name>gateway.ldap.interceptor.knox.password</name>
- <value>admin-password</value>
- <description>Knox admin password</description>
- </property>
- -->
-
<!-- Duplicate Filter Interceptor -->
<property>
<name>gateway.ldap.interceptor.duplicatefilter.interceptorType</name>
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 c41afb0c0..b9ffdcda1 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
@@ -17,6 +17,7 @@
*/
package org.apache.knox.gateway.services.ldap;
+import org.apache.directory.server.core.api.interceptor.Interceptor;
import org.apache.knox.gateway.config.GatewayConfig;
import org.easymock.EasyMock;
import org.apache.directory.api.ldap.model.name.Dn;
@@ -27,9 +28,12 @@ import org.junit.After;
import java.io.File;
import java.net.ServerSocket;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.stream.Collectors;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
@@ -251,7 +255,7 @@ public class KnoxLDAPServerManagerTest {
backendConfig.get("baseDn"),
interceptor.getBackend().getBaseDn());
// LdapNoSuchObjectException will be thrown if expected partition does
not exist
serverManager.directoryService.getPartitionNexus().getPartition(
- new Dn(serverManager.directoryService.getSchemaManager(),
backendConfig.get("baseDn")));
+ new Dn(serverManager.directoryService.getSchemaManager(),
interceptor.getBackend().getBaseDn()));
}
@Test
@@ -276,7 +280,7 @@ public class KnoxLDAPServerManagerTest {
backendConfig.get("remoteBaseDn"),
interceptor.getBackend().getBaseDn());
// LdapNoSuchObjectException will be thrown if expected partition does
not exist
serverManager.directoryService.getPartitionNexus().getPartition(
- new Dn(serverManager.directoryService.getSchemaManager(),
backendConfig.get("baseDn")));
+ new Dn(serverManager.directoryService.getSchemaManager(),
interceptor.getBackend().getBaseDn()));
}
@Test
@@ -297,6 +301,17 @@ public class KnoxLDAPServerManagerTest {
serverManager.start();
+ // assert that interceptors are found in reverse order
+ List<String> expectedInterceptorOrder = new ArrayList<>();
+ expectedInterceptorOrder.addAll(mockConfig.getLDAPInterceptorNames());
+ Collections.reverse(expectedInterceptorOrder);
+ List<String> interceptorNames =
serverManager.directoryService.getInterceptors().stream()
+ .map(Interceptor::getName)
+ .filter(name -> expectedInterceptorOrder.contains(name))
+ .collect(Collectors.toList());
+ assertEquals("Interceptors should be added to directory service in the
order specified by the config",
+ expectedInterceptorOrder, interceptorNames);
+
// Ensure that the partitions are created and backends registered with
the file backend interceptor
UserSearchInterceptor fileInterceptor = (UserSearchInterceptor)
serverManager.directoryService.getInterceptor("filebackend");
assertNotNull("Interceptor should not be null", fileInterceptor);
@@ -304,7 +319,7 @@ public class KnoxLDAPServerManagerTest {
fileBackendConfig.get("baseDn"),
fileInterceptor.getBackend().getBaseDn());
// LdapNoSuchObjectException will be thrown if expected partition does
not exist
serverManager.directoryService.getPartitionNexus().getPartition(
- new Dn(serverManager.directoryService.getSchemaManager(),
fileBackendConfig.get("baseDn")));
+ new Dn(serverManager.directoryService.getSchemaManager(),
fileInterceptor.getBackend().getBaseDn()));
// Ensure that the partitions are created and backends registered with
the ldap backend interceptor
UserSearchInterceptor ldapInterceptor = (UserSearchInterceptor)
serverManager.directoryService.getInterceptor("ldapbackend");
@@ -313,7 +328,49 @@ public class KnoxLDAPServerManagerTest {
ldapBackendConfig.get("remoteBaseDn"),
ldapInterceptor.getBackend().getBaseDn());
// LdapNoSuchObjectException will be thrown if expected partition does
not exist
serverManager.directoryService.getPartitionNexus().getPartition(
- new Dn(serverManager.directoryService.getSchemaManager(),
ldapBackendConfig.get("baseDn")));
+ new Dn(serverManager.directoryService.getSchemaManager(),
ldapInterceptor.getBackend().getBaseDn()));
+ }
+
+ @Test
+ public void testStartWithMultipleBackendsIdCollision() throws Exception {
+ // Partitions are created using the interceptor name as an id.
Whitespace is removed from the
+ // id, but this could result in id collisions and failure to create
the partitions. This test
+ // checks that partitions will still be created for the DNs even if
the interceptor names
+ // collide.
+ 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("ldapbackend",
"ldap backend")).anyTimes();
+
expect(mockConfig.getLDAPBackendDataFile()).andReturn(tempLdapFile.getAbsolutePath()).anyTimes();
+ Map<String, String> ldapBackendConfig =
createLdapBackendInterceptorConfig();
+
expect(mockConfig.getLDAPInterceptorConfig("ldapbackend")).andReturn(ldapBackendConfig).anyTimes();
+ Map<String, String> ldapBackendConfig2 =
createLdapBackendInterceptorConfig();
+ ldapBackendConfig2.put("remoteBaseDn",
"dc=ldapbackend,dc=example,dc=org");
+ expect(mockConfig.getLDAPInterceptorConfig("ldap
backend")).andReturn(ldapBackendConfig2).anyTimes();
+ replay(mockConfig);
+
+ serverManager.initialize(mockConfig);
+
+ serverManager.start();
+
+ // Ensure that the partitions are created and backends registered with
the ldap backend interceptor
+ UserSearchInterceptor ldapInterceptor = (UserSearchInterceptor)
serverManager.directoryService.getInterceptor("ldapbackend");
+ assertNotNull("Interceptor should not be null", ldapInterceptor);
+ assertEquals("LDAP backend dn should match configuration",
+ ldapBackendConfig.get("remoteBaseDn"),
ldapInterceptor.getBackend().getBaseDn());
+ // LdapNoSuchObjectException will be thrown if expected partition does
not exist
+ serverManager.directoryService.getPartitionNexus().getPartition(
+ new Dn(serverManager.directoryService.getSchemaManager(),
ldapInterceptor.getBackend().getBaseDn()));
+
+ // Ensure that the partitions are created and backends registered with
the ldap backend interceptor
+ UserSearchInterceptor ldapInterceptor2 = (UserSearchInterceptor)
serverManager.directoryService.getInterceptor("ldap backend");
+ assertNotNull("Interceptor should not be null", ldapInterceptor2);
+ assertEquals("LDAP backend dn should match configuration",
+ ldapBackendConfig2.get("remoteBaseDn"),
ldapInterceptor2.getBackend().getBaseDn());
+ // LdapNoSuchObjectException will be thrown if expected partition does
not exist
+ serverManager.directoryService.getPartitionNexus().getPartition(
+ new Dn(serverManager.directoryService.getSchemaManager(),
ldapInterceptor2.getBackend().getBaseDn()));
}
@Test
diff --git a/knox-site/docs/service_ldap_server.md
b/knox-site/docs/service_ldap_server.md
index 1ad0f1ab0..45f409b8a 100644
--- a/knox-site/docs/service_ldap_server.md
+++ b/knox-site/docs/service_ldap_server.md
@@ -139,7 +139,7 @@ To configure Knox to act as an LDAP proxy for a local file
and an Active Directo
<property>
<name>gateway.ldap.interceptor.names</name>
- <value>localfile,adexample,duplicatefilter</value>
+ <value>localfile,adexample,extrenalldap,duplicatefilter</value>
</property>
<!-- File-based LDAP backend -->
@@ -156,7 +156,7 @@ To configure Knox to act as an LDAP proxy for a local file
and an Active Directo
<value>${GATEWAY_DATA_HOME}/ldap-users.json</value>
</property>
-<!-- LDAP backend proxy configuration -->
+<!-- LDAP backend proxy configured for Active Directory -->
<property>
<name>gateway.ldap.interceptor.adexample.interceptorType</name>
<value>backend</value>
@@ -194,6 +194,59 @@ To configure Knox to act as an LDAP proxy for a local file
and an Active Directo
<value>true</value>
</property>
+<!--
+Example: Using external LDAP with authentication (supports both naming
conventions)
+<property>
+ <name>gateway.ldap.interceptor.externalldap.backendType</name>
+ <value>ldap</value>
+ <description>Backend type for LDAP service. Currently supported: file,
ldap. Future: jdbc, knox.</description>
+</property>
+<property>
+ <name>gateway.ldap.interceptor.externalldap.url</name>
+ <value>ldap://ldap.example.com:389</value>
+ <description>LDAP server URL</description>
+</property>
+<property>
+ <name>gateway.ldap.interceptor.externalldap.remoteBaseDn</name>
+ <value>dc=example,dc=com</value>
+ <description>Base DN of the remote LDAP server</description>
+</property>
+<property>
+ <name>gateway.ldap.interceptor.externalldap.systemUsername</name>
+ <value>cn=admin,dc=example,dc=com</value>
+ <description>LDAP bind DN for authentication (or use bindDn)</description>
+</property>
+<property>
+ <name>gateway.ldap.interceptor.externalldap.systemPassword</name>
+ <value>secret</value>
+ <description>LDAP bind password (or use bindPassword)</description>
+</property>
+<property>
+ <name>gateway.ldap.interceptor.externalldap.userSearchBase</name>
+ <value>ou=people,dc=example,dc=com</value>
+ <description>Base DN for user searches on remote server (defaults to
ou=people,{remoteBaseDn})</description>
+</property>
+<property>
+ <name>gateway.ldap.interceptor.externalldap.groupSearchBase</name>
+ <value>ou=groups,dc=example,dc=com</value>
+ <description>Base DN for group searches on remote server (defaults to
ou=groups,{remoteBaseDn})</description>
+</property>
+-->
+<!--
+Alternative: Use host and port instead of URL
+<property>
+ <name>gateway.ldap.interceptor.externalldap.host</name>
+ <value>localhost</value>
+ <description>LDAP server hostname</description>
+</property>
+<property>
+ <name>gateway.ldap.interceptor.externalldap.port</name>
+ <value>33389</value>
+ <description>LDAP server port</description>
+</property>
+-->
+
+
<!-- Duplicate Filter Interceptor -->
<property>
<name>gateway.ldap.interceptor.duplicatefilter.interceptorType</name>