This is an automated email from the ASF dual-hosted git repository. smolnar82 pushed a commit to branch knox_idf in repository https://gitbox.apache.org/repos/asf/knox.git
commit 258bc2c1a4964d6b299a7d0a91272bb4fae803e0 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>
